Skip to content

Commit 4c365bd

Browse files
committed
Move transformFunctionBody to factory
It is shared by es2015 and esNext transformers. This commit just adds a convertObjectRest flag to be passed on to flattenDestructuring functions, as well as adding necessary parameters to use the code outside a transformer.
1 parent 4337369 commit 4c365bd

File tree

3 files changed

+367
-499
lines changed

3 files changed

+367
-499
lines changed

src/compiler/factory.ts

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3056,6 +3056,351 @@ namespace ts {
30563056
return tryGetModuleNameFromFile(resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions);
30573057
}
30583058

3059+
/**
3060+
* Transforms the body of a function-like node.
3061+
*
3062+
* @param node A function-like node.
3063+
*/
3064+
export function transformFunctionBody(node: FunctionLikeDeclaration,
3065+
visitor: (node: Node) => VisitResult<Node>,
3066+
currentSourceFile: SourceFile,
3067+
context: TransformationContext,
3068+
enableSubstitutionsForCapturedThis: () => void,
3069+
convertObjectRest?: boolean) {
3070+
let multiLine = false; // indicates whether the block *must* be emitted as multiple lines
3071+
let singleLine = false; // indicates whether the block *may* be emitted as a single line
3072+
let statementsLocation: TextRange;
3073+
let closeBraceLocation: TextRange;
3074+
3075+
const statements: Statement[] = [];
3076+
const body = node.body;
3077+
let statementOffset: number;
3078+
3079+
context.startLexicalEnvironment();
3080+
if (isBlock(body)) {
3081+
// ensureUseStrict is false because no new prologue-directive should be added.
3082+
// addPrologueDirectives will simply put already-existing directives at the beginning of the target statement-array
3083+
statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, visitor);
3084+
}
3085+
3086+
addCaptureThisForNodeIfNeeded(statements, node, enableSubstitutionsForCapturedThis);
3087+
addDefaultValueAssignmentsIfNeeded(statements, node, visitor, convertObjectRest);
3088+
addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false);
3089+
3090+
// If we added any generated statements, this must be a multi-line block.
3091+
if (!multiLine && statements.length > 0) {
3092+
multiLine = true;
3093+
}
3094+
3095+
if (isBlock(body)) {
3096+
statementsLocation = body.statements;
3097+
addRange(statements, visitNodes(body.statements, visitor, isStatement, statementOffset));
3098+
3099+
// If the original body was a multi-line block, this must be a multi-line block.
3100+
if (!multiLine && body.multiLine) {
3101+
multiLine = true;
3102+
}
3103+
}
3104+
else {
3105+
Debug.assert(node.kind === SyntaxKind.ArrowFunction);
3106+
3107+
// To align with the old emitter, we use a synthetic end position on the location
3108+
// for the statement list we synthesize when we down-level an arrow function with
3109+
// an expression function body. This prevents both comments and source maps from
3110+
// being emitted for the end position only.
3111+
statementsLocation = moveRangeEnd(body, -1);
3112+
3113+
const equalsGreaterThanToken = (<ArrowFunction>node).equalsGreaterThanToken;
3114+
if (!nodeIsSynthesized(equalsGreaterThanToken) && !nodeIsSynthesized(body)) {
3115+
if (rangeEndIsOnSameLineAsRangeStart(equalsGreaterThanToken, body, currentSourceFile)) {
3116+
singleLine = true;
3117+
}
3118+
else {
3119+
multiLine = true;
3120+
}
3121+
}
3122+
3123+
const expression = visitNode(body, visitor, isExpression);
3124+
const returnStatement = createReturn(expression, /*location*/ body);
3125+
setEmitFlags(returnStatement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoTrailingSourceMap | EmitFlags.NoTrailingComments);
3126+
statements.push(returnStatement);
3127+
3128+
// To align with the source map emit for the old emitter, we set a custom
3129+
// source map location for the close brace.
3130+
closeBraceLocation = body;
3131+
}
3132+
3133+
const lexicalEnvironment = context.endLexicalEnvironment();
3134+
addRange(statements, lexicalEnvironment);
3135+
3136+
// If we added any final generated statements, this must be a multi-line block
3137+
if (!multiLine && lexicalEnvironment && lexicalEnvironment.length) {
3138+
multiLine = true;
3139+
}
3140+
3141+
const block = createBlock(createNodeArray(statements, statementsLocation), node.body, multiLine);
3142+
if (!multiLine && singleLine) {
3143+
setEmitFlags(block, EmitFlags.SingleLine);
3144+
}
3145+
3146+
if (closeBraceLocation) {
3147+
setTokenSourceMapRange(block, SyntaxKind.CloseBraceToken, closeBraceLocation);
3148+
}
3149+
3150+
setOriginalNode(block, node.body);
3151+
return block;
3152+
}
3153+
3154+
/**
3155+
* Adds a statement to capture the `this` of a function declaration if it is needed.
3156+
*
3157+
* @param statements The statements for the new function body.
3158+
* @param node A node.
3159+
*/
3160+
export function addCaptureThisForNodeIfNeeded(statements: Statement[], node: Node, enableSubstitutionsForCapturedThis: () => void): void {
3161+
if (node.transformFlags & TransformFlags.ContainsCapturedLexicalThis && node.kind !== SyntaxKind.ArrowFunction) {
3162+
captureThisForNode(statements, node, createThis(), enableSubstitutionsForCapturedThis);
3163+
}
3164+
}
3165+
3166+
export function captureThisForNode(statements: Statement[], node: Node, initializer: Expression | undefined, enableSubstitutionsForCapturedThis?: () => void, originalStatement?: Statement): void {
3167+
enableSubstitutionsForCapturedThis();
3168+
const captureThisStatement = createVariableStatement(
3169+
/*modifiers*/ undefined,
3170+
createVariableDeclarationList([
3171+
createVariableDeclaration(
3172+
"_this",
3173+
/*type*/ undefined,
3174+
initializer
3175+
)
3176+
]),
3177+
originalStatement
3178+
);
3179+
3180+
setEmitFlags(captureThisStatement, EmitFlags.NoComments | EmitFlags.CustomPrologue);
3181+
setSourceMapRange(captureThisStatement, node);
3182+
statements.push(captureThisStatement);
3183+
}
3184+
3185+
/**
3186+
* Gets a value indicating whether we need to add default value assignments for a
3187+
* function-like node.
3188+
*
3189+
* @param node A function-like node.
3190+
*/
3191+
function shouldAddDefaultValueAssignments(node: FunctionLikeDeclaration): boolean {
3192+
return (node.transformFlags & TransformFlags.ContainsDefaultValueAssignments) !== 0;
3193+
}
3194+
3195+
/**
3196+
* Adds statements to the body of a function-like node if it contains parameters with
3197+
* binding patterns or initializers.
3198+
*
3199+
* @param statements The statements for the new function body.
3200+
* @param node A function-like node.
3201+
*/
3202+
export function addDefaultValueAssignmentsIfNeeded(statements: Statement[],
3203+
node: FunctionLikeDeclaration,
3204+
visitor: (node: Node) => VisitResult<Node>,
3205+
convertObjectRest: boolean): void {
3206+
if (!shouldAddDefaultValueAssignments(node)) {
3207+
return;
3208+
}
3209+
3210+
for (const parameter of node.parameters) {
3211+
const { name, initializer, dotDotDotToken } = parameter;
3212+
3213+
// A rest parameter cannot have a binding pattern or an initializer,
3214+
// so let's just ignore it.
3215+
if (dotDotDotToken) {
3216+
continue;
3217+
}
3218+
3219+
if (isBindingPattern(name)) {
3220+
addDefaultValueAssignmentForBindingPattern(statements, parameter, name, initializer, visitor, convertObjectRest);
3221+
}
3222+
else if (initializer) {
3223+
addDefaultValueAssignmentForInitializer(statements, parameter, name, initializer, visitor);
3224+
}
3225+
}
3226+
}
3227+
3228+
/**
3229+
* Adds statements to the body of a function-like node for parameters with binding patterns
3230+
*
3231+
* @param statements The statements for the new function body.
3232+
* @param parameter The parameter for the function.
3233+
* @param name The name of the parameter.
3234+
* @param initializer The initializer for the parameter.
3235+
*/
3236+
function addDefaultValueAssignmentForBindingPattern(statements: Statement[],
3237+
parameter: ParameterDeclaration,
3238+
name: BindingPattern, initializer: Expression,
3239+
visitor: (node: Node) => VisitResult<Node>,
3240+
convertObjectRest: boolean): void {
3241+
const temp = getGeneratedNameForNode(parameter);
3242+
3243+
// In cases where a binding pattern is simply '[]' or '{}',
3244+
// we usually don't want to emit a var declaration; however, in the presence
3245+
// of an initializer, we must emit that expression to preserve side effects.
3246+
if (name.elements.length > 0) {
3247+
statements.push(
3248+
setEmitFlags(
3249+
createVariableStatement(
3250+
/*modifiers*/ undefined,
3251+
createVariableDeclarationList(
3252+
flattenParameterDestructuring(parameter, temp, visitor, convertObjectRest)
3253+
)
3254+
),
3255+
EmitFlags.CustomPrologue
3256+
)
3257+
);
3258+
}
3259+
else if (initializer) {
3260+
statements.push(
3261+
setEmitFlags(
3262+
createStatement(
3263+
createAssignment(
3264+
temp,
3265+
visitNode(initializer, visitor, isExpression)
3266+
)
3267+
),
3268+
EmitFlags.CustomPrologue
3269+
)
3270+
);
3271+
}
3272+
}
3273+
3274+
/**
3275+
* Adds statements to the body of a function-like node for parameters with initializers.
3276+
*
3277+
* @param statements The statements for the new function body.
3278+
* @param parameter The parameter for the function.
3279+
* @param name The name of the parameter.
3280+
* @param initializer The initializer for the parameter.
3281+
*/
3282+
function addDefaultValueAssignmentForInitializer(statements: Statement[],
3283+
parameter: ParameterDeclaration,
3284+
name: Identifier,
3285+
initializer: Expression,
3286+
visitor: (node: Node) => VisitResult<Node>): void {
3287+
initializer = visitNode(initializer, visitor, isExpression);
3288+
const statement = createIf(
3289+
createStrictEquality(
3290+
getSynthesizedClone(name),
3291+
createVoidZero()
3292+
),
3293+
setEmitFlags(
3294+
createBlock([
3295+
createStatement(
3296+
createAssignment(
3297+
setEmitFlags(getMutableClone(name), EmitFlags.NoSourceMap),
3298+
setEmitFlags(initializer, EmitFlags.NoSourceMap | getEmitFlags(initializer)),
3299+
/*location*/ parameter
3300+
)
3301+
)
3302+
], /*location*/ parameter),
3303+
EmitFlags.SingleLine | EmitFlags.NoTrailingSourceMap | EmitFlags.NoTokenSourceMaps
3304+
),
3305+
/*elseStatement*/ undefined,
3306+
/*location*/ parameter
3307+
);
3308+
statement.startsOnNewLine = true;
3309+
setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoTrailingSourceMap | EmitFlags.CustomPrologue);
3310+
statements.push(statement);
3311+
}
3312+
3313+
/**
3314+
* Gets a value indicating whether we need to add statements to handle a rest parameter.
3315+
*
3316+
* @param node A ParameterDeclaration node.
3317+
* @param inConstructorWithSynthesizedSuper A value indicating whether the parameter is
3318+
* part of a constructor declaration with a
3319+
* synthesized call to `super`
3320+
*/
3321+
function shouldAddRestParameter(node: ParameterDeclaration, inConstructorWithSynthesizedSuper: boolean) {
3322+
return node && node.dotDotDotToken && node.name.kind === SyntaxKind.Identifier && !inConstructorWithSynthesizedSuper;
3323+
}
3324+
3325+
/**
3326+
* Adds statements to the body of a function-like node if it contains a rest parameter.
3327+
*
3328+
* @param statements The statements for the new function body.
3329+
* @param node A function-like node.
3330+
* @param inConstructorWithSynthesizedSuper A value indicating whether the parameter is
3331+
* part of a constructor declaration with a
3332+
* synthesized call to `super`
3333+
*/
3334+
export function addRestParameterIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, inConstructorWithSynthesizedSuper: boolean): void {
3335+
const parameter = lastOrUndefined(node.parameters);
3336+
if (!shouldAddRestParameter(parameter, inConstructorWithSynthesizedSuper)) {
3337+
return;
3338+
}
3339+
3340+
// `declarationName` is the name of the local declaration for the parameter.
3341+
const declarationName = getMutableClone(<Identifier>parameter.name);
3342+
setEmitFlags(declarationName, EmitFlags.NoSourceMap);
3343+
3344+
// `expressionName` is the name of the parameter used in expressions.
3345+
const expressionName = getSynthesizedClone(<Identifier>parameter.name);
3346+
const restIndex = node.parameters.length - 1;
3347+
const temp = createLoopVariable();
3348+
3349+
// var param = [];
3350+
statements.push(
3351+
setEmitFlags(
3352+
createVariableStatement(
3353+
/*modifiers*/ undefined,
3354+
createVariableDeclarationList([
3355+
createVariableDeclaration(
3356+
declarationName,
3357+
/*type*/ undefined,
3358+
createArrayLiteral([])
3359+
)
3360+
]),
3361+
/*location*/ parameter
3362+
),
3363+
EmitFlags.CustomPrologue
3364+
)
3365+
);
3366+
3367+
// for (var _i = restIndex; _i < arguments.length; _i++) {
3368+
// param[_i - restIndex] = arguments[_i];
3369+
// }
3370+
const forStatement = createFor(
3371+
createVariableDeclarationList([
3372+
createVariableDeclaration(temp, /*type*/ undefined, createLiteral(restIndex))
3373+
], /*location*/ parameter),
3374+
createLessThan(
3375+
temp,
3376+
createPropertyAccess(createIdentifier("arguments"), "length"),
3377+
/*location*/ parameter
3378+
),
3379+
createPostfixIncrement(temp, /*location*/ parameter),
3380+
createBlock([
3381+
startOnNewLine(
3382+
createStatement(
3383+
createAssignment(
3384+
createElementAccess(
3385+
expressionName,
3386+
createSubtract(temp, createLiteral(restIndex))
3387+
),
3388+
createElementAccess(createIdentifier("arguments"), temp)
3389+
),
3390+
/*location*/ parameter
3391+
)
3392+
)
3393+
])
3394+
);
3395+
3396+
setEmitFlags(forStatement, EmitFlags.CustomPrologue);
3397+
startOnNewLine(forStatement);
3398+
statements.push(forStatement);
3399+
}
3400+
3401+
3402+
3403+
30593404
export function convertForOf(node: ForOfStatement, convertedLoopBodyStatements: Statement[],
30603405
visitor: (node: Node) => VisitResult<Node>,
30613406
enableSubstitutionsForBlockScopedBindings: () => void,

0 commit comments

Comments
 (0)