merge new into master

This commit is contained in:
2026-02-05 17:31:20 +00:00
16267 changed files with 2194867 additions and 0 deletions

57
node_modules/hermes-parser/dist/HermesAST.js.flow generated vendored Normal file
View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
// flowlint unclear-type:off
export type HermesPosition = {
/** >= 1 */
+line: number,
/** >= 0 */
+column: number,
};
export type HermesSourceLocation = {
start?: HermesPosition,
end?: HermesPosition,
rangeStart?: number,
rangeEnd?: number,
};
export type HermesNode = {
type: string,
[string]: any,
};
export type HermesToken = {
type:
| 'Boolean'
| 'Identifier'
| 'Keyword'
| 'Null'
| 'Numeric'
| 'BigInt'
| 'Punctuator'
| 'String'
| 'RegularExpression'
| 'Template'
| 'JSXText',
loc: HermesSourceLocation,
value: ?string,
};
export type HermesComment = {
type: 'CommentLine' | 'CommentBlock' | 'InterpreterDirective',
loc: HermesSourceLocation,
value: ?string,
};
export type HermesProgram = {
type: 'Program',
loc: HermesSourceLocation,
body: Array<?HermesNode>,
comments: Array<HermesComment>,
tokens?: Array<HermesToken>,
interpreter?: ?HermesComment,
};

193
node_modules/hermes-parser/dist/HermesASTAdapter.js generated vendored Normal file
View File

@@ -0,0 +1,193 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _ParserVisitorKeys = require("./generated/ParserVisitorKeys");
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
/**
* The base class for transforming the Hermes AST to the desired output format.
* Extended by concrete adapters which output an ESTree or Babel AST.
*/
class HermesASTAdapter {
constructor(options) {
this.sourceFilename = void 0;
this.sourceType = void 0;
this.sourceFilename = options.sourceFilename;
this.sourceType = options.sourceType;
}
/**
* Transform the input Hermes AST to the desired output format.
* This modifies the input AST in place instead of constructing a new AST.
*/
transform(program) {
// Comments are not traversed via visitor keys
const comments = program.comments;
for (let i = 0; i < comments.length; i++) {
const comment = comments[i];
this.fixSourceLocation(comment);
comments[i] = this.mapComment(comment);
} // The first comment may be an interpreter directive and is stored directly on the program node
program.interpreter = comments.length > 0 && comments[0].type === 'InterpreterDirective' ? comments.shift() : null; // Tokens are not traversed via visitor keys
const tokens = program.tokens;
if (tokens) {
for (let i = 0; i < tokens.length; i++) {
this.fixSourceLocation(tokens[i]);
}
}
const resultNode = this.mapNode(program);
if (resultNode.type !== 'Program') {
throw new Error(`HermesToESTreeAdapter: Must return a Program node, instead of "${resultNode.type}". `);
} // $FlowExpectedError[incompatible-return] We know this is a program at this point.
return resultNode;
}
/**
* Transform a Hermes AST node to the output AST format.
*
* This may modify the input node in-place and return that same node, or a completely
* new node may be constructed and returned. Overriden in child classes.
*/
mapNode(_node) {
throw new Error('Implemented in subclasses');
}
mapNodeDefault(node) {
const visitorKeys = _ParserVisitorKeys.HERMES_AST_VISITOR_KEYS[node.type];
for (const key in visitorKeys) {
const childType = visitorKeys[key];
if (childType === _ParserVisitorKeys.NODE_CHILD) {
const child = node[key];
if (child != null) {
node[key] = this.mapNode(child);
}
} else if (childType === _ParserVisitorKeys.NODE_LIST_CHILD) {
const children = node[key];
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child != null) {
children[i] = this.mapNode(child);
}
}
}
}
return node;
}
/**
* Update the source location for this node depending on the output AST format.
* This can modify the input node in-place. Overriden in child classes.
*/
fixSourceLocation(_node) {
throw new Error('Implemented in subclasses');
}
getSourceType() {
var _this$sourceType;
return (_this$sourceType = this.sourceType) != null ? _this$sourceType : 'script';
}
setModuleSourceType() {
if (this.sourceType == null) {
this.sourceType = 'module';
}
}
mapComment(node) {
return node;
}
mapEmpty(_node) {
// $FlowExpectedError
return null;
}
mapImportDeclaration(node) {
if (node.importKind === 'value') {
this.setModuleSourceType();
}
return this.mapNodeDefault(node);
}
mapImportSpecifier(node) {
if (node.importKind === 'value') {
node.importKind = null;
}
return this.mapNodeDefault(node);
}
mapExportDefaultDeclaration(node) {
this.setModuleSourceType();
return this.mapNodeDefault(node);
}
mapExportNamedDeclaration(node) {
if (node.exportKind === 'value') {
this.setModuleSourceType();
}
return this.mapNodeDefault(node);
}
mapExportAllDeclaration(node) {
if (node.exportKind === 'value') {
this.setModuleSourceType();
}
return this.mapNodeDefault(node);
}
formatError(node, message) {
return `${message} (${node.loc.start.line}:${node.loc.start.column})`;
}
getBigIntLiteralValue(bigintString) {
// TODO - once we update flow we can remove this
const bigint = bigintString // estree spec is to not have a trailing `n` on this property
// https://github.com/estree/estree/blob/db962bb417a97effcfe9892f87fbb93c81a68584/es2020.md#bigintliteral
.replace(/n$/, '') // `BigInt` doesn't accept numeric separator and `bigint` property should not include numeric separator
.replace(/_/, '');
return {
bigint,
// coerce the string to a bigint value if supported by the environment
value: typeof BigInt === 'function' ? BigInt(bigint) : null
};
}
}
exports.default = HermesASTAdapter;

View File

@@ -0,0 +1,192 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import type {Program} from 'hermes-estree';
import type {HermesNode} from './HermesAST';
import type {ParserOptions} from './ParserOptions';
import {
HERMES_AST_VISITOR_KEYS,
NODE_CHILD,
NODE_LIST_CHILD,
} from './generated/ParserVisitorKeys';
/**
* The base class for transforming the Hermes AST to the desired output format.
* Extended by concrete adapters which output an ESTree or Babel AST.
*/
export default class HermesASTAdapter {
sourceFilename: ParserOptions['sourceFilename'];
sourceType: ParserOptions['sourceType'];
constructor(options: ParserOptions) {
this.sourceFilename = options.sourceFilename;
this.sourceType = options.sourceType;
}
/**
* Transform the input Hermes AST to the desired output format.
* This modifies the input AST in place instead of constructing a new AST.
*/
transform(program: HermesNode): Program {
// Comments are not traversed via visitor keys
const comments = program.comments;
for (let i = 0; i < comments.length; i++) {
const comment = comments[i];
this.fixSourceLocation(comment);
comments[i] = this.mapComment(comment);
}
// The first comment may be an interpreter directive and is stored directly on the program node
program.interpreter =
comments.length > 0 && comments[0].type === 'InterpreterDirective'
? comments.shift()
: null;
// Tokens are not traversed via visitor keys
const tokens = program.tokens;
if (tokens) {
for (let i = 0; i < tokens.length; i++) {
this.fixSourceLocation(tokens[i]);
}
}
const resultNode = this.mapNode(program);
if (resultNode.type !== 'Program') {
throw new Error(
`HermesToESTreeAdapter: Must return a Program node, instead of "${resultNode.type}". `,
);
}
// $FlowExpectedError[incompatible-return] We know this is a program at this point.
return resultNode;
}
/**
* Transform a Hermes AST node to the output AST format.
*
* This may modify the input node in-place and return that same node, or a completely
* new node may be constructed and returned. Overriden in child classes.
*/
mapNode(_node: HermesNode): HermesNode {
throw new Error('Implemented in subclasses');
}
mapNodeDefault(node: HermesNode): HermesNode {
const visitorKeys = HERMES_AST_VISITOR_KEYS[node.type];
for (const key in visitorKeys) {
const childType = visitorKeys[key];
if (childType === NODE_CHILD) {
const child = node[key];
if (child != null) {
node[key] = this.mapNode(child);
}
} else if (childType === NODE_LIST_CHILD) {
const children = node[key];
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child != null) {
children[i] = this.mapNode(child);
}
}
}
}
return node;
}
/**
* Update the source location for this node depending on the output AST format.
* This can modify the input node in-place. Overriden in child classes.
*/
fixSourceLocation(_node: HermesNode): void {
throw new Error('Implemented in subclasses');
}
getSourceType(): ParserOptions['sourceType'] {
return this.sourceType ?? 'script';
}
setModuleSourceType(): void {
if (this.sourceType == null) {
this.sourceType = 'module';
}
}
mapComment(node: HermesNode): HermesNode {
return node;
}
mapEmpty(_node: HermesNode): HermesNode {
// $FlowExpectedError
return null;
}
mapImportDeclaration(node: HermesNode): HermesNode {
if (node.importKind === 'value') {
this.setModuleSourceType();
}
return this.mapNodeDefault(node);
}
mapImportSpecifier(node: HermesNode): HermesNode {
if (node.importKind === 'value') {
node.importKind = null;
}
return this.mapNodeDefault(node);
}
mapExportDefaultDeclaration(node: HermesNode): HermesNode {
this.setModuleSourceType();
return this.mapNodeDefault(node);
}
mapExportNamedDeclaration(node: HermesNode): HermesNode {
if (node.exportKind === 'value') {
this.setModuleSourceType();
}
return this.mapNodeDefault(node);
}
mapExportAllDeclaration(node: HermesNode): HermesNode {
if (node.exportKind === 'value') {
this.setModuleSourceType();
}
return this.mapNodeDefault(node);
}
formatError(node: HermesNode, message: string): string {
return `${message} (${node.loc.start.line}:${node.loc.start.column})`;
}
getBigIntLiteralValue(bigintString: string): {
bigint: string,
value: $FlowFixMe /* bigint */,
} {
// TODO - once we update flow we can remove this
declare var BigInt: ?(value: $FlowFixMe) => mixed;
const bigint = bigintString
// estree spec is to not have a trailing `n` on this property
// https://github.com/estree/estree/blob/db962bb417a97effcfe9892f87fbb93c81a68584/es2020.md#bigintliteral
.replace(/n$/, '')
// `BigInt` doesn't accept numeric separator and `bigint` property should not include numeric separator
.replace(/_/, '');
return {
bigint,
// coerce the string to a bigint value if supported by the environment
value: typeof BigInt === 'function' ? BigInt(bigint) : null,
};
}
}

108
node_modules/hermes-parser/dist/HermesParser.js generated vendored Normal file
View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parse = parse;
var _HermesParserDeserializer = _interopRequireDefault(require("./HermesParserDeserializer"));
var _HermesParserWASM = _interopRequireDefault(require("./HermesParserWASM"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
let HermesParserWASM;
let hermesParse;
let hermesParseResult_free;
let hermesParseResult_getError;
let hermesParseResult_getErrorLine;
let hermesParseResult_getErrorColumn;
let hermesParseResult_getProgramBuffer;
let hermesParseResult_getPositionBuffer;
let hermesParseResult_getPositionBufferSize;
/**
* Init the WASM wrapper code generated by `emscripten` to preparse the
* HermesParser WASM code.
*/
function initHermesParserWASM() {
if (HermesParserWASM != null) {
return;
}
HermesParserWASM = (0, _HermesParserWASM.default)({
/**
* The emscripten version of `quit` unconditionally assigns the `status` to
* `process.exitCode` which overrides any pre-existing code that has been
* set, even if it is non zero. For our use case we never want an
* `exitCode` to be set so this override removes that functionality.
*/
quit(_status, toThrow) {
throw toThrow;
}
});
hermesParse = HermesParserWASM.cwrap('hermesParse', 'number', ['number', 'number', 'number', 'number', 'number', 'number']);
hermesParseResult_free = HermesParserWASM.cwrap('hermesParseResult_free', 'void', ['number']);
hermesParseResult_getError = HermesParserWASM.cwrap('hermesParseResult_getError', 'string', ['number']);
hermesParseResult_getErrorLine = HermesParserWASM.cwrap('hermesParseResult_getErrorLine', 'number', ['number']);
hermesParseResult_getErrorColumn = HermesParserWASM.cwrap('hermesParseResult_getErrorColumn', 'number', ['number']);
hermesParseResult_getProgramBuffer = HermesParserWASM.cwrap('hermesParseResult_getProgramBuffer', 'number', ['number']);
hermesParseResult_getPositionBuffer = HermesParserWASM.cwrap('hermesParseResult_getPositionBuffer', 'number', ['number']);
hermesParseResult_getPositionBufferSize = HermesParserWASM.cwrap('hermesParseResult_getPositionBufferSize', 'number', ['number']);
} // Copy a string into the WASM heap and null-terminate
function copyToHeap(buffer, addr) {
HermesParserWASM.HEAP8.set(buffer, addr);
HermesParserWASM.HEAP8[addr + buffer.length] = 0;
}
function parse(source, options) {
initHermesParserWASM(); // Allocate space on heap for source text
const sourceBuffer = Buffer.from(source, 'utf8');
const sourceAddr = HermesParserWASM._malloc(sourceBuffer.length + 1);
if (!sourceAddr) {
throw new Error('Parser out of memory');
}
try {
// Copy source text onto WASM heap
copyToHeap(sourceBuffer, sourceAddr);
const parseResult = hermesParse(sourceAddr, sourceBuffer.length + 1, options.flow === 'detect', options.enableExperimentalComponentSyntax, options.tokens, options.allowReturnOutsideFunction);
try {
// Extract and throw error from parse result if parsing failed
const err = hermesParseResult_getError(parseResult);
if (err) {
const syntaxError = new SyntaxError(err); // $FlowExpectedError[prop-missing]
syntaxError.loc = {
line: hermesParseResult_getErrorLine(parseResult),
column: hermesParseResult_getErrorColumn(parseResult)
};
throw syntaxError;
}
const deserializer = new _HermesParserDeserializer.default(hermesParseResult_getProgramBuffer(parseResult), hermesParseResult_getPositionBuffer(parseResult), hermesParseResult_getPositionBufferSize(parseResult), HermesParserWASM, options);
return deserializer.deserialize();
} finally {
hermesParseResult_free(parseResult);
}
} finally {
HermesParserWASM._free(sourceAddr);
}
}

159
node_modules/hermes-parser/dist/HermesParser.js.flow generated vendored Normal file
View File

@@ -0,0 +1,159 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {HermesNode} from './HermesAST';
import type {ParserOptions} from './ParserOptions';
import HermesParserDeserializer from './HermesParserDeserializer';
import HermesParserWASMModule from './HermesParserWASM';
let HermesParserWASM;
let hermesParse;
let hermesParseResult_free;
let hermesParseResult_getError;
let hermesParseResult_getErrorLine;
let hermesParseResult_getErrorColumn;
let hermesParseResult_getProgramBuffer;
let hermesParseResult_getPositionBuffer;
let hermesParseResult_getPositionBufferSize;
/**
* Init the WASM wrapper code generated by `emscripten` to preparse the
* HermesParser WASM code.
*/
function initHermesParserWASM() {
if (HermesParserWASM != null) {
return;
}
HermesParserWASM = HermesParserWASMModule({
/**
* The emscripten version of `quit` unconditionally assigns the `status` to
* `process.exitCode` which overrides any pre-existing code that has been
* set, even if it is non zero. For our use case we never want an
* `exitCode` to be set so this override removes that functionality.
*/
quit(_status: number, toThrow: Error) {
throw toThrow;
},
});
hermesParse = HermesParserWASM.cwrap('hermesParse', 'number', [
'number',
'number',
'number',
'number',
'number',
'number',
]);
hermesParseResult_free = HermesParserWASM.cwrap(
'hermesParseResult_free',
'void',
['number'],
);
hermesParseResult_getError = HermesParserWASM.cwrap(
'hermesParseResult_getError',
'string',
['number'],
);
hermesParseResult_getErrorLine = HermesParserWASM.cwrap(
'hermesParseResult_getErrorLine',
'number',
['number'],
);
hermesParseResult_getErrorColumn = HermesParserWASM.cwrap(
'hermesParseResult_getErrorColumn',
'number',
['number'],
);
hermesParseResult_getProgramBuffer = HermesParserWASM.cwrap(
'hermesParseResult_getProgramBuffer',
'number',
['number'],
);
hermesParseResult_getPositionBuffer = HermesParserWASM.cwrap(
'hermesParseResult_getPositionBuffer',
'number',
['number'],
);
hermesParseResult_getPositionBufferSize = HermesParserWASM.cwrap(
'hermesParseResult_getPositionBufferSize',
'number',
['number'],
);
}
// Copy a string into the WASM heap and null-terminate
function copyToHeap(buffer: Buffer, addr: number) {
HermesParserWASM.HEAP8.set(buffer, addr);
HermesParserWASM.HEAP8[addr + buffer.length] = 0;
}
export function parse(source: string, options: ParserOptions): HermesNode {
initHermesParserWASM();
// Allocate space on heap for source text
const sourceBuffer = Buffer.from(source, 'utf8');
const sourceAddr = HermesParserWASM._malloc(sourceBuffer.length + 1);
if (!sourceAddr) {
throw new Error('Parser out of memory');
}
try {
// Copy source text onto WASM heap
copyToHeap(sourceBuffer, sourceAddr);
const parseResult = hermesParse(
sourceAddr,
sourceBuffer.length + 1,
options.flow === 'detect',
options.enableExperimentalComponentSyntax,
options.tokens,
options.allowReturnOutsideFunction,
);
try {
// Extract and throw error from parse result if parsing failed
const err = hermesParseResult_getError(parseResult);
if (err) {
const syntaxError = new SyntaxError(err);
// $FlowExpectedError[prop-missing]
syntaxError.loc = {
line: hermesParseResult_getErrorLine(parseResult),
column: hermesParseResult_getErrorColumn(parseResult),
};
throw syntaxError;
}
const deserializer = new HermesParserDeserializer(
hermesParseResult_getProgramBuffer(parseResult),
hermesParseResult_getPositionBuffer(parseResult),
hermesParseResult_getPositionBufferSize(parseResult),
HermesParserWASM,
options,
);
return deserializer.deserialize();
} finally {
hermesParseResult_free(parseResult);
}
} finally {
HermesParserWASM._free(sourceAddr);
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
/**
* Decode a UTF-8 encoded string from Hermes with a known length.
* Based on Emscripten's UTF8ToString with the following differences:
* - Always reads all bytes up to the given length, including null bytes. This
* means that we can decode strings that contain null bytes in the middle.
* - Allow UTF-8 encoded code points that are part of a surrogate pair, even though
* this is technically invalid UTF-8 that UTF8ToString would convert to 0xfffd.
*/
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = HermesParserDecodeUTF8String;
function HermesParserDecodeUTF8String(ptrIn, length, heap) {
let ptr = ptrIn;
const endPtr = ptr + length;
let str = '';
while (ptr < endPtr) {
// ASCII characters fit in single byte code point
let u0 = heap[ptr++];
if (!(u0 & 0x80)) {
str += String.fromCharCode(u0);
continue;
} // Two byte code point
const u1 = heap[ptr++] & 0x3f;
if ((u0 & 0xe0) === 0xc0) {
str += String.fromCharCode((u0 & 0x1f) << 6 | u1);
continue;
}
const u2 = heap[ptr++] & 0x3f;
if ((u0 & 0xf0) === 0xe0) {
// Three byte code point
u0 = (u0 & 0x0f) << 12 | u1 << 6 | u2;
} else {
// Four byte code point
u0 = (u0 & 0x07) << 18 | u1 << 12 | u2 << 6 | heap[ptr++] & 0x3f;
}
if (u0 < 0x10000) {
// Code point fits into a single UTF-16 code unit
str += String.fromCharCode(u0);
} else {
// Code point does not fit into single UTF-16 code unit so convert to surrogate pair
u0 -= 0x10000;
str += String.fromCharCode(0xd800 | u0 >> 10, 0xdc00 | u0 & 0x3ff);
}
}
return str;
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
/**
* Decode a UTF-8 encoded string from Hermes with a known length.
* Based on Emscripten's UTF8ToString with the following differences:
* - Always reads all bytes up to the given length, including null bytes. This
* means that we can decode strings that contain null bytes in the middle.
* - Allow UTF-8 encoded code points that are part of a surrogate pair, even though
* this is technically invalid UTF-8 that UTF8ToString would convert to 0xfffd.
*/
export default function HermesParserDecodeUTF8String(
ptrIn: number,
length: number,
heap: Uint8Array,
): string {
let ptr = ptrIn;
const endPtr = ptr + length;
let str = '';
while (ptr < endPtr) {
// ASCII characters fit in single byte code point
let u0 = heap[ptr++];
if (!(u0 & 0x80)) {
str += String.fromCharCode(u0);
continue;
}
// Two byte code point
const u1 = heap[ptr++] & 0x3f;
if ((u0 & 0xe0) === 0xc0) {
str += String.fromCharCode(((u0 & 0x1f) << 6) | u1);
continue;
}
const u2 = heap[ptr++] & 0x3f;
if ((u0 & 0xf0) === 0xe0) {
// Three byte code point
u0 = ((u0 & 0x0f) << 12) | (u1 << 6) | u2;
} else {
// Four byte code point
u0 = ((u0 & 0x07) << 18) | (u1 << 12) | (u2 << 6) | (heap[ptr++] & 0x3f);
}
if (u0 < 0x10000) {
// Code point fits into a single UTF-16 code unit
str += String.fromCharCode(u0);
} else {
// Code point does not fit into single UTF-16 code unit so convert to surrogate pair
u0 -= 0x10000;
str += String.fromCharCode(0xd800 | (u0 >> 10), 0xdc00 | (u0 & 0x3ff));
}
}
return str;
}

View File

@@ -0,0 +1,243 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _HermesParserDecodeUTF8String = _interopRequireDefault(require("./HermesParserDecodeUTF8String"));
var _HermesParserNodeDeserializers = _interopRequireDefault(require("./HermesParserNodeDeserializers"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class HermesParserDeserializer {
// Matches StoredComment::Kind enum in JSLexer.h
// Matches TokenType enum in HermesParserJSSerializer.h
constructor(programBuffer, positionBuffer, positionBufferSize, wasmParser, options) {
this.programBufferIdx = void 0;
this.positionBufferIdx = void 0;
this.positionBufferSize = void 0;
this.locMap = void 0;
this.HEAPU8 = void 0;
this.HEAPU32 = void 0;
this.HEAPF64 = void 0;
this.options = void 0;
this.commentTypes = ['CommentLine', 'CommentBlock', 'InterpreterDirective'];
this.tokenTypes = ['Boolean', 'Identifier', 'Keyword', 'Null', 'Numeric', 'BigInt', 'Punctuator', 'String', 'RegularExpression', 'Template', 'JSXText'];
// Program and position buffer are memory addresses, so we must convert
// into indices into HEAPU32 (an array of 4-byte integers).
this.programBufferIdx = programBuffer / 4;
this.positionBufferIdx = positionBuffer / 4;
this.positionBufferSize = positionBufferSize;
this.locMap = {};
this.HEAPU8 = wasmParser.HEAPU8;
this.HEAPU32 = wasmParser.HEAPU32;
this.HEAPF64 = wasmParser.HEAPF64;
this.options = options;
}
/**
* Consume and return the next 4 bytes in the program buffer.
*/
next() {
const num = this.HEAPU32[this.programBufferIdx++];
return num;
}
deserialize() {
const program = {
type: 'Program',
loc: this.addEmptyLoc(),
body: this.deserializeNodeList(),
comments: this.deserializeComments()
};
if (this.options.tokens === true) {
program.tokens = this.deserializeTokens();
}
this.fillLocs();
return program;
}
/**
* Booleans are serialized as a single 4-byte integer.
*/
deserializeBoolean() {
return Boolean(this.next());
}
/**
* Numbers are serialized directly into program buffer, taking up 8 bytes
* preceded by 4 bytes of alignment padding if necessary.
*/
deserializeNumber() {
let floatIdx; // Numbers are aligned on 8-byte boundaries, so skip padding if we are at
// an odd index into the 4-byte aligned program buffer.
if (this.programBufferIdx % 2 === 0) {
floatIdx = this.programBufferIdx / 2;
this.programBufferIdx += 2;
} else {
floatIdx = (this.programBufferIdx + 1) / 2;
this.programBufferIdx += 3;
}
return this.HEAPF64[floatIdx];
}
/**
* Strings are serialized as a 4-byte pointer into the heap, followed
* by their size as a 4-byte integer. The size is only present if the
* pointer is non-null.
*/
deserializeString() {
const ptr = this.next();
if (ptr === 0) {
return null;
}
const size = this.next();
return (0, _HermesParserDecodeUTF8String.default)(ptr, size, this.HEAPU8);
}
/**
* Nodes are serialized as a 4-byte integer denoting their node kind,
* followed by a 4-byte loc ID, followed by serialized node properties.
*
* If the node kind is 0 the node is null, otherwise the node kind - 1 is an
* index into the array of node deserialization functions.
*/
deserializeNode() {
const nodeType = this.next();
if (nodeType === 0) {
return null;
}
const nodeDeserializer = _HermesParserNodeDeserializers.default[nodeType - 1].bind(this);
return nodeDeserializer();
}
/**
* Node lists are serialized as a 4-byte integer denoting the number of
* elements in the list, followed by the serialized elements.
*/
deserializeNodeList() {
const size = this.next();
const nodeList = [];
for (let i = 0; i < size; i++) {
nodeList.push(this.deserializeNode());
}
return nodeList;
}
/**
* Comments are serialized as a node list, where each comment is serialized
* as a 4-byte integer denoting comment type, followed by a 4-byte value
* denoting the loc ID, followed by a serialized string for the comment value.
*/
deserializeComments() {
const size = this.next();
const comments = [];
for (let i = 0; i < size; i++) {
const commentType = this.commentTypes[this.next()];
const loc = this.addEmptyLoc();
const value = this.deserializeString();
comments.push({
type: commentType,
loc,
value
});
}
return comments;
}
deserializeTokens() {
const size = this.next();
const tokens = [];
for (let i = 0; i < size; i++) {
const tokenType = this.tokenTypes[this.next()];
const loc = this.addEmptyLoc();
const value = this.deserializeString();
tokens.push({
type: tokenType,
loc,
value
});
}
return tokens;
}
/**
* While deserializing the AST locations are represented by
* a 4-byte loc ID. This is used to create a map of loc IDs to empty loc
* objects that are filled after the AST has been deserialized.
*/
addEmptyLoc() {
// $FlowExpectedError
const loc = {};
this.locMap[this.next()] = loc;
return loc;
}
/**
* Positions are serialized as a loc ID which denotes which loc it is associated with,
* followed by kind which denotes whether it is a start or end position,
* followed by line, column, and offset (4-bytes each).
*/
fillLocs() {
for (let i = 0; i < this.positionBufferSize; i++) {
const locId = this.HEAPU32[this.positionBufferIdx++];
const kind = this.HEAPU32[this.positionBufferIdx++];
const line = this.HEAPU32[this.positionBufferIdx++];
const column = this.HEAPU32[this.positionBufferIdx++];
const offset = this.HEAPU32[this.positionBufferIdx++];
const loc = this.locMap[locId];
if (kind === 0) {
loc.start = {
line,
column
};
loc.rangeStart = offset;
} else {
loc.end = {
line,
column
};
loc.rangeEnd = offset;
}
}
}
}
exports.default = HermesParserDeserializer;

View File

@@ -0,0 +1,261 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
HermesSourceLocation,
HermesNode,
HermesToken,
HermesComment,
} from './HermesAST';
import type {HermesParserWASM} from './HermesParserWASM';
import type {ParserOptions} from './ParserOptions';
import HermesParserDecodeUTF8String from './HermesParserDecodeUTF8String';
import NODE_DESERIALIZERS from './HermesParserNodeDeserializers';
export default class HermesParserDeserializer {
programBufferIdx: number;
positionBufferIdx: number;
+positionBufferSize: number;
+locMap: {[number]: HermesSourceLocation};
+HEAPU8: HermesParserWASM['HEAPU8'];
+HEAPU32: HermesParserWASM['HEAPU32'];
+HEAPF64: HermesParserWASM['HEAPF64'];
+options: ParserOptions;
// Matches StoredComment::Kind enum in JSLexer.h
+commentTypes: $ReadOnlyArray<HermesComment['type']> = [
'CommentLine',
'CommentBlock',
'InterpreterDirective',
];
// Matches TokenType enum in HermesParserJSSerializer.h
+tokenTypes: $ReadOnlyArray<HermesToken['type']> = [
'Boolean',
'Identifier',
'Keyword',
'Null',
'Numeric',
'BigInt',
'Punctuator',
'String',
'RegularExpression',
'Template',
'JSXText',
];
constructor(
programBuffer: number,
positionBuffer: number,
positionBufferSize: number,
wasmParser: HermesParserWASM,
options: ParserOptions,
) {
// Program and position buffer are memory addresses, so we must convert
// into indices into HEAPU32 (an array of 4-byte integers).
this.programBufferIdx = programBuffer / 4;
this.positionBufferIdx = positionBuffer / 4;
this.positionBufferSize = positionBufferSize;
this.locMap = {};
this.HEAPU8 = wasmParser.HEAPU8;
this.HEAPU32 = wasmParser.HEAPU32;
this.HEAPF64 = wasmParser.HEAPF64;
this.options = options;
}
/**
* Consume and return the next 4 bytes in the program buffer.
*/
next(): number {
const num = this.HEAPU32[this.programBufferIdx++];
return num;
}
deserialize(): HermesNode {
const program: HermesNode = {
type: 'Program',
loc: this.addEmptyLoc(),
body: this.deserializeNodeList(),
comments: this.deserializeComments(),
};
if (this.options.tokens === true) {
program.tokens = this.deserializeTokens();
}
this.fillLocs();
return program;
}
/**
* Booleans are serialized as a single 4-byte integer.
*/
deserializeBoolean(): boolean {
return Boolean(this.next());
}
/**
* Numbers are serialized directly into program buffer, taking up 8 bytes
* preceded by 4 bytes of alignment padding if necessary.
*/
deserializeNumber(): number {
let floatIdx;
// Numbers are aligned on 8-byte boundaries, so skip padding if we are at
// an odd index into the 4-byte aligned program buffer.
if (this.programBufferIdx % 2 === 0) {
floatIdx = this.programBufferIdx / 2;
this.programBufferIdx += 2;
} else {
floatIdx = (this.programBufferIdx + 1) / 2;
this.programBufferIdx += 3;
}
return this.HEAPF64[floatIdx];
}
/**
* Strings are serialized as a 4-byte pointer into the heap, followed
* by their size as a 4-byte integer. The size is only present if the
* pointer is non-null.
*/
deserializeString(): ?string {
const ptr = this.next();
if (ptr === 0) {
return null;
}
const size = this.next();
return HermesParserDecodeUTF8String(ptr, size, this.HEAPU8);
}
/**
* Nodes are serialized as a 4-byte integer denoting their node kind,
* followed by a 4-byte loc ID, followed by serialized node properties.
*
* If the node kind is 0 the node is null, otherwise the node kind - 1 is an
* index into the array of node deserialization functions.
*/
deserializeNode(): ?HermesNode {
const nodeType = this.next();
if (nodeType === 0) {
return null;
}
const nodeDeserializer = NODE_DESERIALIZERS[nodeType - 1].bind(this);
return nodeDeserializer();
}
/**
* Node lists are serialized as a 4-byte integer denoting the number of
* elements in the list, followed by the serialized elements.
*/
deserializeNodeList(): Array<?HermesNode> {
const size = this.next();
const nodeList = [];
for (let i = 0; i < size; i++) {
nodeList.push(this.deserializeNode());
}
return nodeList;
}
/**
* Comments are serialized as a node list, where each comment is serialized
* as a 4-byte integer denoting comment type, followed by a 4-byte value
* denoting the loc ID, followed by a serialized string for the comment value.
*/
deserializeComments(): Array<HermesComment> {
const size = this.next();
const comments = [];
for (let i = 0; i < size; i++) {
const commentType = this.commentTypes[this.next()];
const loc = this.addEmptyLoc();
const value = this.deserializeString();
comments.push({
type: commentType,
loc,
value,
});
}
return comments;
}
deserializeTokens(): Array<HermesToken> {
const size = this.next();
const tokens = [];
for (let i = 0; i < size; i++) {
const tokenType = this.tokenTypes[this.next()];
const loc = this.addEmptyLoc();
const value = this.deserializeString();
tokens.push({
type: tokenType,
loc,
value,
});
}
return tokens;
}
/**
* While deserializing the AST locations are represented by
* a 4-byte loc ID. This is used to create a map of loc IDs to empty loc
* objects that are filled after the AST has been deserialized.
*/
addEmptyLoc(): HermesSourceLocation {
// $FlowExpectedError
const loc: HermesSourceLocation = {};
this.locMap[this.next()] = loc;
return loc;
}
/**
* Positions are serialized as a loc ID which denotes which loc it is associated with,
* followed by kind which denotes whether it is a start or end position,
* followed by line, column, and offset (4-bytes each).
*/
fillLocs(): void {
for (let i = 0; i < this.positionBufferSize; i++) {
const locId = this.HEAPU32[this.positionBufferIdx++];
const kind = this.HEAPU32[this.positionBufferIdx++];
const line = this.HEAPU32[this.positionBufferIdx++];
const column = this.HEAPU32[this.positionBufferIdx++];
const offset = this.HEAPU32[this.positionBufferIdx++];
const loc = this.locMap[locId];
if (kind === 0) {
loc.start = {
line,
column,
};
loc.rangeStart = offset;
} else {
loc.end = {
line,
column,
};
loc.rangeEnd = offset;
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import type {HermesNode} from './HermesAST';
import type HermesParserDeserializer from './HermesParserDeserializer';
declare module.exports: $ReadOnlyArray<
(this: HermesParserDeserializer) => HermesNode,
>;

6
node_modules/hermes-parser/dist/HermesParserWASM.js generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
type INTENTIONAL_ANY = $FlowFixMe;
type JSType = 'number' | 'string' | 'array' | 'boolean';
type CBoolean = ?(number | boolean);
type WASMModuleOverrides = $ReadOnly<{
quit(status: number, toThrow: Error): void,
}>;
export type HermesParserWASM = $ReadOnly<{
HEAP8: Int8Array,
HEAP16: Int16Array,
HEAP32: Int32Array,
HEAPU8: Uint8Array,
HEAPU16: Uint16Array,
HEAPU32: Uint32Array,
HEAPF32: Float32Array,
HEAPF64: Float64Array,
_malloc(size: number): number,
_free(ptr: number): void,
ccall(
ident: string,
returnType: JSType | null,
argTypes: $ReadOnlyArray<JSType>,
args: $ReadOnlyArray<
number | string | $ReadOnlyArray<INTENTIONAL_ANY> | boolean,
>,
opts?: $ReadOnly<{
async?: boolean | void,
}>,
): INTENTIONAL_ANY,
cwrap: {
(
'hermesParse',
'number',
['number', 'number', 'number', 'number', 'number', 'number'],
): (number, number, CBoolean, CBoolean, CBoolean, CBoolean) => number,
('hermesParseResult_free', 'void', ['number']): number => void,
('hermesParseResult_getError', 'string', ['number']): number => string,
('hermesParseResult_getErrorLine', 'number', ['number']): number => number,
(
'hermesParseResult_getErrorColumn',
'number',
['number'],
): number => number,
(
'hermesParseResult_getProgramBuffer',
'number',
['number'],
): number => number,
(
'hermesParseResult_getPositionBuffer',
'number',
['number'],
): number => number,
(
'hermesParseResult_getPositionBufferSize',
'number',
['number'],
): number => number,
},
stackAlloc(size: number): number,
stackSave(): number,
stackRestore(ptr: number): void,
}>;
declare module.exports: WASMModuleOverrides => HermesParserWASM;

View File

@@ -0,0 +1,441 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _HermesASTAdapter = _interopRequireDefault(require("./HermesASTAdapter"));
var _getModuleDocblock = require("./getModuleDocblock");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
class HermesToESTreeAdapter extends _HermesASTAdapter.default {
constructor(options, code) {
super(options);
this.code = void 0;
this.code = code;
}
fixSourceLocation(node) {
var _this$sourceFilename;
const loc = node.loc;
if (loc == null) {
return;
}
node.loc = {
source: (_this$sourceFilename = this.sourceFilename) != null ? _this$sourceFilename : null,
start: loc.start,
end: loc.end
};
node.range = [loc.rangeStart, loc.rangeEnd];
delete node.start;
delete node.end;
}
mapNode(node) {
this.fixSourceLocation(node);
switch (node.type) {
case 'Program':
return this.mapProgram(node);
case 'NullLiteral':
return this.mapNullLiteral(node);
case 'BooleanLiteral':
case 'StringLiteral':
case 'NumericLiteral':
case 'JSXStringLiteral':
return this.mapSimpleLiteral(node);
case 'BigIntLiteral':
return this.mapBigIntLiteral(node);
case 'RegExpLiteral':
return this.mapRegExpLiteral(node);
case 'Empty':
return this.mapEmpty(node);
case 'TemplateElement':
return this.mapTemplateElement(node);
case 'BigIntLiteralTypeAnnotation':
return this.mapBigIntLiteralTypeAnnotation(node);
case 'GenericTypeAnnotation':
return this.mapGenericTypeAnnotation(node);
case 'ImportDeclaration':
return this.mapImportDeclaration(node);
case 'ImportSpecifier':
return this.mapImportSpecifier(node);
case 'ExportDefaultDeclaration':
return this.mapExportDefaultDeclaration(node);
case 'ExportNamedDeclaration':
return this.mapExportNamedDeclaration(node);
case 'ExportAllDeclaration':
return this.mapExportAllDeclaration(node);
case 'Property':
return this.mapProperty(node);
case 'FunctionDeclaration':
case 'FunctionExpression':
case 'ArrowFunctionExpression':
return this.mapFunction(node);
case 'PrivateName':
return this.mapPrivateName(node);
case 'ClassProperty':
case 'ClassPrivateProperty':
return this.mapClassProperty(node);
case 'MemberExpression':
case 'OptionalMemberExpression':
case 'CallExpression':
case 'OptionalCallExpression':
return this.mapChainExpression(node);
default:
return this.mapNodeDefault(node);
}
}
mapProgram(node) {
const nodeDefault = this.mapNodeDefault(node);
node.sourceType = this.getSourceType();
node.docblock = (0, _getModuleDocblock.getModuleDocblock)(nodeDefault);
return nodeDefault;
}
mapSimpleLiteral(node) {
return {
type: 'Literal',
loc: node.loc,
range: node.range,
value: node.value,
raw: this.code.slice(node.range[0], node.range[1]),
literalType: (() => {
switch (node.type) {
case 'NullLiteral':
return 'null';
case 'BooleanLiteral':
return 'boolean';
case 'StringLiteral':
case 'JSXStringLiteral':
return 'string';
case 'NumericLiteral':
return 'numeric';
case 'BigIntLiteral':
return 'bigint';
case 'RegExpLiteral':
return 'regexp';
}
return null;
})()
};
}
mapBigIntLiteral(node) {
const newNode = this.mapSimpleLiteral(node);
return { ...newNode,
...this.getBigIntLiteralValue(node.bigint)
};
}
mapNullLiteral(node) {
return { ...this.mapSimpleLiteral(node),
value: null
};
}
mapRegExpLiteral(node) {
const {
pattern,
flags
} = node; // Create RegExp value if possible. This can fail when the flags are invalid.
let value;
try {
value = new RegExp(pattern, flags);
} catch (e) {
value = null;
}
return { ...this.mapSimpleLiteral(node),
value,
regex: {
pattern,
flags
}
};
}
mapBigIntLiteralTypeAnnotation(node) {
return { ...node,
...this.getBigIntLiteralValue(node.raw)
};
}
mapTemplateElement(node) {
return {
type: 'TemplateElement',
loc: node.loc,
range: node.range,
tail: node.tail,
value: {
cooked: node.cooked,
raw: node.raw
}
};
}
mapGenericTypeAnnotation(node) {
// Convert simple `this` generic type to ThisTypeAnnotation
if (node.typeParameters == null && node.id.type === 'Identifier' && node.id.name === 'this') {
return {
type: 'ThisTypeAnnotation',
loc: node.loc,
range: node.range
};
}
return this.mapNodeDefault(node);
}
mapProperty(nodeUnprocessed) {
const node = this.mapNodeDefault(nodeUnprocessed);
if (node.value.type === 'FunctionExpression' && (node.method || node.kind !== 'init')) {
node.value.loc.start = node.key.loc.end;
node.value.range[0] = node.key.range[1];
}
return node;
}
mapComment(node) {
if (node.type === 'CommentBlock') {
node.type = 'Block';
} else if (node.type === 'CommentLine') {
node.type = 'Line';
}
return node;
}
mapFunction(nodeUnprocessed) {
const node = this.mapNodeDefault(nodeUnprocessed);
switch (node.type) {
// This prop should ideally only live on `ArrowFunctionExpression` but to
// match espree output we place it on all functions types.
case 'FunctionDeclaration':
case 'FunctionExpression':
node.expression = false;
return node;
case 'ArrowFunctionExpression':
node.expression = node.body.type !== 'BlockStatement';
return node;
}
return node;
}
mapChainExpression(nodeUnprocessed) {
/*
NOTE - In the below comments `MemberExpression` and `CallExpression`
are completely interchangable. For terseness we just reference
one each time.
*/
/*
Hermes uses the old babel-style AST:
```
(one?.two).three?.four;
^^^^^^^^^^^^^^^^^^^^^^ OptionalMemberExpression
^^^^^^^^^^^^^^^^ MemberExpression
^^^^^^^^ OptionalMemberExpression
```
We need to convert it to the ESTree representation:
```
(one?.two).three?.four;
^^^^^^^^^^^^^^^^^^^^^^ ChainExpression
^^^^^^^^^^^^^^^^^^^^^^ MemberExpression[optional = true]
^^^^^^^^^^^^^^^^ MemberExpression[optional = false]
^^^^^^^^ ChainExpression
^^^^^^^^ MemberExpression[optional = true]
```
We do this by converting the AST and its children (depth first), and then unwrapping
the resulting AST as appropriate.
Put another way:
1) traverse to the leaf
2) if the current node is an `OptionalMemberExpression`:
a) if the `.object` is a `ChainExpression`:
i) unwrap the child (`node.object = child.expression`)
b) convert this node to a `MemberExpression[optional = true]`
c) wrap this node (`node = ChainExpression[expression = node]`)
3) if the current node is a `MemberExpression`:
a) convert this node to a `MemberExpression[optional = true]`
*/
const node = this.mapNodeDefault(nodeUnprocessed);
const {
child,
childKey,
isOptional
} = (() => {
const isOptional = node.optional === true;
if (node.type.endsWith('MemberExpression')) {
return {
child: node.object,
childKey: 'object',
isOptional
};
} else if (node.type.endsWith('CallExpression')) {
return {
child: node.callee,
childKey: 'callee',
isOptional
};
} else {
return {
child: node.expression,
childKey: 'expression',
isOptional: false
};
}
})();
const isChildUnwrappable = child.type === 'ChainExpression' && // (x?.y).z is semantically different to `x?.y.z`.
// In the un-parenthesised case `.z` is only executed if and only if `x?.y` returns a non-nullish value.
// In the parenthesised case, `.z` is **always** executed, regardless of the return of `x?.y`.
// As such the AST is different between the two cases.
//
// In the hermes AST - any member part of a non-short-circuited optional chain is represented with `OptionalMemberExpression`
// so if we see a `MemberExpression`, then we know we've hit a parenthesis boundary.
node.type !== 'MemberExpression' && node.type !== 'CallExpression';
if (node.type.startsWith('Optional')) {
node.type = node.type.replace('Optional', '');
node.optional = isOptional;
} else {
node.optional = false;
}
if (!isChildUnwrappable && !isOptional) {
return node;
}
if (isChildUnwrappable) {
const newChild = child.expression;
node[childKey] = newChild;
}
return {
type: 'ChainExpression',
expression: node,
loc: node.loc,
range: node.range
};
}
mapClassProperty(nodeUnprocessed) {
const node = this.mapNodeDefault(nodeUnprocessed);
const key = (() => {
if (node.type === 'ClassPrivateProperty') {
const key = this.mapNodeDefault(node.key);
return {
type: 'PrivateIdentifier',
name: key.name,
range: key.range,
loc: key.loc
};
}
return node.key;
})();
return { ...node,
computed: node.type === 'ClassPrivateProperty' ? false : node.computed,
key,
type: 'PropertyDefinition'
};
}
mapPrivateName(node) {
return {
type: 'PrivateIdentifier',
name: node.id.name,
// estree the location refers to the entire string including the hash token
range: node.range,
loc: node.loc
};
}
mapExportNamedDeclaration(nodeUnprocessed) {
const node = super.mapExportNamedDeclaration(nodeUnprocessed);
const namespaceSpecifier = node.specifiers.find(spec => spec.type === 'ExportNamespaceSpecifier');
if (namespaceSpecifier != null) {
var _node$exportKind;
if (node.specifiers.length !== 1) {
// this should already a hermes parser error - but let's be absolutely sure we're aligned with the spec
throw new Error('Cannot use an export all with any other specifiers');
}
return {
type: 'ExportAllDeclaration',
source: node.source,
exportKind: (_node$exportKind = node.exportKind) != null ? _node$exportKind : 'value',
exported: namespaceSpecifier.exported,
range: node.range,
loc: node.loc
};
}
return node;
}
mapExportAllDeclaration(nodeUnprocessed) {
var _node$exported;
const node = super.mapExportAllDeclaration(nodeUnprocessed);
node.exported = (_node$exported = node.exported) != null ? _node$exported : null;
return node;
}
}
exports.default = HermesToESTreeAdapter;

View File

@@ -0,0 +1,427 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import type {HermesNode} from './HermesAST';
import type {ParserOptions} from './ParserOptions';
import HermesASTAdapter from './HermesASTAdapter';
import {getModuleDocblock} from './getModuleDocblock';
declare var BigInt: ?(value: $FlowFixMe) => mixed;
export default class HermesToESTreeAdapter extends HermesASTAdapter {
+code: string;
constructor(options: ParserOptions, code: string) {
super(options);
this.code = code;
}
fixSourceLocation(node: HermesNode): void {
const loc = node.loc;
if (loc == null) {
return;
}
node.loc = {
source: this.sourceFilename ?? null,
start: loc.start,
end: loc.end,
};
node.range = [loc.rangeStart, loc.rangeEnd];
delete node.start;
delete node.end;
}
mapNode(node: HermesNode): HermesNode {
this.fixSourceLocation(node);
switch (node.type) {
case 'Program':
return this.mapProgram(node);
case 'NullLiteral':
return this.mapNullLiteral(node);
case 'BooleanLiteral':
case 'StringLiteral':
case 'NumericLiteral':
case 'JSXStringLiteral':
return this.mapSimpleLiteral(node);
case 'BigIntLiteral':
return this.mapBigIntLiteral(node);
case 'RegExpLiteral':
return this.mapRegExpLiteral(node);
case 'Empty':
return this.mapEmpty(node);
case 'TemplateElement':
return this.mapTemplateElement(node);
case 'BigIntLiteralTypeAnnotation':
return this.mapBigIntLiteralTypeAnnotation(node);
case 'GenericTypeAnnotation':
return this.mapGenericTypeAnnotation(node);
case 'ImportDeclaration':
return this.mapImportDeclaration(node);
case 'ImportSpecifier':
return this.mapImportSpecifier(node);
case 'ExportDefaultDeclaration':
return this.mapExportDefaultDeclaration(node);
case 'ExportNamedDeclaration':
return this.mapExportNamedDeclaration(node);
case 'ExportAllDeclaration':
return this.mapExportAllDeclaration(node);
case 'Property':
return this.mapProperty(node);
case 'FunctionDeclaration':
case 'FunctionExpression':
case 'ArrowFunctionExpression':
return this.mapFunction(node);
case 'PrivateName':
return this.mapPrivateName(node);
case 'ClassProperty':
case 'ClassPrivateProperty':
return this.mapClassProperty(node);
case 'MemberExpression':
case 'OptionalMemberExpression':
case 'CallExpression':
case 'OptionalCallExpression':
return this.mapChainExpression(node);
default:
return this.mapNodeDefault(node);
}
}
mapProgram(node: HermesNode): HermesNode {
const nodeDefault = this.mapNodeDefault(node);
node.sourceType = this.getSourceType();
node.docblock = getModuleDocblock(nodeDefault);
return nodeDefault;
}
mapSimpleLiteral(node: HermesNode): HermesNode {
return {
type: 'Literal',
loc: node.loc,
range: node.range,
value: node.value,
raw: this.code.slice(node.range[0], node.range[1]),
literalType: (() => {
switch (node.type) {
case 'NullLiteral':
return 'null';
case 'BooleanLiteral':
return 'boolean';
case 'StringLiteral':
case 'JSXStringLiteral':
return 'string';
case 'NumericLiteral':
return 'numeric';
case 'BigIntLiteral':
return 'bigint';
case 'RegExpLiteral':
return 'regexp';
}
return null;
})(),
};
}
mapBigIntLiteral(node: HermesNode): HermesNode {
const newNode = this.mapSimpleLiteral(node);
return {
...newNode,
...this.getBigIntLiteralValue(node.bigint),
};
}
mapNullLiteral(node: HermesNode): HermesNode {
return {
...this.mapSimpleLiteral(node),
value: null,
};
}
mapRegExpLiteral(node: HermesNode): HermesNode {
const {pattern, flags} = node;
// Create RegExp value if possible. This can fail when the flags are invalid.
let value;
try {
value = new RegExp(pattern, flags);
} catch (e) {
value = null;
}
return {
...this.mapSimpleLiteral(node),
value,
regex: {
pattern,
flags,
},
};
}
mapBigIntLiteralTypeAnnotation(node: HermesNode): HermesNode {
return {
...node,
...this.getBigIntLiteralValue(node.raw),
};
}
mapTemplateElement(node: HermesNode): HermesNode {
return {
type: 'TemplateElement',
loc: node.loc,
range: node.range,
tail: node.tail,
value: {
cooked: node.cooked,
raw: node.raw,
},
};
}
mapGenericTypeAnnotation(node: HermesNode): HermesNode {
// Convert simple `this` generic type to ThisTypeAnnotation
if (
node.typeParameters == null &&
node.id.type === 'Identifier' &&
node.id.name === 'this'
) {
return {
type: 'ThisTypeAnnotation',
loc: node.loc,
range: node.range,
};
}
return this.mapNodeDefault(node);
}
mapProperty(nodeUnprocessed: HermesNode): HermesNode {
const node = this.mapNodeDefault(nodeUnprocessed);
if (
node.value.type === 'FunctionExpression' &&
(node.method || node.kind !== 'init')
) {
node.value.loc.start = node.key.loc.end;
node.value.range[0] = node.key.range[1];
}
return node;
}
mapComment(node: HermesNode): HermesNode {
if (node.type === 'CommentBlock') {
node.type = 'Block';
} else if (node.type === 'CommentLine') {
node.type = 'Line';
}
return node;
}
mapFunction(nodeUnprocessed: HermesNode): HermesNode {
const node = this.mapNodeDefault(nodeUnprocessed);
switch (node.type) {
// This prop should ideally only live on `ArrowFunctionExpression` but to
// match espree output we place it on all functions types.
case 'FunctionDeclaration':
case 'FunctionExpression':
node.expression = false;
return node;
case 'ArrowFunctionExpression':
node.expression = node.body.type !== 'BlockStatement';
return node;
}
return node;
}
mapChainExpression(nodeUnprocessed: HermesNode): HermesNode {
/*
NOTE - In the below comments `MemberExpression` and `CallExpression`
are completely interchangable. For terseness we just reference
one each time.
*/
/*
Hermes uses the old babel-style AST:
```
(one?.two).three?.four;
^^^^^^^^^^^^^^^^^^^^^^ OptionalMemberExpression
^^^^^^^^^^^^^^^^ MemberExpression
^^^^^^^^ OptionalMemberExpression
```
We need to convert it to the ESTree representation:
```
(one?.two).three?.four;
^^^^^^^^^^^^^^^^^^^^^^ ChainExpression
^^^^^^^^^^^^^^^^^^^^^^ MemberExpression[optional = true]
^^^^^^^^^^^^^^^^ MemberExpression[optional = false]
^^^^^^^^ ChainExpression
^^^^^^^^ MemberExpression[optional = true]
```
We do this by converting the AST and its children (depth first), and then unwrapping
the resulting AST as appropriate.
Put another way:
1) traverse to the leaf
2) if the current node is an `OptionalMemberExpression`:
a) if the `.object` is a `ChainExpression`:
i) unwrap the child (`node.object = child.expression`)
b) convert this node to a `MemberExpression[optional = true]`
c) wrap this node (`node = ChainExpression[expression = node]`)
3) if the current node is a `MemberExpression`:
a) convert this node to a `MemberExpression[optional = true]`
*/
const node = this.mapNodeDefault(nodeUnprocessed);
const {child, childKey, isOptional} = ((): {
child: HermesNode,
childKey: string,
isOptional: boolean,
} => {
const isOptional: boolean = node.optional === true;
if (node.type.endsWith('MemberExpression')) {
return {
child: node.object,
childKey: 'object',
isOptional,
};
} else if (node.type.endsWith('CallExpression')) {
return {
child: node.callee,
childKey: 'callee',
isOptional,
};
} else {
return {
child: node.expression,
childKey: 'expression',
isOptional: false,
};
}
})();
const isChildUnwrappable =
child.type === 'ChainExpression' &&
// (x?.y).z is semantically different to `x?.y.z`.
// In the un-parenthesised case `.z` is only executed if and only if `x?.y` returns a non-nullish value.
// In the parenthesised case, `.z` is **always** executed, regardless of the return of `x?.y`.
// As such the AST is different between the two cases.
//
// In the hermes AST - any member part of a non-short-circuited optional chain is represented with `OptionalMemberExpression`
// so if we see a `MemberExpression`, then we know we've hit a parenthesis boundary.
node.type !== 'MemberExpression' &&
node.type !== 'CallExpression';
if (node.type.startsWith('Optional')) {
node.type = node.type.replace('Optional', '');
node.optional = isOptional;
} else {
node.optional = false;
}
if (!isChildUnwrappable && !isOptional) {
return node;
}
if (isChildUnwrappable) {
const newChild = child.expression;
node[childKey] = newChild;
}
return {
type: 'ChainExpression',
expression: node,
loc: node.loc,
range: node.range,
};
}
mapClassProperty(nodeUnprocessed: HermesNode): HermesNode {
const node = this.mapNodeDefault(nodeUnprocessed);
const key = (() => {
if (node.type === 'ClassPrivateProperty') {
const key = this.mapNodeDefault(node.key);
return {
type: 'PrivateIdentifier',
name: key.name,
range: key.range,
loc: key.loc,
};
}
return node.key;
})();
return {
...node,
computed: node.type === 'ClassPrivateProperty' ? false : node.computed,
key,
type: 'PropertyDefinition',
};
}
mapPrivateName(node: HermesNode): HermesNode {
return {
type: 'PrivateIdentifier',
name: node.id.name,
// estree the location refers to the entire string including the hash token
range: node.range,
loc: node.loc,
};
}
mapExportNamedDeclaration(nodeUnprocessed: HermesNode): HermesNode {
const node = super.mapExportNamedDeclaration(nodeUnprocessed);
const namespaceSpecifier = node.specifiers.find(
spec => spec.type === 'ExportNamespaceSpecifier',
);
if (namespaceSpecifier != null) {
if (node.specifiers.length !== 1) {
// this should already a hermes parser error - but let's be absolutely sure we're aligned with the spec
throw new Error('Cannot use an export all with any other specifiers');
}
return {
type: 'ExportAllDeclaration',
source: node.source,
exportKind: node.exportKind ?? 'value',
exported: namespaceSpecifier.exported,
range: node.range,
loc: node.loc,
};
}
return node;
}
mapExportAllDeclaration(nodeUnprocessed: HermesNode): HermesNode {
const node = super.mapExportAllDeclaration(nodeUnprocessed);
node.exported = node.exported ?? null;
return node;
}
}

18
node_modules/hermes-parser/dist/ParserOptions.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ParserOptionsKeys = void 0;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
const ParserOptionsKeys = new Set(['allowReturnOutsideFunction', 'babel', 'flow', 'enableExperimentalComponentSyntax', 'reactRuntimeTarget', 'sourceFilename', 'sourceType', 'tokens']);
exports.ParserOptionsKeys = ParserOptionsKeys;

31
node_modules/hermes-parser/dist/ParserOptions.js.flow generated vendored Normal file
View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
export type ParserOptions = {
allowReturnOutsideFunction?: boolean,
babel?: boolean,
flow?: 'all' | 'detect',
enableExperimentalComponentSyntax?: boolean,
reactRuntimeTarget?: '18' | '19',
sourceFilename?: string,
sourceType?: 'module' | 'script' | 'unambiguous',
tokens?: boolean,
};
export const ParserOptionsKeys: $ReadOnlySet<$Keys<ParserOptions>> = new Set([
'allowReturnOutsideFunction',
'babel',
'flow',
'enableExperimentalComponentSyntax',
'reactRuntimeTarget',
'sourceFilename',
'sourceType',
'tokens',
]);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,788 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
/**
* This transforms component syntax (https://flow.org/en/docs/react/component-syntax/)
* and hook syntax (https://flow.org/en/docs/react/hook-syntax/).
*
* It is expected that all transforms create valid ESTree AST output. If
* the transform requires outputting Babel specific AST nodes then it
* should live in `ConvertESTreeToBabel.js`
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.transformProgram = transformProgram;
var _SimpleTransform = require("../transform/SimpleTransform");
var _astNodeMutationHelpers = require("../transform/astNodeMutationHelpers");
var _SimpleTraverser = require("../traverse/SimpleTraverser");
var _createSyntaxError = require("../utils/createSyntaxError");
const nodeWith = _SimpleTransform.SimpleTransform.nodeWith; // Rely on the mapper to fix up parent relationships.
const EMPTY_PARENT = null;
function createDefaultPosition() {
return {
line: 1,
column: 0
};
}
function mapDeclareComponent(node) {
return {
type: 'DeclareVariable',
id: nodeWith(node.id, {
typeAnnotation: {
type: 'TypeAnnotation',
typeAnnotation: {
type: 'AnyTypeAnnotation',
loc: node.loc,
range: node.range,
parent: EMPTY_PARENT
},
loc: node.loc,
range: node.range,
parent: EMPTY_PARENT
}
}),
kind: 'const',
loc: node.loc,
range: node.range,
parent: node.parent
};
}
function getComponentParameterName(paramName) {
switch (paramName.type) {
case 'Identifier':
return paramName.name;
case 'Literal':
return paramName.value;
default:
throw (0, _createSyntaxError.createSyntaxError)(paramName, `Unknown Component parameter name type of "${paramName.type}"`);
}
}
function createPropsTypeAnnotation(propTypes, spread, loc, range) {
// Create empty loc for type annotation nodes
const createParamsTypeLoc = () => ({
loc: {
start: (loc == null ? void 0 : loc.start) != null ? loc.start : createDefaultPosition(),
end: (loc == null ? void 0 : loc.end) != null ? loc.end : createDefaultPosition()
},
range: range != null ? range : [0, 0],
parent: EMPTY_PARENT
}); // Optimize `{...Props}` -> `Props`
if (spread != null && propTypes.length === 0) {
return {
type: 'TypeAnnotation',
typeAnnotation: spread.argument,
...createParamsTypeLoc()
};
}
const typeProperties = [...propTypes];
if (spread != null) {
// Spread needs to be the first type, as inline properties take precedence.
typeProperties.unshift(spread);
}
const propTypeObj = {
type: 'ObjectTypeAnnotation',
callProperties: [],
properties: typeProperties,
indexers: [],
internalSlots: [],
exact: false,
inexact: false,
...createParamsTypeLoc()
};
return {
type: 'TypeAnnotation',
typeAnnotation: {
type: 'GenericTypeAnnotation',
id: {
type: 'Identifier',
name: '$ReadOnly',
optional: false,
typeAnnotation: null,
...createParamsTypeLoc()
},
typeParameters: {
type: 'TypeParameterInstantiation',
params: [propTypeObj],
...createParamsTypeLoc()
},
...createParamsTypeLoc()
},
...createParamsTypeLoc()
};
}
function mapComponentParameters(params, options) {
var _options$reactRuntime;
if (params.length === 0) {
return {
props: null,
ref: null
};
} // Optimize `component Foo(...props: Props) {}` to `function Foo(props: Props) {}
if (params.length === 1 && params[0].type === 'RestElement' && params[0].argument.type === 'Identifier') {
const restElementArgument = params[0].argument;
return {
props: restElementArgument,
ref: null
};
} // Filter out any ref param and capture it's details when targeting React 18.
// React 19+ treats ref as a regular prop for function components.
let refParam = null;
const paramsWithoutRef = ((_options$reactRuntime = options.reactRuntimeTarget) != null ? _options$reactRuntime : '18') === '18' ? params.filter(param => {
if (param.type === 'ComponentParameter' && getComponentParameterName(param.name) === 'ref') {
refParam = param;
return false;
}
return true;
}) : params;
const [propTypes, spread] = paramsWithoutRef.reduce(([propTypes, spread], param) => {
switch (param.type) {
case 'RestElement':
{
if (spread != null) {
throw (0, _createSyntaxError.createSyntaxError)(param, `Invalid state, multiple rest elements found as Component Parameters`);
}
return [propTypes, mapComponentParameterRestElementType(param)];
}
case 'ComponentParameter':
{
propTypes.push(mapComponentParameterType(param));
return [propTypes, spread];
}
}
}, [[], null]);
const propsProperties = paramsWithoutRef.flatMap(mapComponentParameter);
let props = null;
if (propsProperties.length === 0) {
if (refParam == null) {
throw new Error('StripComponentSyntax: Invalid state, ref should always be set at this point if props are empty');
}
const emptyParamsLoc = {
start: refParam.loc.start,
end: refParam.loc.start
};
const emptyParamsRange = [refParam.range[0], refParam.range[0]]; // no properties provided (must have had a single ref)
props = {
type: 'Identifier',
name: '_$$empty_props_placeholder$$',
optional: false,
typeAnnotation: createPropsTypeAnnotation([], null, emptyParamsLoc, emptyParamsRange),
loc: emptyParamsLoc,
range: emptyParamsRange,
parent: EMPTY_PARENT
};
} else {
const lastPropsProperty = propsProperties[propsProperties.length - 1];
props = {
type: 'ObjectPattern',
properties: propsProperties,
typeAnnotation: createPropsTypeAnnotation(propTypes, spread, {
start: lastPropsProperty.loc.end,
end: lastPropsProperty.loc.end
}, [lastPropsProperty.range[1], lastPropsProperty.range[1]]),
loc: {
start: propsProperties[0].loc.start,
end: lastPropsProperty.loc.end
},
range: [propsProperties[0].range[0], lastPropsProperty.range[1]],
parent: EMPTY_PARENT
};
}
let ref = null;
if (refParam != null) {
ref = refParam.local;
}
return {
props,
ref
};
}
function mapComponentParameterType(param) {
var _typeAnnotation$typeA;
const typeAnnotation = param.local.type === 'AssignmentPattern' ? param.local.left.typeAnnotation : param.local.typeAnnotation;
const optional = param.local.type === 'AssignmentPattern' ? true : param.local.type === 'Identifier' ? param.local.optional : false;
return {
type: 'ObjectTypeProperty',
key: (0, _astNodeMutationHelpers.shallowCloneNode)(param.name),
value: (_typeAnnotation$typeA = typeAnnotation == null ? void 0 : typeAnnotation.typeAnnotation) != null ? _typeAnnotation$typeA : {
type: 'AnyTypeAnnotation',
loc: param.local.loc,
range: param.local.range,
parent: EMPTY_PARENT
},
kind: 'init',
optional,
method: false,
static: false,
proto: false,
variance: null,
loc: param.local.loc,
range: param.local.range,
parent: EMPTY_PARENT
};
}
function mapComponentParameterRestElementType(param) {
var _param$argument$typeA, _param$argument$typeA2;
if (param.argument.type !== 'Identifier' && param.argument.type !== 'ObjectPattern') {
throw (0, _createSyntaxError.createSyntaxError)(param, `Invalid ${param.argument.type} encountered in restParameter`);
}
return {
type: 'ObjectTypeSpreadProperty',
argument: (_param$argument$typeA = (_param$argument$typeA2 = param.argument.typeAnnotation) == null ? void 0 : _param$argument$typeA2.typeAnnotation) != null ? _param$argument$typeA : {
type: 'AnyTypeAnnotation',
loc: param.loc,
range: param.range,
parent: EMPTY_PARENT
},
loc: param.loc,
range: param.range,
parent: EMPTY_PARENT
};
}
function mapComponentParameter(param) {
switch (param.type) {
case 'RestElement':
{
switch (param.argument.type) {
case 'Identifier':
{
const a = nodeWith(param, {
typeAnnotation: null,
argument: nodeWith(param.argument, {
typeAnnotation: null
})
});
return [a];
}
case 'ObjectPattern':
{
return param.argument.properties.map(property => {
return nodeWith(property, {
typeAnnotation: null
});
});
}
default:
{
throw (0, _createSyntaxError.createSyntaxError)(param, `Unhandled ${param.argument.type} encountered in restParameter`);
}
}
}
case 'ComponentParameter':
{
let value;
if (param.local.type === 'AssignmentPattern') {
value = nodeWith(param.local, {
left: nodeWith(param.local.left, {
typeAnnotation: null,
optional: false
})
});
} else {
value = nodeWith(param.local, {
typeAnnotation: null,
optional: false
});
} // Shorthand params
if (param.name.type === 'Identifier' && param.shorthand && (value.type === 'Identifier' || value.type === 'AssignmentPattern')) {
return [{
type: 'Property',
key: param.name,
kind: 'init',
value,
method: false,
shorthand: true,
computed: false,
loc: param.loc,
range: param.range,
parent: EMPTY_PARENT
}];
} // Complex params
return [{
type: 'Property',
key: param.name,
kind: 'init',
value,
method: false,
shorthand: false,
computed: false,
loc: param.loc,
range: param.range,
parent: EMPTY_PARENT
}];
}
default:
{
throw (0, _createSyntaxError.createSyntaxError)(param, `Unknown Component parameter type of "${param.type}"`);
}
}
}
function createForwardRefWrapper(originalComponent) {
const internalCompId = {
type: 'Identifier',
name: `${originalComponent.id.name}_withRef`,
optional: false,
typeAnnotation: null,
loc: originalComponent.id.loc,
range: originalComponent.id.range,
parent: EMPTY_PARENT
};
return {
forwardRefStatement: {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
type: 'VariableDeclarator',
id: (0, _astNodeMutationHelpers.shallowCloneNode)(originalComponent.id),
init: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'React',
optional: false,
typeAnnotation: null,
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT
},
property: {
type: 'Identifier',
name: 'forwardRef',
optional: false,
typeAnnotation: null,
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT
},
computed: false,
optional: false,
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT
},
arguments: [(0, _astNodeMutationHelpers.shallowCloneNode)(internalCompId)],
typeArguments: null,
optional: false,
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT
},
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT
}],
loc: originalComponent.loc,
range: originalComponent.range,
parent: originalComponent.parent
},
internalCompId: internalCompId,
forwardRefCompId: originalComponent.id
};
}
function mapComponentDeclaration(node, options) {
// Create empty loc for return type annotation nodes
const createRendersTypeLoc = () => ({
loc: {
start: node.body.loc.end,
end: node.body.loc.end
},
range: [node.body.range[1], node.body.range[1]],
parent: EMPTY_PARENT
});
const returnType = {
type: 'TypeAnnotation',
typeAnnotation: {
type: 'GenericTypeAnnotation',
id: {
type: 'QualifiedTypeIdentifier',
qualification: {
type: 'Identifier',
name: 'React',
optional: false,
typeAnnotation: null,
...createRendersTypeLoc()
},
id: {
type: 'Identifier',
name: 'Node',
optional: false,
typeAnnotation: null,
...createRendersTypeLoc()
},
...createRendersTypeLoc()
},
typeParameters: null,
...createRendersTypeLoc()
},
...createRendersTypeLoc()
};
const {
props,
ref
} = mapComponentParameters(node.params, options);
let forwardRefDetails = null;
if (ref != null) {
forwardRefDetails = createForwardRefWrapper(node);
}
const comp = {
type: 'FunctionDeclaration',
id: forwardRefDetails != null ? (0, _astNodeMutationHelpers.shallowCloneNode)(forwardRefDetails.internalCompId) : (0, _astNodeMutationHelpers.shallowCloneNode)(node.id),
__componentDeclaration: true,
typeParameters: node.typeParameters,
params: props == null ? [] : ref == null ? [props] : [props, ref],
returnType,
body: node.body,
async: false,
generator: false,
predicate: null,
loc: node.loc,
range: node.range,
parent: node.parent
};
return {
comp,
forwardRefDetails
};
}
function mapDeclareHook(node) {
return {
type: 'DeclareFunction',
id: {
type: 'Identifier',
name: node.id.name,
optional: node.id.optional,
typeAnnotation: {
type: 'TypeAnnotation',
typeAnnotation: {
type: 'FunctionTypeAnnotation',
this: null,
params: node.id.typeAnnotation.typeAnnotation.params,
typeParameters: node.id.typeAnnotation.typeAnnotation.typeParameters,
rest: node.id.typeAnnotation.typeAnnotation.rest,
returnType: node.id.typeAnnotation.typeAnnotation.returnType,
loc: node.id.typeAnnotation.typeAnnotation.loc,
range: node.id.typeAnnotation.typeAnnotation.range,
parent: node.id.typeAnnotation.typeAnnotation.parent
},
loc: node.id.typeAnnotation.loc,
range: node.id.typeAnnotation.range,
parent: node.id.typeAnnotation.parent
},
loc: node.id.loc,
range: node.id.range,
parent: node.id.parent
},
loc: node.loc,
range: node.range,
parent: node.parent,
predicate: null
};
}
function mapHookDeclaration(node) {
const comp = {
type: 'FunctionDeclaration',
id: node.id && (0, _astNodeMutationHelpers.shallowCloneNode)(node.id),
__hookDeclaration: true,
typeParameters: node.typeParameters,
params: node.params,
returnType: node.returnType,
body: node.body,
async: false,
generator: false,
predicate: null,
loc: node.loc,
range: node.range,
parent: node.parent
};
return comp;
}
/**
* Scan a list of statements and return the position of the
* first statement that contains a reference to a given component
* or null of no references were found.
*/
function scanForFirstComponentReference(compName, bodyList) {
for (let i = 0; i < bodyList.length; i++) {
const bodyNode = bodyList[i];
let referencePos = null;
_SimpleTraverser.SimpleTraverser.traverse(bodyNode, {
enter(node) {
switch (node.type) {
case 'Identifier':
{
if (node.name === compName) {
// We found a reference, record it and stop.
referencePos = i;
throw _SimpleTraverser.SimpleTraverser.Break;
}
}
}
},
leave(_node) {}
});
if (referencePos != null) {
return referencePos;
}
}
return null;
}
function mapComponentDeclarationIntoList(node, newBody, options, insertExport) {
const {
comp,
forwardRefDetails
} = mapComponentDeclaration(node, options);
if (forwardRefDetails != null) {
// Scan for references to our component.
const referencePos = scanForFirstComponentReference(forwardRefDetails.forwardRefCompId.name, newBody); // If a reference is found insert the forwardRef statement before it (to simulate function hoisting).
if (referencePos != null) {
newBody.splice(referencePos, 0, forwardRefDetails.forwardRefStatement);
} else {
newBody.push(forwardRefDetails.forwardRefStatement);
}
newBody.push(comp);
if (insertExport != null) {
newBody.push(insertExport(forwardRefDetails.forwardRefCompId));
}
return;
}
newBody.push(insertExport != null ? insertExport(comp) : comp);
}
function mapStatementList(stmts, options) {
const newBody = [];
for (const node of stmts) {
switch (node.type) {
case 'ComponentDeclaration':
{
mapComponentDeclarationIntoList(node, newBody, options);
break;
}
case 'HookDeclaration':
{
const decl = mapHookDeclaration(node);
newBody.push(decl);
break;
}
case 'ExportNamedDeclaration':
{
var _node$declaration, _node$declaration2;
if (((_node$declaration = node.declaration) == null ? void 0 : _node$declaration.type) === 'ComponentDeclaration') {
mapComponentDeclarationIntoList(node.declaration, newBody, options, componentOrRef => {
switch (componentOrRef.type) {
case 'FunctionDeclaration':
{
// No ref, so we can export the component directly.
return nodeWith(node, {
declaration: componentOrRef
});
}
case 'Identifier':
{
// If a ref is inserted, we should just export that id.
return {
type: 'ExportNamedDeclaration',
declaration: null,
specifiers: [{
type: 'ExportSpecifier',
exported: (0, _astNodeMutationHelpers.shallowCloneNode)(componentOrRef),
local: (0, _astNodeMutationHelpers.shallowCloneNode)(componentOrRef),
loc: node.loc,
range: node.range,
parent: EMPTY_PARENT
}],
exportKind: 'value',
source: null,
loc: node.loc,
range: node.range,
parent: node.parent
};
}
}
});
break;
}
if (((_node$declaration2 = node.declaration) == null ? void 0 : _node$declaration2.type) === 'HookDeclaration') {
const comp = mapHookDeclaration(node.declaration);
newBody.push(nodeWith(node, {
declaration: comp
}));
break;
}
newBody.push(node);
break;
}
case 'ExportDefaultDeclaration':
{
var _node$declaration3, _node$declaration4;
if (((_node$declaration3 = node.declaration) == null ? void 0 : _node$declaration3.type) === 'ComponentDeclaration') {
mapComponentDeclarationIntoList(node.declaration, newBody, options, componentOrRef => nodeWith(node, {
declaration: componentOrRef
}));
break;
}
if (((_node$declaration4 = node.declaration) == null ? void 0 : _node$declaration4.type) === 'HookDeclaration') {
const comp = mapHookDeclaration(node.declaration);
newBody.push(nodeWith(node, {
declaration: comp
}));
break;
}
newBody.push(node);
break;
}
default:
{
newBody.push(node);
}
}
}
return newBody;
}
function transformProgram(program, options) {
return _SimpleTransform.SimpleTransform.transformProgram(program, {
transform(node) {
switch (node.type) {
case 'DeclareComponent':
{
return mapDeclareComponent(node);
}
case 'DeclareHook':
{
return mapDeclareHook(node);
}
case 'Program':
case 'BlockStatement':
{
return nodeWith(node, {
body: mapStatementList(node.body, options)
});
}
case 'SwitchCase':
{
const consequent = mapStatementList(node.consequent, options);
return nodeWith(node, {
/* $FlowExpectedError[incompatible-call] We know `mapStatementList` will
not return `ModuleDeclaration` nodes if it is not passed any */
consequent
});
}
case 'ComponentDeclaration':
{
var _node$parent;
throw (0, _createSyntaxError.createSyntaxError)(node, `Components must be defined at the top level of a module or within a ` + `BlockStatement, instead got parent of "${(_node$parent = node.parent) == null ? void 0 : _node$parent.type}".`);
}
case 'HookDeclaration':
{
var _node$parent2;
throw (0, _createSyntaxError.createSyntaxError)(node, `Hooks must be defined at the top level of a module or within a ` + `BlockStatement, instead got parent of "${(_node$parent2 = node.parent) == null ? void 0 : _node$parent2.type}".`);
}
default:
{
return node;
}
}
}
});
}

View File

@@ -0,0 +1,863 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
/**
* This transforms component syntax (https://flow.org/en/docs/react/component-syntax/)
* and hook syntax (https://flow.org/en/docs/react/hook-syntax/).
*
* It is expected that all transforms create valid ESTree AST output. If
* the transform requires outputting Babel specific AST nodes then it
* should live in `ConvertESTreeToBabel.js`
*/
'use strict';
import type {ParserOptions} from '../ParserOptions';
import type {
Program,
ESNode,
DeclareComponent,
DeclareVariable,
ComponentDeclaration,
FunctionDeclaration,
TypeAnnotation,
ComponentParameter,
SourceLocation,
Position,
ObjectPattern,
Identifier,
Range,
RestElement,
DestructuringObjectProperty,
VariableDeclaration,
ModuleDeclaration,
DeclareHook,
DeclareFunction,
HookDeclaration,
Statement,
AssignmentPattern,
BindingName,
ObjectTypePropertySignature,
ObjectTypeSpreadProperty,
} from 'hermes-estree';
import {SimpleTransform} from '../transform/SimpleTransform';
import {shallowCloneNode} from '../transform/astNodeMutationHelpers';
import {SimpleTraverser} from '../traverse/SimpleTraverser';
import {createSyntaxError} from '../utils/createSyntaxError';
const nodeWith = SimpleTransform.nodeWith;
// Rely on the mapper to fix up parent relationships.
const EMPTY_PARENT: $FlowFixMe = null;
function createDefaultPosition(): Position {
return {
line: 1,
column: 0,
};
}
function mapDeclareComponent(node: DeclareComponent): DeclareVariable {
return {
type: 'DeclareVariable',
id: nodeWith(node.id, {
typeAnnotation: {
type: 'TypeAnnotation',
typeAnnotation: {
type: 'AnyTypeAnnotation',
loc: node.loc,
range: node.range,
parent: EMPTY_PARENT,
},
loc: node.loc,
range: node.range,
parent: EMPTY_PARENT,
},
}),
kind: 'const',
loc: node.loc,
range: node.range,
parent: node.parent,
};
}
function getComponentParameterName(
paramName: ComponentParameter['name'],
): string {
switch (paramName.type) {
case 'Identifier':
return paramName.name;
case 'Literal':
return paramName.value;
default:
throw createSyntaxError(
paramName,
`Unknown Component parameter name type of "${paramName.type}"`,
);
}
}
function createPropsTypeAnnotation(
propTypes: Array<ObjectTypePropertySignature>,
spread: ?ObjectTypeSpreadProperty,
loc: ?SourceLocation,
range: ?Range,
): TypeAnnotation {
// Create empty loc for type annotation nodes
const createParamsTypeLoc = () => ({
loc: {
start: loc?.start != null ? loc.start : createDefaultPosition(),
end: loc?.end != null ? loc.end : createDefaultPosition(),
},
range: range ?? [0, 0],
parent: EMPTY_PARENT,
});
// Optimize `{...Props}` -> `Props`
if (spread != null && propTypes.length === 0) {
return {
type: 'TypeAnnotation',
typeAnnotation: spread.argument,
...createParamsTypeLoc(),
};
}
const typeProperties: Array<
ObjectTypePropertySignature | ObjectTypeSpreadProperty,
> = [...propTypes];
if (spread != null) {
// Spread needs to be the first type, as inline properties take precedence.
typeProperties.unshift(spread);
}
const propTypeObj = {
type: 'ObjectTypeAnnotation',
callProperties: [],
properties: typeProperties,
indexers: [],
internalSlots: [],
exact: false,
inexact: false,
...createParamsTypeLoc(),
};
return {
type: 'TypeAnnotation',
typeAnnotation: {
type: 'GenericTypeAnnotation',
id: {
type: 'Identifier',
name: '$ReadOnly',
optional: false,
typeAnnotation: null,
...createParamsTypeLoc(),
},
typeParameters: {
type: 'TypeParameterInstantiation',
params: [propTypeObj],
...createParamsTypeLoc(),
},
...createParamsTypeLoc(),
},
...createParamsTypeLoc(),
};
}
function mapComponentParameters(
params: $ReadOnlyArray<ComponentParameter | RestElement>,
options: ParserOptions,
): $ReadOnly<{
props: ?(ObjectPattern | Identifier),
ref: ?(BindingName | AssignmentPattern),
}> {
if (params.length === 0) {
return {props: null, ref: null};
}
// Optimize `component Foo(...props: Props) {}` to `function Foo(props: Props) {}
if (
params.length === 1 &&
params[0].type === 'RestElement' &&
params[0].argument.type === 'Identifier'
) {
const restElementArgument = params[0].argument;
return {
props: restElementArgument,
ref: null,
};
}
// Filter out any ref param and capture it's details when targeting React 18.
// React 19+ treats ref as a regular prop for function components.
let refParam = null;
const paramsWithoutRef =
(options.reactRuntimeTarget ?? '18') === '18'
? params.filter(param => {
if (
param.type === 'ComponentParameter' &&
getComponentParameterName(param.name) === 'ref'
) {
refParam = param;
return false;
}
return true;
})
: params;
const [propTypes, spread] = paramsWithoutRef.reduce<
[Array<ObjectTypePropertySignature>, ?ObjectTypeSpreadProperty],
>(
([propTypes, spread], param) => {
switch (param.type) {
case 'RestElement': {
if (spread != null) {
throw createSyntaxError(
param,
`Invalid state, multiple rest elements found as Component Parameters`,
);
}
return [propTypes, mapComponentParameterRestElementType(param)];
}
case 'ComponentParameter': {
propTypes.push(mapComponentParameterType(param));
return [propTypes, spread];
}
}
},
[[], null],
);
const propsProperties = paramsWithoutRef.flatMap(mapComponentParameter);
let props = null;
if (propsProperties.length === 0) {
if (refParam == null) {
throw new Error(
'StripComponentSyntax: Invalid state, ref should always be set at this point if props are empty',
);
}
const emptyParamsLoc = {
start: refParam.loc.start,
end: refParam.loc.start,
};
const emptyParamsRange = [refParam.range[0], refParam.range[0]];
// no properties provided (must have had a single ref)
props = {
type: 'Identifier',
name: '_$$empty_props_placeholder$$',
optional: false,
typeAnnotation: createPropsTypeAnnotation(
[],
null,
emptyParamsLoc,
emptyParamsRange,
),
loc: emptyParamsLoc,
range: emptyParamsRange,
parent: EMPTY_PARENT,
};
} else {
const lastPropsProperty = propsProperties[propsProperties.length - 1];
props = {
type: 'ObjectPattern',
properties: propsProperties,
typeAnnotation: createPropsTypeAnnotation(
propTypes,
spread,
{
start: lastPropsProperty.loc.end,
end: lastPropsProperty.loc.end,
},
[lastPropsProperty.range[1], lastPropsProperty.range[1]],
),
loc: {
start: propsProperties[0].loc.start,
end: lastPropsProperty.loc.end,
},
range: [propsProperties[0].range[0], lastPropsProperty.range[1]],
parent: EMPTY_PARENT,
};
}
let ref = null;
if (refParam != null) {
ref = refParam.local;
}
return {
props,
ref,
};
}
function mapComponentParameterType(
param: ComponentParameter,
): ObjectTypePropertySignature {
const typeAnnotation =
param.local.type === 'AssignmentPattern'
? param.local.left.typeAnnotation
: param.local.typeAnnotation;
const optional =
param.local.type === 'AssignmentPattern'
? true
: param.local.type === 'Identifier'
? param.local.optional
: false;
return {
type: 'ObjectTypeProperty',
key: shallowCloneNode(param.name),
value: typeAnnotation?.typeAnnotation ?? {
type: 'AnyTypeAnnotation',
loc: param.local.loc,
range: param.local.range,
parent: EMPTY_PARENT,
},
kind: 'init',
optional,
method: false,
static: false,
proto: false,
variance: null,
loc: param.local.loc,
range: param.local.range,
parent: EMPTY_PARENT,
};
}
function mapComponentParameterRestElementType(
param: RestElement,
): ObjectTypeSpreadProperty {
if (
param.argument.type !== 'Identifier' &&
param.argument.type !== 'ObjectPattern'
) {
throw createSyntaxError(
param,
`Invalid ${param.argument.type} encountered in restParameter`,
);
}
return {
type: 'ObjectTypeSpreadProperty',
argument: param.argument.typeAnnotation?.typeAnnotation ?? {
type: 'AnyTypeAnnotation',
loc: param.loc,
range: param.range,
parent: EMPTY_PARENT,
},
loc: param.loc,
range: param.range,
parent: EMPTY_PARENT,
};
}
function mapComponentParameter(
param: ComponentParameter | RestElement,
): Array<DestructuringObjectProperty | RestElement> {
switch (param.type) {
case 'RestElement': {
switch (param.argument.type) {
case 'Identifier': {
const a = nodeWith(param, {
typeAnnotation: null,
argument: nodeWith(param.argument, {typeAnnotation: null}),
});
return [a];
}
case 'ObjectPattern': {
return param.argument.properties.map(property => {
return nodeWith(property, {
typeAnnotation: null,
});
});
}
default: {
throw createSyntaxError(
param,
`Unhandled ${param.argument.type} encountered in restParameter`,
);
}
}
}
case 'ComponentParameter': {
let value;
if (param.local.type === 'AssignmentPattern') {
value = nodeWith(param.local, {
left: nodeWith(param.local.left, {
typeAnnotation: null,
optional: false,
}),
});
} else {
value = nodeWith(param.local, {
typeAnnotation: null,
optional: false,
});
}
// Shorthand params
if (
param.name.type === 'Identifier' &&
param.shorthand &&
(value.type === 'Identifier' || value.type === 'AssignmentPattern')
) {
return [
{
type: 'Property',
key: param.name,
kind: 'init',
value,
method: false,
shorthand: true,
computed: false,
loc: param.loc,
range: param.range,
parent: EMPTY_PARENT,
},
];
}
// Complex params
return [
{
type: 'Property',
key: param.name,
kind: 'init',
value,
method: false,
shorthand: false,
computed: false,
loc: param.loc,
range: param.range,
parent: EMPTY_PARENT,
},
];
}
default: {
throw createSyntaxError(
param,
`Unknown Component parameter type of "${param.type}"`,
);
}
}
}
type ForwardRefDetails = {
forwardRefStatement: VariableDeclaration,
internalCompId: Identifier,
forwardRefCompId: Identifier,
};
function createForwardRefWrapper(
originalComponent: ComponentDeclaration,
): ForwardRefDetails {
const internalCompId = {
type: 'Identifier',
name: `${originalComponent.id.name}_withRef`,
optional: false,
typeAnnotation: null,
loc: originalComponent.id.loc,
range: originalComponent.id.range,
parent: EMPTY_PARENT,
};
return {
forwardRefStatement: {
type: 'VariableDeclaration',
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
id: shallowCloneNode(originalComponent.id),
init: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'React',
optional: false,
typeAnnotation: null,
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT,
},
property: {
type: 'Identifier',
name: 'forwardRef',
optional: false,
typeAnnotation: null,
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT,
},
computed: false,
optional: false,
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT,
},
arguments: [shallowCloneNode(internalCompId)],
typeArguments: null,
optional: false,
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT,
},
loc: originalComponent.loc,
range: originalComponent.range,
parent: EMPTY_PARENT,
},
],
loc: originalComponent.loc,
range: originalComponent.range,
parent: originalComponent.parent,
},
internalCompId: internalCompId,
forwardRefCompId: originalComponent.id,
};
}
function mapComponentDeclaration(
node: ComponentDeclaration,
options: ParserOptions,
): {
comp: FunctionDeclaration,
forwardRefDetails: ?ForwardRefDetails,
} {
// Create empty loc for return type annotation nodes
const createRendersTypeLoc = () => ({
loc: {
start: node.body.loc.end,
end: node.body.loc.end,
},
range: [node.body.range[1], node.body.range[1]],
parent: EMPTY_PARENT,
});
const returnType: TypeAnnotation = {
type: 'TypeAnnotation',
typeAnnotation: {
type: 'GenericTypeAnnotation',
id: {
type: 'QualifiedTypeIdentifier',
qualification: {
type: 'Identifier',
name: 'React',
optional: false,
typeAnnotation: null,
...createRendersTypeLoc(),
},
id: {
type: 'Identifier',
name: 'Node',
optional: false,
typeAnnotation: null,
...createRendersTypeLoc(),
},
...createRendersTypeLoc(),
},
typeParameters: null,
...createRendersTypeLoc(),
},
...createRendersTypeLoc(),
};
const {props, ref} = mapComponentParameters(node.params, options);
let forwardRefDetails: ?ForwardRefDetails = null;
if (ref != null) {
forwardRefDetails = createForwardRefWrapper(node);
}
const comp = {
type: 'FunctionDeclaration',
id:
forwardRefDetails != null
? shallowCloneNode(forwardRefDetails.internalCompId)
: shallowCloneNode(node.id),
__componentDeclaration: true,
typeParameters: node.typeParameters,
params: props == null ? [] : ref == null ? [props] : [props, ref],
returnType,
body: node.body,
async: false,
generator: false,
predicate: null,
loc: node.loc,
range: node.range,
parent: node.parent,
};
return {comp, forwardRefDetails};
}
function mapDeclareHook(node: DeclareHook): DeclareFunction {
return {
type: 'DeclareFunction',
id: {
type: 'Identifier',
name: node.id.name,
optional: node.id.optional,
typeAnnotation: {
type: 'TypeAnnotation',
typeAnnotation: {
type: 'FunctionTypeAnnotation',
this: null,
params: node.id.typeAnnotation.typeAnnotation.params,
typeParameters: node.id.typeAnnotation.typeAnnotation.typeParameters,
rest: node.id.typeAnnotation.typeAnnotation.rest,
returnType: node.id.typeAnnotation.typeAnnotation.returnType,
loc: node.id.typeAnnotation.typeAnnotation.loc,
range: node.id.typeAnnotation.typeAnnotation.range,
parent: node.id.typeAnnotation.typeAnnotation.parent,
},
loc: node.id.typeAnnotation.loc,
range: node.id.typeAnnotation.range,
parent: node.id.typeAnnotation.parent,
},
loc: node.id.loc,
range: node.id.range,
parent: node.id.parent,
},
loc: node.loc,
range: node.range,
parent: node.parent,
predicate: null,
};
}
function mapHookDeclaration(node: HookDeclaration): FunctionDeclaration {
const comp = {
type: 'FunctionDeclaration',
id: node.id && shallowCloneNode(node.id),
__hookDeclaration: true,
typeParameters: node.typeParameters,
params: node.params,
returnType: node.returnType,
body: node.body,
async: false,
generator: false,
predicate: null,
loc: node.loc,
range: node.range,
parent: node.parent,
};
return comp;
}
/**
* Scan a list of statements and return the position of the
* first statement that contains a reference to a given component
* or null of no references were found.
*/
function scanForFirstComponentReference(
compName: string,
bodyList: Array<Statement | ModuleDeclaration>,
): ?number {
for (let i = 0; i < bodyList.length; i++) {
const bodyNode = bodyList[i];
let referencePos = null;
SimpleTraverser.traverse(bodyNode, {
enter(node: ESNode) {
switch (node.type) {
case 'Identifier': {
if (node.name === compName) {
// We found a reference, record it and stop.
referencePos = i;
throw SimpleTraverser.Break;
}
}
}
},
leave(_node: ESNode) {},
});
if (referencePos != null) {
return referencePos;
}
}
return null;
}
function mapComponentDeclarationIntoList(
node: ComponentDeclaration,
newBody: Array<Statement | ModuleDeclaration>,
options: ParserOptions,
insertExport?: (Identifier | FunctionDeclaration) => ModuleDeclaration,
) {
const {comp, forwardRefDetails} = mapComponentDeclaration(node, options);
if (forwardRefDetails != null) {
// Scan for references to our component.
const referencePos = scanForFirstComponentReference(
forwardRefDetails.forwardRefCompId.name,
newBody,
);
// If a reference is found insert the forwardRef statement before it (to simulate function hoisting).
if (referencePos != null) {
newBody.splice(referencePos, 0, forwardRefDetails.forwardRefStatement);
} else {
newBody.push(forwardRefDetails.forwardRefStatement);
}
newBody.push(comp);
if (insertExport != null) {
newBody.push(insertExport(forwardRefDetails.forwardRefCompId));
}
return;
}
newBody.push(insertExport != null ? insertExport(comp) : comp);
}
function mapStatementList(
stmts: $ReadOnlyArray<Statement | ModuleDeclaration>,
options: ParserOptions,
) {
const newBody: Array<Statement | ModuleDeclaration> = [];
for (const node of stmts) {
switch (node.type) {
case 'ComponentDeclaration': {
mapComponentDeclarationIntoList(node, newBody, options);
break;
}
case 'HookDeclaration': {
const decl = mapHookDeclaration(node);
newBody.push(decl);
break;
}
case 'ExportNamedDeclaration': {
if (node.declaration?.type === 'ComponentDeclaration') {
mapComponentDeclarationIntoList(
node.declaration,
newBody,
options,
componentOrRef => {
switch (componentOrRef.type) {
case 'FunctionDeclaration': {
// No ref, so we can export the component directly.
return nodeWith(node, {declaration: componentOrRef});
}
case 'Identifier': {
// If a ref is inserted, we should just export that id.
return {
type: 'ExportNamedDeclaration',
declaration: null,
specifiers: [
{
type: 'ExportSpecifier',
exported: shallowCloneNode(componentOrRef),
local: shallowCloneNode(componentOrRef),
loc: node.loc,
range: node.range,
parent: EMPTY_PARENT,
},
],
exportKind: 'value',
source: null,
loc: node.loc,
range: node.range,
parent: node.parent,
};
}
}
},
);
break;
}
if (node.declaration?.type === 'HookDeclaration') {
const comp = mapHookDeclaration(node.declaration);
newBody.push(nodeWith(node, {declaration: comp}));
break;
}
newBody.push(node);
break;
}
case 'ExportDefaultDeclaration': {
if (node.declaration?.type === 'ComponentDeclaration') {
mapComponentDeclarationIntoList(
node.declaration,
newBody,
options,
componentOrRef => nodeWith(node, {declaration: componentOrRef}),
);
break;
}
if (node.declaration?.type === 'HookDeclaration') {
const comp = mapHookDeclaration(node.declaration);
newBody.push(nodeWith(node, {declaration: comp}));
break;
}
newBody.push(node);
break;
}
default: {
newBody.push(node);
}
}
}
return newBody;
}
export function transformProgram(
program: Program,
options: ParserOptions,
): Program {
return SimpleTransform.transformProgram(program, {
transform(node: ESNode) {
switch (node.type) {
case 'DeclareComponent': {
return mapDeclareComponent(node);
}
case 'DeclareHook': {
return mapDeclareHook(node);
}
case 'Program':
case 'BlockStatement': {
return nodeWith(node, {body: mapStatementList(node.body, options)});
}
case 'SwitchCase': {
const consequent = mapStatementList(node.consequent, options);
return nodeWith(node, {
/* $FlowExpectedError[incompatible-call] We know `mapStatementList` will
not return `ModuleDeclaration` nodes if it is not passed any */
consequent,
});
}
case 'ComponentDeclaration': {
throw createSyntaxError(
node,
`Components must be defined at the top level of a module or within a ` +
`BlockStatement, instead got parent of "${node.parent?.type}".`,
);
}
case 'HookDeclaration': {
throw createSyntaxError(
node,
`Hooks must be defined at the top level of a module or within a ` +
`BlockStatement, instead got parent of "${node.parent?.type}".`,
);
}
default: {
return node;
}
}
},
});
}

View File

@@ -0,0 +1,175 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
/**
* This transform strips all Flow types.
*
* It is expected that all transforms create valid ESTree AST output. If
* the transform requires outputting Babel specific AST nodes then it
* should live in `ConvertESTreeToBabel.js`
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.transformProgram = transformProgram;
var _SimpleTransform = require("../transform/SimpleTransform");
const nodeWith = _SimpleTransform.SimpleTransform.nodeWith;
function transformProgram(program, _options) {
return _SimpleTransform.SimpleTransform.transformProgram(program, {
transform(node) {
switch (node.type) {
case 'AsExpression':
case 'AsConstExpression':
case 'TypeCastExpression':
{
return node.expression;
}
case 'CallExpression':
case 'NewExpression':
{
if (node.typeArguments != null) {
return nodeWith(node, {
typeArguments: null
});
}
return node;
}
case 'ObjectPattern':
case 'ArrayPattern':
case 'Identifier':
{
if (node.typeAnnotation != null) {
return nodeWith(node, {
typeAnnotation: null
});
}
return node;
}
case 'DeclareClass':
case 'DeclareFunction':
case 'DeclareInterface':
case 'DeclareModule':
case 'DeclareModuleExports':
case 'DeclareNamespace':
case 'DeclareOpaqueType':
case 'DeclareTypeAlias':
case 'DeclareVariable':
case 'InterfaceDeclaration':
case 'OpaqueType':
case 'TypeAlias':
{
return null;
}
case 'FunctionDeclaration':
case 'ArrowFunctionExpression':
case 'FunctionExpression':
{
const newParams = [];
for (let i = 0; i < node.params.length; i++) {
if (i === 0 && node.params[0].type === 'Identifier' && node.params[0].name === 'this') {
continue;
}
let param = node.params[i];
if (param.type === 'AssignmentPattern') {
param = param.left;
}
if (param.optional === true) {
param = nodeWith(param, {
optional: false
});
}
newParams.push(param);
}
return nodeWith(node, {
params: newParams,
returnType: null,
typeParameters: null,
predicate: null
});
}
case 'ClassDeclaration':
case 'ClassExpression':
{
return nodeWith(node, {
typeParameters: null,
superTypeParameters: null,
implements: [],
decorators: []
});
}
case 'PropertyDefinition':
{
return nodeWith(node, {
typeAnnotation: null,
variance: null,
declare: false,
optional: false
});
}
case 'ImportDeclaration':
{
if (node.importKind === 'type' || node.importKind === 'typeof') {
return null;
}
const nonTypeSpecifiers = node.specifiers.filter(s => s.type !== 'ImportSpecifier' || s.importKind !== 'type' && s.importKind !== 'typeof');
if (nonTypeSpecifiers.length === 0) {
return null;
}
if (nonTypeSpecifiers.length === node.specifiers.length) {
return node;
}
return nodeWith(node, {
specifiers: nonTypeSpecifiers
});
}
case 'ExportAllDeclaration':
case 'ExportNamedDeclaration':
{
if (node.exportKind === 'type') {
return null;
}
return node;
}
default:
{
return node;
}
}
}
});
}

View File

@@ -0,0 +1,158 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
/**
* This transform strips all Flow types.
*
* It is expected that all transforms create valid ESTree AST output. If
* the transform requires outputting Babel specific AST nodes then it
* should live in `ConvertESTreeToBabel.js`
*/
'use strict';
import type {ParserOptions} from '../ParserOptions';
import type {Program, ESNode} from 'hermes-estree';
import {SimpleTransform} from '../transform/SimpleTransform';
const nodeWith = SimpleTransform.nodeWith;
export function transformProgram(
program: Program,
_options: ParserOptions,
): Program {
return SimpleTransform.transformProgram(program, {
transform(node: ESNode) {
switch (node.type) {
case 'AsExpression':
case 'AsConstExpression':
case 'TypeCastExpression': {
return node.expression;
}
case 'CallExpression':
case 'NewExpression': {
if (node.typeArguments != null) {
return nodeWith(node, {typeArguments: null});
}
return node;
}
case 'ObjectPattern':
case 'ArrayPattern':
case 'Identifier': {
if (node.typeAnnotation != null) {
return nodeWith(node, {typeAnnotation: null});
}
return node;
}
case 'DeclareClass':
case 'DeclareFunction':
case 'DeclareInterface':
case 'DeclareModule':
case 'DeclareModuleExports':
case 'DeclareNamespace':
case 'DeclareOpaqueType':
case 'DeclareTypeAlias':
case 'DeclareVariable':
case 'InterfaceDeclaration':
case 'OpaqueType':
case 'TypeAlias': {
return null;
}
case 'FunctionDeclaration':
case 'ArrowFunctionExpression':
case 'FunctionExpression': {
const newParams = [];
for (let i = 0; i < node.params.length; i++) {
if (
i === 0 &&
node.params[0].type === 'Identifier' &&
node.params[0].name === 'this'
) {
continue;
}
let param = node.params[i];
if (param.type === 'AssignmentPattern') {
param = param.left;
}
if (param.optional === true) {
param = nodeWith(param, {optional: false});
}
newParams.push(param);
}
return nodeWith(node, {
params: newParams,
returnType: null,
typeParameters: null,
predicate: null,
});
}
case 'ClassDeclaration':
case 'ClassExpression': {
return nodeWith(node, {
typeParameters: null,
superTypeParameters: null,
implements: [],
decorators: [],
});
}
case 'PropertyDefinition': {
return nodeWith(node, {
typeAnnotation: null,
variance: null,
declare: false,
optional: false,
});
}
case 'ImportDeclaration': {
if (node.importKind === 'type' || node.importKind === 'typeof') {
return null;
}
const nonTypeSpecifiers = node.specifiers.filter(
s =>
s.type !== 'ImportSpecifier' ||
(s.importKind !== 'type' && s.importKind !== 'typeof'),
);
if (nonTypeSpecifiers.length === 0) {
return null;
}
if (nonTypeSpecifiers.length === node.specifiers.length) {
return node;
}
return nodeWith(node, {
specifiers: nonTypeSpecifiers,
});
}
case 'ExportAllDeclaration':
case 'ExportNamedDeclaration': {
if (node.exportKind === 'type') {
return null;
}
return node;
}
default: {
return node;
}
}
},
});
}

View File

@@ -0,0 +1,215 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
/**
* This transform strips Flow types that are not supported past Babel 7.
*
* It is expected that all transforms create valid ESTree AST output. If
* the transform requires outputting Babel specific AST nodes then it
* should live in `ConvertESTreeToBabel.js`
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.transformProgram = transformProgram;
var _SimpleTransform = require("../transform/SimpleTransform");
var _createSyntaxError = require("../utils/createSyntaxError");
const nodeWith = _SimpleTransform.SimpleTransform.nodeWith; // Rely on the mapper to fix up parent relationships.
const EMPTY_PARENT = null;
function createSimpleGenericTypeAnnotation(name, nodeForLoc) {
return {
type: 'GenericTypeAnnotation',
id: {
type: 'Identifier',
name,
optional: false,
typeAnnotation: null,
loc: nodeForLoc.loc,
range: nodeForLoc.range,
parent: EMPTY_PARENT
},
typeParameters: null,
loc: nodeForLoc.loc,
range: nodeForLoc.range,
parent: nodeForLoc.parent
};
}
function createAnyTypeAnnotation(node) {
return {
type: 'AnyTypeAnnotation',
loc: node.loc,
range: node.range,
parent: node.parent
};
}
/**
* Convert DeclareEnum nodes to DeclareVariable
*/
function mapDeclareEnum(node) {
return {
type: 'DeclareVariable',
kind: 'const',
id: nodeWith(node.id, {
typeAnnotation: {
type: 'TypeAnnotation',
typeAnnotation: createAnyTypeAnnotation(node.body),
loc: node.body.loc,
range: node.body.range,
parent: EMPTY_PARENT
}
}),
loc: node.loc,
range: node.range,
parent: node.parent
};
}
/**
* Convert DeclareNamespace nodes to DeclareVariable
*/
function mapDeclareNamespace(node) {
return {
type: 'DeclareVariable',
kind: 'const',
id: nodeWith(node.id, {
typeAnnotation: {
type: 'TypeAnnotation',
typeAnnotation: createAnyTypeAnnotation(node.body),
loc: node.body.loc,
range: node.body.range,
parent: EMPTY_PARENT
}
}),
loc: node.loc,
range: node.range,
parent: node.parent
};
}
/**
* Remove `this` param from functions.
*/
function mapFunction(node) {
// Remove the first parameter if it is a this-type annotation,
// which is not recognized by Babel.
if (node.params.length !== 0 && node.params[0].name === 'this') {
return nodeWith(node, {
params: node.params.slice(1)
});
}
return node;
}
/**
* Convert to QualifiedTypeIdentifier
*/
function mapQualifiedTypeofIdentifier(node) {
return {
type: 'QualifiedTypeIdentifier',
qualification: node.qualification.type === 'QualifiedTypeofIdentifier' ? mapQualifiedTypeofIdentifier(node.qualification) : node.qualification,
id: node.id,
loc: node.loc,
range: node.range,
parent: node.parent
};
}
function transformProgram(program, _options) {
return _SimpleTransform.SimpleTransform.transformProgram(program, {
transform(node) {
switch (node.type) {
case 'SymbolTypeAnnotation':
{
// Convert to simple generic type annotation
return createSimpleGenericTypeAnnotation('symbol', node);
}
case 'BigIntTypeAnnotation':
{
// Convert to simple generic type annotation
return createSimpleGenericTypeAnnotation('bigint', node);
}
case 'ObjectTypeAnnotation':
{
const shouldStrip = node.properties.some(prop => prop.type === 'ObjectTypeMappedTypeProperty');
if (shouldStrip) {
return createAnyTypeAnnotation(node);
}
return node;
}
case 'ObjectTypeMappedTypeProperty':
{
throw (0, _createSyntaxError.createSyntaxError)(node, `Invalid AST structure, ObjectTypeMappedTypeProperty found outside of an ObjectTypeAnnotation`);
}
case 'IndexedAccessType':
case 'OptionalIndexedAccessType':
case 'KeyofTypeAnnotation':
case 'ConditionalTypeAnnotation':
case 'InferTypeAnnotation':
case 'TupleTypeLabeledElement':
case 'TupleTypeSpreadElement':
case 'ComponentTypeAnnotation':
case 'HookTypeAnnotation':
case 'TypeOperator':
case 'TypePredicate':
{
// Babel does not support these generic types, so convert to any
return createAnyTypeAnnotation(node);
}
case 'QualifiedTypeofIdentifier':
{
return mapQualifiedTypeofIdentifier(node);
}
case 'DeclareEnum':
{
return mapDeclareEnum(node);
}
case 'DeclareNamespace':
{
return mapDeclareNamespace(node);
}
case 'FunctionDeclaration':
case 'FunctionExpression':
{
return mapFunction(node);
}
default:
{
return node;
}
}
}
});
}

View File

@@ -0,0 +1,216 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
/**
* This transform strips Flow types that are not supported past Babel 7.
*
* It is expected that all transforms create valid ESTree AST output. If
* the transform requires outputting Babel specific AST nodes then it
* should live in `ConvertESTreeToBabel.js`
*/
'use strict';
import type {ParserOptions} from '../ParserOptions';
import type {
Program,
ESNode,
DeclareEnum,
DeclareNamespace,
DeclareVariable,
AnyTypeAnnotation,
GenericTypeAnnotation,
QualifiedTypeIdentifier,
QualifiedTypeofIdentifier,
AFunction,
} from 'hermes-estree';
import {SimpleTransform} from '../transform/SimpleTransform';
import {createSyntaxError} from '../utils/createSyntaxError';
const nodeWith = SimpleTransform.nodeWith;
// Rely on the mapper to fix up parent relationships.
const EMPTY_PARENT: $FlowFixMe = null;
function createSimpleGenericTypeAnnotation(
name: string,
nodeForLoc: ESNode,
): GenericTypeAnnotation {
return {
type: 'GenericTypeAnnotation',
id: {
type: 'Identifier',
name,
optional: false,
typeAnnotation: null,
loc: nodeForLoc.loc,
range: nodeForLoc.range,
parent: EMPTY_PARENT,
},
typeParameters: null,
loc: nodeForLoc.loc,
range: nodeForLoc.range,
parent: nodeForLoc.parent,
};
}
function createAnyTypeAnnotation(node: ESNode): AnyTypeAnnotation {
return {
type: 'AnyTypeAnnotation',
loc: node.loc,
range: node.range,
parent: node.parent,
};
}
/**
* Convert DeclareEnum nodes to DeclareVariable
*/
function mapDeclareEnum(node: DeclareEnum): DeclareVariable {
return {
type: 'DeclareVariable',
kind: 'const',
id: nodeWith(node.id, {
typeAnnotation: {
type: 'TypeAnnotation',
typeAnnotation: createAnyTypeAnnotation(node.body),
loc: node.body.loc,
range: node.body.range,
parent: EMPTY_PARENT,
},
}),
loc: node.loc,
range: node.range,
parent: node.parent,
};
}
/**
* Convert DeclareNamespace nodes to DeclareVariable
*/
function mapDeclareNamespace(node: DeclareNamespace): DeclareVariable {
return {
type: 'DeclareVariable',
kind: 'const',
id: nodeWith(node.id, {
typeAnnotation: {
type: 'TypeAnnotation',
typeAnnotation: createAnyTypeAnnotation(node.body),
loc: node.body.loc,
range: node.body.range,
parent: EMPTY_PARENT,
},
}),
loc: node.loc,
range: node.range,
parent: node.parent,
};
}
/**
* Remove `this` param from functions.
*/
function mapFunction(node: AFunction): AFunction {
// Remove the first parameter if it is a this-type annotation,
// which is not recognized by Babel.
if (node.params.length !== 0 && node.params[0].name === 'this') {
return nodeWith(node, {
params: node.params.slice(1),
});
}
return node;
}
/**
* Convert to QualifiedTypeIdentifier
*/
function mapQualifiedTypeofIdentifier(
node: QualifiedTypeofIdentifier,
): QualifiedTypeIdentifier {
return {
type: 'QualifiedTypeIdentifier',
qualification:
node.qualification.type === 'QualifiedTypeofIdentifier'
? mapQualifiedTypeofIdentifier(node.qualification)
: node.qualification,
id: node.id,
loc: node.loc,
range: node.range,
parent: node.parent,
};
}
export function transformProgram(
program: Program,
_options: ParserOptions,
): Program {
return SimpleTransform.transformProgram(program, {
transform(node: ESNode) {
switch (node.type) {
case 'SymbolTypeAnnotation': {
// Convert to simple generic type annotation
return createSimpleGenericTypeAnnotation('symbol', node);
}
case 'BigIntTypeAnnotation': {
// Convert to simple generic type annotation
return createSimpleGenericTypeAnnotation('bigint', node);
}
case 'ObjectTypeAnnotation': {
const shouldStrip = node.properties.some(
prop => prop.type === 'ObjectTypeMappedTypeProperty',
);
if (shouldStrip) {
return createAnyTypeAnnotation(node);
}
return node;
}
case 'ObjectTypeMappedTypeProperty': {
throw createSyntaxError(
node,
`Invalid AST structure, ObjectTypeMappedTypeProperty found outside of an ObjectTypeAnnotation`,
);
}
case 'IndexedAccessType':
case 'OptionalIndexedAccessType':
case 'KeyofTypeAnnotation':
case 'ConditionalTypeAnnotation':
case 'InferTypeAnnotation':
case 'TupleTypeLabeledElement':
case 'TupleTypeSpreadElement':
case 'ComponentTypeAnnotation':
case 'HookTypeAnnotation':
case 'TypeOperator':
case 'TypePredicate': {
// Babel does not support these generic types, so convert to any
return createAnyTypeAnnotation(node);
}
case 'QualifiedTypeofIdentifier': {
return mapQualifiedTypeofIdentifier(node);
}
case 'DeclareEnum': {
return mapDeclareEnum(node);
}
case 'DeclareNamespace': {
return mapDeclareNamespace(node);
}
case 'FunctionDeclaration':
case 'FunctionExpression': {
return mapFunction(node);
}
default: {
return node;
}
}
},
});
}

View File

@@ -0,0 +1,203 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @generated
*/
/*
* !!! GENERATED FILE !!!
*
* Any manual changes to this file will be overwritten. To regenerate run `yarn build`.
*/
// lint directives to let us do some basic validation of generated files
/* eslint no-undef: 'error', no-unused-vars: ['error', {vars: "local"}], no-redeclare: 'error' */
/* global $NonMaybeType, Partial, $ReadOnly, $ReadOnlyArray, $FlowFixMe */
'use strict';
module.exports = {
AnyTypeAnnotation: [],
ArrayExpression: ['elements'],
ArrayPattern: ['elements', 'typeAnnotation'],
ArrayTypeAnnotation: ['elementType'],
ArrowFunctionExpression: ['id', 'params', 'body', 'typeParameters', 'returnType', 'predicate'],
AsConstExpression: ['expression'],
AsExpression: ['expression', 'typeAnnotation'],
AssignmentExpression: ['left', 'right'],
AssignmentPattern: ['left', 'right'],
AwaitExpression: ['argument'],
BigIntLiteralTypeAnnotation: [],
BigIntTypeAnnotation: [],
BinaryExpression: ['left', 'right'],
BlockStatement: ['body'],
BooleanLiteralTypeAnnotation: [],
BooleanTypeAnnotation: [],
BreakStatement: ['label'],
CallExpression: ['callee', 'typeArguments', 'arguments'],
CatchClause: ['param', 'body'],
ChainExpression: ['expression'],
ClassBody: ['body'],
ClassDeclaration: ['id', 'typeParameters', 'superClass', 'superTypeParameters', 'implements', 'decorators', 'body'],
ClassExpression: ['id', 'typeParameters', 'superClass', 'superTypeParameters', 'implements', 'decorators', 'body'],
ClassImplements: ['id', 'typeParameters'],
ComponentDeclaration: ['id', 'params', 'body', 'typeParameters', 'rendersType'],
ComponentParameter: ['name', 'local'],
ComponentTypeAnnotation: ['params', 'rest', 'typeParameters', 'rendersType'],
ComponentTypeParameter: ['name', 'typeAnnotation'],
ConditionalExpression: ['test', 'consequent', 'alternate'],
ConditionalTypeAnnotation: ['checkType', 'extendsType', 'trueType', 'falseType'],
ContinueStatement: ['label'],
DebuggerStatement: [],
DeclareClass: ['id', 'typeParameters', 'extends', 'implements', 'mixins', 'body'],
DeclareComponent: ['id', 'params', 'rest', 'typeParameters', 'rendersType'],
DeclaredPredicate: ['value'],
DeclareEnum: ['id', 'body'],
DeclareExportAllDeclaration: ['source'],
DeclareExportDeclaration: ['declaration', 'specifiers', 'source'],
DeclareFunction: ['id', 'predicate'],
DeclareHook: ['id'],
DeclareInterface: ['id', 'typeParameters', 'extends', 'body'],
DeclareModule: ['id', 'body'],
DeclareModuleExports: ['typeAnnotation'],
DeclareNamespace: ['id', 'body'],
DeclareOpaqueType: ['id', 'typeParameters', 'impltype', 'supertype'],
DeclareTypeAlias: ['id', 'typeParameters', 'right'],
DeclareVariable: ['id'],
DoWhileStatement: ['body', 'test'],
EmptyStatement: [],
EmptyTypeAnnotation: [],
EnumBigIntBody: ['members'],
EnumBigIntMember: ['id', 'init'],
EnumBooleanBody: ['members'],
EnumBooleanMember: ['id', 'init'],
EnumDeclaration: ['id', 'body'],
EnumDefaultedMember: ['id'],
EnumNumberBody: ['members'],
EnumNumberMember: ['id', 'init'],
EnumStringBody: ['members'],
EnumStringMember: ['id', 'init'],
EnumSymbolBody: ['members'],
ExistsTypeAnnotation: [],
ExportAllDeclaration: ['exported', 'source'],
ExportDefaultDeclaration: ['declaration'],
ExportNamedDeclaration: ['declaration', 'specifiers', 'source'],
ExportSpecifier: ['exported', 'local'],
ExpressionStatement: ['expression'],
ForInStatement: ['left', 'right', 'body'],
ForOfStatement: ['left', 'right', 'body'],
ForStatement: ['init', 'test', 'update', 'body'],
FunctionDeclaration: ['id', 'params', 'body', 'typeParameters', 'returnType', 'predicate'],
FunctionExpression: ['id', 'params', 'body', 'typeParameters', 'returnType', 'predicate'],
FunctionTypeAnnotation: ['params', 'this', 'returnType', 'rest', 'typeParameters'],
FunctionTypeParam: ['name', 'typeAnnotation'],
GenericTypeAnnotation: ['id', 'typeParameters'],
HookDeclaration: ['id', 'params', 'body', 'typeParameters', 'returnType'],
HookTypeAnnotation: ['params', 'returnType', 'rest', 'typeParameters'],
Identifier: ['typeAnnotation'],
IfStatement: ['test', 'consequent', 'alternate'],
ImportAttribute: ['key', 'value'],
ImportDeclaration: ['specifiers', 'source', 'assertions'],
ImportDefaultSpecifier: ['local'],
ImportExpression: ['source', 'attributes'],
ImportNamespaceSpecifier: ['local'],
ImportSpecifier: ['imported', 'local'],
IndexedAccessType: ['objectType', 'indexType'],
InferredPredicate: [],
InferTypeAnnotation: ['typeParameter'],
InterfaceDeclaration: ['id', 'typeParameters', 'extends', 'body'],
InterfaceExtends: ['id', 'typeParameters'],
InterfaceTypeAnnotation: ['extends', 'body'],
IntersectionTypeAnnotation: ['types'],
JSXAttribute: ['name', 'value'],
JSXClosingElement: ['name'],
JSXClosingFragment: [],
JSXElement: ['openingElement', 'children', 'closingElement'],
JSXEmptyExpression: [],
JSXExpressionContainer: ['expression'],
JSXFragment: ['openingFragment', 'children', 'closingFragment'],
JSXIdentifier: [],
JSXMemberExpression: ['object', 'property'],
JSXNamespacedName: ['namespace', 'name'],
JSXOpeningElement: ['name', 'attributes', 'typeArguments'],
JSXOpeningFragment: [],
JSXSpreadAttribute: ['argument'],
JSXSpreadChild: ['expression'],
JSXText: [],
KeyofTypeAnnotation: ['argument'],
LabeledStatement: ['label', 'body'],
LogicalExpression: ['left', 'right'],
MemberExpression: ['object', 'property'],
MetaProperty: ['meta', 'property'],
MethodDefinition: ['key', 'value'],
MixedTypeAnnotation: [],
NewExpression: ['callee', 'typeArguments', 'arguments'],
NullableTypeAnnotation: ['typeAnnotation'],
NullLiteralTypeAnnotation: [],
NumberLiteralTypeAnnotation: [],
NumberTypeAnnotation: [],
ObjectExpression: ['properties'],
ObjectPattern: ['properties', 'typeAnnotation'],
ObjectTypeAnnotation: ['properties', 'indexers', 'callProperties', 'internalSlots'],
ObjectTypeCallProperty: ['value'],
ObjectTypeIndexer: ['id', 'key', 'value', 'variance'],
ObjectTypeInternalSlot: ['id', 'value'],
ObjectTypeMappedTypeProperty: ['keyTparam', 'propType', 'sourceType', 'variance'],
ObjectTypeProperty: ['key', 'value', 'variance'],
ObjectTypeSpreadProperty: ['argument'],
OpaqueType: ['id', 'typeParameters', 'impltype', 'supertype'],
OptionalIndexedAccessType: ['objectType', 'indexType'],
PrivateIdentifier: [],
Program: ['body'],
Property: ['key', 'value'],
PropertyDefinition: ['key', 'value', 'variance', 'typeAnnotation'],
QualifiedTypeIdentifier: ['qualification', 'id'],
QualifiedTypeofIdentifier: ['qualification', 'id'],
RestElement: ['argument'],
ReturnStatement: ['argument'],
SequenceExpression: ['expressions'],
SpreadElement: ['argument'],
StringLiteralTypeAnnotation: [],
StringTypeAnnotation: [],
Super: [],
SwitchCase: ['test', 'consequent'],
SwitchStatement: ['discriminant', 'cases'],
SymbolTypeAnnotation: [],
TaggedTemplateExpression: ['tag', 'quasi'],
TemplateElement: [],
TemplateLiteral: ['quasis', 'expressions'],
ThisExpression: [],
ThisTypeAnnotation: [],
ThrowStatement: ['argument'],
TryStatement: ['block', 'handler', 'finalizer'],
TupleTypeAnnotation: ['types'],
TupleTypeLabeledElement: ['label', 'elementType', 'variance'],
TupleTypeSpreadElement: ['label', 'typeAnnotation'],
TypeAlias: ['id', 'typeParameters', 'right'],
TypeAnnotation: ['typeAnnotation'],
TypeCastExpression: ['expression', 'typeAnnotation'],
TypeofTypeAnnotation: ['argument', 'typeArguments'],
TypeOperator: ['typeAnnotation'],
TypeParameter: ['bound', 'variance', 'default'],
TypeParameterDeclaration: ['params'],
TypeParameterInstantiation: ['params'],
TypePredicate: ['parameterName', 'typeAnnotation'],
UnaryExpression: ['argument'],
UnionTypeAnnotation: ['types'],
UpdateExpression: ['argument'],
VariableDeclaration: ['declarations'],
VariableDeclarator: ['id', 'init'],
Variance: [],
VoidTypeAnnotation: [],
WhileStatement: ['test', 'body'],
WithStatement: ['object', 'body'],
YieldExpression: ['argument'],
OptionalMemberExpression: ['object', 'property'],
OptionalCallExpression: ['callee', 'typeArguments', 'arguments'],
Literal: []
};

View File

@@ -0,0 +1,15 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
export type VisitorKeys = $ReadOnly<{[string]: $ReadOnlyArray<string>}>;
declare module.exports: VisitorKeys;

View File

@@ -0,0 +1,730 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @generated
*/
/*
* !!! GENERATED FILE !!!
*
* Any manual changes to this file will be overwritten. To regenerate run `yarn build`.
*/
// lint directives to let us do some basic validation of generated files
/* eslint no-undef: 'error', no-unused-vars: ['error', {vars: "local"}], no-redeclare: 'error' */
/* global $NonMaybeType, Partial, $ReadOnly, $ReadOnlyArray, $FlowFixMe */
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.NODE_LIST_CHILD = exports.NODE_CHILD = exports.HERMES_AST_VISITOR_KEYS = void 0;
const NODE_CHILD = 'Node';
exports.NODE_CHILD = NODE_CHILD;
const NODE_LIST_CHILD = 'NodeList';
exports.NODE_LIST_CHILD = NODE_LIST_CHILD;
const HERMES_AST_VISITOR_KEYS = {
AnyTypeAnnotation: {},
ArrayExpression: {
elements: 'NodeList'
},
ArrayPattern: {
elements: 'NodeList',
typeAnnotation: 'Node'
},
ArrayTypeAnnotation: {
elementType: 'Node'
},
ArrowFunctionExpression: {
id: 'Node',
params: 'NodeList',
body: 'Node',
typeParameters: 'Node',
returnType: 'Node',
predicate: 'Node'
},
AsConstExpression: {
expression: 'Node'
},
AsExpression: {
expression: 'Node',
typeAnnotation: 'Node'
},
AssignmentExpression: {
left: 'Node',
right: 'Node'
},
AssignmentPattern: {
left: 'Node',
right: 'Node'
},
AwaitExpression: {
argument: 'Node'
},
BigIntLiteral: {},
BigIntLiteralTypeAnnotation: {},
BigIntTypeAnnotation: {},
BinaryExpression: {
left: 'Node',
right: 'Node'
},
BlockStatement: {
body: 'NodeList'
},
BooleanLiteral: {},
BooleanLiteralTypeAnnotation: {},
BooleanTypeAnnotation: {},
BreakStatement: {
label: 'Node'
},
CallExpression: {
callee: 'Node',
typeArguments: 'Node',
arguments: 'NodeList'
},
CatchClause: {
param: 'Node',
body: 'Node'
},
ChainExpression: {
expression: 'Node'
},
ClassBody: {
body: 'NodeList'
},
ClassDeclaration: {
id: 'Node',
typeParameters: 'Node',
superClass: 'Node',
superTypeParameters: 'Node',
implements: 'NodeList',
decorators: 'NodeList',
body: 'Node'
},
ClassExpression: {
id: 'Node',
typeParameters: 'Node',
superClass: 'Node',
superTypeParameters: 'Node',
implements: 'NodeList',
decorators: 'NodeList',
body: 'Node'
},
ClassImplements: {
id: 'Node',
typeParameters: 'Node'
},
ComponentDeclaration: {
id: 'Node',
params: 'NodeList',
body: 'Node',
typeParameters: 'Node',
rendersType: 'Node'
},
ComponentParameter: {
name: 'Node',
local: 'Node'
},
ComponentTypeAnnotation: {
params: 'NodeList',
rest: 'Node',
typeParameters: 'Node',
rendersType: 'Node'
},
ComponentTypeParameter: {
name: 'Node',
typeAnnotation: 'Node'
},
ConditionalExpression: {
test: 'Node',
alternate: 'Node',
consequent: 'Node'
},
ConditionalTypeAnnotation: {
checkType: 'Node',
extendsType: 'Node',
trueType: 'Node',
falseType: 'Node'
},
ContinueStatement: {
label: 'Node'
},
DebuggerStatement: {},
DeclareClass: {
id: 'Node',
typeParameters: 'Node',
extends: 'NodeList',
implements: 'NodeList',
mixins: 'NodeList',
body: 'Node'
},
DeclareComponent: {
id: 'Node',
params: 'NodeList',
rest: 'Node',
typeParameters: 'Node',
rendersType: 'Node'
},
DeclaredPredicate: {
value: 'Node'
},
DeclareEnum: {
id: 'Node',
body: 'Node'
},
DeclareExportAllDeclaration: {
source: 'Node'
},
DeclareExportDeclaration: {
declaration: 'Node',
specifiers: 'NodeList',
source: 'Node'
},
DeclareFunction: {
id: 'Node',
predicate: 'Node'
},
DeclareHook: {
id: 'Node'
},
DeclareInterface: {
id: 'Node',
typeParameters: 'Node',
extends: 'NodeList',
body: 'Node'
},
DeclareModule: {
id: 'Node',
body: 'Node'
},
DeclareModuleExports: {
typeAnnotation: 'Node'
},
DeclareNamespace: {
id: 'Node',
body: 'Node'
},
DeclareOpaqueType: {
id: 'Node',
typeParameters: 'Node',
impltype: 'Node',
supertype: 'Node'
},
DeclareTypeAlias: {
id: 'Node',
typeParameters: 'Node',
right: 'Node'
},
DeclareVariable: {
id: 'Node'
},
DoWhileStatement: {
body: 'Node',
test: 'Node'
},
EmptyStatement: {},
EmptyTypeAnnotation: {},
EnumBigIntBody: {
members: 'NodeList'
},
EnumBigIntMember: {
id: 'Node',
init: 'Node'
},
EnumBooleanBody: {
members: 'NodeList'
},
EnumBooleanMember: {
id: 'Node',
init: 'Node'
},
EnumDeclaration: {
id: 'Node',
body: 'Node'
},
EnumDefaultedMember: {
id: 'Node'
},
EnumNumberBody: {
members: 'NodeList'
},
EnumNumberMember: {
id: 'Node',
init: 'Node'
},
EnumStringBody: {
members: 'NodeList'
},
EnumStringMember: {
id: 'Node',
init: 'Node'
},
EnumSymbolBody: {
members: 'NodeList'
},
ExistsTypeAnnotation: {},
ExportAllDeclaration: {
exported: 'Node',
source: 'Node'
},
ExportDefaultDeclaration: {
declaration: 'Node'
},
ExportNamedDeclaration: {
declaration: 'Node',
specifiers: 'NodeList',
source: 'Node'
},
ExportSpecifier: {
exported: 'Node',
local: 'Node'
},
ExpressionStatement: {
expression: 'Node'
},
ForInStatement: {
left: 'Node',
right: 'Node',
body: 'Node'
},
ForOfStatement: {
left: 'Node',
right: 'Node',
body: 'Node'
},
ForStatement: {
init: 'Node',
test: 'Node',
update: 'Node',
body: 'Node'
},
FunctionDeclaration: {
id: 'Node',
params: 'NodeList',
body: 'Node',
typeParameters: 'Node',
returnType: 'Node',
predicate: 'Node'
},
FunctionExpression: {
id: 'Node',
params: 'NodeList',
body: 'Node',
typeParameters: 'Node',
returnType: 'Node',
predicate: 'Node'
},
FunctionTypeAnnotation: {
params: 'NodeList',
this: 'Node',
returnType: 'Node',
rest: 'Node',
typeParameters: 'Node'
},
FunctionTypeParam: {
name: 'Node',
typeAnnotation: 'Node'
},
GenericTypeAnnotation: {
id: 'Node',
typeParameters: 'Node'
},
HookDeclaration: {
id: 'Node',
params: 'NodeList',
body: 'Node',
typeParameters: 'Node',
returnType: 'Node'
},
HookTypeAnnotation: {
params: 'NodeList',
returnType: 'Node',
rest: 'Node',
typeParameters: 'Node'
},
Identifier: {
typeAnnotation: 'Node'
},
IfStatement: {
test: 'Node',
consequent: 'Node',
alternate: 'Node'
},
ImportAttribute: {
key: 'Node',
value: 'Node'
},
ImportDeclaration: {
specifiers: 'NodeList',
source: 'Node',
assertions: 'NodeList'
},
ImportDefaultSpecifier: {
local: 'Node'
},
ImportExpression: {
source: 'Node',
attributes: 'Node'
},
ImportNamespaceSpecifier: {
local: 'Node'
},
ImportSpecifier: {
imported: 'Node',
local: 'Node'
},
IndexedAccessType: {
objectType: 'Node',
indexType: 'Node'
},
InferredPredicate: {},
InferTypeAnnotation: {
typeParameter: 'Node'
},
InterfaceDeclaration: {
id: 'Node',
typeParameters: 'Node',
extends: 'NodeList',
body: 'Node'
},
InterfaceExtends: {
id: 'Node',
typeParameters: 'Node'
},
InterfaceTypeAnnotation: {
extends: 'NodeList',
body: 'Node'
},
IntersectionTypeAnnotation: {
types: 'NodeList'
},
JSXAttribute: {
name: 'Node',
value: 'Node'
},
JSXClosingElement: {
name: 'Node'
},
JSXClosingFragment: {},
JSXElement: {
openingElement: 'Node',
children: 'NodeList',
closingElement: 'Node'
},
JSXEmptyExpression: {},
JSXExpressionContainer: {
expression: 'Node'
},
JSXFragment: {
openingFragment: 'Node',
children: 'NodeList',
closingFragment: 'Node'
},
JSXIdentifier: {},
JSXMemberExpression: {
object: 'Node',
property: 'Node'
},
JSXNamespacedName: {
namespace: 'Node',
name: 'Node'
},
JSXOpeningElement: {
name: 'Node',
attributes: 'NodeList',
typeArguments: 'Node'
},
JSXOpeningFragment: {},
JSXSpreadAttribute: {
argument: 'Node'
},
JSXSpreadChild: {
expression: 'Node'
},
JSXText: {},
KeyofTypeAnnotation: {
argument: 'Node'
},
LabeledStatement: {
label: 'Node',
body: 'Node'
},
LogicalExpression: {
left: 'Node',
right: 'Node'
},
MemberExpression: {
object: 'Node',
property: 'Node'
},
MetaProperty: {
meta: 'Node',
property: 'Node'
},
MethodDefinition: {
key: 'Node',
value: 'Node'
},
MixedTypeAnnotation: {},
NewExpression: {
callee: 'Node',
typeArguments: 'Node',
arguments: 'NodeList'
},
NullableTypeAnnotation: {
typeAnnotation: 'Node'
},
NullLiteral: {},
NullLiteralTypeAnnotation: {},
NumberLiteralTypeAnnotation: {},
NumberTypeAnnotation: {},
NumericLiteral: {},
ObjectExpression: {
properties: 'NodeList'
},
ObjectPattern: {
properties: 'NodeList',
typeAnnotation: 'Node'
},
ObjectTypeAnnotation: {
properties: 'NodeList',
indexers: 'NodeList',
callProperties: 'NodeList',
internalSlots: 'NodeList'
},
ObjectTypeCallProperty: {
value: 'Node'
},
ObjectTypeIndexer: {
id: 'Node',
key: 'Node',
value: 'Node',
variance: 'Node'
},
ObjectTypeInternalSlot: {
id: 'Node',
value: 'Node'
},
ObjectTypeMappedTypeProperty: {
keyTparam: 'Node',
propType: 'Node',
sourceType: 'Node',
variance: 'Node'
},
ObjectTypeProperty: {
key: 'Node',
value: 'Node',
variance: 'Node'
},
ObjectTypeSpreadProperty: {
argument: 'Node'
},
OpaqueType: {
id: 'Node',
typeParameters: 'Node',
impltype: 'Node',
supertype: 'Node'
},
OptionalIndexedAccessType: {
objectType: 'Node',
indexType: 'Node'
},
PrivateIdentifier: {},
Program: {
body: 'NodeList'
},
Property: {
key: 'Node',
value: 'Node'
},
PropertyDefinition: {
key: 'Node',
value: 'Node',
variance: 'Node',
typeAnnotation: 'Node'
},
QualifiedTypeIdentifier: {
qualification: 'Node',
id: 'Node'
},
QualifiedTypeofIdentifier: {
qualification: 'Node',
id: 'Node'
},
RegExpLiteral: {},
RestElement: {
argument: 'Node'
},
ReturnStatement: {
argument: 'Node'
},
SequenceExpression: {
expressions: 'NodeList'
},
SpreadElement: {
argument: 'Node'
},
StringLiteral: {},
StringLiteralTypeAnnotation: {},
StringTypeAnnotation: {},
Super: {},
SwitchCase: {
test: 'Node',
consequent: 'NodeList'
},
SwitchStatement: {
discriminant: 'Node',
cases: 'NodeList'
},
SymbolTypeAnnotation: {},
TaggedTemplateExpression: {
tag: 'Node',
quasi: 'Node'
},
TemplateElement: {},
TemplateLiteral: {
quasis: 'NodeList',
expressions: 'NodeList'
},
ThisExpression: {},
ThisTypeAnnotation: {},
ThrowStatement: {
argument: 'Node'
},
TryStatement: {
block: 'Node',
handler: 'Node',
finalizer: 'Node'
},
TupleTypeAnnotation: {
types: 'NodeList'
},
TupleTypeLabeledElement: {
label: 'Node',
elementType: 'Node',
variance: 'Node'
},
TupleTypeSpreadElement: {
label: 'Node',
typeAnnotation: 'Node'
},
TypeAlias: {
id: 'Node',
typeParameters: 'Node',
right: 'Node'
},
TypeAnnotation: {
typeAnnotation: 'Node'
},
TypeCastExpression: {
expression: 'Node',
typeAnnotation: 'Node'
},
TypeofTypeAnnotation: {
argument: 'Node',
typeArguments: 'Node'
},
TypeOperator: {
typeAnnotation: 'Node'
},
TypeParameter: {
bound: 'Node',
variance: 'Node',
default: 'Node'
},
TypeParameterDeclaration: {
params: 'NodeList'
},
TypeParameterInstantiation: {
params: 'NodeList'
},
TypePredicate: {
parameterName: 'Node',
typeAnnotation: 'Node'
},
UnaryExpression: {
argument: 'Node'
},
UnionTypeAnnotation: {
types: 'NodeList'
},
UpdateExpression: {
argument: 'Node'
},
VariableDeclaration: {
declarations: 'NodeList'
},
VariableDeclarator: {
init: 'Node',
id: 'Node'
},
Variance: {},
VoidTypeAnnotation: {},
WhileStatement: {
body: 'Node',
test: 'Node'
},
WithStatement: {
object: 'Node',
body: 'Node'
},
YieldExpression: {
argument: 'Node'
},
File: {
program: 'Node'
},
ObjectProperty: {
key: 'Node',
value: 'Node'
},
ObjectMethod: {
key: 'Node',
params: 'NodeList',
body: 'Node',
returnType: 'Node',
typeParameters: 'NodeList'
},
ClassMethod: {
key: 'Node',
params: 'NodeList',
body: 'Node',
returnType: 'Node',
typeParameters: 'NodeList'
},
Import: {},
ClassProperty: {
key: 'Node',
value: 'Node',
variance: 'Node',
typeAnnotation: 'Node'
},
ClassPrivateProperty: {
key: 'Node',
value: 'Node',
variance: 'Node',
typeAnnotation: 'Node'
},
PrivateName: {
id: 'Node'
},
OptionalCallExpression: {
callee: 'Node',
typeArguments: 'Node',
arguments: 'NodeList'
},
OptionalMemberExpression: {
object: 'Node',
property: 'Node'
},
ExportNamespaceSpecifier: {
exported: 'Node'
}
};
exports.HERMES_AST_VISITOR_KEYS = HERMES_AST_VISITOR_KEYS;

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
declare export var NODE_CHILD: 'Node';
declare export var NODE_LIST_CHILD: 'NodeList';
declare export var HERMES_AST_VISITOR_KEYS: $ReadOnly<{
[string]: $ReadOnly<{
[string]: 'Node' | 'NodeList',
}>,
}>;

112
node_modules/hermes-parser/dist/getModuleDocblock.js generated vendored Normal file
View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getModuleDocblock = getModuleDocblock;
exports.parseDocblockString = parseDocblockString;
const DIRECTIVE_REGEX = /^\s*@([a-zA-Z0-9_-]+)( +.+)?$/;
function parseDocblockString(docblock) {
const directiveLines = docblock.split('\n') // remove the leading " *" from each line
.map(line => line.trimStart().replace(/^\* ?/, '').trim()).filter(line => line.startsWith('@'));
const directives = // $FlowExpectedError[incompatible-type] - flow types this return as {...}
Object.create(null);
for (const line of directiveLines) {
var _match$;
const match = DIRECTIVE_REGEX.exec(line);
if (match == null) {
continue;
}
const name = match[1]; // explicitly use an empty string if there's no value
// this way the array length tracks how many instances of the directive there was
const value = ((_match$ = match[2]) != null ? _match$ : '').trim();
if (directives[name]) {
directives[name].push(value);
} else {
directives[name] = [value];
}
}
return directives;
}
function getModuleDocblock(hermesProgram) {
const docblockNode = (() => {
if (hermesProgram.type !== 'Program') {
return null;
} // $FlowExpectedError[incompatible-type] - escape out of the unsafe hermes types
const program = hermesProgram;
if (program.comments.length === 0) {
return null;
}
const firstComment = (() => {
const first = program.comments[0];
if (first.type === 'Block') {
return first;
}
if (program.comments.length === 1) {
return null;
} // ESLint will always strip out the shebang comment from the code before passing it to the parser
// https://github.com/eslint/eslint/blob/21d647904dc30f9484b22acdd9243a6d0ecfba38/lib/linter/linter.js#L779
// this means that we're forced to parse it as a line comment :(
// this hacks around it by selecting the second comment in this case
const second = program.comments[1];
if (first.type === 'Line' && first.range[0] === 0 && second.type === 'Block') {
return second;
}
return null;
})();
if (firstComment == null) {
return null;
}
/*
Handle cases like this where the comment isn't actually the first thing in the code:
```
const x = 1; /* docblock *./
```
*/
if (program.body.length > 0 && program.body[0].range[0] < firstComment.range[0]) {
return null;
}
return firstComment;
})();
if (docblockNode == null) {
return null;
}
return {
directives: parseDocblockString(docblockNode.value),
comment: docblockNode
};
}

View File

@@ -0,0 +1,118 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {HermesNode} from './HermesAST';
import type {DocblockDirectives, Program} from 'hermes-estree';
const DIRECTIVE_REGEX = /^\s*@([a-zA-Z0-9_-]+)( +.+)?$/;
export function parseDocblockString(docblock: string): DocblockDirectives {
const directiveLines = docblock
.split('\n')
// remove the leading " *" from each line
.map(line => line.trimStart().replace(/^\* ?/, '').trim())
.filter(line => line.startsWith('@'));
const directives: {
[string]: Array<string>,
} =
// $FlowExpectedError[incompatible-type] - flow types this return as {...}
Object.create(null);
for (const line of directiveLines) {
const match = DIRECTIVE_REGEX.exec(line);
if (match == null) {
continue;
}
const name = match[1];
// explicitly use an empty string if there's no value
// this way the array length tracks how many instances of the directive there was
const value = (match[2] ?? '').trim();
if (directives[name]) {
directives[name].push(value);
} else {
directives[name] = [value];
}
}
return directives;
}
export function getModuleDocblock(
hermesProgram: HermesNode,
): Program['docblock'] {
const docblockNode = (() => {
if (hermesProgram.type !== 'Program') {
return null;
}
// $FlowExpectedError[incompatible-type] - escape out of the unsafe hermes types
const program: Program = hermesProgram;
if (program.comments.length === 0) {
return null;
}
const firstComment = (() => {
const first = program.comments[0];
if (first.type === 'Block') {
return first;
}
if (program.comments.length === 1) {
return null;
}
// ESLint will always strip out the shebang comment from the code before passing it to the parser
// https://github.com/eslint/eslint/blob/21d647904dc30f9484b22acdd9243a6d0ecfba38/lib/linter/linter.js#L779
// this means that we're forced to parse it as a line comment :(
// this hacks around it by selecting the second comment in this case
const second = program.comments[1];
if (
first.type === 'Line' &&
first.range[0] === 0 &&
second.type === 'Block'
) {
return second;
}
return null;
})();
if (firstComment == null) {
return null;
}
/*
Handle cases like this where the comment isn't actually the first thing in the code:
```
const x = 1; /* docblock *./
```
*/
if (
program.body.length > 0 &&
program.body[0].range[0] < firstComment.range[0]
) {
return null;
}
return firstComment;
})();
if (docblockNode == null) {
return null;
}
return {
directives: parseDocblockString(docblockNode.value),
comment: docblockNode,
};
}

146
node_modules/hermes-parser/dist/index.js generated vendored Normal file
View File

@@ -0,0 +1,146 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _exportNames = {
parse: true,
mutateESTreeASTForPrettier: true,
Transforms: true,
FlowVisitorKeys: true,
astArrayMutationHelpers: true,
astNodeMutationHelpers: true
};
exports.mutateESTreeASTForPrettier = exports.astNodeMutationHelpers = exports.astArrayMutationHelpers = exports.Transforms = void 0;
exports.parse = parse;
var HermesParser = _interopRequireWildcard(require("./HermesParser"));
var _HermesToESTreeAdapter = _interopRequireDefault(require("./HermesToESTreeAdapter"));
var _ESTreeVisitorKeys = _interopRequireDefault(require("./generated/ESTreeVisitorKeys"));
exports.FlowVisitorKeys = _ESTreeVisitorKeys.default;
var StripComponentSyntax = _interopRequireWildcard(require("./estree/StripComponentSyntax"));
var StripFlowTypesForBabel = _interopRequireWildcard(require("./estree/StripFlowTypesForBabel"));
var TransformESTreeToBabel = _interopRequireWildcard(require("./babel/TransformESTreeToBabel"));
var StripFlowTypes = _interopRequireWildcard(require("./estree/StripFlowTypes"));
var _ParserOptions = require("./ParserOptions");
Object.keys(_ParserOptions).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _ParserOptions[key]) return;
exports[key] = _ParserOptions[key];
});
var _SimpleTraverser = require("./traverse/SimpleTraverser");
Object.keys(_SimpleTraverser).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _SimpleTraverser[key]) return;
exports[key] = _SimpleTraverser[key];
});
var _SimpleTransform = require("./transform/SimpleTransform");
Object.keys(_SimpleTransform).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _SimpleTransform[key]) return;
exports[key] = _SimpleTransform[key];
});
var _getVisitorKeys = require("./traverse/getVisitorKeys");
Object.keys(_getVisitorKeys).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _getVisitorKeys[key]) return;
exports[key] = _getVisitorKeys[key];
});
var _astArrayMutationHelpers = _interopRequireWildcard(require("./transform/astArrayMutationHelpers"));
exports.astArrayMutationHelpers = _astArrayMutationHelpers;
var _astNodeMutationHelpers = _interopRequireWildcard(require("./transform/astNodeMutationHelpers"));
exports.astNodeMutationHelpers = _astNodeMutationHelpers;
var _mutateESTreeASTForPrettier = _interopRequireDefault(require("./utils/mutateESTreeASTForPrettier"));
exports.mutateESTreeASTForPrettier = _mutateESTreeASTForPrettier.default;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
const DEFAULTS = {
flow: 'detect'
};
function getOptions(options = { ...DEFAULTS
}) {
// Default to detecting whether to parse Flow syntax by the presence
// of an pragma.
if (options.flow == null) {
options.flow = DEFAULTS.flow;
} else if (options.flow !== 'all' && options.flow !== 'detect') {
throw new Error('flow option must be "all" or "detect"');
}
if (options.sourceType === 'unambiguous') {
// Clear source type so that it will be detected from the contents of the file
delete options.sourceType;
} else if (options.sourceType != null && options.sourceType !== 'script' && options.sourceType !== 'module') {
throw new Error('sourceType option must be "script", "module", or "unambiguous" if set');
}
if (options.enableExperimentalComponentSyntax == null) {
options.enableExperimentalComponentSyntax = true; // Enable by default
}
options.tokens = options.tokens === true;
options.allowReturnOutsideFunction = options.allowReturnOutsideFunction === true;
return options;
}
// eslint-disable-next-line no-redeclare
function parse(code, opts) {
const options = getOptions(opts);
const ast = HermesParser.parse(code, options);
const estreeAdapter = new _HermesToESTreeAdapter.default(options, code);
const estreeAST = estreeAdapter.transform(ast);
if (options.babel !== true) {
return estreeAST;
}
const loweredESTreeAST = [StripComponentSyntax.transformProgram, StripFlowTypesForBabel.transformProgram].reduce((ast, transform) => transform(ast, options), estreeAST);
return TransformESTreeToBabel.transformProgram(loweredESTreeAST, options);
}
const Transforms = {
stripComponentSyntax: StripComponentSyntax.transformProgram,
stripFlowTypesForBabel: StripFlowTypesForBabel.transformProgram,
stripFlowTypes: StripFlowTypes.transformProgram
};
exports.Transforms = Transforms;

112
node_modules/hermes-parser/dist/index.js.flow generated vendored Normal file
View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {Program as ESTreeProgram} from 'hermes-estree';
import type {ParserOptions} from './ParserOptions';
import type {BabelFile} from './babel/TransformESTreeToBabel';
import * as HermesParser from './HermesParser';
import HermesToESTreeAdapter from './HermesToESTreeAdapter';
import FlowVisitorKeys from './generated/ESTreeVisitorKeys';
import * as StripComponentSyntax from './estree/StripComponentSyntax';
import * as StripFlowTypesForBabel from './estree/StripFlowTypesForBabel';
import * as TransformESTreeToBabel from './babel/TransformESTreeToBabel';
import * as StripFlowTypes from './estree/StripFlowTypes';
const DEFAULTS = {
flow: 'detect',
};
function getOptions(options?: ParserOptions = {...DEFAULTS}) {
// Default to detecting whether to parse Flow syntax by the presence
// of an @flow pragma.
if (options.flow == null) {
options.flow = DEFAULTS.flow;
} else if (options.flow !== 'all' && options.flow !== 'detect') {
throw new Error('flow option must be "all" or "detect"');
}
if (options.sourceType === 'unambiguous') {
// Clear source type so that it will be detected from the contents of the file
delete options.sourceType;
} else if (
options.sourceType != null &&
options.sourceType !== 'script' &&
options.sourceType !== 'module'
) {
throw new Error(
'sourceType option must be "script", "module", or "unambiguous" if set',
);
}
if (options.enableExperimentalComponentSyntax == null) {
options.enableExperimentalComponentSyntax = true; // Enable by default
}
options.tokens = options.tokens === true;
options.allowReturnOutsideFunction =
options.allowReturnOutsideFunction === true;
return options;
}
declare function parse(
code: string,
opts: {...ParserOptions, babel: true},
): BabelFile;
// eslint-disable-next-line no-redeclare
declare function parse(
code: string,
opts?:
| {...ParserOptions, babel?: false | void}
| {...ParserOptions, babel: false},
): ESTreeProgram;
// eslint-disable-next-line no-redeclare
export function parse(
code: string,
opts?: ParserOptions,
): BabelFile | ESTreeProgram {
const options = getOptions(opts);
const ast = HermesParser.parse(code, options);
const estreeAdapter = new HermesToESTreeAdapter(options, code);
const estreeAST = estreeAdapter.transform(ast);
if (options.babel !== true) {
return estreeAST;
}
const loweredESTreeAST = [
StripComponentSyntax.transformProgram,
StripFlowTypesForBabel.transformProgram,
].reduce((ast, transform) => transform(ast, options), estreeAST);
return TransformESTreeToBabel.transformProgram(loweredESTreeAST, options);
}
export type {ParserOptions} from './ParserOptions';
export * from './ParserOptions';
export * from './traverse/SimpleTraverser';
export * from './transform/SimpleTransform';
export * from './traverse/getVisitorKeys';
export {FlowVisitorKeys};
export * as astArrayMutationHelpers from './transform/astArrayMutationHelpers';
export * as astNodeMutationHelpers from './transform/astNodeMutationHelpers';
export {default as mutateESTreeASTForPrettier} from './utils/mutateESTreeASTForPrettier';
const Transforms = {
stripComponentSyntax: StripComponentSyntax.transformProgram,
stripFlowTypesForBabel: StripFlowTypesForBabel.transformProgram,
stripFlowTypes: StripFlowTypes.transformProgram,
};
export {Transforms};

View File

@@ -0,0 +1,120 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SimpleTransform = void 0;
var _SimpleTraverser = require("../traverse/SimpleTraverser");
var _astNodeMutationHelpers = require("./astNodeMutationHelpers");
function setParentPointer(node, parent) {
if (parent != null) {
// $FlowExpectedError[cannot-write]
node.parent = parent;
}
}
/**
* A simple class to recursively tranform AST trees.
*/
class SimpleTransform {
/**
* Transform the given AST tree.
* @param rootNode The root node to traverse.
* @param options The option object.
* @return The modified rootNode or a new node if the rootNode was transformed directly.
*/
transform(rootNode, options) {
let resultRootNode = rootNode;
_SimpleTraverser.SimpleTraverser.traverse(rootNode, {
enter: (node, parent) => {
// Ensure the parent pointers are correctly set before entering the node.
setParentPointer(node, parent);
const resultNode = options.transform(node);
if (resultNode !== node) {
let traversedResultNode = null;
if (resultNode != null) {
// Ensure the new node has the correct parent pointers before recursing again.
setParentPointer(resultNode, parent);
traversedResultNode = this.transform(resultNode, options);
}
if (parent == null) {
if (node !== rootNode) {
throw new Error('SimpleTransform infra error: Parent not set on non root node, this should not be possible');
}
resultRootNode = traversedResultNode;
} else if (traversedResultNode == null) {
(0, _astNodeMutationHelpers.removeNodeOnParent)(node, parent, options.visitorKeys);
} else {
(0, _astNodeMutationHelpers.replaceNodeOnParent)(node, parent, traversedResultNode, options.visitorKeys);
setParentPointer(traversedResultNode, parent);
}
throw _SimpleTraverser.SimpleTraverser.Skip;
}
},
leave(_node) {},
visitorKeys: options.visitorKeys
});
return resultRootNode;
}
/**
* Transform the given AST tree.
* @param node The root node to traverse.
* @param options The option object.
*/
static transform(node, options) {
return new SimpleTransform().transform(node, options);
}
static transformProgram(program, options) {
const result = SimpleTransform.transform(program, options);
if ((result == null ? void 0 : result.type) === 'Program') {
return result;
}
throw new Error('SimpleTransform.transformProgram: Expected program node.');
}
/**
* Return a new AST node with the given properties overrided if needed.
*
* This function takes care to only create new nodes when needed. Referential equality of nodes
* is important as its used to know if a node should be re-traversed.
*
* @param node The base AST node.
* @param overrideProps New properties to apply to the node.
* @return Either the orginal node if the properties matched the existing node or a new node with
* the new properties.
*/
static nodeWith(node, overrideProps, visitorKeys) {
return (0, _astNodeMutationHelpers.nodeWith)(node, overrideProps, visitorKeys);
}
}
exports.SimpleTransform = SimpleTransform;

View File

@@ -0,0 +1,143 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {VisitorKeysType} from '../traverse/getVisitorKeys';
import type {ESNode, Program} from 'hermes-estree';
import {SimpleTraverser} from '../traverse/SimpleTraverser';
import {
nodeWith,
removeNodeOnParent,
replaceNodeOnParent,
} from './astNodeMutationHelpers';
/**
* Transform callback
* @param node The node we are visiting
* @returns
* - return input node, signals no changes were made will continue to the next node.
* - return new node, the old node will be replaced in the AST. The new node and its
* children are then traversed.
* - return null, signals the node should be deleted from the AST.
*/
export type TransformCallback = (node: ESNode) => ESNode | null;
export type TransformOptions = $ReadOnly<{
/** The callback function which is called on entering each node. */
transform: TransformCallback,
/** The set of visitor keys to use for traversal. Defaults to the Flow ESTree visitor keys */
visitorKeys?: ?VisitorKeysType,
}>;
function setParentPointer(node: ESNode, parent: ?ESNode): void {
if (parent != null) {
// $FlowExpectedError[cannot-write]
node.parent = parent;
}
}
/**
* A simple class to recursively tranform AST trees.
*/
export class SimpleTransform {
/**
* Transform the given AST tree.
* @param rootNode The root node to traverse.
* @param options The option object.
* @return The modified rootNode or a new node if the rootNode was transformed directly.
*/
transform(rootNode: ESNode, options: TransformOptions): ESNode | null {
let resultRootNode: ESNode | null = rootNode;
SimpleTraverser.traverse(rootNode, {
enter: (node: ESNode, parent: ?ESNode) => {
// Ensure the parent pointers are correctly set before entering the node.
setParentPointer(node, parent);
const resultNode = options.transform(node);
if (resultNode !== node) {
let traversedResultNode = null;
if (resultNode != null) {
// Ensure the new node has the correct parent pointers before recursing again.
setParentPointer(resultNode, parent);
traversedResultNode = this.transform(resultNode, options);
}
if (parent == null) {
if (node !== rootNode) {
throw new Error(
'SimpleTransform infra error: Parent not set on non root node, this should not be possible',
);
}
resultRootNode = traversedResultNode;
} else if (traversedResultNode == null) {
removeNodeOnParent(node, parent, options.visitorKeys);
} else {
replaceNodeOnParent(
node,
parent,
traversedResultNode,
options.visitorKeys,
);
setParentPointer(traversedResultNode, parent);
}
throw SimpleTraverser.Skip;
}
},
leave(_node: ESNode) {},
visitorKeys: options.visitorKeys,
});
return resultRootNode;
}
/**
* Transform the given AST tree.
* @param node The root node to traverse.
* @param options The option object.
*/
static transform(node: ESNode, options: TransformOptions): ESNode | null {
return new SimpleTransform().transform(node, options);
}
static transformProgram(
program: Program,
options: TransformOptions,
): Program {
const result = SimpleTransform.transform(program, options);
if (result?.type === 'Program') {
return result;
}
throw new Error('SimpleTransform.transformProgram: Expected program node.');
}
/**
* Return a new AST node with the given properties overrided if needed.
*
* This function takes care to only create new nodes when needed. Referential equality of nodes
* is important as its used to know if a node should be re-traversed.
*
* @param node The base AST node.
* @param overrideProps New properties to apply to the node.
* @return Either the orginal node if the properties matched the existing node or a new node with
* the new properties.
*/
static nodeWith<T: ESNode>(
node: T,
overrideProps: Partial<T>,
visitorKeys?: VisitorKeysType,
): T {
return nodeWith<T>(node, overrideProps, visitorKeys);
}
}

View File

@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.arrayIsEqual = arrayIsEqual;
exports.insertInArray = insertInArray;
exports.removeFromArray = removeFromArray;
exports.replaceInArray = replaceInArray;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
function assertArrayBounds(array, index) {
if (index < 0 || index >= array.length) {
throw new Error(`Invalid Mutation: Tried to mutate an elements array with an out of bounds index. Index: ${index}, Array Size: ${array.length}`);
}
}
function arrayIsEqual(a1, a2) {
if (a1 === a2) {
return true;
}
if (a1.length !== a2.length) {
return false;
}
for (let i = 0; i < a1.length; i++) {
if (a1[i] !== a2[i]) {
return false;
}
}
return true;
}
function insertInArray(array, index, elements) {
if (index === array.length) {
// Support the insert at end of array case.
return array.concat(elements);
}
assertArrayBounds(array, index);
return array.slice(0, index).concat(elements).concat(array.slice(index));
}
function removeFromArray(array, index) {
assertArrayBounds(array, index);
return [...array.slice(0, index), ...array.slice(index + 1)];
}
function replaceInArray(array, index, elements) {
assertArrayBounds(array, index);
return array.slice(0, index).concat(elements).concat(array.slice(index + 1));
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
function assertArrayBounds<T>(array: $ReadOnlyArray<T>, index: number): void {
if (index < 0 || index >= array.length) {
throw new Error(
`Invalid Mutation: Tried to mutate an elements array with an out of bounds index. Index: ${index}, Array Size: ${array.length}`,
);
}
}
export function arrayIsEqual(
a1: $ReadOnlyArray<mixed>,
a2: $ReadOnlyArray<mixed>,
): boolean {
if (a1 === a2) {
return true;
}
if (a1.length !== a2.length) {
return false;
}
for (let i = 0; i < a1.length; i++) {
if (a1[i] !== a2[i]) {
return false;
}
}
return true;
}
export function insertInArray<T>(
array: $ReadOnlyArray<T>,
index: number,
elements: $ReadOnlyArray<T>,
): Array<T> {
if (index === array.length) {
// Support the insert at end of array case.
return array.concat(elements);
}
assertArrayBounds(array, index);
return array.slice(0, index).concat(elements).concat(array.slice(index));
}
export function removeFromArray<T>(
array: $ReadOnlyArray<T>,
index: number,
): Array<T> {
assertArrayBounds(array, index);
return [...array.slice(0, index), ...array.slice(index + 1)];
}
export function replaceInArray<T>(
array: $ReadOnlyArray<T>,
index: number,
elements: $ReadOnlyArray<T>,
): Array<T> {
assertArrayBounds(array, index);
return array
.slice(0, index)
.concat(elements)
.concat(array.slice(index + 1));
}

View File

@@ -0,0 +1,195 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.deepCloneNode = deepCloneNode;
exports.nodeWith = nodeWith;
exports.removeNodeOnParent = removeNodeOnParent;
exports.replaceNodeOnParent = replaceNodeOnParent;
exports.setParentPointersInDirectChildren = setParentPointersInDirectChildren;
exports.shallowCloneNode = shallowCloneNode;
exports.updateAllParentPointers = updateAllParentPointers;
var _astArrayMutationHelpers = require("./astArrayMutationHelpers");
var _getVisitorKeys = require("../traverse/getVisitorKeys");
var _SimpleTraverser = require("../traverse/SimpleTraverser");
function getParentKey(target, parent, visitorKeys) {
if (parent == null) {
throw new Error(`Expected parent node to be set on "${target.type}"`);
}
for (const key of (0, _getVisitorKeys.getVisitorKeys)(parent, visitorKeys)) {
if ((0, _getVisitorKeys.isNode)( // $FlowExpectedError[prop-missing]
parent[key])) {
if (parent[key] === target) {
return {
type: 'single',
node: parent,
key
};
}
} else if (Array.isArray(parent[key])) {
for (let i = 0; i < parent[key].length; i += 1) {
// $FlowExpectedError[invalid-tuple-index]
const current = parent[key][i];
if (current === target) {
return {
type: 'array',
node: parent,
key,
targetIndex: i
};
}
}
}
} // this shouldn't happen ever
throw new Error(`Expected to find the ${target.type} as a direct child of the ${parent.type}.`);
}
/**
* Replace a node with a new node within an AST (via the parent pointer).
*/
function replaceNodeOnParent(originalNode, originalNodeParent, nodeToReplaceWith, visitorKeys) {
const replacementParent = getParentKey(originalNode, originalNodeParent, visitorKeys);
const parent = replacementParent.node;
if (replacementParent.type === 'array') {
// $FlowExpectedError[prop-missing]
parent[replacementParent.key] = (0, _astArrayMutationHelpers.replaceInArray)( // $FlowExpectedError[prop-missing]
parent[replacementParent.key], replacementParent.targetIndex, [nodeToReplaceWith]);
} else {
// $FlowExpectedError[prop-missing]
parent[replacementParent.key] = nodeToReplaceWith;
}
}
/**
* Remove a node from the AST its connected to (via the parent pointer).
*/
function removeNodeOnParent(originalNode, originalNodeParent, visitorKeys) {
const replacementParent = getParentKey(originalNode, originalNodeParent, visitorKeys);
const parent = replacementParent.node;
if (replacementParent.type === 'array') {
// $FlowExpectedError[prop-missing]
parent[replacementParent.key] = (0, _astArrayMutationHelpers.removeFromArray)( // $FlowExpectedError[prop-missing]
parent[replacementParent.key], replacementParent.targetIndex);
} else {
// $FlowExpectedError[prop-missing]
parent[replacementParent.key] = null;
}
}
/**
* Corrects the parent pointers in direct children of the given node.
*/
function setParentPointersInDirectChildren(node, visitorKeys) {
for (const key of (0, _getVisitorKeys.getVisitorKeys)(node, visitorKeys)) {
if ((0, _getVisitorKeys.isNode)(node[key])) {
// $FlowExpectedError[cannot-write]
node[key].parent = node;
} else if (Array.isArray(node[key])) {
for (const child of node[key]) {
child.parent = node;
}
}
}
}
/**
* Traverses the entire subtree to ensure the parent pointers are set correctly.
*/
function updateAllParentPointers(node, visitorKeys) {
_SimpleTraverser.SimpleTraverser.traverse(node, {
enter(node, parent) {
// $FlowExpectedError[cannot-write]
node.parent = parent;
},
leave() {},
visitorKeys
});
}
/**
* Clone node and add new props.
*
* This will only create a new object if the overrides actually result in a change.
*/
function nodeWith(node, overrideProps, visitorKeys) {
// Check if this will actually result in a change, maintaining referential equality is important.
const willBeUnchanged = Object.entries(overrideProps).every(([key, value]) => {
const node_ = node;
if (Array.isArray(value)) {
return Array.isArray(node_[key]) ? (0, _astArrayMutationHelpers.arrayIsEqual)(node_[key], value) : false;
}
return node_[key] === value;
});
if (willBeUnchanged) {
return node;
} // Create new node.
// $FlowExpectedError[cannot-spread-interface]
const newNode = { ...node,
...overrideProps
}; // Ensure parent pointers are correctly set within this nodes children.
setParentPointersInDirectChildren(newNode, visitorKeys);
return newNode;
}
/**
* Shallow clones node, providing a new reference for an existing node.
*/
function shallowCloneNode(node, visitorKeys) {
// $FlowExpectedError[cannot-spread-interface]
const newNode = { ...node
}; // Ensure parent pointers are correctly set within this nodes children.
setParentPointersInDirectChildren(newNode, visitorKeys);
return newNode;
}
/**
* Deeply clones node and its entire tree.
*/
function deepCloneNode(node, visitorKeys) {
const clone = JSON.parse(JSON.stringify(node, (key, value) => {
// null out parent pointers
if (key === 'parent') {
return undefined;
}
return value;
}));
updateAllParentPointers(clone, visitorKeys);
return clone;
}

View File

@@ -0,0 +1,238 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {ESNode} from 'hermes-estree';
import type {VisitorKeysType} from '../traverse/getVisitorKeys';
import {
arrayIsEqual,
removeFromArray,
replaceInArray,
} from './astArrayMutationHelpers';
import {getVisitorKeys, isNode} from '../traverse/getVisitorKeys';
import {SimpleTraverser} from '../traverse/SimpleTraverser';
function getParentKey(
target: ESNode,
parent: ESNode,
visitorKeys?: ?VisitorKeysType,
): $ReadOnly<
| {
type: 'single',
node: ESNode,
key: string,
}
| {
type: 'array',
node: ESNode,
key: string,
targetIndex: number,
},
> {
if (parent == null) {
throw new Error(`Expected parent node to be set on "${target.type}"`);
}
for (const key of getVisitorKeys(parent, visitorKeys)) {
if (
isNode(
// $FlowExpectedError[prop-missing]
parent[key],
)
) {
if (parent[key] === target) {
return {type: 'single', node: parent, key};
}
} else if (Array.isArray(parent[key])) {
for (let i = 0; i < parent[key].length; i += 1) {
// $FlowExpectedError[invalid-tuple-index]
const current = parent[key][i];
if (current === target) {
return {type: 'array', node: parent, key, targetIndex: i};
}
}
}
}
// this shouldn't happen ever
throw new Error(
`Expected to find the ${target.type} as a direct child of the ${parent.type}.`,
);
}
/**
* Replace a node with a new node within an AST (via the parent pointer).
*/
export function replaceNodeOnParent(
originalNode: ESNode,
originalNodeParent: ESNode,
nodeToReplaceWith: ESNode,
visitorKeys?: ?VisitorKeysType,
): void {
const replacementParent = getParentKey(
originalNode,
originalNodeParent,
visitorKeys,
);
const parent = replacementParent.node;
if (replacementParent.type === 'array') {
// $FlowExpectedError[prop-missing]
parent[replacementParent.key] = replaceInArray(
// $FlowExpectedError[prop-missing]
parent[replacementParent.key],
replacementParent.targetIndex,
[nodeToReplaceWith],
);
} else {
// $FlowExpectedError[prop-missing]
parent[replacementParent.key] = nodeToReplaceWith;
}
}
/**
* Remove a node from the AST its connected to (via the parent pointer).
*/
export function removeNodeOnParent(
originalNode: ESNode,
originalNodeParent: ESNode,
visitorKeys?: ?VisitorKeysType,
): void {
const replacementParent = getParentKey(
originalNode,
originalNodeParent,
visitorKeys,
);
const parent = replacementParent.node;
if (replacementParent.type === 'array') {
// $FlowExpectedError[prop-missing]
parent[replacementParent.key] = removeFromArray(
// $FlowExpectedError[prop-missing]
parent[replacementParent.key],
replacementParent.targetIndex,
);
} else {
// $FlowExpectedError[prop-missing]
parent[replacementParent.key] = null;
}
}
/**
* Corrects the parent pointers in direct children of the given node.
*/
export function setParentPointersInDirectChildren(
node: ESNode,
visitorKeys?: ?VisitorKeysType,
): void {
for (const key: $FlowFixMe of getVisitorKeys(node, visitorKeys)) {
if (isNode(node[key])) {
// $FlowExpectedError[cannot-write]
node[key].parent = node;
} else if (Array.isArray(node[key])) {
for (const child of node[key]) {
child.parent = node;
}
}
}
}
/**
* Traverses the entire subtree to ensure the parent pointers are set correctly.
*/
export function updateAllParentPointers(
node: ESNode,
visitorKeys?: ?VisitorKeysType,
) {
SimpleTraverser.traverse(node, {
enter(node, parent) {
// $FlowExpectedError[cannot-write]
node.parent = parent;
},
leave() {},
visitorKeys,
});
}
/**
* Clone node and add new props.
*
* This will only create a new object if the overrides actually result in a change.
*/
export function nodeWith<T: ESNode>(
node: T,
overrideProps: Partial<T>,
visitorKeys?: ?VisitorKeysType,
): T {
// Check if this will actually result in a change, maintaining referential equality is important.
const willBeUnchanged = Object.entries(overrideProps).every(
([key, value]) => {
const node_: $FlowFixMe = node;
if (Array.isArray(value)) {
return Array.isArray(node_[key])
? arrayIsEqual(node_[key], value)
: false;
}
return node_[key] === value;
},
);
if (willBeUnchanged) {
return node;
}
// Create new node.
// $FlowExpectedError[cannot-spread-interface]
const newNode: T = {
...node,
...overrideProps,
};
// Ensure parent pointers are correctly set within this nodes children.
setParentPointersInDirectChildren(newNode, visitorKeys);
return newNode;
}
/**
* Shallow clones node, providing a new reference for an existing node.
*/
export function shallowCloneNode<T: ESNode>(
node: T,
visitorKeys?: ?VisitorKeysType,
): T {
// $FlowExpectedError[cannot-spread-interface]
const newNode: T = {...node};
// Ensure parent pointers are correctly set within this nodes children.
setParentPointersInDirectChildren(newNode, visitorKeys);
return newNode;
}
/**
* Deeply clones node and its entire tree.
*/
export function deepCloneNode<T: ESNode>(
node: T,
visitorKeys?: ?VisitorKeysType,
): T {
const clone: T = JSON.parse(
JSON.stringify(node, (key, value) => {
// null out parent pointers
if (key === 'parent') {
return undefined;
}
return value;
}),
);
updateAllParentPointers(clone, visitorKeys);
return clone;
}

View File

@@ -0,0 +1,137 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SimpleTraverserSkip = exports.SimpleTraverserBreak = exports.SimpleTraverser = void 0;
var _getVisitorKeys = require("./getVisitorKeys");
/**
* Can be thrown within the traversal "enter" function to prevent the traverser
* from traversing the node any further, essentially culling the remainder of the
* AST branch
*/
const SimpleTraverserSkip = new Error();
/**
* Can be thrown at any point during the traversal to immediately stop traversal
* entirely.
*/
exports.SimpleTraverserSkip = SimpleTraverserSkip;
const SimpleTraverserBreak = new Error();
/**
* A very simple traverser class to traverse AST trees.
*/
exports.SimpleTraverserBreak = SimpleTraverserBreak;
class SimpleTraverser {
/**
* Traverse the given AST tree.
* @param node The root node to traverse.
* @param options The option object.
*/
traverse(node, options) {
try {
this._traverse(node, null, options);
} catch (ex) {
if (ex === SimpleTraverserBreak) {
return;
}
throw ex;
}
}
/**
* Traverse the given AST tree recursively.
* @param node The current node.
* @param parent The parent node.
* @private
*/
_traverse(node, parent, options) {
if (!(0, _getVisitorKeys.isNode)(node)) {
return;
}
try {
options.enter(node, parent);
} catch (ex) {
if (ex === SimpleTraverserSkip) {
return;
}
this._setErrorContext(ex, node);
throw ex;
}
const keys = (0, _getVisitorKeys.getVisitorKeys)(node, options.visitorKeys);
for (const key of keys) {
const child = node[key];
if (Array.isArray(child)) {
for (let j = 0; j < child.length; ++j) {
this._traverse(child[j], node, options);
}
} else {
this._traverse(child, node, options);
}
}
try {
options.leave(node, parent);
} catch (ex) {
if (ex === SimpleTraverserSkip) {
return;
}
this._setErrorContext(ex, node);
throw ex;
}
}
/**
* Set useful contextual information onto the error object.
* @param ex The error object.
* @param node The current node.
* @private
*/
_setErrorContext(ex, node) {
// $FlowFixMe[prop-missing]
ex.currentNode = {
type: node.type,
range: node.range,
loc: node.loc
};
}
/**
* Traverse the given AST tree.
* @param node The root node to traverse.
* @param options The option object.
*/
static traverse(node, options) {
new SimpleTraverser().traverse(node, options);
}
}
exports.SimpleTraverser = SimpleTraverser;
SimpleTraverser.Break = SimpleTraverserBreak;
SimpleTraverser.Skip = SimpleTraverserSkip;

View File

@@ -0,0 +1,133 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {VisitorKeysType} from './getVisitorKeys';
import type {ESNode} from 'hermes-estree';
import {getVisitorKeys, isNode} from './getVisitorKeys';
export type TraverserCallback = (node: ESNode, parent: ?ESNode) => void;
export type TraverserOptions = $ReadOnly<{
/** The callback function which is called on entering each node. */
enter: TraverserCallback,
/** The callback function which is called on leaving each node. */
leave: TraverserCallback,
/** The set of visitor keys to use for traversal. Defaults to the Flow ESTree visitor keys */
visitorKeys?: ?VisitorKeysType,
}>;
/**
* Can be thrown within the traversal "enter" function to prevent the traverser
* from traversing the node any further, essentially culling the remainder of the
* AST branch
*/
export const SimpleTraverserSkip: Error = new Error();
/**
* Can be thrown at any point during the traversal to immediately stop traversal
* entirely.
*/
export const SimpleTraverserBreak: Error = new Error();
/**
* A very simple traverser class to traverse AST trees.
*/
export class SimpleTraverser {
static Break: Error = SimpleTraverserBreak;
static Skip: Error = SimpleTraverserSkip;
/**
* Traverse the given AST tree.
* @param node The root node to traverse.
* @param options The option object.
*/
traverse(node: ESNode, options: TraverserOptions): void {
try {
this._traverse(node, null, options);
} catch (ex) {
if (ex === SimpleTraverserBreak) {
return;
}
throw ex;
}
}
/**
* Traverse the given AST tree recursively.
* @param node The current node.
* @param parent The parent node.
* @private
*/
_traverse(node: ESNode, parent: ?ESNode, options: TraverserOptions): void {
if (!isNode(node)) {
return;
}
try {
options.enter(node, parent);
} catch (ex) {
if (ex === SimpleTraverserSkip) {
return;
}
this._setErrorContext(ex, node);
throw ex;
}
const keys = getVisitorKeys(node, options.visitorKeys);
for (const key of keys) {
const child: ESNode | $ReadOnlyArray<ESNode> = (node[
(key: $FlowFixMe)
]: $FlowFixMe);
if (Array.isArray(child)) {
for (let j = 0; j < child.length; ++j) {
this._traverse(child[j], node, options);
}
} else {
this._traverse(child, node, options);
}
}
try {
options.leave(node, parent);
} catch (ex) {
if (ex === SimpleTraverserSkip) {
return;
}
this._setErrorContext(ex, node);
throw ex;
}
}
/**
* Set useful contextual information onto the error object.
* @param ex The error object.
* @param node The current node.
* @private
*/
_setErrorContext(ex: Error, node: ESNode): void {
// $FlowFixMe[prop-missing]
ex.currentNode = {
type: node.type,
range: node.range,
loc: node.loc,
};
}
/**
* Traverse the given AST tree.
* @param node The root node to traverse.
* @param options The option object.
*/
static traverse(node: ESNode, options: TraverserOptions) {
new SimpleTraverser().traverse(node, options);
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @noformat
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getVisitorKeys = getVisitorKeys;
exports.isNode = isNode;
var _ESTreeVisitorKeys = _interopRequireDefault(require("../generated/ESTreeVisitorKeys"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function isNode(thing)
/*: implies thing is {+[string]: mixed} */
{
return typeof thing === 'object' && thing != null && typeof thing.type === 'string';
}
function getVisitorKeys(node, visitorKeys) {
const keys = (visitorKeys != null ? visitorKeys : _ESTreeVisitorKeys.default)[node.type];
if (keys == null) {
throw new Error(`No visitor keys found for node type "${node.type}".`);
} // $FlowExpectedError[prop-missing]
return keys;
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @noformat
*/
'use strict';
import type {ESNode} from 'hermes-estree';
import type {VisitorKeys as VisitorKeysType} from '../generated/ESTreeVisitorKeys';
import FlowVisitorKeys from '../generated/ESTreeVisitorKeys';
export function isNode(thing: mixed) /*: implies thing is {+[string]: mixed} */ {
return (
typeof thing === 'object' && thing != null && typeof thing.type === 'string'
);
}
export type {VisitorKeysType};
export function getVisitorKeys<T: ESNode>(
node: T,
visitorKeys?: ?VisitorKeysType,
): $ReadOnlyArray<$Keys<T>> {
const keys = (visitorKeys ?? FlowVisitorKeys)[node.type];
if (keys == null) {
throw new Error(`No visitor keys found for node type "${node.type}".`);
}
// $FlowExpectedError[prop-missing]
return keys;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createSyntaxError = createSyntaxError;
function createSyntaxError(node, err) {
const syntaxError = new SyntaxError(err); // $FlowExpectedError[prop-missing]
syntaxError.loc = {
line: node.loc.start.line,
column: node.loc.start.column
};
return syntaxError;
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {ESNode} from 'hermes-estree';
export function createSyntaxError(node: ESNode, err: string): SyntaxError {
const syntaxError = new SyntaxError(err);
// $FlowExpectedError[prop-missing]
syntaxError.loc = {
line: node.loc.start.line,
column: node.loc.start.column,
};
return syntaxError;
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = mutate;
var _SimpleTransform = require("../transform/SimpleTransform");
// https://github.com/prettier/prettier/blob/d962466a828f8ef51435e3e8840178d90b7ec6cd/src/language-js/parse/postprocess/index.js#L161-L182
function transformChainExpression(node, comments) {
if (comments != null) {
var _node$comments;
// $FlowExpectedError[prop-missing]
const joinedComments = comments.concat((_node$comments = node.comments) != null ? _node$comments : []); // $FlowExpectedError[prop-missing]
// $FlowFixMe[cannot-write]
node.comments = joinedComments;
}
switch (node.type) {
case 'CallExpression':
// $FlowExpectedError[cannot-spread-interface]
return { ...node,
type: 'OptionalCallExpression',
callee: transformChainExpression(node.callee)
};
case 'MemberExpression':
// $FlowExpectedError[cannot-spread-interface]
return { ...node,
type: 'OptionalMemberExpression',
object: transformChainExpression(node.object)
};
// No default
}
return node;
}
function mutate(rootNode, visitorKeys) {
// Since we don't return the result of `transform` we need to be careful not to replace the Program root node.
_SimpleTransform.SimpleTransform.transform(rootNode, {
transform(node) {
// prettier fully expects the parent pointers are NOT set and
// certain cases can crash due to prettier infinite-looping
// whilst naively traversing the parent property
// https://github.com/prettier/prettier/issues/11793
// Note: Only needed for prettier V2, this is supported in V3
if (node.parent) {
// $FlowExpectedError[cannot-write]
delete node.parent;
} // prettier currently relies on the AST being in the old-school, deprecated AST format for optional chaining
// so we have to apply their transform to our AST so it can actually format it.
// Note: Only needed for prettier V2, this is supported in V3
if (node.type === 'ChainExpression') {
// $FlowFixMe[prop-missing]
return transformChainExpression(node.expression, node == null ? void 0 : node.comments);
} // Prettier currently relies on comparing the `node` vs `node.value` start positions to know if an
// `ObjectTypeProperty` is a method or not (instead of using the `node.method` boolean). To correctly print
// the node when its not a method we need the start position to be different from the `node.value`s start
// position.
if (node.type === 'ObjectTypeProperty') {
if (node.method === false && node.kind === 'init' && node.range[0] === 1 && node.value.range[0] === 1) {
// $FlowExpectedError[cannot-write]
// $FlowExpectedError[cannot-spread-interface]
node.value = { ...node.value,
range: [2, node.value.range[1]]
};
}
return node;
} // Prettier currently relies on comparing the the start positions to know if the import/export specifier should have a
// rename (eg `Name` vs `Name as Name`) when the name is exactly the same
// So we need to ensure that the range is always the same to avoid the useless code printing
if (node.type === 'ImportSpecifier') {
if (node.local.name === node.imported.name) {
if (node.local.range == null) {
// for our TS-ast printing which has no locs
// $FlowExpectedError[cannot-write]
node.local.range = [0, 0];
} // $FlowExpectedError[cannot-write]
node.imported.range = [...node.local.range];
}
return node;
}
if (node.type === 'ExportSpecifier') {
if (node.local.name === node.exported.name) {
if (node.local.range == null) {
// for our TS-ast printing which has no locs
// $FlowExpectedError[cannot-write]
node.local.range = [0, 0];
} // $FlowExpectedError[cannot-write]
node.exported.range = [...node.local.range];
}
return node;
}
return node;
},
visitorKeys
});
}

View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {ESNode, Program, Comment} from 'hermes-estree';
import type {VisitorKeysType} from '../traverse/getVisitorKeys';
import {SimpleTransform} from '../transform/SimpleTransform';
// https://github.com/prettier/prettier/blob/d962466a828f8ef51435e3e8840178d90b7ec6cd/src/language-js/parse/postprocess/index.js#L161-L182
function transformChainExpression(
node: ESNode,
comments: ?$ReadOnlyArray<Comment>,
): ESNode {
if (comments != null) {
// $FlowExpectedError[prop-missing]
const joinedComments = comments.concat(node.comments ?? []);
// $FlowExpectedError[prop-missing]
// $FlowFixMe[cannot-write]
node.comments = joinedComments;
}
switch (node.type) {
case 'CallExpression':
// $FlowExpectedError[cannot-spread-interface]
return {
...node,
type: 'OptionalCallExpression',
callee: transformChainExpression(node.callee),
};
case 'MemberExpression':
// $FlowExpectedError[cannot-spread-interface]
return {
...node,
type: 'OptionalMemberExpression',
object: transformChainExpression(node.object),
};
// No default
}
return node;
}
export default function mutate(
rootNode: Program,
visitorKeys: ?VisitorKeysType,
): void {
// Since we don't return the result of `transform` we need to be careful not to replace the Program root node.
SimpleTransform.transform(rootNode, {
transform(node): ESNode | null {
// prettier fully expects the parent pointers are NOT set and
// certain cases can crash due to prettier infinite-looping
// whilst naively traversing the parent property
// https://github.com/prettier/prettier/issues/11793
// Note: Only needed for prettier V2, this is supported in V3
if (node.parent) {
// $FlowExpectedError[cannot-write]
delete node.parent;
}
// prettier currently relies on the AST being in the old-school, deprecated AST format for optional chaining
// so we have to apply their transform to our AST so it can actually format it.
// Note: Only needed for prettier V2, this is supported in V3
if (node.type === 'ChainExpression') {
// $FlowFixMe[prop-missing]
return transformChainExpression(node.expression, node?.comments);
}
// Prettier currently relies on comparing the `node` vs `node.value` start positions to know if an
// `ObjectTypeProperty` is a method or not (instead of using the `node.method` boolean). To correctly print
// the node when its not a method we need the start position to be different from the `node.value`s start
// position.
if (node.type === 'ObjectTypeProperty') {
if (
node.method === false &&
node.kind === 'init' &&
node.range[0] === 1 &&
node.value.range[0] === 1
) {
// $FlowExpectedError[cannot-write]
// $FlowExpectedError[cannot-spread-interface]
node.value = {
...node.value,
range: [2, node.value.range[1]],
};
}
return node;
}
// Prettier currently relies on comparing the the start positions to know if the import/export specifier should have a
// rename (eg `Name` vs `Name as Name`) when the name is exactly the same
// So we need to ensure that the range is always the same to avoid the useless code printing
if (node.type === 'ImportSpecifier') {
if (node.local.name === node.imported.name) {
if (node.local.range == null) {
// for our TS-ast printing which has no locs
// $FlowExpectedError[cannot-write]
node.local.range = [0, 0];
}
// $FlowExpectedError[cannot-write]
node.imported.range = [...node.local.range];
}
return node;
}
if (node.type === 'ExportSpecifier') {
if (node.local.name === node.exported.name) {
if (node.local.range == null) {
// for our TS-ast printing which has no locs
// $FlowExpectedError[cannot-write]
node.local.range = [0, 0];
}
// $FlowExpectedError[cannot-write]
node.exported.range = [...node.local.range];
}
return node;
}
return node;
},
visitorKeys,
});
}