amis-rpc-design/node_modules/hermes-parser/dist/HermesToESTreeAdapter.js.flow
2023-10-07 19:42:30 +08:00

423 lines
11 KiB
Plaintext

/**
* 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.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) {
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 `MembedExpression`:
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;
}
}