diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b65c05ed3ca..b0ae5804b5f 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1968,16 +1968,20 @@ namespace ts { transformFlags = TransformFlags.AssertTypeScript; } } + break; case SyntaxKind.ExpressionStatement: - // if (node.flags & NodeFlags.Generated) { - // let expression = (node).expression; - // if (expression.kind === SyntaxKind.CallExpression - // && (expression).expression.kind === SyntaxKind.SuperKeyword) { - // transformFlags |= TransformFlags.AssertES6; - // } - // } + if (nodeIsSynthesized(node)) { + const expression = (node).expression; + if (nodeIsSynthesized(expression) + && isCallExpression(expression) + && expression.expression.kind === SyntaxKind.SuperKeyword) { + // A synthesized call to `super` should be transformed to a cleaner emit + // when transpiling to ES5/3. + transformFlags |= TransformFlags.AssertES6; + } + } break; @@ -2088,17 +2092,16 @@ namespace ts { case SyntaxKind.VariableDeclarationList: // If a VariableDeclarationList is `let` or `const`, then it is ES6 syntax. - if (node.flags & NodeFlags.Let - || node.flags & NodeFlags.Const) { + if (node.flags & NodeFlags.BlockScoped) { transformFlags |= TransformFlags.AssertES6; } break; case SyntaxKind.VariableStatement: - // If a VariableStatement is exported, then it is ES6 syntax. + // If a VariableStatement is exported, then it is either ES6 or TypeScript syntax. if (node.flags & NodeFlags.Export) { - transformFlags |= TransformFlags.AssertES6; + transformFlags |= TransformFlags.AssertES6 | TransformFlags.AssertTypeScript; } break; @@ -2120,13 +2123,13 @@ namespace ts { break; case SyntaxKind.HeritageClause: - // An `extends` HertiageClause is ES6 syntax. if ((node).token === SyntaxKind.ExtendsKeyword) { + // An `extends` HeritageClause is ES6 syntax. transformFlags |= TransformFlags.AssertES6; } - - // An `implements` HeritageClause is TypeScript syntax. - else if ((node).token === SyntaxKind.ImplementsKeyword) { + else { + // An `implements` HeritageClause is TypeScript syntax. + Debug.assert((node).token === SyntaxKind.ImplementsKeyword); transformFlags |= TransformFlags.AssertTypeScript; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 350a5165e66..19726482cb3 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -188,8 +188,19 @@ namespace ts { export function concatenate(array1: T[], array2: T[]): T[] { if (!array2 || !array2.length) return array1; if (!array1 || !array1.length) return array2; + return [...array1, ...array2]; + } - return array1.concat(array2); + export function append(array: T[], value: T): T[] { + if (value === undefined) return array; + if (!array || !array.length) return [value]; + return [...array, value]; + } + + export function prepend(array: T[], value: T): T[] { + if (value === undefined) return array; + if (!array || !array.length) return [value]; + return [value, ...array]; } export function deduplicate(array: T[]): T[] { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 35d017d3ca2..6e195314253 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -78,14 +78,17 @@ namespace ts { return array; } - function setModifiers(node: Node, modifiers: Modifier[]) { + export function setModifiers(node: T, modifiers: Modifier[]) { if (modifiers) { - node.modifiers = createSynthesizedModifiersArray(modifiers); - node.flags |= node.modifiers.flags; + const array = createModifiersArray(modifiers); + node.modifiers = array; + node.flags |= array.flags; } else { node.modifiers = undefined; } + + return node; } export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node { @@ -153,17 +156,17 @@ namespace ts { export function createLiteral(value: string): StringLiteral; export function createLiteral(value: number): LiteralExpression; export function createLiteral(value: string | number | boolean): PrimaryExpression; - export function createLiteral(value: string | number | boolean): T { + export function createLiteral(value: string | number | boolean): PrimaryExpression { if (typeof value === "number") { - const node = createNode(SyntaxKind.NumericLiteral); + const node = createNode(SyntaxKind.NumericLiteral); node.text = value.toString(); return node; } else if (typeof value === "boolean") { - return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); + return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); } else { - const node = createNode(SyntaxKind.StringLiteral); + const node = createNode(SyntaxKind.StringLiteral); node.text = String(value); return node; } @@ -224,7 +227,7 @@ namespace ts { const node = createNode(SyntaxKind.MethodDeclaration, location); node.decorators = undefined; setModifiers(node, modifiers); - node.name = coercePropertyName(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; node.typeParameters = undefined; node.parameters = createNodeArray(parameters); node.body = body; @@ -246,7 +249,7 @@ namespace ts { const node = createNode(SyntaxKind.GetAccessor, location); node.decorators = undefined; setModifiers(node, modifiers); - node.name = coercePropertyName(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; node.typeParameters = undefined; node.parameters = createNodeArray(); node.body = body; @@ -257,7 +260,7 @@ namespace ts { const node = createNode(SyntaxKind.SetAccessor, location); node.decorators = undefined; setModifiers(node, modifiers); - node.name = coercePropertyName(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; node.typeParameters = undefined; node.parameters = createNodeArray([parameter]); node.body = body; @@ -269,7 +272,7 @@ namespace ts { node.decorators = undefined; node.modifiers = undefined; node.dotDotDotToken = undefined; - node.name = coerceBindingName(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; node.questionToken = undefined; node.type = undefined; node.initializer = initializer; @@ -295,14 +298,14 @@ namespace ts { const node = createNode(SyntaxKind.PropertyAccessExpression, location); node.expression = parenthesizeForAccess(expression); node.dotToken = createSynthesizedNode(SyntaxKind.DotToken); - node.name = coerceIdentifier(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; return node; } - export function createElementAccess(expression: Expression, index: string | number | Expression, location?: TextRange) { + export function createElementAccess(expression: Expression, index: number | Expression, location?: TextRange) { const node = createNode(SyntaxKind.ElementAccessExpression, location); node.expression = parenthesizeForAccess(expression); - node.argumentExpression = coerceExpression(index); + node.argumentExpression = typeof index === "number" ? createLiteral(index) : index; return node; } @@ -323,7 +326,7 @@ namespace ts { const node = createNode(SyntaxKind.FunctionExpression, location); node.modifiers = undefined; node.asteriskToken = asteriskToken; - node.name = coerceIdentifier(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; node.typeParameters = undefined; node.parameters = createNodeArray(parameters); node.type = undefined; @@ -356,9 +359,9 @@ namespace ts { export function createBinary(left: Expression, operator: SyntaxKind, right: Expression, location?: TextRange) { const node = createNode(SyntaxKind.BinaryExpression, location); - node.left = parenthesizeForBinary(left, operator, BinaryOperand.Left); + node.left = parenthesizeBinaryOperand(operator, left, /*isLeftSideOfBinary*/ true); node.operatorToken = createSynthesizedNode(operator); - node.right = parenthesizeForBinary(right, operator, BinaryOperand.Right); + node.right = parenthesizeBinaryOperand(operator, right, /*isLeftSideOfBinary*/ false); return node; } @@ -434,7 +437,7 @@ namespace ts { export function createVariableDeclaration(name: string | BindingPattern | Identifier, initializer?: Expression, location?: TextRange): VariableDeclaration { const node = createNode(SyntaxKind.VariableDeclaration, location); - node.name = coerceBindingName(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; node.initializer = initializer; return node; } @@ -456,7 +459,7 @@ namespace ts { node.decorators = undefined; setModifiers(node, modifiers); node.asteriskToken = asteriskToken; - node.name = coerceIdentifier(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; node.typeParameters = undefined; node.parameters = createNodeArray(parameters); node.type = undefined; @@ -497,8 +500,8 @@ namespace ts { export function createExportSpecifier(name: string | Identifier, propertyName?: string | Identifier) { const node = createNode(SyntaxKind.ExportSpecifier); - node.name = coerceIdentifier(name); - node.propertyName = coerceIdentifier(propertyName); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.propertyName = typeof propertyName === "string" ? createIdentifier(propertyName) : propertyName; return node; } @@ -554,7 +557,11 @@ namespace ts { } export function createArraySlice(array: Expression, start?: number | Expression) { - const argumentsList: Expression[] = start !== undefined ? [coerceExpression(start)] : []; + const argumentsList: Expression[] = []; + if (start !== undefined) { + argumentsList.push(typeof start === "number" ? createLiteral(start) : start); + } + return createCall(createPropertyAccess(array, "slice"), argumentsList); } @@ -791,111 +798,150 @@ namespace ts { // Utilities - - function coerceIdentifier(value: string | Identifier) { - if (typeof value === "string") { - return createIdentifier(value); - } - else { - return value; - } - } - - function coerceBindingName(value: string | BindingName) { - if (typeof value === "string") { - return createIdentifier(value); - } - else { - return value; - } - } - - function coercePropertyName(value: string | PropertyName) { - if (typeof value === "string") { - return createIdentifier(value); - } - else { - return value; - } - } - - function coerceExpression(value: string | number | boolean | Expression): Expression { - if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { - return createLiteral(value); - } - else { - return value; - } - } - - const enum BinaryOperand { - Left, - Right - } - - function parenthesizeForBinary(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { + /** + * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended + * order of operations. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean) { // When diagnosing whether the expression needs parentheses, the decision should be based // on the innermost expression in a chain of nested type assertions. - while (operand.kind === SyntaxKind.TypeAssertionExpression || operand.kind === SyntaxKind.AsExpression) { - operand = (operand).expression; - } + operand = skipAssertions(operand); // If the resulting expression is already parenthesized, we do not need to do any further processing. if (operand.kind === SyntaxKind.ParenthesizedExpression) { return operand; } - return needsParenthesesForBinary(operand, operator, side) + return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary) ? createParen(operand) : operand; } - function needsParenthesesForBinary(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { + /** + * Determines whether the operand to a BinaryExpression needs to be parenthesized. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean) { + // If the operand has lower precedence, then it needs to be parenthesized to preserve the + // intent of the expression. For example, if the operand is `a + b` and the operator is + // `*`, then we need to parenthesize the operand to preserve the intended order of + // operations: `(a + b) * x`. + // + // If the operand has higher precedence, then it does not need to be parenthesized. For + // example, if the operand is `a * b` and the operator is `+`, then we do not need to + // parenthesize to preserve the intended order of operations: `a * b + x`. + // + // If the operand has the same precedence, then we need to check the associativity of + // the operator based on whether this is the left or right operand of the expression. + // + // For example, if `a / d` is on the right of operator `*`, we need to parenthesize + // to preserve the intended order of operations: `x * (a / d)` + // + // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve + // the intended order of operations: `(a ** b) ** c` + const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); const operandPrecedence = getExpressionPrecedence(operand); - const operatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, operator); - switch (compareValues(operandPrecedence, operatorPrecedence)) { + switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { case Comparison.LessThan: return true; - case Comparison.EqualTo: - return isRightAssociativeOperandOnLeftHandSide(operand, side) - || isModuloOperandOnRightHandSide(operand, operator, side); + case Comparison.GreaterThan: return false; + + case Comparison.EqualTo: + if (isLeftSideOfBinary) { + // No need to parenthesize the left operand when the binary operator is + // left associative: + // (a*b)/x -> a*b/x + // (a**b)/x -> a**b/x + + // Parentheses are needed for the left operand when the binary operator is + // right associative: + // (a/b)**x -> (a/b)**x + // (a**b)**x -> (a**b)**x + const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); + return binaryOperatorAssociativity === Associativity.Right; + } + else { + // No need to parenthesize the right operand when the binary operator and + // operand are the same and one of the following: + // x*(a*b) => x*a*b + // x|(a|b) => x|a|b + // x&(a&b) => x&a&b + // x^(a^b) => x^a^b + if (isBinaryExpression(operand) + && operand.operatorToken.kind === binaryOperator + && isMathAssociativeOperator(binaryOperator)) { + return false; + } + + // No need to parenthesize the right operand when the operand is right + // associative: + // x/(a**b) -> x/a**b + // x**(a**b) -> x**a**b + + // Parentheses are needed for the right operand when the operand is left + // associative: + // x/(a*b) -> x/(a*b) + // x**(a/b) -> x**(a/b) + const operandAssociativity = getExpressionAssociativity(operand); + return operandAssociativity === Associativity.Left; + } } } - function isRightAssociativeOperandOnLeftHandSide(operand: Expression, side: BinaryOperand) { - return side === BinaryOperand.Left - && getExpressionAssociativity(operand) === Associativity.Right; - } - - function isModuloOperandOnRightHandSide(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { - return side === BinaryOperand.Right - && operator !== SyntaxKind.PercentToken - && operand.kind === SyntaxKind.BinaryExpression - && (operand).operatorToken.kind === SyntaxKind.PercentToken; + /** + * Determines whether a binary operator is mathematically associative. + * + * @param binaryOperator The binary operator. + */ + function isMathAssociativeOperator(binaryOperator: SyntaxKind) { + // The following operators are associative in JavaScript: + // (a*b)*c -> a*(b*c) -> a*b*c + // (a|b)|c -> a|(b|c) -> a|b|c + // (a&b)&c -> a&(b&c) -> a&b&c + // (a^b)^c -> a^(b^c) -> a^b^c + // + // While addition is associative in mathematics, JavaScript's `+` is not + // guaranteed to be associative as it is overloaded with string concatenation. + return binaryOperator === SyntaxKind.AsteriskToken + || binaryOperator === SyntaxKind.BarToken + || binaryOperator === SyntaxKind.AmpersandToken + || binaryOperator === SyntaxKind.CaretToken; } + /** + * Wraps an expression in parentheses if it is needed in order to use the expression for + * property or element access. + * + * @param expr The expression node. + */ function parenthesizeForAccess(expr: Expression): LeftHandSideExpression { // When diagnosing whether the expression needs parentheses, the decision should be based // on the innermost expression in a chain of nested type assertions. - while (expr.kind === SyntaxKind.TypeAssertionExpression || expr.kind === SyntaxKind.AsExpression) { - expr = (expr).expression; - } + expr = skipAssertions(expr); // isLeftHandSideExpression is almost the correct criterion for when it is not necessary // to parenthesize the expression before a dot. The known exceptions are: // // NewExpression: // new C.x -> not the same as (new C).x - // NumberLiteral + // NumericLiteral // 1.x -> not the same as (1).x // if (isLeftHandSideExpression(expr) && expr.kind !== SyntaxKind.NewExpression && expr.kind !== SyntaxKind.NumericLiteral) { - - return expr; + return expr; } return createParen(expr); @@ -909,6 +955,18 @@ namespace ts { return createParen(operand); } + /** + * Skips past any TypeAssertionExpression or AsExpression nodes to their inner expression. + * + * @param node The expression node. + */ + function skipAssertions(node: Expression) { + while (node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) { + node = (node).expression; + } + + return node; + } export function startOnNewLine(node: T): T { (node).startsOnNewLine = true; diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 51129506e89..8a0725f5236 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -85,8 +85,8 @@ const _super = (function (geti, seti) { })(name => super[name], (name, value) => super[name] = value);`; const compilerOptions = host.getCompilerOptions(); - const languageVersion = getLanguageVersion(compilerOptions); - const moduleKind = getModuleKind(compilerOptions); + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); const sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined; const emitterDiagnostics = createDiagnosticCollection(); diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 7552efddb21..b2c1007b716 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -19,10 +19,16 @@ namespace ts { [ModuleKind.None]: transformModule }; + const enum SyntaxKindFeatureFlags { + ExpressionSubstitution = 1 << 0, + EmitNotifications = 1 << 1, + } + + export function getTransformers(compilerOptions: CompilerOptions) { const jsx = compilerOptions.jsx; - const languageVersion = getLanguageVersion(compilerOptions); - const moduleKind = getModuleKind(compilerOptions); + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); const transformers: Transformer[] = []; transformers.push(transformTypeScript); @@ -56,8 +62,7 @@ namespace ts { const nodeEmitFlags: NodeEmitFlags[] = []; const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; - const enabledExpressionSubstitutions = new Array(SyntaxKind.Count); - const enabledEmitNotifications = new Array(SyntaxKind.Count); + const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); let lexicalEnvironmentStackOffset = 0; let hoistedVariableDeclarations: VariableDeclaration[]; @@ -108,19 +113,19 @@ namespace ts { } function enableExpressionSubstitution(kind: SyntaxKind) { - enabledExpressionSubstitutions[kind] = true; + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.ExpressionSubstitution; } function isExpressionSubstitutionEnabled(node: Node) { - return enabledExpressionSubstitutions[node.kind]; + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.ExpressionSubstitution) !== 0; } function enableEmitNotification(kind: SyntaxKind) { - enabledEmitNotifications[kind] = true; + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; } function isEmitNotificationEnabled(node: Node) { - return enabledEmitNotifications[node.kind] + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 || (getNodeEmitFlags(node) & NodeEmitFlags.AdviseOnEmitNode) !== 0; } @@ -226,6 +231,8 @@ namespace ts { return generateNameForImportOrExportDeclaration(node); case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: + Debug.assert((node.flags & NodeFlags.Default) !== 0, "Can only generate a name for a default export."); + return generateNameForExportDefault(); case SyntaxKind.ExportAssignment: return generateNameForExportDefault(); case SyntaxKind.ClassExpression: @@ -258,14 +265,16 @@ namespace ts { } /** - * Records a hoisted variable declaration within a lexical environment. + * Records a hoisted variable declaration for the provided name within a lexical environment. */ function hoistVariableDeclaration(name: Identifier): void { + const decl = createVariableDeclaration(name); if (!hoistedVariableDeclarations) { - hoistedVariableDeclarations = []; + hoistedVariableDeclarations = [decl]; + } + else { + hoistedVariableDeclarations.push(decl); } - - hoistedVariableDeclarations.push(createVariableDeclaration(name)); } /** @@ -273,10 +282,11 @@ namespace ts { */ function hoistFunctionDeclaration(func: FunctionDeclaration): void { if (!hoistedFunctionDeclarations) { - hoistedFunctionDeclarations = []; + hoistedFunctionDeclarations = [func]; + } + else { + hoistedFunctionDeclarations.push(func); } - - hoistedFunctionDeclarations.push(func); } /** @@ -291,7 +301,6 @@ namespace ts { lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedVariableDeclarations; lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedFunctionDeclarations; lexicalEnvironmentStackOffset++; - hoistedVariableDeclarations = undefined; hoistedFunctionDeclarations = undefined; } @@ -303,15 +312,12 @@ namespace ts { function endLexicalEnvironment(): Statement[] { let statements: Statement[]; if (hoistedVariableDeclarations || hoistedFunctionDeclarations) { - statements = []; if (hoistedFunctionDeclarations) { - for (const declaration of hoistedFunctionDeclarations) { - statements.push(declaration); - } + statements = [...hoistedFunctionDeclarations]; } if (hoistedVariableDeclarations) { - statements.push( + statements = append(statements, createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList(hoistedVariableDeclarations) @@ -390,8 +396,9 @@ namespace ts { * Makes an array from an ArrayLike. */ function arrayOf(arrayLike: ArrayLike) { - const array: T[] = []; - for (let i = 0; i < arrayLike.length; i++) { + const length = arrayLike.length; + const array: T[] = new Array(length); + for (let i = 0; i < length; i++) { array[i] = arrayLike[i]; } return array; diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 38e7a639b1d..616457e8b32 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -20,7 +20,7 @@ namespace ts { const resolver = context.getEmitResolver(); const compilerOptions = context.getCompilerOptions(); - const languageVersion = getLanguageVersion(compilerOptions); + const languageVersion = getEmitScriptTarget(compilerOptions); const decoratedClassAliases: Map = {}; const currentDecoratedClassAliases: Map = {}; const previousExpressionSubstitution = context.expressionSubstitution; @@ -755,8 +755,8 @@ namespace ts { function findInitialSuperCall(ctor: ConstructorDeclaration): ExpressionStatement { if (ctor.body) { const statements = ctor.body.statements; - const statement = statements.length ? statements[0] : undefined; - if (statement.kind === SyntaxKind.ExpressionStatement) { + const statement = firstOrUndefined(statements); + if (statement && statement.kind === SyntaxKind.ExpressionStatement) { const expression = (statement).expression; if (expression.kind === SyntaxKind.CallExpression) { if ((expression).expression.kind === SyntaxKind.SuperKeyword) { @@ -1853,7 +1853,7 @@ namespace ts { const visited = visitEachChild(body, visitor, context); const declarations = endLexicalEnvironment(); currentScope = savedCurrentScope; - return mergeFunctionBodyLexicalEnvironment(visited, declarations, visited !== body); + return mergeFunctionBodyLexicalEnvironment(visited, declarations); } function transformConciseBody(node: ArrowFunction): ConciseBody { @@ -1872,7 +1872,7 @@ namespace ts { startLexicalEnvironment(); const visited: Expression | Block = visitNode(body, visitor, isConciseBody); const declarations = endLexicalEnvironment(); - const merged = mergeConciseBodyLexicalEnvironment(visited, declarations, visited !== body); + const merged = mergeConciseBodyLexicalEnvironment(visited, declarations); if (forceBlockFunctionBody && !isBlock(merged)) { return createBlock([ createReturn(merged) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2ba3e4fa118..847b00a1c97 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2797,7 +2797,7 @@ namespace ts { EmitSuperHelper = 1 << 2, // Emit the basic _super helper for async methods. EmitAdvancedSuperHelper = 1 << 3, // Emit the advanced _super helper for async methods. UMDDefine = 1 << 4, // This node should be replaced with the UMD define helper. - NoLexicalEnvironment = 1 << 5, // A new LexicalEnvironment should *not* be introduced when emitting this node. + NoLexicalEnvironment = 1 << 5, // A new LexicalEnvironment should *not* be introduced when emitting this node, this is primarily used when printing a SystemJS module. SingleLine = 1 << 6, // The contents of this node should be emit on a single line. MultiLine = 1 << 7, // The contents of this node should be emit on multiple lines. AdviseOnEmitNode = 1 << 8, // The node printer should invoke the onBeforeEmitNode and onAfterEmitNode callbacks when printing this node. @@ -2824,13 +2824,53 @@ namespace ts { getGeneratedNameForNode(node: Node): Identifier; nodeHasGeneratedName(node: Node): boolean; makeUniqueName(baseName: string): Identifier; - enableExpressionSubstitution(kind: SyntaxKind): void; - isExpressionSubstitutionEnabled(node: Node): boolean; + + /** + * Hook used by transformers to substitute non-expression identifiers + * just before theyare emitted by the pretty printer. + */ identifierSubstitution?: (node: Identifier) => Identifier; + + /** + * Enables expression substitutions in the pretty printer for + * the provided SyntaxKind. + */ + enableExpressionSubstitution(kind: SyntaxKind): void; + + /** + * Determines whether expression substitutions are enabled for the + * provided node. + */ + isExpressionSubstitutionEnabled(node: Node): boolean; + + /** + * Hook used by transformers to substitute expressions just before they + * are emitted by the pretty printer. + */ expressionSubstitution?: (node: Expression) => Expression; + + /** + * Enables before/after emit notifications in the pretty printer for + * the provided SyntaxKind. + */ enableEmitNotification(kind: SyntaxKind): void; + + /** + * Determines whether before/after emit notifications should be raised + * in the pretty printer when it emits a node. + */ isEmitNotificationEnabled(node: Node): boolean; + + /** + * Hook used to notify transformers immediately before the pretty printer + * emits a node. + */ onBeforeEmitNode?: (node: Node) => void; + + /** + * Hook used to notify transformers immediately after the pretty printer + * emits a node. + */ onAfterEmitNode?: (node: Node) => void; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 563921e7e9b..a5a0a05a0be 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -103,22 +103,6 @@ namespace ts { return true; } - export function getLanguageVersion(compilerOptions: CompilerOptions) { - return compilerOptions.target || ScriptTarget.ES3; - } - - export function getModuleKind(compilerOptions: CompilerOptions) { - if (compilerOptions.module) { - return compilerOptions.module; - } - - if (getLanguageVersion(compilerOptions) === ScriptTarget.ES6) { - return ModuleKind.ES6; - } - - return ModuleKind.None; - } - export function hasResolvedModule(sourceFile: SourceFile, moduleNameText: string): boolean { return sourceFile.resolvedModules && hasProperty(sourceFile.resolvedModules, moduleNameText); } @@ -1369,17 +1353,7 @@ namespace ts { return false; } - export function isLiteralKind(kind: SyntaxKind): boolean { - return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken; - } - export function isTextualLiteralKind(kind: SyntaxKind): boolean { - return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral; - } - - export function isTemplateLiteralKind(kind: SyntaxKind): boolean { - return SyntaxKind.FirstTemplateToken <= kind && kind <= SyntaxKind.LastTemplateToken; - } export function isNodeDescendentOf(node: Node, ancestor: Node): boolean { while (node) { @@ -1648,8 +1622,17 @@ namespace ts { return node; } - export function nodeStartsNewLexicalEnvironment(n: Node): boolean { - return isFunctionLike(n) || n.kind === SyntaxKind.ModuleDeclaration || n.kind === SyntaxKind.SourceFile; + export function nodeStartsNewLexicalEnvironment(node: Node): boolean { + const kind = node.kind; + return kind === SyntaxKind.Constructor + || kind === SyntaxKind.FunctionExpression + || kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.ArrowFunction + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor + || kind === SyntaxKind.ModuleDeclaration + || kind === SyntaxKind.SourceFile; } /** @@ -2813,23 +2796,111 @@ namespace ts { // All node tests in the following list should *not* reference parent pointers so that // they may be used with transformations. - export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { - return node.kind === SyntaxKind.PropertyAccessExpression; + // Node Arrays + + export function isNodeArray(array: T[]): array is NodeArray { + return (>array).arrayKind === ArrayKind.NodeArray; } - export function isElementAccessExpression(node: Node): node is ElementAccessExpression { - return node.kind === SyntaxKind.ElementAccessExpression; + export function isModifiersArray(array: Modifier[]): array is ModifiersArray { + return (array).arrayKind === ArrayKind.ModifiersArray; } - function isBindingPatternKind(kind: SyntaxKind) { - return kind === SyntaxKind.ArrayBindingPattern - || kind === SyntaxKind.ObjectBindingPattern; + // Literals + + export function isLiteralKind(kind: SyntaxKind): boolean { + return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken; } - export function isBindingPattern(node: Node): node is BindingPattern { - return node && isBindingPatternKind(node.kind); + export function isTextualLiteralKind(kind: SyntaxKind): boolean { + return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral; } + export function isLiteralExpression(node: Node): node is LiteralExpression { + return isLiteralKind(node.kind); + } + + // Pseudo-literals + + export function isTemplateLiteralKind(kind: SyntaxKind): boolean { + return SyntaxKind.FirstTemplateToken <= kind && kind <= SyntaxKind.LastTemplateToken; + } + + function isTemplateLiteralFragmentKind(kind: SyntaxKind) { + return kind === SyntaxKind.TemplateHead + || kind === SyntaxKind.TemplateMiddle + || kind === SyntaxKind.TemplateTail; + } + + export function isTemplateLiteralFragment(node: Node): node is TemplateLiteralFragment { + return isTemplateLiteralFragmentKind(node.kind); + } + + // Identifiers + + export function isIdentifier(node: Node): node is Identifier { + return node.kind === SyntaxKind.Identifier; + } + + // Keywords + + export function isModifier(node: Node): node is Modifier { + return isModifierKind(node.kind); + } + + // Names + + export function isQualifiedName(node: Node): node is QualifiedName { + return node.kind === SyntaxKind.QualifiedName; + } + + export function isComputedPropertyName(node: Node): node is ComputedPropertyName { + return node.kind === SyntaxKind.ComputedPropertyName; + } + + export function isEntityName(node: Node): node is EntityName { + const kind = node.kind; + return kind === SyntaxKind.QualifiedName + || kind === SyntaxKind.Identifier; + } + + export function isPropertyName(node: Node): node is PropertyName { + const kind = node.kind; + return kind === SyntaxKind.Identifier + || kind === SyntaxKind.StringLiteral + || kind === SyntaxKind.NumericLiteral + || kind === SyntaxKind.ComputedPropertyName; + } + + export function isModuleName(node: Node): node is ModuleName { + const kind = node.kind; + return kind === SyntaxKind.Identifier + || kind === SyntaxKind.StringLiteral; + } + + export function isBindingName(node: Node): node is BindingName { + const kind = node.kind; + return kind === SyntaxKind.Identifier + || kind === SyntaxKind.ObjectBindingPattern + || kind === SyntaxKind.ArrayBindingPattern; + } + + // Signature elements + + export function isTypeParameter(node: Node): node is TypeParameterDeclaration { + return node.kind === SyntaxKind.TypeParameter; + } + + export function isParameter(node: Node): node is ParameterDeclaration { + return node.kind === SyntaxKind.Parameter; + } + + export function isDecorator(node: Node): node is Decorator { + return node.kind === SyntaxKind.Decorator; + } + + // Type members + export function isClassElement(node: Node): node is ClassElement { const kind = node.kind; return kind === SyntaxKind.Constructor @@ -2840,29 +2911,62 @@ namespace ts { || kind === SyntaxKind.IndexSignature; } - export function isQualifiedName(node: Node): node is QualifiedName { - return node.kind === SyntaxKind.QualifiedName; + export function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { + const kind = node.kind; + return kind === SyntaxKind.PropertyAssignment + || kind === SyntaxKind.ShorthandPropertyAssignment + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor + || kind === SyntaxKind.MissingDeclaration; } - export function isLiteralExpression(node: Node): node is LiteralExpression { - return isLiteralKind(node.kind); + // Type + + function isTypeNodeKind(kind: SyntaxKind) { + return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) + || kind === SyntaxKind.AnyKeyword + || kind === SyntaxKind.NumberKeyword + || kind === SyntaxKind.BooleanKeyword + || kind === SyntaxKind.StringKeyword + || kind === SyntaxKind.SymbolKeyword + || kind === SyntaxKind.VoidKeyword + || kind === SyntaxKind.ExpressionWithTypeArguments; } - function isEntityNameKind(kind: SyntaxKind) { - return kind === SyntaxKind.QualifiedName - || kind === SyntaxKind.Identifier; + /** + * Node test that determines whether a node is a valid type node. + * This differs from the `isPartOfTypeNode` function which determines whether a node is *part* + * of a TypeNode. + */ + export function isTypeNode(node: Node): node is TypeNode { + return isTypeNodeKind(node.kind); } - export function isEntityName(node: Node): node is EntityName { - return isEntityNameKind(node.kind); + // Binding patterns + + export function isBindingPattern(node: Node): node is BindingPattern { + if (node) { + const kind = node.kind; + return kind === SyntaxKind.ArrayBindingPattern + || kind === SyntaxKind.ObjectBindingPattern; + } + + return false; } - export function isIdentifier(node: Node): node is Identifier { - return node.kind === SyntaxKind.Identifier; + export function isBindingElement(node: Node): node is BindingElement { + return node.kind === SyntaxKind.BindingElement; } - export function isComputedPropertyName(node: Node): node is ComputedPropertyName { - return node.kind === SyntaxKind.ComputedPropertyName; + // Expression + + export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { + return node.kind === SyntaxKind.PropertyAccessExpression; + } + + export function isElementAccessExpression(node: Node): node is ElementAccessExpression { + return node.kind === SyntaxKind.ElementAccessExpression; } export function isBinaryExpression(node: Node): node is BinaryExpression { @@ -2873,8 +2977,18 @@ namespace ts { return node.kind === SyntaxKind.ConditionalExpression; } - export function isShortHandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { - return node.kind === SyntaxKind.ShorthandPropertyAssignment; + export function isCallExpression(node: Node): node is CallExpression { + return node.kind === SyntaxKind.CallExpression; + } + + export function isTemplate(node: Node): node is Template { + const kind = node.kind; + return kind === SyntaxKind.TemplateExpression + || kind === SyntaxKind.NoSubstitutionTemplateLiteral; + } + + export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { + return node.kind === SyntaxKind.ExpressionWithTypeArguments; } function isLeftHandSideExpressionKind(kind: SyntaxKind) { @@ -2901,7 +3015,7 @@ namespace ts { || kind === SyntaxKind.ThisKeyword || kind === SyntaxKind.TrueKeyword || kind === SyntaxKind.SuperKeyword; - } + } export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { return isLeftHandSideExpressionKind(node.kind); @@ -2937,32 +3051,70 @@ namespace ts { return isExpressionKind(node.kind); } - export function isDecorator(node: Node): node is Decorator { - return node.kind === SyntaxKind.Decorator; + // Misc + + export function isTemplateSpan(node: Node): node is TemplateSpan { + return node.kind === SyntaxKind.TemplateSpan; } - export function isModifier(node: Node): node is Modifier { - return isModifierKind(node.kind); + // Element + + export function isBlock(node: Node): node is Block { + return node.kind === SyntaxKind.Block; } - function isTypeNodeKind(kind: SyntaxKind) { - return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) - || kind === SyntaxKind.AnyKeyword - || kind === SyntaxKind.NumberKeyword - || kind === SyntaxKind.BooleanKeyword - || kind === SyntaxKind.StringKeyword - || kind === SyntaxKind.SymbolKeyword - || kind === SyntaxKind.VoidKeyword - || kind === SyntaxKind.ExpressionWithTypeArguments; + export function isConciseBody(node: Node): node is ConciseBody { + return isBlock(node) + || isExpression(node); } - /** - * Node test that determines whether a node is a valid type node. - * This differs from the `isPartOfTypeNode` function which determines whether a node is *part* - * of a TypeNode. - */ - export function isTypeNode(node: Node): node is TypeNode { - return isTypeNodeKind(node.kind); + export function isFunctionBody(node: Node): node is FunctionBody { + return isBlock(node); + } + + export function isForInitializer(node: Node): node is ForInitializer { + return isVariableDeclarationList(node) + || isExpression(node); + } + + export function isVariableDeclaration(node: Node): node is VariableDeclaration { + return node.kind === SyntaxKind.VariableDeclaration; + } + + export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { + return node.kind === SyntaxKind.VariableDeclarationList; + } + + export function isCaseBlock(node: Node): node is CaseBlock { + return node.kind === SyntaxKind.CaseBlock; + } + + export function isModuleBody(node: Node): node is ModuleBody { + const kind = node.kind; + return kind === SyntaxKind.ModuleBlock + || kind === SyntaxKind.ModuleDeclaration; + } + + export function isImportClause(node: Node): node is ImportClause { + return node.kind === SyntaxKind.ImportClause; + } + + export function isNamedImportBindings(node: Node): node is NamedImportBindings { + const kind = node.kind; + return kind === SyntaxKind.NamedImports + || kind === SyntaxKind.NamespaceImport; + } + + export function isImportSpecifier(node: Node): node is ImportSpecifier { + return node.kind === SyntaxKind.ImportSpecifier; + } + + export function isNamedExports(node: Node): node is NamedExports { + return node.kind === SyntaxKind.NamedExports; + } + + export function isExportSpecifier(node: Node): node is ExportSpecifier { + return node.kind === SyntaxKind.ExportSpecifier; } function isDeclarationKind(kind: SyntaxKind) { @@ -2996,10 +3148,6 @@ namespace ts { || kind === SyntaxKind.VariableDeclaration; } - export function isDeclaration(node: Node): node is Declaration { - return isDeclarationKind(node.kind); - } - function isDeclarationStatementKind(kind: SyntaxKind) { return kind === SyntaxKind.FunctionDeclaration || kind === SyntaxKind.MissingDeclaration @@ -3013,10 +3161,6 @@ namespace ts { || kind === SyntaxKind.ExportAssignment; } - export function isDeclarationStatement(node: Node): node is DeclarationStatement { - return isDeclarationStatementKind(node.kind); - } - function isStatementKindButNotDeclarationKind(kind: SyntaxKind) { return kind === SyntaxKind.BreakStatement || kind === SyntaxKind.ContinueStatement @@ -3038,6 +3182,14 @@ namespace ts { || kind === SyntaxKind.WithStatement; } + export function isDeclaration(node: Node): node is Declaration { + return isDeclarationKind(node.kind); + } + + export function isDeclarationStatement(node: Node): node is DeclarationStatement { + return isDeclarationStatementKind(node.kind); + } + /** * Determines whether the node is a statement that is not also a declaration */ @@ -3045,171 +3197,23 @@ namespace ts { return isStatementKindButNotDeclarationKind(node.kind); } - function isStatementKind(kind: SyntaxKind) { - return isStatementKindButNotDeclarationKind(kind) - || isDeclarationStatementKind(kind); - } - export function isStatement(node: Node): node is Statement { - return isStatementKind(node.kind); + const kind = node.kind; + return isStatementKindButNotDeclarationKind(kind) + || isDeclarationStatementKind(kind) + || kind === SyntaxKind.Block; } - function isPropertyNameKind(kind: SyntaxKind) { - return kind === SyntaxKind.Identifier - || kind === SyntaxKind.StringLiteral - || kind === SyntaxKind.NumericLiteral - || kind === SyntaxKind.ComputedPropertyName; - } - - export function isPropertyName(node: Node): node is PropertyName { - return isPropertyNameKind(node.kind); - } - - function isConciseBodyKind(kind: SyntaxKind) { - return kind === SyntaxKind.Block - || isExpressionKind(kind); - } - - export function isConciseBody(node: Node): node is ConciseBody { - return isConciseBodyKind(node.kind); - } - - export function isTypeParameter(node: Node): node is TypeParameterDeclaration { - return node.kind === SyntaxKind.TypeParameter; - } - - export function isParameter(node: Node): node is ParameterDeclaration { - return node.kind === SyntaxKind.Parameter; - } - - export function isBindingElement(node: Node): node is BindingElement { - return node.kind === SyntaxKind.BindingElement; - } - - function isObjectLiteralElementKind(kind: SyntaxKind) { - return kind === SyntaxKind.PropertyAssignment - || kind === SyntaxKind.ShorthandPropertyAssignment - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor - || kind === SyntaxKind.MissingDeclaration; - } - - export function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { - return isObjectLiteralElementKind(node.kind); - } - - function isTemplateKind(kind: SyntaxKind) { - return kind === SyntaxKind.TemplateExpression - || kind === SyntaxKind.NoSubstitutionTemplateLiteral; - } - - export function isTemplate(node: Node): node is Template { - return isTemplateKind(node.kind); - } - - function isTemplateLiteralFragmentKind(kind: SyntaxKind) { - return kind === SyntaxKind.TemplateHead - || kind === SyntaxKind.TemplateMiddle - || kind === SyntaxKind.TemplateTail; - } - - export function isTemplateLiteralFragment(node: Node): node is TemplateLiteralFragment { - return isTemplateLiteralFragmentKind(node.kind); - } - - export function isTemplateSpan(node: Node): node is TemplateSpan { - return node.kind === SyntaxKind.TemplateSpan; - } - - export function isHeritageClause(node: Node): node is HeritageClause { - return node.kind === SyntaxKind.HeritageClause; - } - - export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { - return node.kind === SyntaxKind.VariableDeclarationList; - } - - function isForInitializerKind(kind: SyntaxKind) { - return kind === SyntaxKind.VariableDeclarationList - || isExpressionKind(kind); - } - - export function isForInitializer(node: Node): node is ForInitializer { - return isForInitializerKind(node.kind); - } - - export function isCaseBlock(node: Node): node is CaseBlock { - return node.kind === SyntaxKind.CaseBlock; - } - - export function isBlock(node: Node): node is Block { - return node.kind === SyntaxKind.Block; - } - - export function isCatchClause(node: Node): node is CatchClause { - return node.kind === SyntaxKind.CatchClause; - } - - export function isVariableDeclaration(node: Node): node is VariableDeclaration { - return node.kind === SyntaxKind.VariableDeclaration; - } - - export function isEnumMember(node: Node): node is EnumMember { - return node.kind === SyntaxKind.EnumMember; - } - - function isModuleNameKind(kind: SyntaxKind) { - return kind === SyntaxKind.Identifier - || kind === SyntaxKind.StringLiteral; - } - - export function isModuleName(node: Node): node is ModuleName { - return isModuleNameKind(node.kind); - } - - function isCaseOrDefaultClauseKind(kind: SyntaxKind) { - return kind === SyntaxKind.CaseClause - || kind === SyntaxKind.DefaultClause; - } - - export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { - return isCaseOrDefaultClauseKind(node.kind); - } - - function isModuleReferenceKind(kind: SyntaxKind) { - return kind === SyntaxKind.ExternalModuleReference - || isEntityNameKind(kind); - } + // Module references export function isModuleReference(node: Node): node is ModuleReference { - return isModuleReferenceKind(node.kind); + const kind = node.kind; + return kind === SyntaxKind.ExternalModuleReference + || kind === SyntaxKind.QualifiedName + || kind === SyntaxKind.Identifier; } - export function isImportClause(node: Node): node is ImportClause { - return node.kind === SyntaxKind.ImportClause; - } - - function isNamedImportBindingsKind(kind: SyntaxKind) { - return kind === SyntaxKind.NamedImports - || kind === SyntaxKind.NamespaceImport; - } - - export function isNamedImportBindings(node: Node): node is NamedImportBindings { - return isNamedImportBindingsKind(node.kind); - } - - export function isImportSpecifier(node: Node): node is ImportSpecifier { - return node.kind === SyntaxKind.ImportSpecifier; - } - - export function isNamedExports(node: Node): node is NamedExports { - return node.kind === SyntaxKind.NamedExports; - } - - export function isExportSpecifier(node: Node): node is ExportSpecifier { - return node.kind === SyntaxKind.ExportSpecifier; - } + // JSX export function isJsxOpeningElement(node: Node): node is JsxOpeningElement { return node.kind === SyntaxKind.JsxOpeningElement; @@ -3219,59 +3223,56 @@ namespace ts { return node.kind === SyntaxKind.JsxClosingElement; } - function isJsxChildKind(kind: SyntaxKind) { + export function isJsxChild(node: Node): node is JsxChild { + const kind = node.kind; return kind === SyntaxKind.JsxElement || kind === SyntaxKind.JsxExpression || kind === SyntaxKind.JsxSelfClosingElement || kind === SyntaxKind.JsxText; } - export function isJsxChild(node: Node): node is JsxChild { - return isJsxChildKind(node.kind); - } - - function isJsxAttributeLikeKind(kind: SyntaxKind) { + export function isJsxAttributeLike(node: Node): node is JsxAttributeLike { + const kind = node.kind; return kind === SyntaxKind.JsxAttribute || kind === SyntaxKind.JsxSpreadAttribute; } - export function isJsxAttributeLike(node: Node): node is JsxAttributeLike { - return isJsxAttributeLikeKind(node.kind); + // Clauses + + export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { + const kind = node.kind; + return kind === SyntaxKind.CaseClause + || kind === SyntaxKind.DefaultClause; } - export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { - return node.kind === SyntaxKind.ExpressionWithTypeArguments; + export function isHeritageClause(node: Node): node is HeritageClause { + return node.kind === SyntaxKind.HeritageClause; } - function isModuleBodyKind(kind: SyntaxKind) { - return kind === SyntaxKind.ModuleBlock - || kind === SyntaxKind.ModuleDeclaration; + export function isCatchClause(node: Node): node is CatchClause { + return node.kind === SyntaxKind.CatchClause; } - export function isModuleBody(node: Node): node is ModuleBody { - return isModuleBodyKind(node.kind); + + // Property assignments + + export function isShortHandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { + const kind = node.kind; + return kind === SyntaxKind.ShorthandPropertyAssignment; } - function isBindingNameKind(kind: SyntaxKind) { - return kind === SyntaxKind.Identifier - || isBindingPatternKind(kind); + // Enum + + export function isEnumMember(node: Node): node is EnumMember { + return node.kind === SyntaxKind.EnumMember; } - export function isBindingName(node: Node): node is BindingName { - return isBindingNameKind(node.kind); - } + + // Synthesized export function isNodeArrayNode(node: Node): node is NodeArrayNode { return node.kind === SyntaxKind.NodeArrayNode; } - - export function isNodeArray(array: T[]): array is NodeArray { - return (>array).arrayKind === ArrayKind.ModifiersArray; - } - - export function isModifiersArray(array: Modifier[]): array is ModifiersArray { - return (array).arrayKind === ArrayKind.ModifiersArray; - } } namespace ts { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index ee68fce3a6a..0ddcc770a9f 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -471,10 +471,10 @@ namespace ts { * @param node The Node to visit. * @param visitor The callback used to visit the Node. * @param test A callback to execute to verify the Node is valid. - * @param lift A callback to execute to lift a NodeArrayNode into a valid Node. - * @param optional A value indicating whether the Node is optional. + * @param optional An optional value indicating whether the Node is itself optional. + * @param lift An optional callback to execute to lift a NodeArrayNode into a valid Node. */ - export function visitNode(node: T, visitor: (node: Node) => Node, test?: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { + export function visitNode(node: T, visitor: (node: Node) => Node, test: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { if (node === undefined) { return undefined; } @@ -502,53 +502,53 @@ namespace ts { * @param nodes The NodeArray to visit. * @param visitor The callback used to visit a Node. * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. */ - export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test?: (node: Node) => boolean, start?: number, count?: number): TArray; - export function visitNodes(nodes: T[], visitor: (node: T) => U): NodeArray; - export function visitNodes(nodes: T[], visitor: (node: Node) => Node, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; - export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test?: (node: Node) => boolean, start?: number, count?: number): TArray { + export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test: (node: Node) => boolean, start?: number, count?: number): TArray { if (nodes === undefined) { return undefined; } - const len = nodes.length; - start = start !== undefined ? Math.max(start, 0) : 0; - count = count !== undefined ? Math.min(count, len - start) : len - start; - let updated: T[]; - if (start > 0 || count < len) { + + // Ensure start and count have valid values + const length = nodes.length; + if (start === undefined || start < 0) { + start = 0; + } + + if (count === undefined || count > length - start) { + count = length - start; + } + + // If we are not visiting all of the original nodes, we must always create a new array. + if (start > 0 || count < length) { updated = []; } + // Visit each original node. for (let i = 0; i < count; i++) { const node = nodes[i + start]; - if (node === undefined) { - continue; - } - - const visited = >visitor(node); + const visited = node && >visitor(node); if (updated !== undefined || visited === undefined || visited !== node) { if (updated === undefined) { + // Ensure we have a copy of `nodes`, up to the current index. updated = nodes.slice(0, i); } - if (visited === undefined) { - continue; - } - if (visited !== node) { aggregateTransformFlags(visited); - visited.original = node; } addNode(updated, visited, test); } } - if (updated) { + if (updated !== undefined) { return (isModifiersArray(nodes) - ? createModifiersArray(updated, /*location*/ nodes) - : createNodeArray(updated, /*location*/ nodes)); + ? createModifiersArray(updated, nodes) + : createNodeArray(updated, nodes)); } return nodes; @@ -559,36 +559,40 @@ namespace ts { * * @param node The Node whose children will be visited. * @param visitor The callback used to visit each child. - * @param environment An optional lexical environment context for the visitor. + * @param context A lexical environment context for the visitor. */ - export function visitEachChild(node: T, visitor: (node: Node) => Node, environment?: LexicalEnvironment): T { + export function visitEachChild(node: T, visitor: (node: Node) => Node, context: LexicalEnvironment): T; + export function visitEachChild(node: T & Map, visitor: (node: Node) => Node, context: LexicalEnvironment): T { if (node === undefined) { return undefined; } - const isNewLexicalEnvironment = environment !== undefined && nodeStartsNewLexicalEnvironment(node); + let updated: T & Map; + + // If this node starts a new lexical environment, start a new lexical environment on the context. + const isNewLexicalEnvironment = nodeStartsNewLexicalEnvironment(node); if (isNewLexicalEnvironment) { - environment.startLexicalEnvironment(); + context.startLexicalEnvironment(); } - let modifiers: NodeFlags; - let updated: T & Map; const edgeTraversalPath = nodeEdgeTraversalMap[node.kind]; if (edgeTraversalPath) { for (const edge of edgeTraversalPath) { - const value = (>node)[edge.name]; + const value = >node[edge.name]; if (value !== undefined) { const visited = visitEdge(edge, value, visitor); if (updated !== undefined || visited !== value) { if (updated === undefined) { - updated = cloneNode(node, /*location*/ undefined, node.flags & ~NodeFlags.Modifier, /*parent*/ undefined, /*original*/ node); + updated = cloneNode(node, /*location*/ node, node.flags & ~NodeFlags.Modifier, /*parent*/ undefined, /*original*/ node); } - updated[edge.name] = visited; - } - - if (visited && isArray(visited) && isModifiersArray(visited)) { - modifiers = visited.flags; + if (visited && isArray(visited) && isModifiersArray(visited)) { + updated[edge.name] = visited; + updated.flags |= visited.flags; + } + else { + updated[edge.name] = visited; + } } } } @@ -597,14 +601,11 @@ namespace ts { if (updated === undefined) { updated = node; } - else if (modifiers) { - updated.flags |= modifiers; - } if (isNewLexicalEnvironment) { - const declarations = environment.endLexicalEnvironment(); + const declarations = context.endLexicalEnvironment(); if (declarations !== undefined && declarations.length > 0) { - updated = mergeLexicalEnvironment(updated, declarations, /*nodeIsMutable*/ updated !== node); + updated = mergeLexicalEnvironment(updated, declarations); } } @@ -625,7 +626,7 @@ namespace ts { */ function visitEdge(edge: NodeEdge, value: Node | NodeArray, visitor: (node: Node) => Node) { return isArray(value) - ? visitNodes(>value, visitor, edge.test) + ? visitNodes(>value, visitor, edge.test, /*start*/ undefined, /*count*/ undefined) : visitNode(value, visitor, edge.test, edge.optional, edge.lift); } @@ -665,72 +666,105 @@ namespace ts { /** * Merge generated declarations of a lexical environment. + * + * @param node The source node. + * @param declarations The generated lexical declarations. */ - function mergeLexicalEnvironment(node: Node, declarations: Statement[], nodeIsMutable: boolean) { - const mutableNode = nodeIsMutable ? node : cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + function mergeLexicalEnvironment(node: Node, declarations: Statement[]): Node { switch (node.kind) { case SyntaxKind.SourceFile: - mergeSourceFileLexicalEnvironment(mutableNode, declarations); - break; + return mergeSourceFileLexicalEnvironment(node, declarations); case SyntaxKind.ModuleDeclaration: - mergeModuleDeclarationLexicalEnvironment(mutableNode, declarations); - break; + return mergeModuleDeclarationLexicalEnvironment(node, declarations); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.Constructor: - mergeFunctionLikeLexicalEnvironment(mutableNode, declarations); - break; - - case SyntaxKind.ModuleBlock: - case SyntaxKind.Block: - mergeBlockLexicalEnvironment(mutableNode, declarations); - break; + case SyntaxKind.ArrowFunction: + return mergeFunctionLikeLexicalEnvironment(node, declarations); } - return mutableNode; + Debug.fail("Node is not a valid lexical environment."); } /** * Merge generated declarations of a lexical environment into a SourceFile. + * + * @param node The SourceFile node. + * @param declarations The generated lexical declarations. */ - function mergeSourceFileLexicalEnvironment(node: SourceFile, declarations: Statement[]) { - node.statements = mergeStatements(node.statements, declarations); + export function mergeSourceFileLexicalEnvironment(node: SourceFile, declarations: Statement[]) { + if (declarations !== undefined && declarations.length) { + const mutableNode = cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + mutableNode.statements = mergeStatements(mutableNode.statements, declarations); + return mutableNode; + } + + return node; } /** * Merge generated declarations of a lexical environment into a ModuleDeclaration. + * + * @param node The ModuleDeclaration node. + * @param declarations The generated lexical declarations. */ - function mergeModuleDeclarationLexicalEnvironment(node: ModuleDeclaration, declarations: Statement[]) { + export function mergeModuleDeclarationLexicalEnvironment(node: ModuleDeclaration, declarations: Statement[]) { Debug.assert(node.body.kind === SyntaxKind.ModuleBlock); - node.body = mergeLexicalEnvironment(node.body, declarations, /*nodeIsMutable*/ false); + if (declarations !== undefined && declarations.length) { + const mutableNode = cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + mutableNode.body = mergeBlockLexicalEnvironment(node.body, declarations); + return mutableNode; + } + + return node; } /** * Merge generated declarations of a lexical environment into a FunctionLikeDeclaration. + * + * @param node The function-like node. + * @param declarations The generated lexical declarations. */ function mergeFunctionLikeLexicalEnvironment(node: FunctionLikeDeclaration, declarations: Statement[]) { Debug.assert(node.body !== undefined); - node.body = mergeConciseBodyLexicalEnvironment(node.body, declarations); + if (declarations !== undefined && declarations.length) { + const mutableNode = cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + mutableNode.body = mergeConciseBodyLexicalEnvironment(mutableNode.body, declarations); + return mutableNode; + } + + return node; } - export function mergeFunctionBodyLexicalEnvironment(body: FunctionBody, declarations: Statement[], nodeIsMutable?: boolean): FunctionBody { - if (declarations && declarations.length > 0) { - return mergeLexicalEnvironment(body, declarations, nodeIsMutable); + /** + * Merges generated lexical declarations into the FunctionBody of a non-arrow function-like declaration. + * + * @param node The ConciseBody of an arrow function. + * @param declarations The lexical declarations to merge. + */ + export function mergeFunctionBodyLexicalEnvironment(body: FunctionBody, declarations: Statement[]) { + if (declarations !== undefined && declarations.length > 0) { + return mergeBlockLexicalEnvironment(body, declarations); } return body; } - export function mergeConciseBodyLexicalEnvironment(body: ConciseBody, declarations: Statement[], nodeIsMutable?: boolean): ConciseBody { - if (declarations && declarations.length > 0) { + /** + * Merges generated lexical declarations into the ConciseBody of an ArrowFunction. + * + * @param node The ConciseBody of an arrow function. + * @param declarations The lexical declarations to merge. + */ + export function mergeConciseBodyLexicalEnvironment(body: ConciseBody, declarations: Statement[]) { + if (declarations !== undefined && declarations.length > 0) { if (isBlock(body)) { - return mergeLexicalEnvironment(body, declarations, nodeIsMutable); + return mergeBlockLexicalEnvironment(body, declarations); } else { return createBlock([ @@ -739,20 +773,27 @@ namespace ts { ]); } } - else { - return body; - } + + return body; } /** * Merge generated declarations of a lexical environment into a FunctionBody or ModuleBlock. + * + * @param node The block into which to merge lexical declarations. + * @param declarations The lexical declarations to merge. */ - function mergeBlockLexicalEnvironment(node: FunctionBody | ModuleBlock, declarations: Statement[]) { - node.statements = mergeStatements(node.statements, declarations); + function mergeBlockLexicalEnvironment(node: T, declarations: Statement[]) { + const mutableNode = cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + mutableNode.statements = mergeStatements(node.statements, declarations); + return mutableNode; } /** * Merge generated declarations of a lexical environment into a NodeArray of Statement. + * + * @param statements The node array to concatentate with the supplied lexical declarations. + * @param declarations The lexical declarations to merge. */ function mergeStatements(statements: NodeArray, declarations: Statement[]) { return createNodeArray(concatenate(statements, declarations), /*location*/ statements);