first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

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-type] 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,864 @@
/**
* 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,
ObjectTypeAnnotation,
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: ObjectTypeAnnotation = {
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 | ObjectPattern | Identifier = 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: Identifier = {
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: FunctionDeclaration = {
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: FunctionDeclaration = {
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-type] 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;
}
}
},
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,912 @@
/**
* 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';
/**
* Transform match expressions and statements.
*/
import type {ParserOptions} from '../ParserOptions';
import type {
BinaryExpression,
BreakStatement,
DestructuringObjectProperty,
ESNode,
Expression,
Identifier,
Literal,
MatchExpression,
MatchMemberPattern,
MatchPattern,
MatchStatement,
MemberExpression,
ObjectPattern,
Program,
Statement,
Super,
UnaryExpression,
VariableDeclaration,
} from 'hermes-estree';
import {SimpleTransform} from '../transform/SimpleTransform';
import {
deepCloneNode,
shallowCloneNode,
} from '../transform/astNodeMutationHelpers';
import {createSyntaxError} from '../utils/createSyntaxError';
import {
EMPTY_PARENT,
callExpression,
conjunction,
disjunction,
etc,
ident,
iife,
nullLiteral,
numberLiteral,
stringLiteral,
throwStatement,
typeofExpression,
variableDeclaration,
} from '../utils/Builders';
import {createGenID} from '../utils/GenID';
/**
* Generated identifiers.
* `GenID` is initialized in the transform.
*/
let GenID: ?ReturnType<typeof createGenID> = null;
function genIdent(): Identifier {
if (GenID == null) {
throw Error('GenID must be initialized at the start of the transform.');
}
return ident(GenID.genID());
}
/**
* A series of properties.
* When combined with the match argument (the root expression), provides the
* location of to be tested against, or location to be extracted to a binding.
*/
type Key = Array<Identifier | Literal>;
/**
* The conditional aspect of a match pattern for a single location.
*/
type Condition =
| {type: 'eq', key: Key, arg: Expression}
| {type: 'is-nan', key: Key}
| {type: 'array', key: Key, length: number, lengthOp: 'eq' | 'gte'}
| {type: 'object', key: Key}
| {type: 'prop-exists', key: Key, propName: string}
| {type: 'or', orConditions: Array<Array<Condition>>};
/**
* A binding introduced by a match pattern.
*/
type Binding =
| {type: 'id', key: Key, kind: BindingKind, id: Identifier}
| {
type: 'array-rest',
key: Key,
kind: BindingKind,
id: Identifier,
exclude: number,
}
| {
type: 'object-rest',
key: Key,
kind: BindingKind,
id: Identifier,
exclude: Array<Identifier | Literal>,
};
type BindingKind = VariableDeclaration['kind'];
function objKeyToString(node: Identifier | Literal): string {
switch (node.type) {
case 'Identifier':
return node.name;
case 'Literal': {
const {value} = node;
if (typeof value === 'number') {
return String(value);
} else if (typeof value === 'string') {
return value;
} else {
return node.raw;
}
}
}
}
function convertMemberPattern(pattern: MatchMemberPattern): MemberExpression {
const {base, property, loc, range} = pattern;
const object =
base.type === 'MatchIdentifierPattern'
? base.id
: convertMemberPattern(base);
if (property.type === 'Identifier') {
return {
type: 'MemberExpression',
object,
property,
computed: false,
optional: false,
...etc({loc, range}),
};
} else {
return {
type: 'MemberExpression',
object,
property,
computed: true,
optional: false,
...etc({loc, range}),
};
}
}
function checkDuplicateBindingName(
seenBindingNames: Set<string>,
node: MatchPattern,
name: string,
): void {
if (seenBindingNames.has(name)) {
throw createSyntaxError(
node,
`Duplicate variable name '${name}' in match case pattern.`,
);
}
seenBindingNames.add(name);
}
function checkBindingKind(node: MatchPattern, kind: BindingKind): void {
if (kind === 'var') {
throw createSyntaxError(
node,
`'var' bindings are not allowed. Use 'const' or 'let'.`,
);
}
}
/**
* Does an object property's pattern require a `prop-exists` condition added?
* If the pattern is a literal like `0`, then it's not required, since the `eq`
* condition implies the prop exists. However, if we could be doing an equality
* check against `undefined`, then it is required, since that will be true even
* if the property doesn't exist.
*/
function needsPropExistsCond(pattern: MatchPattern): boolean {
switch (pattern.type) {
case 'MatchWildcardPattern':
case 'MatchBindingPattern':
case 'MatchIdentifierPattern':
case 'MatchMemberPattern':
return true;
case 'MatchLiteralPattern':
case 'MatchUnaryPattern':
case 'MatchObjectPattern':
case 'MatchArrayPattern':
return false;
case 'MatchAsPattern': {
const {pattern: asPattern} = pattern;
return needsPropExistsCond(asPattern);
}
case 'MatchOrPattern': {
const {patterns} = pattern;
return patterns.some(needsPropExistsCond);
}
}
}
/**
* Analyzes a match pattern, and produced both the conditions and bindings
* produced by that pattern.
*/
function analyzePattern(
pattern: MatchPattern,
key: Key,
seenBindingNames: Set<string>,
): {
conditions: Array<Condition>,
bindings: Array<Binding>,
} {
switch (pattern.type) {
case 'MatchWildcardPattern': {
return {conditions: [], bindings: []};
}
case 'MatchLiteralPattern': {
const {literal} = pattern;
const condition: Condition = {type: 'eq', key, arg: literal};
return {conditions: [condition], bindings: []};
}
case 'MatchUnaryPattern': {
const {operator, argument, loc, range} = pattern;
if (argument.value === 0) {
// We haven't decided whether we will compare these using `===` or `Object.is`
throw createSyntaxError(
pattern,
`'+0' and '-0' are not yet supported in match unary patterns.`,
);
}
const arg: UnaryExpression = {
type: 'UnaryExpression',
operator,
argument,
prefix: true,
...etc({loc, range}),
};
const condition: Condition = {type: 'eq', key, arg};
return {conditions: [condition], bindings: []};
}
case 'MatchIdentifierPattern': {
const {id} = pattern;
const condition: Condition =
id.name === 'NaN' ? {type: 'is-nan', key} : {type: 'eq', key, arg: id};
return {conditions: [condition], bindings: []};
}
case 'MatchMemberPattern': {
const arg = convertMemberPattern(pattern);
const condition: Condition = {type: 'eq', key, arg};
return {conditions: [condition], bindings: []};
}
case 'MatchBindingPattern': {
const {id, kind} = pattern;
checkDuplicateBindingName(seenBindingNames, pattern, id.name);
checkBindingKind(pattern, kind);
const binding: Binding = {type: 'id', key, kind, id};
return {conditions: [], bindings: [binding]};
}
case 'MatchAsPattern': {
const {pattern: asPattern, target} = pattern;
if (asPattern.type === 'MatchBindingPattern') {
throw createSyntaxError(
pattern,
`Match 'as' patterns are not allowed directly on binding patterns.`,
);
}
const {conditions, bindings} = analyzePattern(
asPattern,
key,
seenBindingNames,
);
const [id, kind] =
target.type === 'MatchBindingPattern'
? [target.id, target.kind]
: [target, ('const': 'const')];
checkDuplicateBindingName(seenBindingNames, pattern, id.name);
checkBindingKind(pattern, kind);
const binding: Binding = {type: 'id', key, kind, id};
return {conditions, bindings: bindings.concat(binding)};
}
case 'MatchArrayPattern': {
const {elements, rest} = pattern;
const lengthOp = rest == null ? 'eq' : 'gte';
const conditions: Array<Condition> = [
{type: 'array', key, length: elements.length, lengthOp},
];
const bindings: Array<Binding> = [];
elements.forEach((element, i) => {
const elementKey = key.concat(numberLiteral(i));
const {conditions: childConditions, bindings: childBindings} =
analyzePattern(element, elementKey, seenBindingNames);
conditions.push(...childConditions);
bindings.push(...childBindings);
});
if (rest != null && rest.argument != null) {
const {id, kind} = rest.argument;
checkDuplicateBindingName(seenBindingNames, rest.argument, id.name);
checkBindingKind(pattern, kind);
bindings.push({
type: 'array-rest',
key,
exclude: elements.length,
kind,
id,
});
}
return {conditions, bindings};
}
case 'MatchObjectPattern': {
const {properties, rest} = pattern;
const conditions: Array<Condition> = [{type: 'object', key}];
const bindings: Array<Binding> = [];
const objKeys: Array<Identifier | Literal> = [];
const seenNames = new Set<string>();
properties.forEach(prop => {
const {key: objKey, pattern: propPattern} = prop;
objKeys.push(objKey);
const name = objKeyToString(objKey);
if (seenNames.has(name)) {
throw createSyntaxError(
propPattern,
`Duplicate property name '${name}' in match object pattern.`,
);
}
seenNames.add(name);
const propKey: Key = key.concat(objKey);
if (needsPropExistsCond(propPattern)) {
conditions.push({
type: 'prop-exists',
key,
propName: name,
});
}
const {conditions: childConditions, bindings: childBindings} =
analyzePattern(propPattern, propKey, seenBindingNames);
conditions.push(...childConditions);
bindings.push(...childBindings);
});
if (rest != null && rest.argument != null) {
const {id, kind} = rest.argument;
checkDuplicateBindingName(seenBindingNames, rest.argument, id.name);
checkBindingKind(pattern, kind);
bindings.push({
type: 'object-rest',
key,
exclude: objKeys,
kind,
id,
});
}
return {conditions, bindings};
}
case 'MatchOrPattern': {
const {patterns} = pattern;
let hasWildcard = false;
const orConditions = patterns.map(subpattern => {
const {conditions, bindings} = analyzePattern(
subpattern,
key,
seenBindingNames,
);
if (bindings.length > 0) {
// We will implement this in the future.
throw createSyntaxError(
pattern,
`Bindings in match 'or' patterns are not yet supported.`,
);
}
if (conditions.length === 0) {
hasWildcard = true;
}
return conditions;
});
if (hasWildcard) {
return {conditions: [], bindings: []};
}
return {
conditions: [{type: 'or', orConditions}],
bindings: [],
};
}
}
}
function expressionOfKey(root: Expression, key: Key): Expression {
return key.reduce(
(acc, prop) =>
prop.type === 'Identifier'
? {
type: 'MemberExpression',
object: acc,
property: shallowCloneNode(prop),
computed: false,
optional: false,
...etc(),
}
: {
type: 'MemberExpression',
object: acc,
property: shallowCloneNode(prop),
computed: true,
optional: false,
...etc(),
},
deepCloneNode(root),
);
}
function testsOfCondition(
root: Expression,
condition: Condition,
): Array<Expression> {
switch (condition.type) {
case 'eq': {
// <x> === <arg>
const {key, arg} = condition;
return [
{
type: 'BinaryExpression',
left: expressionOfKey(root, key),
right: arg,
operator: '===',
...etc(),
},
];
}
case 'is-nan': {
// Number.isNaN(<x>)
const {key} = condition;
const callee: MemberExpression = {
type: 'MemberExpression',
object: ident('Number'),
property: ident('isNaN'),
computed: false,
optional: false,
...etc(),
};
return [callExpression(callee, [expressionOfKey(root, key)])];
}
case 'array': {
// Array.isArray(<x>) && <x>.length === <length>
const {key, length, lengthOp} = condition;
const operator = lengthOp === 'eq' ? '===' : '>=';
const isArray = callExpression(
{
type: 'MemberExpression',
object: ident('Array'),
property: ident('isArray'),
computed: false,
optional: false,
...etc(),
},
[expressionOfKey(root, key)],
);
const lengthCheck: BinaryExpression = {
type: 'BinaryExpression',
left: {
type: 'MemberExpression',
object: expressionOfKey(root, key),
property: ident('length'),
computed: false,
optional: false,
...etc(),
},
right: numberLiteral(length),
operator,
...etc(),
};
return [isArray, lengthCheck];
}
case 'object': {
// (typeof <x> === 'object' && <x> !== null) || typeof <x> === 'function'
const {key} = condition;
const typeofObject = typeofExpression(
expressionOfKey(root, key),
'object',
);
const typeofFunction = typeofExpression(
expressionOfKey(root, key),
'function',
);
const notNull: BinaryExpression = {
type: 'BinaryExpression',
left: expressionOfKey(root, key),
right: nullLiteral(),
operator: '!==',
...etc(),
};
return [
disjunction([conjunction([typeofObject, notNull]), typeofFunction]),
];
}
case 'prop-exists': {
// <propName> in <x>
const {key, propName} = condition;
const inObject: BinaryExpression = {
type: 'BinaryExpression',
left: stringLiteral(propName),
right: expressionOfKey(root, key),
operator: 'in',
...etc(),
};
return [inObject];
}
case 'or': {
// <a> || <b> || ...
const {orConditions} = condition;
const tests = orConditions.map(conditions =>
conjunction(testsOfConditions(root, conditions)),
);
return [disjunction(tests)];
}
}
}
function testsOfConditions(
root: Expression,
conditions: Array<Condition>,
): Array<Expression> {
return conditions.flatMap(condition => testsOfCondition(root, condition));
}
function statementsOfBindings(
root: Expression,
bindings: Array<Binding>,
): Array<Statement> {
return bindings.map(binding => {
switch (binding.type) {
case 'id': {
// const <id> = <x>;
const {key, kind, id} = binding;
return variableDeclaration(kind, id, expressionOfKey(root, key));
}
case 'array-rest': {
// const <id> = <x>.slice(<exclude>);
const {key, kind, id, exclude} = binding;
const init = callExpression(
{
type: 'MemberExpression',
object: expressionOfKey(root, key),
property: ident('slice'),
computed: false,
optional: false,
...etc(),
},
[numberLiteral(exclude)],
);
return variableDeclaration(kind, id, init);
}
case 'object-rest': {
// const {a: _, b: _, ...<id>} = <x>;
const {key, kind, id, exclude} = binding;
const destructuring: ObjectPattern = {
type: 'ObjectPattern',
properties: exclude
.map((prop): DestructuringObjectProperty =>
prop.type === 'Identifier'
? {
type: 'Property',
key: shallowCloneNode(prop),
value: genIdent(),
kind: 'init',
computed: false,
method: false,
shorthand: false,
...etc(),
parent: EMPTY_PARENT,
}
: {
type: 'Property',
key: shallowCloneNode(prop),
value: genIdent(),
kind: 'init',
computed: true,
method: false,
shorthand: false,
...etc(),
parent: EMPTY_PARENT,
},
)
.concat({
type: 'RestElement',
argument: id,
...etc(),
}),
typeAnnotation: null,
...etc(),
};
return variableDeclaration(
kind,
destructuring,
expressionOfKey(root, key),
);
}
}
});
}
/**
* For throwing an error if no cases are matched.
*/
const fallthroughErrorMsgText = `Match: No case succesfully matched. Make exhaustive or add a wildcard case using '_'.`;
function fallthroughErrorMsg(value: Expression): Expression {
return {
type: 'BinaryExpression',
operator: '+',
left: stringLiteral(`${fallthroughErrorMsgText} Argument: `),
right: value,
...etc(),
};
}
function fallthroughError(value: Expression): Statement {
return throwStatement(fallthroughErrorMsg(value));
}
/**
* If the argument has no side-effects (ignoring getters). Either an identifier
* or member expression with identifier root and non-computed/literal properties.
*/
function calculateSimpleArgument(node: Expression | Super): boolean {
switch (node.type) {
case 'Identifier':
case 'Super':
return true;
case 'MemberExpression': {
const {object, property, computed} = node;
if (computed && property.type !== 'Literal') {
return false;
}
return calculateSimpleArgument(object);
}
default:
return false;
}
}
/**
* Analyze the match cases and return information we will use to build the result.
*/
type CaseAnalysis<T> = {
+conditions: Array<Condition>,
+bindings: Array<Binding>,
+guard: Expression | null,
+body: T,
};
interface MatchCase<T> {
+pattern: MatchPattern;
+guard: Expression | null;
+body: T;
}
function analyzeCases<T>(cases: $ReadOnlyArray<MatchCase<T>>): {
hasBindings: boolean,
hasWildcard: boolean,
analyses: Array<CaseAnalysis<T>>,
} {
let hasBindings = false;
let hasWildcard = false;
const analyses: Array<CaseAnalysis<T>> = [];
for (let i = 0; i < cases.length; i++) {
const {pattern, guard, body} = cases[i];
const {conditions, bindings} = analyzePattern(
pattern,
[],
new Set<string>(),
);
hasBindings = hasBindings || bindings.length > 0;
analyses.push({
conditions,
bindings,
guard,
body,
});
// This case catches everything, no reason to continue.
if (conditions.length === 0 && guard == null) {
hasWildcard = true;
break;
}
}
return {
hasBindings,
hasWildcard,
analyses,
};
}
/**
* Match expression transform entry point.
*/
function mapMatchExpression(node: MatchExpression): Expression {
const {argument, cases} = node;
const {hasBindings, hasWildcard, analyses} = analyzeCases(cases);
const isSimpleArgument = !hasBindings && calculateSimpleArgument(argument);
const genRoot: Identifier | null = !isSimpleArgument ? genIdent() : null;
const root: Expression = genRoot == null ? argument : genRoot;
// No bindings and a simple argument means we can use nested conditional
// expressions.
if (isSimpleArgument) {
const wildcardAnalaysis = hasWildcard ? analyses.pop() : null;
const lastBody =
wildcardAnalaysis != null
? wildcardAnalaysis.body
: iife([fallthroughError(shallowCloneNode(root))]);
return analyses.reverse().reduce((acc, analysis) => {
const {conditions, guard, body} = analysis;
const tests = testsOfConditions(root, conditions);
if (guard != null) {
tests.push(guard);
}
// <tests> ? <body> : <acc>
return {
type: 'ConditionalExpression',
test: conjunction(tests),
consequent: body,
alternate: acc,
...etc(),
};
}, lastBody);
}
// There are bindings, so we produce an immediately invoked arrow expression.
// If the original argument is simple, no need for a new variable.
const statements: Array<Statement> = analyses.map(
({conditions, bindings, guard, body}) => {
const returnNode: Statement = {
type: 'ReturnStatement',
argument: body,
...etc(),
};
// If we have a guard, then we use a nested if statement
// `if (<guard>) return <body>`
const bodyNode: Statement =
guard == null
? returnNode
: {
type: 'IfStatement',
test: guard,
consequent: returnNode,
...etc(),
};
const bindingNodes = statementsOfBindings(root, bindings);
const caseBody: Array<Statement> = bindingNodes.concat(bodyNode);
if (conditions.length > 0) {
const tests = testsOfConditions(root, conditions);
return {
type: 'IfStatement',
test: conjunction(tests),
consequent: {
type: 'BlockStatement',
body: caseBody,
...etc(),
},
...etc(),
};
} else {
// No conditions, so no if statement
if (bindingNodes.length > 0) {
// Bindings require a block to introduce a new scope
return {
type: 'BlockStatement',
body: caseBody,
...etc(),
};
} else {
return bodyNode;
}
}
},
);
if (!hasWildcard) {
statements.push(fallthroughError(shallowCloneNode(root)));
}
const [params, args] = genRoot == null ? [[], []] : [[genRoot], [argument]];
// `((<params>) => { ... })(<args>)`, or
// `(() => { ... })()` if is simple argument.
return iife(statements, params, args);
}
/**
* Match statement transform entry point.
*/
function mapMatchStatement(node: MatchStatement): Statement {
const {argument, cases} = node;
const {hasBindings, hasWildcard, analyses} = analyzeCases(cases);
const topLabel: Identifier = genIdent();
const isSimpleArgument = !hasBindings && calculateSimpleArgument(argument);
const genRoot: Identifier | null = !isSimpleArgument ? genIdent() : null;
const root: Expression = genRoot == null ? argument : genRoot;
const statements: Array<Statement> = [];
if (genRoot != null) {
statements.push(variableDeclaration('const', genRoot, argument));
}
analyses.forEach(({conditions, bindings, guard, body}) => {
const breakNode: BreakStatement = {
type: 'BreakStatement',
label: shallowCloneNode(topLabel),
...etc(),
};
const bodyStatements = body.body.concat(breakNode);
// If we have a guard, then we use a nested if statement
// `if (<guard>) return <body>`
const guardedBodyStatements: Array<Statement> =
guard == null
? bodyStatements
: [
{
type: 'IfStatement',
test: guard,
consequent: {
type: 'BlockStatement',
body: bodyStatements,
...etc(),
},
...etc(),
},
];
const bindingNodes = statementsOfBindings(root, bindings);
const caseBody: Array<Statement> = bindingNodes.concat(
guardedBodyStatements,
);
if (conditions.length > 0) {
const tests = testsOfConditions(root, conditions);
statements.push({
type: 'IfStatement',
test: conjunction(tests),
consequent: {
type: 'BlockStatement',
body: caseBody,
...etc(),
},
...etc(),
});
} else {
// No conditions, so no if statement
statements.push({
type: 'BlockStatement',
body: caseBody,
...etc(),
});
}
});
if (!hasWildcard) {
statements.push(fallthroughError(shallowCloneNode(root)));
}
return {
type: 'LabeledStatement',
label: topLabel,
body: {
type: 'BlockStatement',
body: statements,
...etc(),
},
...etc(),
};
}
export function transformProgram(
program: Program,
_options: ParserOptions,
): Program {
// Initialize so each file transformed starts freshly incrementing the
// variable name counter, and has its own usage tracking.
GenID = createGenID('m');
return SimpleTransform.transformProgram(program, {
transform(node: ESNode) {
switch (node.type) {
case 'MatchExpression': {
return mapMatchExpression(node);
}
case 'MatchStatement': {
return mapMatchStatement(node);
}
case 'Identifier': {
// A rudimentary check to avoid some collisions with our generated
// variable names. Ideally, we would have access a scope analyzer
// inside the transform instead.
if (GenID == null) {
throw Error(
'GenID must be initialized at the start of the transform.',
);
}
GenID.addUsage(node.name);
return node;
}
default: {
return node;
}
}
},
});
}