mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-16 05:58:32 -06:00
added support for captured block scoped bindings
This commit is contained in:
parent
6e86b13d46
commit
564e134f5e
@ -482,6 +482,19 @@ namespace ts {
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createSwitch(expression: Expression, caseBlock: CaseBlock, location?: TextRange): SwitchStatement {
|
||||
const node = <SwitchStatement>createNode(SyntaxKind.SwitchStatement, location);
|
||||
node.expression = parenthesizeExpressionForList(expression);
|
||||
node.caseBlock = caseBlock;
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createCaseBlock(clauses: CaseClause[], location?: TextRange): CaseBlock {
|
||||
const node = <CaseBlock>createNode(SyntaxKind.CaseBlock, location);
|
||||
node.clauses = createNodeArray(clauses);
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement, location?: TextRange) {
|
||||
const node = <ForStatement>createNode(SyntaxKind.ForStatement, location);
|
||||
node.initializer = initializer;
|
||||
@ -687,6 +700,22 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
export function createBreak(label?: Identifier, location?: TextRange): BreakStatement {
|
||||
const node = <BreakStatement>createNode(SyntaxKind.BreakStatement, location);
|
||||
if (label) {
|
||||
node.label = label;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createContinue(label?: Identifier, location?: TextRange): BreakStatement {
|
||||
const node = <ContinueStatement>createNode(SyntaxKind.ContinueStatement, location);
|
||||
if (label) {
|
||||
node.label = label;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createFunctionApply(func: Expression, thisArg: Expression, argumentsExpression: Expression, location?: TextRange) {
|
||||
return createCall(
|
||||
createPropertyAccess(func, "apply"),
|
||||
@ -1349,8 +1378,11 @@ namespace ts {
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
else if (getLeftmostExpression(expression).kind === SyntaxKind.ObjectLiteralExpression) {
|
||||
return createParen(expression, /*location*/ expression);
|
||||
else {
|
||||
const leftmostExpressionKind = getLeftmostExpression(expression).kind;
|
||||
if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) {
|
||||
return createParen(expression, /*location*/ expression);
|
||||
}
|
||||
}
|
||||
|
||||
return expression;
|
||||
|
||||
@ -1751,7 +1751,15 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
|
||||
function emitCaseOrDefaultClauseStatements(parentNode: Node, statements: NodeArray<Statement>) {
|
||||
if (statements.length === 1 && rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile)) {
|
||||
const emitAsSingleStatement =
|
||||
statements.length === 1 &&
|
||||
(
|
||||
// treat synthesized nodes as located on the same line for emit purposes
|
||||
nodeIsSynthesized(parentNode) ||
|
||||
nodeIsSynthesized(statements[0]) ||
|
||||
rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile)
|
||||
);
|
||||
if (emitAsSingleStatement) {
|
||||
write(" ");
|
||||
emit(statements[0]);
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ namespace ts {
|
||||
|
||||
const pendingAssignments: Expression[] = [];
|
||||
|
||||
flattenDestructuring(context, node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment);
|
||||
flattenDestructuring(context, node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment, visitor);
|
||||
|
||||
const expression = inlineExpressions(pendingAssignments);
|
||||
aggregateTransformFlags(expression);
|
||||
|
||||
@ -10,6 +10,133 @@ namespace ts {
|
||||
/** Enables substitutions for block-scoped bindings. */
|
||||
BlockScopedBindings = 1 << 1,
|
||||
}
|
||||
/**
|
||||
* If loop contains block scoped binding captured in some function then loop body is converted to a function.
|
||||
* Lexical bindings declared in loop initializer will be passed into the loop body function as parameters,
|
||||
* however if this binding is modified inside the body - this new value should be propagated back to the original binding.
|
||||
* This is done by declaring new variable (out parameter holder) outside of the loop for every binding that is reassigned inside the body.
|
||||
* On every iteration this variable is initialized with value of corresponding binding.
|
||||
* At every point where control flow leaves the loop either explicitly (break/continue) or implicitly (at the end of loop body)
|
||||
* we copy the value inside the loop to the out parameter holder.
|
||||
*
|
||||
* for (let x;;) {
|
||||
* let a = 1;
|
||||
* let b = () => a;
|
||||
* x++
|
||||
* if (...) break;
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* will be converted to
|
||||
*
|
||||
* var out_x;
|
||||
* var loop = function(x) {
|
||||
* var a = 1;
|
||||
* var b = function() { return a; }
|
||||
* x++;
|
||||
* if (...) return out_x = x, "break";
|
||||
* ...
|
||||
* out_x = x;
|
||||
* }
|
||||
* for (var x;;) {
|
||||
* out_x = x;
|
||||
* var state = loop(x);
|
||||
* x = out_x;
|
||||
* if (state === "break") break;
|
||||
* }
|
||||
*
|
||||
* NOTE: values to out parameters are not copies if loop is abrupted with 'return' - in this case this will end the entire enclosing function
|
||||
* so nobody can observe this new value.
|
||||
*/
|
||||
interface LoopOutParameter {
|
||||
originalName: Identifier;
|
||||
outParamName: Identifier;
|
||||
}
|
||||
|
||||
const enum CopyDirection {
|
||||
ToOriginal,
|
||||
ToOutParameter
|
||||
}
|
||||
|
||||
const enum Jump {
|
||||
Break = 1 << 1,
|
||||
Continue = 1 << 2,
|
||||
Return = 1 << 3
|
||||
}
|
||||
|
||||
interface ConvertedLoopState {
|
||||
/*
|
||||
* set of labels that occurred inside the converted loop
|
||||
* used to determine if labeled jump can be emitted as is or it should be dispatched to calling code
|
||||
*/
|
||||
labels?: Map<string>;
|
||||
/*
|
||||
* collection of labeled jumps that transfer control outside the converted loop.
|
||||
* maps store association 'label -> labelMarker' where
|
||||
* - label - value of label as it appear in code
|
||||
* - label marker - return value that should be interpreted by calling code as 'jump to <label>'
|
||||
*/
|
||||
labeledNonLocalBreaks?: Map<string>;
|
||||
labeledNonLocalContinues?: Map<string>;
|
||||
|
||||
/*
|
||||
* set of non-labeled jumps that transfer control outside the converted loop
|
||||
* used to emit dispatching logic in the caller of converted loop
|
||||
*/
|
||||
nonLocalJumps?: Jump;
|
||||
|
||||
/*
|
||||
* set of non-labeled jumps that should be interpreted as local
|
||||
* i.e. if converted loop contains normal loop or switch statement then inside this loop break should be treated as local jump
|
||||
*/
|
||||
allowedNonLabeledJumps?: Jump;
|
||||
|
||||
/*
|
||||
* alias for 'arguments' object from the calling code stack frame
|
||||
* i.e.
|
||||
* for (let x;;) <statement that captures x in closure and uses 'arguments'>
|
||||
* should be converted to
|
||||
* var loop = function(x) { <code where 'arguments' is replaced with 'arguments_1'> }
|
||||
* var arguments_1 = arguments
|
||||
* for (var x;;) loop(x);
|
||||
* otherwise semantics of the code will be different since 'arguments' inside converted loop body
|
||||
* will refer to function that holds converted loop.
|
||||
* This value is set on demand.
|
||||
*/
|
||||
argumentsName?: Identifier;
|
||||
|
||||
/*
|
||||
* alias for 'this' from the calling code stack frame in case if this was used inside the converted loop
|
||||
*/
|
||||
thisName?: Identifier;
|
||||
|
||||
/*
|
||||
* set to true if node contains lexical 'this' so we can mark function that wraps convered loop body as 'CapturedThis' for subsequence substitution.
|
||||
*/
|
||||
containsLexicalThis?: boolean;
|
||||
|
||||
/*
|
||||
* list of non-block scoped variable declarations that appear inside converted loop
|
||||
* such variable declarations should be moved outside the loop body
|
||||
* for (let x;;) {
|
||||
* var y = 1;
|
||||
* ...
|
||||
* }
|
||||
* should be converted to
|
||||
* var loop = function(x) {
|
||||
* y = 1;
|
||||
* ...
|
||||
* }
|
||||
* var y;
|
||||
* for (var x;;) loop(x);
|
||||
*/
|
||||
hoistedLocalVariables?: Identifier[];
|
||||
|
||||
/**
|
||||
* List of loop out parameters - detailed descripion can be found in the comment to LoopOutParameter
|
||||
*/
|
||||
loopOutParameters?: LoopOutParameter[];
|
||||
}
|
||||
|
||||
export function transformES6(context: TransformationContext) {
|
||||
const {
|
||||
@ -35,6 +162,11 @@ namespace ts {
|
||||
let enclosingBlockScopeContainerParent: Node;
|
||||
let containingNonArrowFunction: FunctionLikeDeclaration;
|
||||
|
||||
/**
|
||||
* Used to track if we are emitting body of the converted loop
|
||||
*/
|
||||
let convertedLoopState: ConvertedLoopState;
|
||||
|
||||
/**
|
||||
* Keeps track of whether substitutions have been enabled for specific cases.
|
||||
* They are persisted between each SourceFile transformation and should not
|
||||
@ -52,6 +184,7 @@ namespace ts {
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
currentSourceFile = node;
|
||||
enclosingBlockScopeContainer = node;
|
||||
currentNode = node;
|
||||
return visitEachChild(node, visitor, context);
|
||||
}
|
||||
|
||||
@ -62,10 +195,19 @@ namespace ts {
|
||||
const savedEnclosingBlockScopeContainer = enclosingBlockScopeContainer;
|
||||
const savedEnclosingBlockScopeContainerParent = enclosingBlockScopeContainerParent;
|
||||
|
||||
const savedConvertedLoopState = convertedLoopState;
|
||||
if (nodeStartsNewLexicalEnvironment(node) || isClassLike(node)) {
|
||||
// don't treat content of nodes that start new lexical environment or class-like nodes as part of converted loop copy
|
||||
convertedLoopState = undefined;
|
||||
}
|
||||
|
||||
onBeforeVisitNode(node);
|
||||
|
||||
const visited = visitorWorker(node);
|
||||
const visited = convertedLoopState
|
||||
? visitorForConvertedLoopWorker(node)
|
||||
: visitorWorker(node);
|
||||
|
||||
convertedLoopState = savedConvertedLoopState;
|
||||
containingNonArrowFunction = savedContainingNonArrowFunction;
|
||||
currentParent = savedCurrentParent;
|
||||
currentNode = savedCurrentNode;
|
||||
@ -74,8 +216,14 @@ namespace ts {
|
||||
return visited;
|
||||
}
|
||||
|
||||
function shouldCheckNode(node: Node): boolean {
|
||||
return (node.transformFlags & TransformFlags.ES6) !== 0 ||
|
||||
node.kind === SyntaxKind.LabeledStatement ||
|
||||
(isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node));
|
||||
}
|
||||
|
||||
function visitorWorker(node: Node): VisitResult<Node> {
|
||||
if (node.transformFlags & TransformFlags.ES6) {
|
||||
if (shouldCheckNode(node)) {
|
||||
return visitJavaScript(node);
|
||||
}
|
||||
else if (node.transformFlags & TransformFlags.ContainsES6) {
|
||||
@ -86,6 +234,52 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function visitorForConvertedLoopWorker(node: Node): VisitResult<Node> {
|
||||
const savedUseCapturedThis = useCapturedThis;
|
||||
|
||||
if (nodeStartsNewLexicalEnvironment(node) || isClassLike(node)) {
|
||||
useCapturedThis = false
|
||||
}
|
||||
|
||||
let result: VisitResult<Node>;
|
||||
|
||||
if (shouldCheckNode(node)) {
|
||||
result = visitJavaScript(node);
|
||||
}
|
||||
else {
|
||||
result = visitNodesInConvertedLoop(node);
|
||||
}
|
||||
|
||||
useCapturedThis = savedUseCapturedThis;
|
||||
return result;
|
||||
}
|
||||
|
||||
function visitNodesInConvertedLoop(node: Node): VisitResult<Node> {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ReturnStatement:
|
||||
return visitReturnStatement(<ReturnStatement>node);
|
||||
|
||||
case SyntaxKind.VariableStatement:
|
||||
return visitVariableStatement(<VariableStatement>node);
|
||||
|
||||
case SyntaxKind.SwitchStatement:
|
||||
return visitSwitchStatement(<SwitchStatement>node);
|
||||
|
||||
case SyntaxKind.BreakStatement:
|
||||
case SyntaxKind.ContinueStatement:
|
||||
return visitBreakOrContinueStatement(<BreakOrContinueStatement>node);
|
||||
|
||||
case SyntaxKind.ThisKeyword:
|
||||
return visitThisKeyword(node);
|
||||
|
||||
case SyntaxKind.Identifier:
|
||||
return visitIdentifier(<Identifier>node);
|
||||
|
||||
default:
|
||||
return visitEachChild(node, visitor, context);
|
||||
}
|
||||
}
|
||||
|
||||
function visitJavaScript(node: Node): VisitResult<Node> {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
@ -109,6 +303,9 @@ namespace ts {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return visitVariableDeclaration(<VariableDeclaration>node);
|
||||
|
||||
case SyntaxKind.Identifier:
|
||||
return visitIdentifier(<Identifier>node);
|
||||
|
||||
case SyntaxKind.VariableDeclarationList:
|
||||
return visitVariableDeclarationList(<VariableDeclarationList>node);
|
||||
|
||||
@ -206,6 +403,116 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function visitSwitchStatement(node: SwitchStatement): SwitchStatement {
|
||||
Debug.assert(convertedLoopState !== undefined);
|
||||
|
||||
const savedAllowedNonLabeledJumps = convertedLoopState.allowedNonLabeledJumps;
|
||||
// for switch statement allow only non-labeled break
|
||||
convertedLoopState.allowedNonLabeledJumps |= Jump.Break;
|
||||
|
||||
const result = visitEachChild(node, visitor, context);
|
||||
|
||||
convertedLoopState.allowedNonLabeledJumps = savedAllowedNonLabeledJumps;
|
||||
return result;
|
||||
}
|
||||
|
||||
function visitReturnStatement(node: ReturnStatement): Statement {
|
||||
Debug.assert(convertedLoopState !== undefined);
|
||||
|
||||
convertedLoopState.nonLocalJumps |= Jump.Return;
|
||||
return createReturn(
|
||||
createObjectLiteral(
|
||||
[
|
||||
createPropertyAssignment(
|
||||
createIdentifier("value"),
|
||||
node.expression
|
||||
? visitNode(node.expression, visitor, isExpression)
|
||||
: createVoidZero()
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function visitThisKeyword(node: Node): Node {
|
||||
Debug.assert(convertedLoopState !== undefined);
|
||||
|
||||
if (useCapturedThis) {
|
||||
// if useCapturedThis is true then 'this' keyword is contained inside the arrow function.
|
||||
convertedLoopState.containsLexicalThis = true;
|
||||
return node;
|
||||
}
|
||||
return convertedLoopState.thisName || (convertedLoopState.thisName = createUniqueName("this"));
|
||||
}
|
||||
|
||||
function visitIdentifier(node: Identifier): Identifier {
|
||||
if (!convertedLoopState) {
|
||||
return node;
|
||||
}
|
||||
if (isGeneratedIdentifier(node)) {
|
||||
return node;
|
||||
}
|
||||
if (node.text !== "arguments" && !resolver.isArgumentsLocalBinding(<Identifier>getOriginalNode(node))) {
|
||||
return node;
|
||||
}
|
||||
return convertedLoopState.argumentsName || (convertedLoopState.argumentsName = createUniqueName("arguments"));
|
||||
}
|
||||
|
||||
function visitBreakOrContinueStatement(node: BreakOrContinueStatement): Statement {
|
||||
if (convertedLoopState) {
|
||||
// check if we can emit break\continue as is
|
||||
// it is possible if either
|
||||
// - break\continue is labeled and label is located inside the converted loop
|
||||
// - break\continue is non-labeled and located in non-converted loop\switch statement
|
||||
const jump = node.kind === SyntaxKind.BreakStatement ? Jump.Break : Jump.Continue;
|
||||
const canUseBreakOrContinue =
|
||||
(node.label && convertedLoopState.labels && convertedLoopState.labels[node.label.text]) ||
|
||||
(!node.label && (convertedLoopState.allowedNonLabeledJumps & jump));
|
||||
|
||||
if (!canUseBreakOrContinue) {
|
||||
let labelMarker: string;
|
||||
if (!node.label) {
|
||||
if (node.kind === SyntaxKind.BreakStatement) {
|
||||
convertedLoopState.nonLocalJumps |= Jump.Break;
|
||||
labelMarker = "break";
|
||||
}
|
||||
else {
|
||||
convertedLoopState.nonLocalJumps |= Jump.Continue;
|
||||
// note: return value is emitted only to simplify debugging, call to converted loop body does not do any dispatching on it.
|
||||
labelMarker = "continue";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (node.kind === SyntaxKind.BreakStatement) {
|
||||
labelMarker = `break-${node.label.text}`;
|
||||
setLabeledJump(convertedLoopState, /*isBreak*/ true, node.label.text, labelMarker);
|
||||
}
|
||||
else {
|
||||
labelMarker = `continue-${node.label.text}`;
|
||||
setLabeledJump(convertedLoopState, /*isBreak*/ false, node.label.text, labelMarker);
|
||||
}
|
||||
}
|
||||
let returnExpression: Expression = createLiteral(labelMarker);
|
||||
if (convertedLoopState.loopOutParameters.length) {
|
||||
const outParams = convertedLoopState.loopOutParameters;
|
||||
let expr: Expression;
|
||||
for (let i = 0; i < outParams.length; ++i) {
|
||||
const copyExpr = copyOutParameter(outParams[i], CopyDirection.ToOutParameter);
|
||||
if (i === 0) {
|
||||
expr = copyExpr
|
||||
}
|
||||
else {
|
||||
expr = createBinary(expr, SyntaxKind.CommaToken, copyExpr);
|
||||
}
|
||||
}
|
||||
returnExpression = createBinary(expr, SyntaxKind.CommaToken, returnExpression);
|
||||
}
|
||||
return createReturn(returnExpression);
|
||||
}
|
||||
}
|
||||
return visitEachChild(node, visitor, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a ClassDeclaration and transforms it into a variable statement.
|
||||
*
|
||||
@ -781,7 +1088,12 @@ namespace ts {
|
||||
enableSubstitutionsForCapturedThis();
|
||||
}
|
||||
|
||||
const savedUseCapturedThis = useCapturedThis;
|
||||
useCapturedThis = true;
|
||||
|
||||
const func = transformFunctionLikeToExpression(node, /*location*/ node, /*name*/ undefined);
|
||||
|
||||
useCapturedThis = savedUseCapturedThis;
|
||||
setNodeEmitFlags(func, NodeEmitFlags.CapturesThis);
|
||||
return func;
|
||||
}
|
||||
@ -967,6 +1279,34 @@ namespace ts {
|
||||
return flattenDestructuringAssignment(context, node, needsDestructuringValue, hoistVariableDeclaration, visitor);
|
||||
}
|
||||
|
||||
function visitVariableStatement(node: VariableStatement): Statement {
|
||||
if (convertedLoopState && (getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) == 0) {
|
||||
// we are inside a converted loop - hoist variable declarations
|
||||
let assignments: Expression[];
|
||||
for (const decl of node.declarationList.declarations) {
|
||||
hoistVariableDeclarationDeclaredInConvertedLoop(convertedLoopState, decl);
|
||||
if (decl.initializer) {
|
||||
let assignment: Expression;
|
||||
if (isBindingPattern(decl.name)) {
|
||||
assignment = flattenVariableDestructuringToExpression(context, decl, hoistVariableDeclaration,/*nameSubstitution*/ undefined, visitor);
|
||||
}
|
||||
else {
|
||||
assignment = createBinary(<Identifier>decl.name, SyntaxKind.EqualsToken, decl.initializer);
|
||||
}
|
||||
(assignments || (assignments = [])).push(assignment);
|
||||
}
|
||||
}
|
||||
if (assignments) {
|
||||
return createStatement(reduceLeft(assignments, (acc, v) => createBinary(v, SyntaxKind.CommaToken, acc)));
|
||||
}
|
||||
else {
|
||||
// none of declarations has initializer - the entire variable statement can be deleted
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return visitEachChild(node, visitor, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a VariableDeclarationList that is block scoped (e.g. `let` or `const`).
|
||||
*
|
||||
@ -1093,29 +1433,43 @@ namespace ts {
|
||||
return flattenVariableDestructuring(context, node, /*value*/ undefined, visitor);
|
||||
}
|
||||
|
||||
function visitLabeledStatement(node: LabeledStatement) {
|
||||
// TODO: Convert loop body for block scoped bindings.
|
||||
return visitEachChild(node, visitor, context);
|
||||
function visitLabeledStatement(node: LabeledStatement): VisitResult<Statement> {
|
||||
if (convertedLoopState) {
|
||||
if (!convertedLoopState.labels) {
|
||||
convertedLoopState.labels = {};
|
||||
}
|
||||
convertedLoopState.labels[node.label.text] = node.label.text;
|
||||
}
|
||||
|
||||
let result: VisitResult<Statement>;
|
||||
if (isIterationStatement(node.statement, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(<IterationStatement>node.statement)) {
|
||||
result = visitNodes(createNodeArray([node.statement]), visitor, isStatement);
|
||||
}
|
||||
else {
|
||||
result = visitEachChild(node, visitor, context);
|
||||
}
|
||||
|
||||
if (convertedLoopState) {
|
||||
convertedLoopState.labels[node.label.text] = undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function visitDoStatement(node: DoStatement) {
|
||||
// TODO: Convert loop body for block scoped bindings.
|
||||
return visitEachChild(node, visitor, context);
|
||||
return convertIterationStatementBodyIfNecessary(node);
|
||||
}
|
||||
|
||||
function visitWhileStatement(node: WhileStatement) {
|
||||
// TODO: Convert loop body for block scoped bindings.
|
||||
return visitEachChild(node, visitor, context);
|
||||
return convertIterationStatementBodyIfNecessary(node);
|
||||
}
|
||||
|
||||
function visitForStatement(node: ForStatement) {
|
||||
// TODO: Convert loop body for block scoped bindings.
|
||||
return visitEachChild(node, visitor, context);
|
||||
return convertIterationStatementBodyIfNecessary(node);
|
||||
}
|
||||
|
||||
function visitForInStatement(node: ForInStatement) {
|
||||
// TODO: Convert loop body for block scoped bindings.
|
||||
return visitEachChild(node, visitor, context);
|
||||
return convertIterationStatementBodyIfNecessary(node);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1123,9 +1477,30 @@ namespace ts {
|
||||
*
|
||||
* @param node A ForOfStatement.
|
||||
*/
|
||||
function visitForOfStatement(node: ForOfStatement): Statement {
|
||||
// TODO: Convert loop body for block scoped bindings.
|
||||
function visitForOfStatement(node: ForOfStatement): VisitResult<Statement> {
|
||||
const statementOrStatements = convertIterationStatementBodyIfNecessary(node);
|
||||
const lastStatement = isArray(statementOrStatements) ? lastOrUndefined(statementOrStatements) : statementOrStatements;
|
||||
const loop = lastStatement.kind === SyntaxKind.LabeledStatement
|
||||
? (<LabeledStatement>lastStatement).statement
|
||||
: lastStatement;
|
||||
|
||||
Debug.assert(loop.kind === SyntaxKind.ForOfStatement);
|
||||
|
||||
const statement =
|
||||
lastStatement.kind === SyntaxKind.LabeledStatement
|
||||
? createLabel((<LabeledStatement>lastStatement).label, convertForOfToFor(<ForOfStatement>loop))
|
||||
: convertForOfToFor(<ForOfStatement>loop);
|
||||
|
||||
if (isArray(statementOrStatements)) {
|
||||
statementOrStatements[statementOrStatements.length - 1] = statement;
|
||||
return statementOrStatements;
|
||||
}
|
||||
else {
|
||||
return statement;
|
||||
}
|
||||
}
|
||||
|
||||
function convertForOfToFor(node: ForOfStatement): ForStatement {
|
||||
// The following ES6 code:
|
||||
//
|
||||
// for (let v of expr) { }
|
||||
@ -1147,7 +1522,7 @@ namespace ts {
|
||||
// Note also that because an extra statement is needed to assign to the LHS,
|
||||
// for-of bodies are always emitted as blocks.
|
||||
|
||||
const expression = visitNode(node.expression, visitor, isExpression);
|
||||
const expression = node.expression;
|
||||
const initializer = node.initializer;
|
||||
const statements: Statement[] = [];
|
||||
|
||||
@ -1223,12 +1598,11 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
const statement = visitNode(node.statement, visitor, isStatement);
|
||||
if (isBlock(statement)) {
|
||||
addRange(statements, statement.statements);
|
||||
if (isBlock(node.statement)) {
|
||||
addRange(statements, (<Block>node.statement).statements);
|
||||
}
|
||||
else {
|
||||
statements.push(statement);
|
||||
statements.push(node.statement);
|
||||
}
|
||||
|
||||
return createFor(
|
||||
@ -1301,6 +1675,369 @@ namespace ts {
|
||||
return createParen(inlineExpressions(expressions));
|
||||
}
|
||||
|
||||
function shouldConvertIterationStatementBody(node: IterationStatement): boolean {
|
||||
return (resolver.getNodeCheckFlags(getOriginalNode(node)) & NodeCheckFlags.LoopWithCapturedBlockScopedBinding) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records constituents of name for the given variable to be hoisted in the outer scope.
|
||||
*/
|
||||
function hoistVariableDeclarationDeclaredInConvertedLoop(state: ConvertedLoopState, node: VariableDeclaration): void {
|
||||
if (!state.hoistedLocalVariables) {
|
||||
state.hoistedLocalVariables = [];
|
||||
}
|
||||
|
||||
visit(node.name);
|
||||
|
||||
function visit(node: Identifier | BindingPattern) {
|
||||
if (node.kind === SyntaxKind.Identifier) {
|
||||
state.hoistedLocalVariables.push((<Identifier>node));
|
||||
}
|
||||
else {
|
||||
for (const element of (<BindingPattern>node).elements) {
|
||||
visit(element.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertIterationStatementBodyIfNecessary(node: IterationStatement): VisitResult<Statement> {
|
||||
if (!shouldConvertIterationStatementBody(node)) {
|
||||
let saveAllowedNonLabeledJumps: Jump;
|
||||
if (convertedLoopState) {
|
||||
// we get here if we are trying to emit normal loop loop inside converted loop
|
||||
// set allowedNonLabeledJumps to Break | Continue to mark that break\continue inside the loop should be emitted as is
|
||||
saveAllowedNonLabeledJumps = convertedLoopState.allowedNonLabeledJumps;
|
||||
convertedLoopState.allowedNonLabeledJumps = Jump.Break | Jump.Continue;
|
||||
}
|
||||
|
||||
const result = visitEachChild(node, visitor, context);
|
||||
|
||||
if (convertedLoopState) {
|
||||
convertedLoopState.allowedNonLabeledJumps = saveAllowedNonLabeledJumps;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const functionName = createUniqueName("_loop");
|
||||
let loopInitializer: VariableDeclarationList;
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
const initializer = (<ForStatement | ForInStatement | ForOfStatement>node).initializer;
|
||||
if (initializer && initializer.kind === SyntaxKind.VariableDeclarationList) {
|
||||
loopInitializer = <VariableDeclarationList>(<ForStatement | ForInStatement | ForOfStatement>node).initializer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// variables that will be passed to the loop as parameters
|
||||
const loopParameters: ParameterDeclaration[] = [];
|
||||
// variables declared in the loop initializer that will be changed inside the loop
|
||||
const loopOutParameters: LoopOutParameter[] = [];
|
||||
if (loopInitializer && (getCombinedNodeFlags(loopInitializer) & NodeFlags.BlockScoped)) {
|
||||
for (const decl of loopInitializer.declarations) {
|
||||
processLoopVariableDeclaration(decl, loopParameters, loopOutParameters);
|
||||
}
|
||||
}
|
||||
|
||||
const outerConvertedLoopState = convertedLoopState;
|
||||
convertedLoopState = { loopOutParameters };
|
||||
if (outerConvertedLoopState) {
|
||||
// convertedOuterLoopState !== undefined means that this converted loop is nested in another converted loop.
|
||||
// if outer converted loop has already accumulated some state - pass it through
|
||||
if (outerConvertedLoopState.argumentsName) {
|
||||
// outer loop has already used 'arguments' so we've already have some name to alias it
|
||||
// use the same name in all nested loops
|
||||
convertedLoopState.argumentsName = outerConvertedLoopState.argumentsName;
|
||||
}
|
||||
if (outerConvertedLoopState.thisName) {
|
||||
// outer loop has already used 'this' so we've already have some name to alias it
|
||||
// use the same name in all nested loops
|
||||
convertedLoopState.thisName = outerConvertedLoopState.thisName;
|
||||
}
|
||||
if (outerConvertedLoopState.hoistedLocalVariables) {
|
||||
// we've already collected some non-block scoped variable declarations in enclosing loop
|
||||
// use the same storage in nested loop
|
||||
convertedLoopState.hoistedLocalVariables = outerConvertedLoopState.hoistedLocalVariables;
|
||||
}
|
||||
}
|
||||
|
||||
let loopBody = visitEachChild(node.statement, visitor, context);
|
||||
|
||||
const currentState = convertedLoopState;
|
||||
convertedLoopState = outerConvertedLoopState;
|
||||
|
||||
if (loopOutParameters.length) {
|
||||
const statements = isBlock(loopBody) ? (<Block>loopBody).statements.slice() : [loopBody];
|
||||
copyOutParameters(loopOutParameters, CopyDirection.ToOutParameter, statements);
|
||||
loopBody = createBlock(statements, /*location*/ undefined, /*multiline*/ true);
|
||||
}
|
||||
|
||||
if (!isBlock(loopBody)) {
|
||||
loopBody = createBlock([loopBody], /*location*/ undefined, /*multiline*/ true);
|
||||
}
|
||||
const convertedLoopVariable =
|
||||
createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
createVariableDeclarationList(
|
||||
[
|
||||
createVariableDeclaration(
|
||||
functionName,
|
||||
setNodeEmitFlags(
|
||||
createFunctionExpression(
|
||||
/*asteriskToken*/ undefined,
|
||||
/*name*/ undefined,
|
||||
loopParameters,
|
||||
<Block>loopBody
|
||||
),
|
||||
currentState.containsLexicalThis
|
||||
? NodeEmitFlags.CapturesThis
|
||||
: 0
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
const statements: Statement[] = [convertedLoopVariable];
|
||||
|
||||
let extraVariableDeclarations: VariableDeclaration[];
|
||||
// propagate state from the inner loop to the outer loop if necessary
|
||||
if (currentState.argumentsName) {
|
||||
// if alias for arguments is set
|
||||
if (outerConvertedLoopState) {
|
||||
// pass it to outer converted loop
|
||||
outerConvertedLoopState.argumentsName = currentState.argumentsName;
|
||||
}
|
||||
else {
|
||||
// this is top level converted loop and we need to create an alias for 'arguments' object
|
||||
(extraVariableDeclarations || (extraVariableDeclarations = [])).push(
|
||||
createVariableDeclaration(
|
||||
currentState.argumentsName,
|
||||
createIdentifier("arguments")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentState.thisName) {
|
||||
// if alias for this is set
|
||||
if (outerConvertedLoopState) {
|
||||
// pass it to outer converted loop
|
||||
outerConvertedLoopState.thisName = currentState.thisName;
|
||||
}
|
||||
else {
|
||||
// this is top level converted loop so we need to create an alias for 'this' here
|
||||
// NOTE:
|
||||
// if converted loops were all nested in arrow function then we'll always emit '_this' so convertedLoopState.thisName will not be set.
|
||||
// If it is set this means that all nested loops are not nested in arrow function and it is safe to capture 'this'.
|
||||
(extraVariableDeclarations || (extraVariableDeclarations = [])).push(
|
||||
createVariableDeclaration(
|
||||
currentState.thisName,
|
||||
createIdentifier("this")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentState.hoistedLocalVariables) {
|
||||
// if hoistedLocalVariables !== undefined this means that we've possibly collected some variable declarations to be hoisted later
|
||||
if (outerConvertedLoopState) {
|
||||
// pass them to outer converted loop
|
||||
outerConvertedLoopState.hoistedLocalVariables = currentState.hoistedLocalVariables;
|
||||
}
|
||||
else {
|
||||
if (!extraVariableDeclarations) {
|
||||
extraVariableDeclarations = [];
|
||||
}
|
||||
// hoist collected variable declarations
|
||||
for (const name in currentState.hoistedLocalVariables) {
|
||||
const identifier = currentState.hoistedLocalVariables[name];
|
||||
extraVariableDeclarations.push(createVariableDeclaration(identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add extra variables to hold out parameters if necessary
|
||||
if (loopOutParameters.length) {
|
||||
if (!extraVariableDeclarations) {
|
||||
extraVariableDeclarations = [];
|
||||
}
|
||||
for (const outParam of loopOutParameters) {
|
||||
extraVariableDeclarations.push(createVariableDeclaration(outParam.outParamName));
|
||||
}
|
||||
}
|
||||
|
||||
// create variable statement to hold all introduced variable declarations
|
||||
if (extraVariableDeclarations) {
|
||||
statements.push(createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
createVariableDeclarationList(extraVariableDeclarations)
|
||||
));
|
||||
}
|
||||
|
||||
let loop = <IterationStatement>getMutableClone(node);
|
||||
// clean statement part
|
||||
loop.statement = undefined;
|
||||
// visit childnodes to transform initializer/condition/incrementor parts
|
||||
loop = visitEachChild(loop, visitor, context);
|
||||
// set loop statement
|
||||
loop.statement = createBlock(
|
||||
generateCallToConvertedLoop(functionName, loopParameters, currentState),
|
||||
/*location*/ undefined,
|
||||
/*multiline*/ true
|
||||
);
|
||||
statements.push(
|
||||
currentParent.kind === SyntaxKind.LabeledStatement
|
||||
? createLabel((<LabeledStatement>currentParent).label, loop)
|
||||
: loop
|
||||
);
|
||||
return statements;
|
||||
}
|
||||
|
||||
function copyOutParameter(outParam: LoopOutParameter, copyDirection: CopyDirection): BinaryExpression {
|
||||
const source = copyDirection === CopyDirection.ToOriginal ? outParam.outParamName : outParam.originalName;
|
||||
const target = copyDirection === CopyDirection.ToOriginal ? outParam.originalName : outParam.outParamName;
|
||||
return createBinary(target, SyntaxKind.EqualsToken, source);
|
||||
}
|
||||
|
||||
function copyOutParameters(outParams: LoopOutParameter[], copyDirection: CopyDirection, statements: Statement[]): void {
|
||||
for (const outParam of outParams) {
|
||||
statements.push(createStatement(copyOutParameter(outParam, copyDirection)));
|
||||
}
|
||||
}
|
||||
|
||||
function generateCallToConvertedLoop(loopFunctionExpressionName: Identifier, parameters: ParameterDeclaration[], state: ConvertedLoopState): Statement[] {
|
||||
const outerConvertedLoopState = convertedLoopState;
|
||||
|
||||
const statements: Statement[] = [];
|
||||
// loop is considered simple if it does not have any return statements or break\continue that transfer control outside of the loop
|
||||
// simple loops are emitted as just 'loop()';
|
||||
// NOTE: if loop uses only 'continue' it still will be emitted as simple loop
|
||||
const isSimpleLoop =
|
||||
!(state.nonLocalJumps & ~Jump.Continue) &&
|
||||
!state.labeledNonLocalBreaks &&
|
||||
!state.labeledNonLocalContinues;
|
||||
|
||||
const call = createCall(loopFunctionExpressionName, map(parameters, p => <Identifier>p.name));
|
||||
if (isSimpleLoop) {
|
||||
statements.push(createStatement(call));
|
||||
copyOutParameters(state.loopOutParameters, CopyDirection.ToOriginal, statements);
|
||||
}
|
||||
else {
|
||||
const loopResultName = createUniqueName("state");
|
||||
const stateVariable = createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
createVariableDeclarationList(
|
||||
[createVariableDeclaration(loopResultName, call)]
|
||||
)
|
||||
);
|
||||
statements.push(stateVariable);
|
||||
copyOutParameters(state.loopOutParameters, CopyDirection.ToOriginal, statements);
|
||||
|
||||
if (state.nonLocalJumps & Jump.Return) {
|
||||
let returnStatement: ReturnStatement;
|
||||
if (outerConvertedLoopState) {
|
||||
outerConvertedLoopState.nonLocalJumps |= Jump.Return;
|
||||
returnStatement = createReturn(loopResultName);
|
||||
}
|
||||
else {
|
||||
returnStatement = createReturn(createPropertyAccess(loopResultName, "value"));
|
||||
}
|
||||
statements.push(
|
||||
createIf(
|
||||
createBinary(
|
||||
createTypeOf(loopResultName),
|
||||
SyntaxKind.EqualsEqualsEqualsToken,
|
||||
createLiteral("object")
|
||||
),
|
||||
returnStatement
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (state.nonLocalJumps & Jump.Break) {
|
||||
statements.push(
|
||||
createIf(
|
||||
createBinary(
|
||||
loopResultName,
|
||||
SyntaxKind.EqualsEqualsEqualsToken,
|
||||
createLiteral("break")
|
||||
),
|
||||
createBreak()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (state.labeledNonLocalBreaks || state.labeledNonLocalContinues) {
|
||||
const caseClauses: CaseClause[] = [];
|
||||
processLabeledJumps(state.labeledNonLocalBreaks, /*isBreak*/ true, loopResultName, outerConvertedLoopState, caseClauses);
|
||||
processLabeledJumps(state.labeledNonLocalContinues, /*isBreak*/ false, loopResultName, outerConvertedLoopState, caseClauses);
|
||||
statements.push(
|
||||
createSwitch(
|
||||
loopResultName,
|
||||
createCaseBlock(caseClauses)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return statements;
|
||||
}
|
||||
|
||||
function setLabeledJump(state: ConvertedLoopState, isBreak: boolean, labelText: string, labelMarker: string): void {
|
||||
if (isBreak) {
|
||||
if (!state.labeledNonLocalBreaks) {
|
||||
state.labeledNonLocalBreaks = {};
|
||||
}
|
||||
state.labeledNonLocalBreaks[labelText] = labelMarker;
|
||||
}
|
||||
else {
|
||||
if (!state.labeledNonLocalContinues) {
|
||||
state.labeledNonLocalContinues = {};
|
||||
}
|
||||
state.labeledNonLocalContinues[labelText] = labelMarker;
|
||||
}
|
||||
}
|
||||
|
||||
function processLabeledJumps(table: Map<string>, isBreak: boolean, loopResultName: Identifier, outerLoop: ConvertedLoopState, caseClauses: CaseClause[]): void {
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
for (const labelText in table) {
|
||||
const labelMarker = table[labelText];
|
||||
const statements: Statement[] = [];
|
||||
// if there are no outer converted loop or outer label in question is located inside outer converted loop
|
||||
// then emit labeled break\continue
|
||||
// otherwise propagate pair 'label -> marker' to outer converted loop and emit 'return labelMarker' so outer loop can later decide what to do
|
||||
if (!outerLoop || (outerLoop.labels && outerLoop.labels[labelText])) {
|
||||
const label = createIdentifier(labelText);
|
||||
statements.push(isBreak ? createBreak(label) : createContinue(label));
|
||||
}
|
||||
else {
|
||||
setLabeledJump(outerLoop, isBreak, labelText, labelMarker);
|
||||
statements.push(createReturn(loopResultName));
|
||||
}
|
||||
caseClauses.push(createCaseClause(createLiteral(labelMarker), statements));
|
||||
}
|
||||
}
|
||||
|
||||
function processLoopVariableDeclaration(decl: VariableDeclaration | BindingElement, loopParameters: ParameterDeclaration[], loopOutParameters: LoopOutParameter[]) {
|
||||
const name = decl.name;
|
||||
if (isBindingPattern(name)) {
|
||||
for (const element of name.elements) {
|
||||
processLoopVariableDeclaration(element, loopParameters, loopOutParameters);
|
||||
}
|
||||
}
|
||||
else {
|
||||
loopParameters.push(createParameter(name));
|
||||
if (resolver.getNodeCheckFlags(decl) & NodeCheckFlags.NeedsLoopOutParameter) {
|
||||
const outParamName = createUniqueName("out_" + name.text);
|
||||
loopOutParameters.push({ originalName: name, outParamName });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the members of an object literal to an array of expressions.
|
||||
*
|
||||
|
||||
@ -43,11 +43,16 @@ namespace ts {
|
||||
let exportedLocalNames: Identifier[];
|
||||
let exportedFunctionDeclarations: ExpressionStatement[];
|
||||
|
||||
let enclosingBlockScopedContainer: Node;
|
||||
let currentParent: Node;
|
||||
let currentNode: Node;
|
||||
|
||||
return transformSourceFile;
|
||||
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isExternalModule(node) || compilerOptions.isolatedModules) {
|
||||
currentSourceFile = node;
|
||||
currentNode = node;
|
||||
|
||||
// Perform the transformation.
|
||||
const updated = transformSystemModuleWorker(node);
|
||||
@ -442,6 +447,28 @@ namespace ts {
|
||||
}
|
||||
|
||||
function visitNestedNode(node: Node): VisitResult<Node> {
|
||||
const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
|
||||
const savedCurrentParent = currentParent;
|
||||
const savedCurrentNode = currentNode;
|
||||
|
||||
const currentGrandparent = currentParent;
|
||||
currentParent = currentNode;
|
||||
currentNode = node;
|
||||
|
||||
if (currentParent && isBlockScope(currentParent, currentGrandparent)) {
|
||||
enclosingBlockScopedContainer = currentParent;
|
||||
}
|
||||
|
||||
const result = visitNestedNodeWorker(node);
|
||||
|
||||
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
|
||||
currentParent = savedCurrentParent;
|
||||
currentNode = savedCurrentNode;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function visitNestedNodeWorker(node: Node): VisitResult<Node> {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.VariableStatement:
|
||||
return visitVariableStatement(<VariableStatement>node);
|
||||
@ -562,10 +589,17 @@ namespace ts {
|
||||
* @param node The variable statement to visit.
|
||||
*/
|
||||
function visitVariableStatement(node: VariableStatement): VisitResult<Statement> {
|
||||
// hoist only non-block scoped declarations or block scoped declarations parented by source file
|
||||
const shouldHoist =
|
||||
((getCombinedNodeFlags(getOriginalNode(node.declarationList)) & NodeFlags.BlockScoped) == 0) ||
|
||||
enclosingBlockScopedContainer.kind === SyntaxKind.SourceFile;
|
||||
if (!shouldHoist) {
|
||||
return node;
|
||||
}
|
||||
const isExported = hasModifier(node, ModifierFlags.Export);
|
||||
const expressions: Expression[] = [];
|
||||
for (const variable of node.declarationList.declarations) {
|
||||
addNode(expressions, transformVariable(variable, isExported));
|
||||
addNode(expressions, <Expression>transformVariable(variable, isExported));
|
||||
}
|
||||
|
||||
if (expressions.length) {
|
||||
@ -581,7 +615,7 @@ namespace ts {
|
||||
* @param node The VariableDeclaration to transform.
|
||||
* @param isExported A value used to indicate whether the containing statement was exported.
|
||||
*/
|
||||
function transformVariable(node: VariableDeclaration, isExported: boolean): Expression {
|
||||
function transformVariable(node: VariableDeclaration, isExported: boolean): VariableDeclaration | Expression {
|
||||
// Hoist any bound names within the declaration.
|
||||
hoistBindingElement(node, isExported);
|
||||
|
||||
@ -685,6 +719,10 @@ namespace ts {
|
||||
return statements;
|
||||
}
|
||||
|
||||
function shouldHoistLoopInitializer(node: VariableDeclarationList | Expression) {
|
||||
return isVariableDeclarationList(node) && (getCombinedNodeFlags(node) & NodeFlags.BlockScoped) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the body of a ForStatement to hoist declarations.
|
||||
*
|
||||
@ -692,10 +730,10 @@ namespace ts {
|
||||
*/
|
||||
function visitForStatement(node: ForStatement): ForStatement {
|
||||
const initializer = node.initializer;
|
||||
if (isVariableDeclarationList(initializer)) {
|
||||
if (shouldHoistLoopInitializer(initializer)) {
|
||||
const expressions: Expression[] = [];
|
||||
for (const variable of initializer.declarations) {
|
||||
addNode(expressions, transformVariable(variable, /*isExported*/ false));
|
||||
for (const variable of (<VariableDeclarationList>initializer).declarations) {
|
||||
addNode(expressions, <Expression>transformVariable(variable, /*isExported*/ false));
|
||||
};
|
||||
|
||||
return createFor(
|
||||
@ -735,9 +773,9 @@ namespace ts {
|
||||
*/
|
||||
function visitForInStatement(node: ForInStatement): ForInStatement {
|
||||
const initializer = node.initializer;
|
||||
if (isVariableDeclarationList(initializer)) {
|
||||
if (shouldHoistLoopInitializer(initializer)) {
|
||||
const updated = getMutableClone(node);
|
||||
updated.initializer = transformForBinding(initializer);
|
||||
updated.initializer = transformForBinding(<VariableDeclarationList>initializer);
|
||||
updated.statement = visitNode(node.statement, visitNestedNode, isStatement, /*optional*/ false, liftToBlock);
|
||||
return updated;
|
||||
}
|
||||
@ -753,9 +791,9 @@ namespace ts {
|
||||
*/
|
||||
function visitForOfStatement(node: ForOfStatement): ForOfStatement {
|
||||
const initializer = node.initializer;
|
||||
if (isVariableDeclarationList(initializer)) {
|
||||
if (shouldHoistLoopInitializer(initializer)) {
|
||||
const updated = getMutableClone(node);
|
||||
updated.initializer = transformForBinding(initializer);
|
||||
updated.initializer = transformForBinding(<VariableDeclarationList>initializer);
|
||||
updated.statement = visitNode(node.statement, visitNestedNode, isStatement, /*optional*/ false, liftToBlock);
|
||||
return updated;
|
||||
}
|
||||
@ -1312,7 +1350,7 @@ namespace ts {
|
||||
exportedFunctionDeclarations.push(createDeclarationExport(node));
|
||||
}
|
||||
|
||||
function hoistBindingElement(node: VariableDeclaration | BindingElement, isExported: boolean) {
|
||||
function hoistBindingElement(node: VariableDeclaration | BindingElement, isExported: boolean): void {
|
||||
const name = node.name;
|
||||
if (isIdentifier(name)) {
|
||||
hoistVariableDeclaration(getSynthesizedClone(name));
|
||||
|
||||
@ -799,7 +799,7 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isIterationStatement(node: Node, lookInLabeledStatements: boolean): boolean {
|
||||
export function isIterationStatement(node: Node, lookInLabeledStatements: boolean): node is IterationStatement {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
|
||||
@ -851,9 +851,9 @@ namespace ts {
|
||||
*
|
||||
* @param nodes The NodeArray.
|
||||
*/
|
||||
export function liftToBlock(nodes: Node[]): Block {
|
||||
export function liftToBlock(nodes: Node[]): Statement {
|
||||
Debug.assert(every(nodes, isStatement), "Cannot lift nodes to a Block.");
|
||||
return createBlock(<NodeArray<Statement>>nodes);
|
||||
return <Statement>singleOrUndefined(nodes) || createBlock(<NodeArray<Statement>>nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user