864 lines
23 KiB
Plaintext
864 lines
23 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
|
|
*/
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
},
|
|
});
|
|
}
|