From 6a1ccd8de449e23f4d2fdd4ece979ac8a616f5bd Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 8 Dec 2016 17:31:18 -0800 Subject: [PATCH 1/7] Adds support for new.target --- src/compiler/binder.ts | 1 + src/compiler/checker.ts | 64 +++ src/compiler/core.ts | 63 ++- src/compiler/diagnosticMessages.json | 16 + src/compiler/emitter.ts | 27 +- src/compiler/parser.ts | 15 +- src/compiler/transformers/es2015.ts | 420 +++++++++++++----- src/compiler/types.ts | 10 + src/compiler/utilities.ts | 16 + .../reference/invalidNewTarget.es5.errors.txt | 78 ++++ .../reference/invalidNewTarget.es5.js | 77 ++++ .../reference/invalidNewTarget.es6.errors.txt | 78 ++++ .../reference/invalidNewTarget.es6.js | 50 +++ tests/baselines/reference/newTarget.es5.js | 74 +++ .../baselines/reference/newTarget.es5.symbols | 63 +++ tests/baselines/reference/newTarget.es5.types | 95 ++++ tests/baselines/reference/newTarget.es6.js | 61 +++ .../baselines/reference/newTarget.es6.symbols | 63 +++ tests/baselines/reference/newTarget.es6.types | 95 ++++ .../es6/newTarget/invalidNewTarget.es5.ts | 25 ++ .../es6/newTarget/invalidNewTarget.es6.ts | 25 ++ .../es6/newTarget/newTarget.es5.ts | 32 ++ .../es6/newTarget/newTarget.es6.ts | 32 ++ 23 files changed, 1352 insertions(+), 128 deletions(-) create mode 100644 tests/baselines/reference/invalidNewTarget.es5.errors.txt create mode 100644 tests/baselines/reference/invalidNewTarget.es5.js create mode 100644 tests/baselines/reference/invalidNewTarget.es6.errors.txt create mode 100644 tests/baselines/reference/invalidNewTarget.es6.js create mode 100644 tests/baselines/reference/newTarget.es5.js create mode 100644 tests/baselines/reference/newTarget.es5.symbols create mode 100644 tests/baselines/reference/newTarget.es5.types create mode 100644 tests/baselines/reference/newTarget.es6.js create mode 100644 tests/baselines/reference/newTarget.es6.symbols create mode 100644 tests/baselines/reference/newTarget.es6.types create mode 100644 tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts create mode 100644 tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts create mode 100644 tests/cases/conformance/es6/newTarget/newTarget.es5.ts create mode 100644 tests/cases/conformance/es6/newTarget/newTarget.es6.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a6f5993f6c8..f9ebc9688a9 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3133,6 +3133,7 @@ namespace ts { case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.StaticKeyword: + case SyntaxKind.MetaProperty: // These nodes are ES6 syntax. transformFlags |= TransformFlags.AssertES2015; break; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cdef31ee400..39e673b2b57 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -241,6 +241,7 @@ namespace ts { const visitedFlowNodes: FlowNode[] = []; const visitedFlowTypes: FlowType[] = []; const potentialThisCollisions: Node[] = []; + const potentialNewTargetCollisions: Node[] = []; const awaitedTypeStack: number[] = []; const diagnostics = createDiagnosticCollection(); @@ -10196,6 +10197,7 @@ namespace ts { checkCollisionWithCapturedSuperVariable(node, node); checkCollisionWithCapturedThisVariable(node, node); + checkCollisionWithCapturedNewTargetVariable(node, node); checkNestedBlockScopedBinding(node, symbol); const type = getTypeOfSymbol(localOrExportSymbol); @@ -13720,6 +13722,24 @@ namespace ts { return getNonNullableType(checkExpression(node.expression)); } + function checkMetaProperty(node: MetaProperty) { + checkGrammarMetaProperty(node); + Debug.assert(node.keywordToken === SyntaxKind.NewKeyword && node.name.text === "target", "Unrecognized meta-property."); + const container = getNewTargetContainer(node); + if (!container) { + error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target"); + return unknownType; + } + else if (container.kind === SyntaxKind.Constructor) { + const symbol = getSymbolOfNode(container.parent); + return getTypeOfSymbol(symbol); + } + else { + const symbol = getSymbolOfNode(container); + return getTypeOfSymbol(symbol); + } + } + function getTypeOfParameter(symbol: Symbol) { const type = getTypeOfSymbol(symbol); if (strictNullChecks) { @@ -14128,6 +14148,7 @@ namespace ts { if (produceDiagnostics && node.kind !== SyntaxKind.MethodDeclaration) { checkCollisionWithCapturedSuperVariable(node, (node).name); checkCollisionWithCapturedThisVariable(node, (node).name); + checkCollisionWithCapturedNewTargetVariable(node, (node).name); } return type; @@ -15152,6 +15173,8 @@ namespace ts { return checkAssertion(node); case SyntaxKind.NonNullExpression: return checkNonNullAssertion(node); + case SyntaxKind.MetaProperty: + return checkMetaProperty(node); case SyntaxKind.DeleteExpression: return checkDeleteExpression(node); case SyntaxKind.VoidExpression: @@ -16552,6 +16575,7 @@ namespace ts { checkCollisionWithCapturedSuperVariable(node, node.name); checkCollisionWithCapturedThisVariable(node, node.name); + checkCollisionWithCapturedNewTargetVariable(node, node.name); checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); } @@ -16835,6 +16859,12 @@ namespace ts { } } + function checkCollisionWithCapturedNewTargetVariable(node: Node, name: Identifier): void { + if (needCollisionCheckForIdentifier(node, name, "_newTarget")) { + potentialNewTargetCollisions.push(node); + } + } + // this function will run after checking the source file so 'CaptureThis' is correct for all nodes function checkIfThisIsCapturedInEnclosingScope(node: Node): void { let current = node; @@ -16853,6 +16883,23 @@ namespace ts { } } + function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { + let current = node; + while (current) { + if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { + const isDeclaration = node.kind !== SyntaxKind.Identifier; + if (isDeclaration) { + error((node).name, Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference); + } + else { + error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); + } + return; + } + current = current.parent; + } + } + function checkCollisionWithCapturedSuperVariable(node: Node, name: Identifier) { if (!needCollisionCheckForIdentifier(node, name, "_super")) { return; @@ -17148,6 +17195,7 @@ namespace ts { } checkCollisionWithCapturedSuperVariable(node, node.name); checkCollisionWithCapturedThisVariable(node, node.name); + checkCollisionWithCapturedNewTargetVariable(node, node.name); checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); } @@ -17984,6 +18032,7 @@ namespace ts { if (node.name) { checkTypeNameIsReserved(node.name, Diagnostics.Class_name_cannot_be_0); checkCollisionWithCapturedThisVariable(node, node.name); + checkCollisionWithCapturedNewTargetVariable(node, node.name); checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); } @@ -18518,6 +18567,7 @@ namespace ts { checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0); checkCollisionWithCapturedThisVariable(node, node.name); + checkCollisionWithCapturedNewTargetVariable(node, node.name); checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); checkExportsOnMergedDeclarations(node); @@ -19223,6 +19273,7 @@ namespace ts { checkGrammarSourceFile(node); potentialThisCollisions.length = 0; + potentialNewTargetCollisions.length = 0; deferredNodes = []; deferredUnusedIdentifierNodes = produceDiagnostics && noUnusedIdentifiers ? [] : undefined; @@ -19251,6 +19302,11 @@ namespace ts { potentialThisCollisions.length = 0; } + if (potentialNewTargetCollisions.length) { + forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope) + potentialNewTargetCollisions.length = 0; + } + links.flags |= NodeCheckFlags.TypeChecked; } } @@ -21581,6 +21637,14 @@ namespace ts { } } + function checkGrammarMetaProperty(node: MetaProperty) { + if (node.keywordToken === SyntaxKind.NewKeyword) { + if (node.name.text !== "target") { + return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_0, node.name.text, tokenToString(node.keywordToken), "target"); + } + } + } + function hasParseDiagnostics(sourceFile: SourceFile): boolean { return sourceFile.parseDiagnostics.length > 0; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 89057dd2939..70674d400c3 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -568,14 +568,35 @@ namespace ts { * is created if `value` was appended. * @param value The value to append to the array. If `value` is `undefined`, nothing is * appended. + * @param copyOnWrite Indicates whether to return a fresh array rather than modify the + * existing array. */ - export function append(to: T[] | undefined, value: T | undefined): T[] | undefined { + export function append(to: T[] | undefined, value: T | undefined, copyOnWrite?: boolean): T[] | undefined { if (value === undefined) return to; if (to === undefined) return [value]; + if (copyOnWrite) return [...to, value]; to.push(value); return to; } + /** + * Prepends a value to an array, returning the array. + * + * @param to The array to which `value` is to be prepended. If `to` is `undefined`, a new array + * is created if `value` was prepended. + * @param value The value to prepend to the array. If `value` is `undefined`, nothing is + * prepended. + * @param copyOnWrite Indicates whether to return a fresh array rather than modify the + * existing array. + */ + export function prepend(to: T[] | undefined, value: T | undefined, copyOnWrite?: boolean): T[] | undefined { + if (value === undefined) return to; + if (to === undefined) return [value]; + if (copyOnWrite) return [value, ...to]; + to.unshift(value); + return to; + } + /** * Appends a range of value to an array, returning the array. * @@ -583,11 +604,45 @@ namespace ts { * is created if `value` was appended. * @param from The values to append to the array. If `from` is `undefined`, nothing is * appended. If an element of `from` is `undefined`, that element is not appended. + * @param copyOnWrite Indicates whether to return a fresh array rather than modify the + * existing array. */ - export function addRange(to: T[] | undefined, from: T[] | undefined): T[] | undefined { + export function addRange(to: T[] | undefined, from: T[] | undefined, copyOnWrite?: boolean): T[] | undefined { if (from === undefined) return to; - for (const v of from) { - to = append(to, v); + from = filter(from, isDefined); + if (to === undefined) return from; + if (from.length > 0) { + if (copyOnWrite) { + return [...to, ...from]; + } + for (const v of from) { + to.push(v); + } + } + return to; + } + + /** + * Appends a range of value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param from The values to append to the array. If `from` is `undefined`, nothing is + * appended. If an element of `from` is `undefined`, that element is not appended. + * @param copyOnWrite Indicates whether to return a fresh array rather than modify the + * existing array. + */ + export function prependRange(to: T[] | undefined, from: T[] | undefined, copyOnWrite?: boolean): T[] | undefined { + if (from === undefined) return to; + from = filter(from, isDefined); + if (to === undefined) return from; + if (from.length > 0) { + if (copyOnWrite) { + return [...from, ...to]; + } + for (let i = from.length - 1; i >= 0; i--) { + to.unshift(from[i]); + } } return to; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b59921c2948..c063182ba6f 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1771,6 +1771,14 @@ "category": "Error", "code": 2542 }, + "Duplicate identifier '_newTarget'. Compiler uses variable declaration '_newTarget' to capture 'new.target' meta-property reference.": { + "category": "Error", + "code": 2543 + }, + "Expression resolves to variable declaration '_newTarget' that compiler uses to capture 'new.target' meta-property reference.": { + "category": "Error", + "code": 2544 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 @@ -3169,6 +3177,14 @@ "category": "Error", "code": 17011 }, + "'{0}' is not a valid meta-property for keyword '{1}'. Did you mean '{0}'?": { + "category": "Error", + "code": 17012 + }, + "Meta-property '{0}' is only allowed in the body of a function declaration, function expression, or constructor.": { + "category": "Error", + "code": 17013 + }, "Circularity detected while resolving configuration: {0}": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 90738d828be..8b383304352 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -660,6 +660,8 @@ namespace ts { return emitAsExpression(node); case SyntaxKind.NonNullExpression: return emitNonNullExpression(node); + case SyntaxKind.MetaProperty: + return emitMetaProperty(node); // JSX case SyntaxKind.JsxElement: @@ -1249,6 +1251,12 @@ namespace ts { write("!"); } + function emitMetaProperty(node: MetaProperty) { + writeToken(node.keywordToken, node.pos); + write("."); + emit(node.name); + } + // // Misc // @@ -2571,6 +2579,13 @@ namespace ts { return makeUniqueName("class"); } + function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration) { + if (isIdentifier(node.name)) { + return generateNameForNodeCached(node.name); + } + return makeTempVariableName(TempFlags.Auto); + } + /** * Generates a unique name from a node. * @@ -2592,6 +2607,10 @@ namespace ts { return generateNameForExportDefault(); case SyntaxKind.ClassExpression: return generateNameForClassExpression(); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return generateNameForMethodOrAccessor(node); default: return makeTempVariableName(TempFlags.Auto); } @@ -2642,6 +2661,11 @@ namespace ts { return node; } + function generateNameForNodeCached(node: Node) { + const nodeId = getNodeId(node); + return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = unescapeIdentifier(generateNameForNode(node))); + } + /** * Gets the generated identifier text from a generated identifier. * @@ -2652,8 +2676,7 @@ namespace ts { // Generated names generate unique names based on their original node // and are cached based on that node's id const node = getNodeForGeneratedName(name); - const nodeId = getNodeId(node); - return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = unescapeIdentifier(generateNameForNode(node))); + return generateNameForNodeCached(node); } else { // Auto, Loop, and Unique names are cached based on their unique diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4aefa160077..c3506d441cd 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -198,6 +198,8 @@ namespace ts { visitNode(cbNode, (node).type); case SyntaxKind.NonNullExpression: return visitNode(cbNode, (node).expression); + case SyntaxKind.MetaProperty: + return visitNode(cbNode, (node).name); case SyntaxKind.ConditionalExpression: return visitNode(cbNode, (node).condition) || visitNode(cbNode, (node).questionToken) || @@ -4329,15 +4331,22 @@ namespace ts { return isIdentifier() ? parseIdentifier() : undefined; } - function parseNewExpression(): NewExpression { - const node = createNode(SyntaxKind.NewExpression); + function parseNewExpression(): NewExpression | MetaProperty { + const fullStart = scanner.getStartPos(); parseExpected(SyntaxKind.NewKeyword); + if (parseOptional(SyntaxKind.DotToken)) { + const node = createNode(SyntaxKind.MetaProperty, fullStart); + node.keywordToken = SyntaxKind.NewKeyword; + node.name = parseIdentifierName(); + return finishNode(node); + } + + const node = createNode(SyntaxKind.NewExpression, fullStart); node.expression = parseMemberExpressionOrHigher(); node.typeArguments = tryParse(parseTypeArgumentsInExpression); if (node.typeArguments || token() === SyntaxKind.OpenParenToken) { node.arguments = parseArgumentList(); } - return finishNode(node); } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 094261f7002..644e52c33c6 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -164,22 +164,32 @@ namespace ts { type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[]) => Statement; - // Facts we track as we descend the tree - const enum AncestorFacts { + // Facts we track as we traverse the tree + const enum HierarchyFacts { None = 0, - Function = 1 << 0, // Enclosed in a non-arrow function - ArrowFunction = 1 << 1, // Enclosed in an arrow function - AsyncFunctionBody = 1 << 2, // Enclosed in an async function body - NonStaticClassElement = 1 << 3, // Enclosed in a non-static, non-async class element - CapturesThis = 1 << 4, // Enclosed in a function that captures the lexical 'this' (used in substitution) - ExportedVariableStatement = 1 << 5, // Enclosed in an exported variable statement in the current scope - TopLevel = 1 << 6, // Enclosing block-scoped container is a top-level container - Block = 1 << 7, // Enclosing block-scoped container is a Block - IterationStatement = 1 << 8, // Enclosed in an IterationStatement - IterationStatementBlock = 1 << 9, // Enclosing Block is enclosed in an IterationStatement - ForStatement = 1 << 10, // Enclosing block-scoped container is a ForStatement - ForInOrForOfStatement = 1 << 11, // Enclosing block-scoped container is a ForInStatement or ForOfStatement - ConstructorWithCapturedSuper = 1 << 12, // Enclosed in a constructor that captures 'this' for use with 'super' + + // Ancestor facts + Function = 1 << 0, // Enclosed in a non-arrow function + ArrowFunction = 1 << 1, // Enclosed in an arrow function + AsyncFunctionBody = 1 << 2, // Enclosed in an async function body + NonStaticClassElement = 1 << 3, // Enclosed in a non-static, non-async class element + CapturesThis = 1 << 4, // Enclosed in a function that captures the lexical 'this' (used in substitution) + ExportedVariableStatement = 1 << 5, // Enclosed in an exported variable statement in the current scope + TopLevel = 1 << 6, // Enclosing block-scoped container is a top-level container + Block = 1 << 7, // Enclosing block-scoped container is a Block + IterationStatement = 1 << 8, // Enclosed in an IterationStatement + IterationStatementBlock = 1 << 9, // Enclosing Block is enclosed in an IterationStatement + ForStatement = 1 << 10, // Enclosing block-scoped container is a ForStatement + ForInOrForOfStatement = 1 << 11, // Enclosing block-scoped container is a ForInStatement or ForOfStatement + ConstructorWithCapturedSuper = 1 << 12, // Enclosed in a constructor that captures 'this' for use with 'super' + ComputedPropertyName = 1 << 13, // Enclosed in a computed property name + AncestorFactsMask = (ComputedPropertyName << 1) - 1, + + // Subtree facts + NewTarget = 1 << 14, // Contains a 'new.target' meta-property + NewTargetInComputedPropertyName = 1 << 15, // Contains a 'new.target' meta-property in a computed property name. + + SubtreeFactsMask = ~AncestorFactsMask, // We are always in *some* kind of block scope, but only specific block-scope containers are // top-level or Blocks. @@ -192,11 +202,11 @@ namespace ts { // Functions, methods, and accessors are both new lexical scopes and new block scopes. FunctionIncludes = Function | TopLevel, - FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper, + FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper | ComputedPropertyName, // Arrow functions are lexically scoped to their container, but are new block scopes. ArrowFunctionIncludes = ArrowFunction | TopLevel, - ArrowFunctionExcludes = BlockScopeExcludes & ~TopLevel | ConstructorWithCapturedSuper, + ArrowFunctionExcludes = BlockScopeExcludes & ~TopLevel | ConstructorWithCapturedSuper | ComputedPropertyName, // Constructors are both new lexical scopes and new block scopes. Constructors are also // always considered non-static members of a class. @@ -221,6 +231,11 @@ namespace ts { // Blocks (other than function bodies) are new block scopes. BlockIncludes = Block, BlockExcludes = BlockScopeExcludes & ~Block, + + ComputedPropertyNameIncludes = ComputedPropertyName, + ComputedPropertyNameExcludes = None, + + PropagateNewTargetMask = NewTarget | NewTargetInComputedPropertyName, } export function transformES2015(context: TransformationContext) { @@ -239,7 +254,7 @@ namespace ts { let currentSourceFile: SourceFile; let currentText: string; - let ancestorFacts: AncestorFacts; + let hierarchyFacts: HierarchyFacts; /** * Used to track if we are emitting body of the converted loop @@ -268,49 +283,73 @@ namespace ts { currentSourceFile = undefined; currentText = undefined; - ancestorFacts = AncestorFacts.None; + hierarchyFacts = HierarchyFacts.None; return visited; } - function setAncestorFacts(excludeFacts: AncestorFacts, includeFacts: AncestorFacts, node?: Node, container?: Node) { + function getAncestorFacts() { + return hierarchyFacts & HierarchyFacts.AncestorFactsMask; + } + + function restoreAncestorFacts(facts: HierarchyFacts) { + hierarchyFacts = facts & HierarchyFacts.AncestorFactsMask | getSubtreeFacts(); + } + + function setAncestorFacts(excludeFacts: HierarchyFacts, includeFacts: HierarchyFacts, node?: Node, container?: Node) { if (node) { switch (node.kind) { case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: if (container && isClassLike(container) && !hasModifier(node, ModifierFlags.Static)) { - includeFacts |= AncestorFacts.NonStaticClassElement; + includeFacts |= HierarchyFacts.NonStaticClassElement; } break; case SyntaxKind.FunctionExpression: const emitFlags = getEmitFlags(node); if (emitFlags & EmitFlags.CapturesThis) { - includeFacts |= AncestorFacts.CapturesThis; + includeFacts |= HierarchyFacts.CapturesThis; } if (emitFlags & EmitFlags.AsyncFunctionBody) { - excludeFacts &= ~AncestorFacts.NonStaticClassElement; - includeFacts |= AncestorFacts.AsyncFunctionBody; + excludeFacts &= ~HierarchyFacts.NonStaticClassElement; + includeFacts |= HierarchyFacts.AsyncFunctionBody; } break; case SyntaxKind.FunctionDeclaration: if (getEmitFlags(node) & EmitFlags.CapturesThis) { - includeFacts |= AncestorFacts.CapturesThis; + includeFacts |= HierarchyFacts.CapturesThis; } break; case SyntaxKind.Block: - if (ancestorFacts & AncestorFacts.IterationStatement) { - includeFacts = includeFacts & ~AncestorFacts.Block | AncestorFacts.IterationStatementBlock; - excludeFacts |= AncestorFacts.Block; + if (hierarchyFacts & HierarchyFacts.IterationStatement) { + includeFacts = includeFacts & ~HierarchyFacts.Block | HierarchyFacts.IterationStatementBlock; + excludeFacts |= HierarchyFacts.Block; } break; } } - ancestorFacts = ancestorFacts & ~excludeFacts | includeFacts; + hierarchyFacts = hierarchyFacts & ~excludeFacts | includeFacts; + } + + function getSubtreeFacts() { + return hierarchyFacts & HierarchyFacts.SubtreeFactsMask; + } + + function resetSubtreeFacts() { + hierarchyFacts = getAncestorFacts(); + } + + function propagateHierarchyFacts(savedHierarchyFacts: HierarchyFacts, propagateSubtreeMask: HierarchyFacts, propagateSubtreeFacts: HierarchyFacts) { + const subtreeFacts = getSubtreeFacts(); + hierarchyFacts = savedHierarchyFacts; + if (subtreeFacts & propagateSubtreeMask) { + hierarchyFacts |= propagateSubtreeFacts & HierarchyFacts.SubtreeFactsMask; + } } function isReturnVoidStatementInConstructorWithCapturedSuper(node: Node): boolean { - return ancestorFacts & AncestorFacts.ConstructorWithCapturedSuper + return hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && node.kind === SyntaxKind.ReturnStatement && !(node).expression; } @@ -318,7 +357,7 @@ namespace ts { function shouldVisitNode(node: Node): boolean { return (node.transformFlags & TransformFlags.ContainsES2015) !== 0 || convertedLoopState !== undefined - || (ancestorFacts & AncestorFacts.ConstructorWithCapturedSuper && isStatement(node)) + || (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && isStatement(node)) || (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node)); } @@ -460,6 +499,9 @@ namespace ts { case SyntaxKind.ThisKeyword: return visitThisKeyword(node); + case SyntaxKind.MetaProperty: + return visitMetaProperty(node); + case SyntaxKind.MethodDeclaration: return visitMethodDeclaration(node); @@ -479,15 +521,17 @@ namespace ts { } function visitSourceFile(node: SourceFile): SourceFile { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.SourceFileExcludes, AncestorFacts.SourceFileIncludes); + const savedHierarchyFacts = hierarchyFacts; + setAncestorFacts(HierarchyFacts.SourceFileExcludes, HierarchyFacts.SourceFileIncludes); + const statements: Statement[] = []; startLexicalEnvironment(); const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ false, visitor); addCaptureThisForNodeIfNeeded(statements, node); addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); addRange(statements, endLexicalEnvironment()); - ancestorFacts = savedAncestorFacts; + + hierarchyFacts = savedHierarchyFacts; return updateSourceFileNode( node, createNodeArray(statements, node.statements) @@ -507,10 +551,10 @@ namespace ts { } function visitCaseBlock(node: CaseBlock): CaseBlock { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.BlockScopeExcludes, AncestorFacts.BlockScopeIncludes); + const savedAncestorFacts = getAncestorFacts(); + setAncestorFacts(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); const updated = visitEachChild(node, visitor, context); - ancestorFacts = savedAncestorFacts; + restoreAncestorFacts(savedAncestorFacts); return updated; } @@ -545,7 +589,7 @@ namespace ts { function visitThisKeyword(node: Node): Node { if (convertedLoopState) { - if (ancestorFacts & AncestorFacts.ArrowFunction) { + if (hierarchyFacts & HierarchyFacts.ArrowFunction) { // if the enclosing function is an ArrowFunction is then we use the captured 'this' keyword. convertedLoopState.containsLexicalThis = true; return node; @@ -826,11 +870,12 @@ namespace ts { * @param extendsClauseElement The expression for the class `extends` clause. */ function addConstructor(statements: Statement[], node: ClassExpression | ClassDeclaration, extendsClauseElement: ExpressionWithTypeArguments): void { - const savedAncestorFacts = ancestorFacts; + const savedHierarchyFacts = hierarchyFacts; const savedConvertedLoopState = convertedLoopState; - setAncestorFacts(AncestorFacts.ConstructorExcludes, AncestorFacts.ConstructorIncludes); convertedLoopState = undefined; + setAncestorFacts(HierarchyFacts.ConstructorExcludes, HierarchyFacts.ConstructorIncludes); + resetSubtreeFacts(); const constructor = getFirstConstructorWithBody(node); const hasSynthesizedSuper = hasSynthesizedDefaultSuperCall(constructor, extendsClauseElement !== undefined); @@ -852,8 +897,7 @@ namespace ts { } statements.push(constructorFunction); - - ancestorFacts = savedAncestorFacts; + hierarchyFacts = savedHierarchyFacts; convertedLoopState = savedConvertedLoopState; } @@ -915,7 +959,7 @@ namespace ts { if (constructor) { if (superCaptureStatus === SuperCaptureResult.ReplaceSuperCapture) { - ancestorFacts |= AncestorFacts.ConstructorWithCapturedSuper; + hierarchyFacts |= HierarchyFacts.ConstructorWithCapturedSuper; } addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset)); @@ -934,6 +978,11 @@ namespace ts { } addRange(statements, endLexicalEnvironment()); + + if (constructor) { + prependCaptureNewTargetIfNeeded(statements, constructor, /*copyOnWrite*/ false); + } + const block = createBlock( createNodeArray( statements, @@ -1374,6 +1423,73 @@ namespace ts { statements.push(captureThisStatement); } + function prependCaptureNewTargetIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, copyOnWrite: boolean): Statement[] { + if (hierarchyFacts & HierarchyFacts.NewTarget) { + let newTarget: Expression; + switch (node.kind) { + case SyntaxKind.ArrowFunction: + return statements; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // Methods and accessors cannot be constructors, so 'new.target' will + // always return 'undefined'. + newTarget = createVoidZero(); + break; + + case SyntaxKind.Constructor: + // Class constructors can only be called with `new`, so `this.constructor` + // should be relatively safe to use. + newTarget = createPropertyAccess( + setEmitFlags(createThis(), EmitFlags.NoSubstitution), + "constructor" + ); + break; + + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + // Functions can be called or constructed, and may have a `this` due to + // being a member or when calling an imported function via `other_1.f()`. + newTarget = createConditional( + createLogicalAnd( + setEmitFlags(createThis(), EmitFlags.NoSubstitution), + createBinary( + setEmitFlags(createThis(), EmitFlags.NoSubstitution), + SyntaxKind.InstanceOfKeyword, + getLocalName(node) + ) + ), + createPropertyAccess( + setEmitFlags(createThis(), EmitFlags.NoSubstitution), + "constructor" + ), + createVoidZero() + ); + break; + } + + const captureNewTargetStatement = createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + "_newTarget", + /*type*/ undefined, + newTarget + ) + ]) + ); + + if (copyOnWrite) { + return [captureNewTargetStatement, ...statements]; + } + + statements.unshift(captureNewTargetStatement); + } + + return statements; + } + /** * Adds statements to the class body function for a class to define the members of the * class. @@ -1428,6 +1544,9 @@ namespace ts { * @param member The MethodDeclaration node. */ function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration, container: Node) { + const savedHierarchyFacts = hierarchyFacts; + resetSubtreeFacts(); + const commentRange = getCommentRange(member); const sourceMapRange = getSourceMapRange(member); const memberName = createMemberAccessForPropertyName(receiver, visitNode(member.name, visitor, isPropertyName), /*location*/ member.name); @@ -1447,6 +1566,8 @@ namespace ts { // No source map should be emitted for this statement to align with the // old emitter. setEmitFlags(statement, EmitFlags.NoSourceMap); + + propagateHierarchyFacts(savedHierarchyFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.NewTarget); return statement; } @@ -1476,6 +1597,9 @@ namespace ts { * @param receiver The receiver for the member. */ function transformAccessorsToExpression(receiver: LeftHandSideExpression, { firstAccessor, getAccessor, setAccessor }: AllAccessorDeclarations, container: Node, startsOnNewLine: boolean): Expression { + const savedHierarchyFacts = hierarchyFacts; + resetSubtreeFacts(); + // To align with source maps in the old emitter, the receiver and property name // arguments are both mapped contiguously to the accessor name. const target = getMutableClone(receiver); @@ -1522,6 +1646,8 @@ namespace ts { if (startsOnNewLine) { call.startsOnNewLine = true; } + + propagateHierarchyFacts(savedHierarchyFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.NewTarget); return call; } @@ -1535,10 +1661,10 @@ namespace ts { enableSubstitutionsForCapturedThis(); } - const savedAncestorFacts = ancestorFacts; + const savedAncestorFacts = getAncestorFacts(); const savedConvertedLoopState = convertedLoopState; - setAncestorFacts(AncestorFacts.ArrowFunctionExcludes, AncestorFacts.ArrowFunctionIncludes); + setAncestorFacts(HierarchyFacts.ArrowFunctionExcludes, HierarchyFacts.ArrowFunctionIncludes); convertedLoopState = undefined; const func = createFunctionExpression( @@ -1554,7 +1680,7 @@ namespace ts { setOriginalNode(func, node); setEmitFlags(func, EmitFlags.CapturesThis); - ancestorFacts = savedAncestorFacts; + restoreAncestorFacts(savedAncestorFacts); convertedLoopState = savedConvertedLoopState; return func; } @@ -1565,27 +1691,32 @@ namespace ts { * @param node a FunctionExpression node. */ function visitFunctionExpression(node: FunctionExpression): Expression { - const savedAncestorFacts = ancestorFacts; + const savedHierarchyFacts = hierarchyFacts; const savedConvertedLoopState = convertedLoopState; - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + setAncestorFacts(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes, node); + resetSubtreeFacts(); convertedLoopState = undefined; - const updated = updateFunctionExpression( + const parameters = visitParameterList(node.parameters, visitor, context); + const body = node.transformFlags & TransformFlags.ES2015 + ? transformFunctionBody(node) + : visitFunctionBodyDownLevel(node); + const name = hierarchyFacts & HierarchyFacts.NewTarget + ? getLocalName(node) + : node.name; + + hierarchyFacts = savedHierarchyFacts; + convertedLoopState = savedConvertedLoopState; + return updateFunctionExpression( node, /*modifiers*/ undefined, - node.name, + name, /*typeParameters*/ undefined, - visitParameterList(node.parameters, visitor, context), + parameters, /*type*/ undefined, - node.transformFlags & TransformFlags.ES2015 - ? transformFunctionBody(node) - : visitFunctionBody(node.body, functionBodyVisitor, context) + body ); - - ancestorFacts = savedAncestorFacts; - convertedLoopState = savedConvertedLoopState; - return updated; } /** @@ -1594,28 +1725,33 @@ namespace ts { * @param node a FunctionDeclaration node. */ function visitFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { - const savedAncestorFacts = ancestorFacts; + const savedHierarchyFacts = hierarchyFacts; const savedConvertedLoopState = convertedLoopState; - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + setAncestorFacts(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes, node); + resetSubtreeFacts(); convertedLoopState = undefined; - const updated = updateFunctionDeclaration( + const parameters = visitParameterList(node.parameters, visitor, context); + const body = node.transformFlags & TransformFlags.ES2015 + ? transformFunctionBody(node) + : visitFunctionBodyDownLevel(node); + const name = hierarchyFacts & HierarchyFacts.NewTarget + ? getLocalName(node) + : node.name; + + hierarchyFacts = savedHierarchyFacts; + convertedLoopState = savedConvertedLoopState; + return updateFunctionDeclaration( node, /*decorators*/ undefined, visitNodes(node.modifiers, visitor, isModifier), - node.name, + name, /*typeParameters*/ undefined, - visitParameterList(node.parameters, visitor, context), + parameters, /*type*/ undefined, - node.transformFlags & TransformFlags.ES2015 - ? transformFunctionBody(node) - : visitFunctionBody(node.body, functionBodyVisitor, context) + body ); - - ancestorFacts = savedAncestorFacts; - convertedLoopState = savedConvertedLoopState; - return updated; } /** @@ -1626,29 +1762,34 @@ namespace ts { * @param name The name of the new FunctionExpression. */ function transformFunctionLikeToExpression(node: FunctionLikeDeclaration, location: TextRange, name: Identifier, container: Node): FunctionExpression { - const savedAncestorFacts = ancestorFacts; + const savedHierarchyFacts = hierarchyFacts; const savedConvertedLoopState = convertedLoopState; - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node, container); + setAncestorFacts(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes, node, container); + resetSubtreeFacts(); convertedLoopState = undefined; - const expression = setOriginalNode( + const parameters = visitParameterList(node.parameters, visitor, context); + const body = transformFunctionBody(node); + if (hierarchyFacts & HierarchyFacts.NewTarget && !name && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) { + name = getGeneratedNameForNode(node); + } + + hierarchyFacts = savedHierarchyFacts; + convertedLoopState = savedConvertedLoopState; + return setOriginalNode( createFunctionExpression( /*modifiers*/ undefined, node.asteriskToken, name, /*typeParameters*/ undefined, - visitParameterList(node.parameters, visitor, context), + parameters, /*type*/ undefined, - transformFunctionBody(node), + body, location ), /*original*/ node ); - - ancestorFacts = savedAncestorFacts; - convertedLoopState = savedConvertedLoopState; - return expression; } /** @@ -1723,6 +1864,8 @@ namespace ts { const lexicalEnvironment = context.endLexicalEnvironment(); addRange(statements, lexicalEnvironment); + prependCaptureNewTargetIfNeeded(statements, node, /*copyOnWrite*/ false); + // If we added any final generated statements, this must be a multi-line block if (!multiLine && lexicalEnvironment && lexicalEnvironment.length) { multiLine = true; @@ -1741,12 +1884,23 @@ namespace ts { return block; } + function visitFunctionBodyDownLevel(node: FunctionDeclaration | FunctionExpression) { + const updated = visitFunctionBody(node.body, functionBodyVisitor, context); + return updateBlock( + updated, + createNodeArray( + prependCaptureNewTargetIfNeeded(updated.statements, node, /*copyOnWrite*/ true), + /*location*/ updated.statements + ) + ); + } + function visitBlock(node: Block, isFunctionBody: boolean): Block { if (!isFunctionBody) { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.BlockExcludes, AncestorFacts.BlockIncludes, node); + const savedAncestorFacts = getAncestorFacts(); + setAncestorFacts(HierarchyFacts.BlockExcludes, HierarchyFacts.BlockIncludes, node); const updated = visitEachChild(node, visitor, context); - ancestorFacts = savedAncestorFacts; + restoreAncestorFacts(savedAncestorFacts); return updated; } @@ -1811,13 +1965,13 @@ namespace ts { } function visitVariableStatement(node: VariableStatement): Statement { - const savedAncestorFacts = ancestorFacts; + const savedAncestorFacts = getAncestorFacts(); if (hasModifier(node, ModifierFlags.Export)) { - ancestorFacts |= AncestorFacts.ExportedVariableStatement; + hierarchyFacts |= HierarchyFacts.ExportedVariableStatement; } let updated: Statement; - if (convertedLoopState && (getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) == 0) { + if (convertedLoopState && (node.declarationList.flags & NodeFlags.BlockScoped) == 0) { // we are inside a converted loop - hoist variable declarations let assignments: Expression[]; for (const decl of node.declarationList.declarations) { @@ -1850,7 +2004,7 @@ namespace ts { updated = visitEachChild(node, visitor, context); } - ancestorFacts = savedAncestorFacts; + restoreAncestorFacts(savedAncestorFacts); return updated; } @@ -1939,18 +2093,18 @@ namespace ts { const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding; const isDeclaredInLoop = flags & NodeCheckFlags.BlockScopedBindingInLoop; const emittedAsTopLevel = - (ancestorFacts & AncestorFacts.TopLevel) !== 0 + (hierarchyFacts & HierarchyFacts.TopLevel) !== 0 || (isCapturedInFunction && isDeclaredInLoop - && (ancestorFacts & AncestorFacts.IterationStatementBlock) !== 0); + && (hierarchyFacts & HierarchyFacts.IterationStatementBlock) !== 0); const emitExplicitInitializer = !emittedAsTopLevel - && (ancestorFacts & AncestorFacts.ForInOrForOfStatement) === 0 + && (hierarchyFacts & HierarchyFacts.ForInOrForOfStatement) === 0 && (!resolver.isDeclarationWithCollidingName(node) || (isDeclaredInLoop && !isCapturedInFunction - && (ancestorFacts & (AncestorFacts.ForStatement | AncestorFacts.ForInOrForOfStatement)) === 0)); + && (hierarchyFacts & (HierarchyFacts.ForStatement | HierarchyFacts.ForInOrForOfStatement)) === 0)); return emitExplicitInitializer; } @@ -1984,8 +2138,8 @@ namespace ts { * @param node A VariableDeclaration node. */ function visitVariableDeclaration(node: VariableDeclaration): VisitResult { - const savedAncestorFacts = ancestorFacts; - ancestorFacts &= ~AncestorFacts.ExportedVariableStatement; + const savedAncestorFacts = getAncestorFacts(); + hierarchyFacts &= ~HierarchyFacts.ExportedVariableStatement; let updated: VisitResult; if (isBindingPattern(node.name)) { @@ -1995,14 +2149,14 @@ namespace ts { context, FlattenLevel.All, /*value*/ undefined, - (savedAncestorFacts & AncestorFacts.ExportedVariableStatement) !== 0 + (savedAncestorFacts & HierarchyFacts.ExportedVariableStatement) !== 0 ); } else { updated = visitEachChild(node, visitor, context); } - ancestorFacts = savedAncestorFacts; + restoreAncestorFacts(savedAncestorFacts); return updated; } @@ -2044,42 +2198,42 @@ namespace ts { ); } - function visitIterationStatementWithFacts(excludeFacts: AncestorFacts, includeFacts: AncestorFacts, node: IterationStatement, outermostLabeledStatement: LabeledStatement, convert?: LoopConverter) { - const savedAncestorFacts = ancestorFacts; + function visitIterationStatementWithFacts(excludeFacts: HierarchyFacts, includeFacts: HierarchyFacts, node: IterationStatement, outermostLabeledStatement: LabeledStatement, convert?: LoopConverter) { + const savedAncestorFacts = getAncestorFacts(); setAncestorFacts(excludeFacts, includeFacts, node); const updated = convertIterationStatementBodyIfNecessary(node, outermostLabeledStatement, convert); - ancestorFacts = savedAncestorFacts; + restoreAncestorFacts(savedAncestorFacts); return updated; } function visitDoOrWhileStatement(node: DoStatement | WhileStatement, outermostLabeledStatement: LabeledStatement) { return visitIterationStatementWithFacts( - AncestorFacts.DoOrWhileStatementExcludes, - AncestorFacts.DoOrWhileStatementIncludes, + HierarchyFacts.DoOrWhileStatementExcludes, + HierarchyFacts.DoOrWhileStatementIncludes, node, outermostLabeledStatement); } function visitForStatement(node: ForStatement, outermostLabeledStatement: LabeledStatement) { return visitIterationStatementWithFacts( - AncestorFacts.ForStatementExcludes, - AncestorFacts.ForStatementIncludes, + HierarchyFacts.ForStatementExcludes, + HierarchyFacts.ForStatementIncludes, node, outermostLabeledStatement); } function visitForInStatement(node: ForInStatement, outermostLabeledStatement: LabeledStatement) { return visitIterationStatementWithFacts( - AncestorFacts.ForInOrForOfStatementExcludes, - AncestorFacts.ForInOrForOfStatementIncludes, + HierarchyFacts.ForInOrForOfStatementExcludes, + HierarchyFacts.ForInOrForOfStatementIncludes, node, outermostLabeledStatement); } function visitForOfStatement(node: ForOfStatement, outermostLabeledStatement: LabeledStatement): VisitResult { return visitIterationStatementWithFacts( - AncestorFacts.ForInOrForOfStatementExcludes, - AncestorFacts.ForInOrForOfStatementIncludes, + HierarchyFacts.ForInOrForOfStatementExcludes, + HierarchyFacts.ForInOrForOfStatementIncludes, node, outermostLabeledStatement, convertForOfToFor); @@ -2285,7 +2439,7 @@ namespace ts { let numInitialPropertiesWithoutYield = numProperties; for (let i = 0; i < numProperties; i++) { const property = properties[i]; - if ((property.transformFlags & TransformFlags.ContainsYield && ancestorFacts & AncestorFacts.AsyncFunctionBody) + if ((property.transformFlags & TransformFlags.ContainsYield && hierarchyFacts & HierarchyFacts.AsyncFunctionBody) && i < numInitialPropertiesWithoutYield) { numInitialPropertiesWithoutYield = i; } @@ -2442,7 +2596,7 @@ namespace ts { } const isAsyncBlockContainingAwait = - ancestorFacts & AncestorFacts.AsyncFunctionBody + hierarchyFacts & HierarchyFacts.AsyncFunctionBody && (node.statement.transformFlags & TransformFlags.ContainsYield) !== 0; let loopBodyFlags: EmitFlags = 0; @@ -2828,6 +2982,8 @@ namespace ts { * @param receiver The receiver for the assignment. */ function transformObjectLiteralMethodDeclarationToExpression(method: MethodDeclaration, receiver: Expression, container: Node, startsOnNewLine: boolean) { + const savedHierarchyFacts = hierarchyFacts; + resetSubtreeFacts(); const expression = createAssignment( createMemberAccessForPropertyName( receiver, @@ -2839,12 +2995,14 @@ namespace ts { if (startsOnNewLine) { expression.startsOnNewLine = true; } + + propagateHierarchyFacts(savedHierarchyFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.NewTarget); return expression; } function visitCatchClause(node: CatchClause): CatchClause { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.BlockScopeExcludes, AncestorFacts.BlockScopeIncludes); + const savedAncestorFacts = getAncestorFacts(); + setAncestorFacts(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); let updated: CatchClause; if (isBindingPattern(node.variableDeclaration.name)) { const temp = createTempVariable(undefined); @@ -2863,7 +3021,7 @@ namespace ts { else { updated = visitEachChild(node, visitor, context); } - ancestorFacts = savedAncestorFacts; + restoreAncestorFacts(savedAncestorFacts); return updated; } @@ -2898,15 +3056,17 @@ namespace ts { * @param node An AccessorDeclaration node. */ function visitAccessorDeclaration(node: AccessorDeclaration): AccessorDeclaration { - const savedAncestorFacts = ancestorFacts; + Debug.assert(!isComputedPropertyName(node.name)); + + const savedHierarchyFacts = hierarchyFacts; const savedConvertedLoopState = convertedLoopState; - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + setAncestorFacts(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes, node); convertedLoopState = undefined; const updated = visitEachChild(node, visitor, context); - ancestorFacts = savedAncestorFacts; + hierarchyFacts = savedHierarchyFacts; convertedLoopState = savedConvertedLoopState; return updated; } @@ -2925,9 +3085,18 @@ namespace ts { } function visitComputedPropertyName(node: ComputedPropertyName) { - return visitEachChild(node, visitor, context); + const savedHierarchyFacts = hierarchyFacts; + + setAncestorFacts(HierarchyFacts.ComputedPropertyNameExcludes, HierarchyFacts.ArrowFunctionIncludes); + resetSubtreeFacts(); + + const updated = visitEachChild(node, visitor, context); + + propagateHierarchyFacts(savedHierarchyFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.NewTargetInComputedPropertyName); + return updated; } + /** * Visits a YieldExpression node. * @@ -3297,12 +3466,25 @@ namespace ts { * Visits the `super` keyword */ function visitSuperKeyword(isExpressionOfCall: boolean): LeftHandSideExpression { - return ancestorFacts & AncestorFacts.NonStaticClassElement + return hierarchyFacts & HierarchyFacts.NonStaticClassElement && !isExpressionOfCall ? createPropertyAccess(createIdentifier("_super"), "prototype") : createIdentifier("_super"); } + function visitMetaProperty(node: MetaProperty) { + if (node.keywordToken === SyntaxKind.NewKeyword && node.name.text === "target") { + if (hierarchyFacts & HierarchyFacts.ComputedPropertyName) { + hierarchyFacts |= HierarchyFacts.NewTargetInComputedPropertyName; + } + else { + hierarchyFacts |= HierarchyFacts.NewTarget; + } + return createIdentifier("_newTarget"); + } + return node; + } + /** * Called by the printer just before a node is printed. * @@ -3311,10 +3493,10 @@ namespace ts { function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) { if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis && isFunctionLike(node)) { // If we are tracking a captured `this`, keep track of the enclosing function. - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + const savedHierarchyFacts = hierarchyFacts; + setAncestorFacts(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes, node); previousOnEmitNode(emitContext, node, emitCallback); - ancestorFacts = savedAncestorFacts; + hierarchyFacts = savedHierarchyFacts; return; } previousOnEmitNode(emitContext, node, emitCallback); @@ -3445,7 +3627,7 @@ namespace ts { */ function substituteThisKeyword(node: PrimaryExpression): PrimaryExpression { if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis - && ancestorFacts & AncestorFacts.CapturesThis) { + && hierarchyFacts & HierarchyFacts.CapturesThis) { return createIdentifier("_this", /*location*/ node); } return node; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c1592d30eae..dcba3672d78 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -253,6 +253,7 @@ namespace ts { ExpressionWithTypeArguments, AsExpression, NonNullExpression, + MetaProperty, // Misc TemplateSpan, @@ -1444,6 +1445,14 @@ namespace ts { expression: Expression; } + // NOTE: MetaProperty is really a MemberExpression, but we consider it a PrimaryExpression + // for the same reasons we treat NewExpression as a PrimaryExpression. + export interface MetaProperty extends PrimaryExpression { + kind: SyntaxKind.MetaProperty; + keywordToken: SyntaxKind; + name: Identifier; + } + /// A JSX expression of the form ... export interface JsxElement extends PrimaryExpression { kind: SyntaxKind.JsxElement; @@ -2713,6 +2722,7 @@ namespace ts { TypeChecked = 0x00000001, // Node has been type checked LexicalThis = 0x00000002, // Lexical 'this' reference CaptureThis = 0x00000004, // Lexical 'this' used in body + CaptureNewTarget = 0x00000008, // Lexical 'new.target' used in body SuperInstance = 0x00000100, // Instance 'super' reference SuperStatic = 0x00000200, // Static 'super' reference ContextChecked = 0x00000400, // Contextual types have been assigned diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 328f80abc5c..ff619f07020 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -996,6 +996,20 @@ namespace ts { } } + export function getNewTargetContainer(node: Node) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + if (container) { + switch (container.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + return container; + } + } + + return undefined; + } + /** * Given an super call/property node, returns the closest node where * - a super call/property access is legal in the node and not legal in the parent node the node. @@ -1203,6 +1217,7 @@ namespace ts { case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.YieldExpression: case SyntaxKind.AwaitExpression: + case SyntaxKind.MetaProperty: return true; case SyntaxKind.QualifiedName: while (node.parent.kind === SyntaxKind.QualifiedName) { @@ -3892,6 +3907,7 @@ namespace ts { || kind === SyntaxKind.TrueKeyword || kind === SyntaxKind.SuperKeyword || kind === SyntaxKind.NonNullExpression + || kind === SyntaxKind.MetaProperty || kind === SyntaxKind.RawExpression; } diff --git a/tests/baselines/reference/invalidNewTarget.es5.errors.txt b/tests/baselines/reference/invalidNewTarget.es5.errors.txt new file mode 100644 index 00000000000..080267c36eb --- /dev/null +++ b/tests/baselines/reference/invalidNewTarget.es5.errors.txt @@ -0,0 +1,78 @@ +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(1,11): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(2,17): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(5,6): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(6,18): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(7,22): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(8,20): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(9,15): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(11,13): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(12,25): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(13,29): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(14,27): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(15,22): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(19,6): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(20,18): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(21,22): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(22,20): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts(23,8): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + + +==== tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts (17 errors) ==== + const a = new.target; + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + const b = () => new.target; + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + + class C { + [new.target]() { } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + c() { return new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + get d() { return new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + set e(_) { _ = new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + f = () => new.target; + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + + static [new.target]() { } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + static g() { return new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + static get h() { return new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + static set i(_) { _ = new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + static j = () => new.target; + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + } + + const O = { + [new.target]: undefined, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + k() { return new.target; }, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + get l() { return new.target; }, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + set m(_) { _ = new.target; }, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + n: new.target, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + }; \ No newline at end of file diff --git a/tests/baselines/reference/invalidNewTarget.es5.js b/tests/baselines/reference/invalidNewTarget.es5.js new file mode 100644 index 00000000000..866dac270af --- /dev/null +++ b/tests/baselines/reference/invalidNewTarget.es5.js @@ -0,0 +1,77 @@ +//// [invalidNewTarget.es5.ts] +const a = new.target; +const b = () => new.target; + +class C { + [new.target]() { } + c() { return new.target; } + get d() { return new.target; } + set e(_) { _ = new.target; } + f = () => new.target; + + static [new.target]() { } + static g() { return new.target; } + static get h() { return new.target; } + static set i(_) { _ = new.target; } + static j = () => new.target; +} + +const O = { + [new.target]: undefined, + k() { return new.target; }, + get l() { return new.target; }, + set m(_) { _ = new.target; }, + n: new.target, +}; + +//// [invalidNewTarget.es5.js] +var a = _newTarget; +var b = function () { return _newTarget; }; +var C = (function () { + function C() { + var _newTarget = this.constructor; + this.f = function () { return _newTarget; }; + } + C.prototype[_newTarget] = function () { }; + C.prototype.c = function () { var _newTarget = void 0; return _newTarget; }; + Object.defineProperty(C.prototype, "d", { + get: function () { var _newTarget = void 0; return _newTarget; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(C.prototype, "e", { + set: function (_) { var _newTarget = void 0; _ = _newTarget; }, + enumerable: true, + configurable: true + }); + C[_newTarget] = function () { }; + C.g = function () { var _newTarget = void 0; return _newTarget; }; + Object.defineProperty(C, "h", { + get: function () { var _newTarget = void 0; return _newTarget; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(C, "i", { + set: function (_) { var _newTarget = void 0; _ = _newTarget; }, + enumerable: true, + configurable: true + }); + return C; +}()); +C.j = function () { return _newTarget; }; +var O = (_a = {}, + _a[_newTarget] = undefined, + _a.k = function () { var _newTarget = void 0; return _newTarget; }, + Object.defineProperty(_a, "l", { + get: function () { var _newTarget = void 0; return _newTarget; }, + enumerable: true, + configurable: true + }), + Object.defineProperty(_a, "m", { + set: function (_) { var _newTarget = void 0; _ = _newTarget; }, + enumerable: true, + configurable: true + }), + _a.n = _newTarget, + _a); +var _a; diff --git a/tests/baselines/reference/invalidNewTarget.es6.errors.txt b/tests/baselines/reference/invalidNewTarget.es6.errors.txt new file mode 100644 index 00000000000..ee5404ed781 --- /dev/null +++ b/tests/baselines/reference/invalidNewTarget.es6.errors.txt @@ -0,0 +1,78 @@ +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(1,11): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(2,17): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(5,6): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(6,18): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(7,22): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(8,20): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(9,15): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(11,13): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(12,25): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(13,29): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(14,27): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(15,22): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(19,6): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(20,18): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(21,22): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(22,20): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. +tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts(23,8): error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + + +==== tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts (17 errors) ==== + const a = new.target; + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + const b = () => new.target; + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + + class C { + [new.target]() { } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + c() { return new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + get d() { return new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + set e(_) { _ = new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + f = () => new.target; + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + + static [new.target]() { } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + static g() { return new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + static get h() { return new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + static set i(_) { _ = new.target; } + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + static j = () => new.target; + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + } + + const O = { + [new.target]: undefined, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + k() { return new.target; }, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + get l() { return new.target; }, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + set m(_) { _ = new.target; }, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + n: new.target, + ~~~~~~~~~~ +!!! error TS17013: Meta-property 'new.target' is only allowed in the body of a function declaration, function expression, or constructor. + }; \ No newline at end of file diff --git a/tests/baselines/reference/invalidNewTarget.es6.js b/tests/baselines/reference/invalidNewTarget.es6.js new file mode 100644 index 00000000000..ed1b2cc1592 --- /dev/null +++ b/tests/baselines/reference/invalidNewTarget.es6.js @@ -0,0 +1,50 @@ +//// [invalidNewTarget.es6.ts] +const a = new.target; +const b = () => new.target; + +class C { + [new.target]() { } + c() { return new.target; } + get d() { return new.target; } + set e(_) { _ = new.target; } + f = () => new.target; + + static [new.target]() { } + static g() { return new.target; } + static get h() { return new.target; } + static set i(_) { _ = new.target; } + static j = () => new.target; +} + +const O = { + [new.target]: undefined, + k() { return new.target; }, + get l() { return new.target; }, + set m(_) { _ = new.target; }, + n: new.target, +}; + +//// [invalidNewTarget.es6.js] +const a = new.target; +const b = () => new.target; +class C { + constructor() { + this.f = () => new.target; + } + [new.target]() { } + c() { return new.target; } + get d() { return new.target; } + set e(_) { _ = new.target; } + static [new.target]() { } + static g() { return new.target; } + static get h() { return new.target; } + static set i(_) { _ = new.target; } +} +C.j = () => new.target; +const O = { + [new.target]: undefined, + k() { return new.target; }, + get l() { return new.target; }, + set m(_) { _ = new.target; }, + n: new.target, +}; diff --git a/tests/baselines/reference/newTarget.es5.js b/tests/baselines/reference/newTarget.es5.js new file mode 100644 index 00000000000..0de967d5477 --- /dev/null +++ b/tests/baselines/reference/newTarget.es5.js @@ -0,0 +1,74 @@ +//// [newTarget.es5.ts] +class A { + constructor() { + const a = new.target; + const b = () => new.target; + } + static c = function () { return new.target; } + d = function () { return new.target; } +} + +class B extends A { + constructor() { + super(); + const e = new.target; + const f = () => new.target; + } +} + +function f1() { + const g = new.target; + const h = () => new.target; +} + +const f2 = function () { + const i = new.target; + const j = () => new.target; +} + +const O = { + k: function () { return new.target; } +}; + + + +//// [newTarget.es5.js] +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var A = (function () { + function A() { + var _newTarget = this.constructor; + this.d = function _a() { var _newTarget = this && this instanceof _a ? this.constructor : void 0; return _newTarget; }; + var a = _newTarget; + var b = function () { return _newTarget; }; + } + return A; +}()); +A.c = function _a() { var _newTarget = this && this instanceof _a ? this.constructor : void 0; return _newTarget; }; +var B = (function (_super) { + __extends(B, _super); + function B() { + var _newTarget = this.constructor; + var _this = _super.call(this) || this; + var e = _newTarget; + var f = function () { return _newTarget; }; + return _this; + } + return B; +}(A)); +function f1() { + var _newTarget = this && this instanceof f1 ? this.constructor : void 0; + var g = _newTarget; + var h = function () { return _newTarget; }; +} +var f2 = function _b() { + var _newTarget = this && this instanceof _b ? this.constructor : void 0; + var i = _newTarget; + var j = function () { return _newTarget; }; +}; +var O = { + k: function _c() { var _newTarget = this && this instanceof _c ? this.constructor : void 0; return _newTarget; } +}; diff --git a/tests/baselines/reference/newTarget.es5.symbols b/tests/baselines/reference/newTarget.es5.symbols new file mode 100644 index 00000000000..34cdb3f34c3 --- /dev/null +++ b/tests/baselines/reference/newTarget.es5.symbols @@ -0,0 +1,63 @@ +=== tests/cases/conformance/es6/newTarget/newTarget.es5.ts === +class A { +>A : Symbol(A, Decl(newTarget.es5.ts, 0, 0)) + + constructor() { + const a = new.target; +>a : Symbol(a, Decl(newTarget.es5.ts, 2, 13)) + + const b = () => new.target; +>b : Symbol(b, Decl(newTarget.es5.ts, 3, 13)) + } + static c = function () { return new.target; } +>c : Symbol(A.c, Decl(newTarget.es5.ts, 4, 5)) + + d = function () { return new.target; } +>d : Symbol(A.d, Decl(newTarget.es5.ts, 5, 49)) +} + +class B extends A { +>B : Symbol(B, Decl(newTarget.es5.ts, 7, 1)) +>A : Symbol(A, Decl(newTarget.es5.ts, 0, 0)) + + constructor() { + super(); +>super : Symbol(A, Decl(newTarget.es5.ts, 0, 0)) + + const e = new.target; +>e : Symbol(e, Decl(newTarget.es5.ts, 12, 13)) + + const f = () => new.target; +>f : Symbol(f, Decl(newTarget.es5.ts, 13, 13)) + } +} + +function f1() { +>f1 : Symbol(f1, Decl(newTarget.es5.ts, 15, 1)) + + const g = new.target; +>g : Symbol(g, Decl(newTarget.es5.ts, 18, 9)) + + const h = () => new.target; +>h : Symbol(h, Decl(newTarget.es5.ts, 19, 9)) +} + +const f2 = function () { +>f2 : Symbol(f2, Decl(newTarget.es5.ts, 22, 5)) + + const i = new.target; +>i : Symbol(i, Decl(newTarget.es5.ts, 23, 9)) + + const j = () => new.target; +>j : Symbol(j, Decl(newTarget.es5.ts, 24, 9)) +} + +const O = { +>O : Symbol(O, Decl(newTarget.es5.ts, 27, 5)) + + k: function () { return new.target; } +>k : Symbol(k, Decl(newTarget.es5.ts, 27, 11)) + +}; + + diff --git a/tests/baselines/reference/newTarget.es5.types b/tests/baselines/reference/newTarget.es5.types new file mode 100644 index 00000000000..ddbe78537b2 --- /dev/null +++ b/tests/baselines/reference/newTarget.es5.types @@ -0,0 +1,95 @@ +=== tests/cases/conformance/es6/newTarget/newTarget.es5.ts === +class A { +>A : A + + constructor() { + const a = new.target; +>a : typeof A +>new.target : typeof A +>target : any + + const b = () => new.target; +>b : () => typeof A +>() => new.target : () => typeof A +>new.target : typeof A +>target : any + } + static c = function () { return new.target; } +>c : () => any +>function () { return new.target; } : () => any +>new.target : () => any +>target : any + + d = function () { return new.target; } +>d : () => any +>function () { return new.target; } : () => any +>new.target : () => any +>target : any +} + +class B extends A { +>B : B +>A : A + + constructor() { + super(); +>super() : void +>super : typeof A + + const e = new.target; +>e : typeof B +>new.target : typeof B +>target : any + + const f = () => new.target; +>f : () => typeof B +>() => new.target : () => typeof B +>new.target : typeof B +>target : any + } +} + +function f1() { +>f1 : () => void + + const g = new.target; +>g : () => void +>new.target : () => void +>target : any + + const h = () => new.target; +>h : () => () => void +>() => new.target : () => () => void +>new.target : () => void +>target : any +} + +const f2 = function () { +>f2 : () => void +>function () { const i = new.target; const j = () => new.target;} : () => void + + const i = new.target; +>i : () => void +>new.target : () => void +>target : any + + const j = () => new.target; +>j : () => () => void +>() => new.target : () => () => void +>new.target : () => void +>target : any +} + +const O = { +>O : { k: () => any; } +>{ k: function () { return new.target; }} : { k: () => any; } + + k: function () { return new.target; } +>k : () => any +>function () { return new.target; } : () => any +>new.target : () => any +>target : any + +}; + + diff --git a/tests/baselines/reference/newTarget.es6.js b/tests/baselines/reference/newTarget.es6.js new file mode 100644 index 00000000000..2bd95ff9f98 --- /dev/null +++ b/tests/baselines/reference/newTarget.es6.js @@ -0,0 +1,61 @@ +//// [newTarget.es6.ts] +class A { + constructor() { + const a = new.target; + const b = () => new.target; + } + static c = function () { return new.target; } + d = function () { return new.target; } +} + +class B extends A { + constructor() { + super(); + const e = new.target; + const f = () => new.target; + } +} + +function f1() { + const g = new.target; + const h = () => new.target; +} + +const f2 = function () { + const i = new.target; + const j = () => new.target; +} + +const O = { + k: function () { return new.target; } +}; + + + +//// [newTarget.es6.js] +class A { + constructor() { + this.d = function () { return new.target; }; + const a = new.target; + const b = () => new.target; + } +} +A.c = function () { return new.target; }; +class B extends A { + constructor() { + super(); + const e = new.target; + const f = () => new.target; + } +} +function f1() { + const g = new.target; + const h = () => new.target; +} +const f2 = function () { + const i = new.target; + const j = () => new.target; +}; +const O = { + k: function () { return new.target; } +}; diff --git a/tests/baselines/reference/newTarget.es6.symbols b/tests/baselines/reference/newTarget.es6.symbols new file mode 100644 index 00000000000..b20ea3dba93 --- /dev/null +++ b/tests/baselines/reference/newTarget.es6.symbols @@ -0,0 +1,63 @@ +=== tests/cases/conformance/es6/newTarget/newTarget.es6.ts === +class A { +>A : Symbol(A, Decl(newTarget.es6.ts, 0, 0)) + + constructor() { + const a = new.target; +>a : Symbol(a, Decl(newTarget.es6.ts, 2, 13)) + + const b = () => new.target; +>b : Symbol(b, Decl(newTarget.es6.ts, 3, 13)) + } + static c = function () { return new.target; } +>c : Symbol(A.c, Decl(newTarget.es6.ts, 4, 5)) + + d = function () { return new.target; } +>d : Symbol(A.d, Decl(newTarget.es6.ts, 5, 49)) +} + +class B extends A { +>B : Symbol(B, Decl(newTarget.es6.ts, 7, 1)) +>A : Symbol(A, Decl(newTarget.es6.ts, 0, 0)) + + constructor() { + super(); +>super : Symbol(A, Decl(newTarget.es6.ts, 0, 0)) + + const e = new.target; +>e : Symbol(e, Decl(newTarget.es6.ts, 12, 13)) + + const f = () => new.target; +>f : Symbol(f, Decl(newTarget.es6.ts, 13, 13)) + } +} + +function f1() { +>f1 : Symbol(f1, Decl(newTarget.es6.ts, 15, 1)) + + const g = new.target; +>g : Symbol(g, Decl(newTarget.es6.ts, 18, 9)) + + const h = () => new.target; +>h : Symbol(h, Decl(newTarget.es6.ts, 19, 9)) +} + +const f2 = function () { +>f2 : Symbol(f2, Decl(newTarget.es6.ts, 22, 5)) + + const i = new.target; +>i : Symbol(i, Decl(newTarget.es6.ts, 23, 9)) + + const j = () => new.target; +>j : Symbol(j, Decl(newTarget.es6.ts, 24, 9)) +} + +const O = { +>O : Symbol(O, Decl(newTarget.es6.ts, 27, 5)) + + k: function () { return new.target; } +>k : Symbol(k, Decl(newTarget.es6.ts, 27, 11)) + +}; + + diff --git a/tests/baselines/reference/newTarget.es6.types b/tests/baselines/reference/newTarget.es6.types new file mode 100644 index 00000000000..08ef8429b3c --- /dev/null +++ b/tests/baselines/reference/newTarget.es6.types @@ -0,0 +1,95 @@ +=== tests/cases/conformance/es6/newTarget/newTarget.es6.ts === +class A { +>A : A + + constructor() { + const a = new.target; +>a : typeof A +>new.target : typeof A +>target : any + + const b = () => new.target; +>b : () => typeof A +>() => new.target : () => typeof A +>new.target : typeof A +>target : any + } + static c = function () { return new.target; } +>c : () => any +>function () { return new.target; } : () => any +>new.target : () => any +>target : any + + d = function () { return new.target; } +>d : () => any +>function () { return new.target; } : () => any +>new.target : () => any +>target : any +} + +class B extends A { +>B : B +>A : A + + constructor() { + super(); +>super() : void +>super : typeof A + + const e = new.target; +>e : typeof B +>new.target : typeof B +>target : any + + const f = () => new.target; +>f : () => typeof B +>() => new.target : () => typeof B +>new.target : typeof B +>target : any + } +} + +function f1() { +>f1 : () => void + + const g = new.target; +>g : () => void +>new.target : () => void +>target : any + + const h = () => new.target; +>h : () => () => void +>() => new.target : () => () => void +>new.target : () => void +>target : any +} + +const f2 = function () { +>f2 : () => void +>function () { const i = new.target; const j = () => new.target;} : () => void + + const i = new.target; +>i : () => void +>new.target : () => void +>target : any + + const j = () => new.target; +>j : () => () => void +>() => new.target : () => () => void +>new.target : () => void +>target : any +} + +const O = { +>O : { k: () => any; } +>{ k: function () { return new.target; }} : { k: () => any; } + + k: function () { return new.target; } +>k : () => any +>function () { return new.target; } : () => any +>new.target : () => any +>target : any + +}; + + diff --git a/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts b/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts new file mode 100644 index 00000000000..cda2d4d2ce7 --- /dev/null +++ b/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts @@ -0,0 +1,25 @@ +// @target: es5 +const a = new.target; +const b = () => new.target; + +class C { + [new.target]() { } + c() { return new.target; } + get d() { return new.target; } + set e(_) { _ = new.target; } + f = () => new.target; + + static [new.target]() { } + static g() { return new.target; } + static get h() { return new.target; } + static set i(_) { _ = new.target; } + static j = () => new.target; +} + +const O = { + [new.target]: undefined, + k() { return new.target; }, + get l() { return new.target; }, + set m(_) { _ = new.target; }, + n: new.target, +}; \ No newline at end of file diff --git a/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts b/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts new file mode 100644 index 00000000000..5043c160e10 --- /dev/null +++ b/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts @@ -0,0 +1,25 @@ +// @target: es6 +const a = new.target; +const b = () => new.target; + +class C { + [new.target]() { } + c() { return new.target; } + get d() { return new.target; } + set e(_) { _ = new.target; } + f = () => new.target; + + static [new.target]() { } + static g() { return new.target; } + static get h() { return new.target; } + static set i(_) { _ = new.target; } + static j = () => new.target; +} + +const O = { + [new.target]: undefined, + k() { return new.target; }, + get l() { return new.target; }, + set m(_) { _ = new.target; }, + n: new.target, +}; \ No newline at end of file diff --git a/tests/cases/conformance/es6/newTarget/newTarget.es5.ts b/tests/cases/conformance/es6/newTarget/newTarget.es5.ts new file mode 100644 index 00000000000..b912f5f0596 --- /dev/null +++ b/tests/cases/conformance/es6/newTarget/newTarget.es5.ts @@ -0,0 +1,32 @@ +// @target: es5 +class A { + constructor() { + const a = new.target; + const b = () => new.target; + } + static c = function () { return new.target; } + d = function () { return new.target; } +} + +class B extends A { + constructor() { + super(); + const e = new.target; + const f = () => new.target; + } +} + +function f1() { + const g = new.target; + const h = () => new.target; +} + +const f2 = function () { + const i = new.target; + const j = () => new.target; +} + +const O = { + k: function () { return new.target; } +}; + diff --git a/tests/cases/conformance/es6/newTarget/newTarget.es6.ts b/tests/cases/conformance/es6/newTarget/newTarget.es6.ts new file mode 100644 index 00000000000..de61a49334a --- /dev/null +++ b/tests/cases/conformance/es6/newTarget/newTarget.es6.ts @@ -0,0 +1,32 @@ +// @target: es6 +class A { + constructor() { + const a = new.target; + const b = () => new.target; + } + static c = function () { return new.target; } + d = function () { return new.target; } +} + +class B extends A { + constructor() { + super(); + const e = new.target; + const f = () => new.target; + } +} + +function f1() { + const g = new.target; + const h = () => new.target; +} + +const f2 = function () { + const i = new.target; + const j = () => new.target; +} + +const O = { + k: function () { return new.target; } +}; + From c0a73b2ed84ab0b542fd301274924ad782f09d47 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 8 Dec 2016 18:27:49 -0800 Subject: [PATCH 2/7] Revert changes to core.ts --- src/compiler/core.ts | 61 +++----------------------------------------- 1 file changed, 4 insertions(+), 57 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 70674d400c3..72c09e490c3 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -571,32 +571,13 @@ namespace ts { * @param copyOnWrite Indicates whether to return a fresh array rather than modify the * existing array. */ - export function append(to: T[] | undefined, value: T | undefined, copyOnWrite?: boolean): T[] | undefined { + export function append(to: T[] | undefined, value: T | undefined): T[] | undefined { if (value === undefined) return to; if (to === undefined) return [value]; - if (copyOnWrite) return [...to, value]; to.push(value); return to; } - /** - * Prepends a value to an array, returning the array. - * - * @param to The array to which `value` is to be prepended. If `to` is `undefined`, a new array - * is created if `value` was prepended. - * @param value The value to prepend to the array. If `value` is `undefined`, nothing is - * prepended. - * @param copyOnWrite Indicates whether to return a fresh array rather than modify the - * existing array. - */ - export function prepend(to: T[] | undefined, value: T | undefined, copyOnWrite?: boolean): T[] | undefined { - if (value === undefined) return to; - if (to === undefined) return [value]; - if (copyOnWrite) return [value, ...to]; - to.unshift(value); - return to; - } - /** * Appends a range of value to an array, returning the array. * @@ -604,45 +585,11 @@ namespace ts { * is created if `value` was appended. * @param from The values to append to the array. If `from` is `undefined`, nothing is * appended. If an element of `from` is `undefined`, that element is not appended. - * @param copyOnWrite Indicates whether to return a fresh array rather than modify the - * existing array. */ - export function addRange(to: T[] | undefined, from: T[] | undefined, copyOnWrite?: boolean): T[] | undefined { + export function addRange(to: T[] | undefined, from: T[] | undefined): T[] | undefined { if (from === undefined) return to; - from = filter(from, isDefined); - if (to === undefined) return from; - if (from.length > 0) { - if (copyOnWrite) { - return [...to, ...from]; - } - for (const v of from) { - to.push(v); - } - } - return to; - } - - /** - * Appends a range of value to an array, returning the array. - * - * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array - * is created if `value` was appended. - * @param from The values to append to the array. If `from` is `undefined`, nothing is - * appended. If an element of `from` is `undefined`, that element is not appended. - * @param copyOnWrite Indicates whether to return a fresh array rather than modify the - * existing array. - */ - export function prependRange(to: T[] | undefined, from: T[] | undefined, copyOnWrite?: boolean): T[] | undefined { - if (from === undefined) return to; - from = filter(from, isDefined); - if (to === undefined) return from; - if (from.length > 0) { - if (copyOnWrite) { - return [...from, ...to]; - } - for (let i = from.length - 1; i >= 0; i--) { - to.unshift(from[i]); - } + for (const v of from) { + to = append(to, v); } return to; } From 97f48e8fe75f52dc4e01efbccf3496329c27ecc2 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 19 Dec 2016 11:29:52 -0800 Subject: [PATCH 3/7] PR cleanup --- src/compiler/core.ts | 2 -- src/compiler/utilities.ts | 13 ------------- 2 files changed, 15 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 72c09e490c3..89057dd2939 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -568,8 +568,6 @@ namespace ts { * is created if `value` was appended. * @param value The value to append to the array. If `value` is `undefined`, nothing is * appended. - * @param copyOnWrite Indicates whether to return a fresh array rather than modify the - * existing array. */ export function append(to: T[] | undefined, value: T | undefined): T[] | undefined { if (value === undefined) return to; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ff619f07020..f3b98794762 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -883,19 +883,6 @@ namespace ts { return false; } - export function getAllLabeledStatements(node: LabeledStatement): { statement: Statement; labeledStatements: LabeledStatement[]; } { - switch (node.statement.kind) { - case SyntaxKind.LabeledStatement: - const result = getAllLabeledStatements(node.statement); - if (result) { - result.labeledStatements.push(node); - } - return result; - default: - return { statement: node.statement, labeledStatements: [node] }; - } - } - export function isFunctionBlock(node: Node) { return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent); } From 4620fbd551eb659d68cbb475f242d436ffd8a9ea Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 21 Dec 2016 15:14:34 -0800 Subject: [PATCH 4/7] PR Feedback --- src/compiler/transformers/es2015.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 2f00f66767f..e295ba11526 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -569,7 +569,7 @@ namespace ts { function visitThisKeyword(node: Node): Node { if (convertedLoopState) { if (hierarchyFacts & HierarchyFacts.ArrowFunction) { - // if the enclosing function is an ArrowFunction is then we use the captured 'this' keyword. + // if the enclosing function is an ArrowFunction then we use the captured 'this' keyword. convertedLoopState.containsLexicalThis = true; return node; } @@ -1445,6 +1445,10 @@ namespace ts { createVoidZero() ); break; + + default: + Debug.failBadSyntaxKind(node); + break; } const captureNewTargetStatement = createVariableStatement( From 498568b16f5b8eef0f70a96d4816d98001f68ed9 Mon Sep 17 00:00:00 2001 From: Slawomir Sadziak Date: Wed, 28 Dec 2016 05:18:53 +0100 Subject: [PATCH 5/7] #13063 Fix strictNullChecks breaking typeof * Allow typeof to use not-auto variable in strictNullChecks mode --- src/compiler/checker.ts | 2 +- tests/baselines/reference/typeofStrictNull.js | 8 ++++++++ tests/baselines/reference/typeofStrictNull.symbols | 9 +++++++++ tests/baselines/reference/typeofStrictNull.types | 9 +++++++++ tests/cases/compiler/typeofStrictNull.ts | 4 ++++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/typeofStrictNull.js create mode 100644 tests/baselines/reference/typeofStrictNull.symbols create mode 100644 tests/baselines/reference/typeofStrictNull.types create mode 100644 tests/cases/compiler/typeofStrictNull.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 139f3f052cb..c08a44b347e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10372,7 +10372,7 @@ namespace ts { // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). const assumeInitialized = isParameter || isOuterVariable || - type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0) || + type !== autoType && type !== autoArrayType && (!strictNullChecks || isInTypeQuery(node) || (type.flags & TypeFlags.Any) !== 0) || isInAmbientContext(declaration); const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph diff --git a/tests/baselines/reference/typeofStrictNull.js b/tests/baselines/reference/typeofStrictNull.js new file mode 100644 index 00000000000..520a72b9f0f --- /dev/null +++ b/tests/baselines/reference/typeofStrictNull.js @@ -0,0 +1,8 @@ +//// [typeofStrictNull.ts] + +let a: number; +let b: typeof a; + +//// [typeofStrictNull.js] +var a; +var b; diff --git a/tests/baselines/reference/typeofStrictNull.symbols b/tests/baselines/reference/typeofStrictNull.symbols new file mode 100644 index 00000000000..7b2dfb41797 --- /dev/null +++ b/tests/baselines/reference/typeofStrictNull.symbols @@ -0,0 +1,9 @@ +=== tests/cases/compiler/typeofStrictNull.ts === + +let a: number; +>a : Symbol(a, Decl(typeofStrictNull.ts, 1, 3)) + +let b: typeof a; +>b : Symbol(b, Decl(typeofStrictNull.ts, 2, 3)) +>a : Symbol(a, Decl(typeofStrictNull.ts, 1, 3)) + diff --git a/tests/baselines/reference/typeofStrictNull.types b/tests/baselines/reference/typeofStrictNull.types new file mode 100644 index 00000000000..59344727987 --- /dev/null +++ b/tests/baselines/reference/typeofStrictNull.types @@ -0,0 +1,9 @@ +=== tests/cases/compiler/typeofStrictNull.ts === + +let a: number; +>a : number + +let b: typeof a; +>b : number +>a : number + diff --git a/tests/cases/compiler/typeofStrictNull.ts b/tests/cases/compiler/typeofStrictNull.ts new file mode 100644 index 00000000000..ede2b857305 --- /dev/null +++ b/tests/cases/compiler/typeofStrictNull.ts @@ -0,0 +1,4 @@ +// @strictNullChecks: true + +let a: number; +let b: typeof a; \ No newline at end of file From 5317f13c165e84881f25a8a44b282bf103914e42 Mon Sep 17 00:00:00 2001 From: Slawomir Sadziak Date: Wed, 28 Dec 2016 20:22:24 +0100 Subject: [PATCH 6/7] #13063 Optimization Add isInTypeQuery as the last OR --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c08a44b347e..a944918f9a3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10372,7 +10372,7 @@ namespace ts { // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). const assumeInitialized = isParameter || isOuterVariable || - type !== autoType && type !== autoArrayType && (!strictNullChecks || isInTypeQuery(node) || (type.flags & TypeFlags.Any) !== 0) || + type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isInTypeQuery(node)) || isInAmbientContext(declaration); const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph From 69e0677ea1630daec111b5129aae609809d511e9 Mon Sep 17 00:00:00 2001 From: Zhengbo Li Date: Wed, 28 Dec 2016 15:04:15 -0800 Subject: [PATCH 7/7] Support quick fixes for UMD global (#12545) * Support quick fixes for UMD global * refactor --- src/services/codefixes/importFixes.ts | 22 +++++++++++++------ .../fourslash/importNameCodeFixUMDGlobal0.ts | 16 ++++++++++++++ .../fourslash/importNameCodeFixUMDGlobal1.ts | 19 ++++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/importNameCodeFixUMDGlobal0.ts create mode 100644 tests/cases/fourslash/importNameCodeFixUMDGlobal1.ts diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index bda310f2d33..3dc33e39897 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -112,7 +112,10 @@ namespace ts.codefix { } registerCodeFix({ - errorCodes: [Diagnostics.Cannot_find_name_0.code], + errorCodes: [ + Diagnostics.Cannot_find_name_0.code, + Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code + ], getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; const checker = context.program.getTypeChecker(); @@ -127,6 +130,12 @@ namespace ts.codefix { const cachedImportDeclarations = createMap<(ImportDeclaration | ImportEqualsDeclaration)[]>(); let cachedNewImportInsertPosition: number; + const currentTokenMeaning = getMeaningFromLocation(token); + if (context.errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code) { + const symbol = checker.getAliasedSymbol(checker.getSymbolAtLocation(token)); + return getCodeActionForImport(symbol, /*isDefault*/ false, /*isNamespaceImport*/ true); + } + const allPotentialModules = checker.getAmbientModules(); for (const otherSourceFile of allSourceFiles) { if (otherSourceFile !== sourceFile && isExternalOrCommonJsModule(otherSourceFile)) { @@ -134,7 +143,6 @@ namespace ts.codefix { } } - const currentTokenMeaning = getMeaningFromLocation(token); for (const moduleSymbol of allPotentialModules) { context.cancellationToken.throwIfCancellationRequested(); @@ -203,7 +211,7 @@ namespace ts.codefix { return declarations ? some(symbol.declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning)) : false; } - function getCodeActionForImport(moduleSymbol: Symbol, isDefault?: boolean): ImportCodeAction[] { + function getCodeActionForImport(moduleSymbol: Symbol, isDefault?: boolean, isNamespaceImport?: boolean): ImportCodeAction[] { const existingDeclarations = getImportDeclarations(moduleSymbol); if (existingDeclarations.length > 0) { // With an existing import statement, there are more than one actions the user can do. @@ -213,8 +221,6 @@ namespace ts.codefix { return [getCodeActionForNewImport()]; } - - function getCodeActionsForExistingImport(declarations: (ImportDeclaration | ImportEqualsDeclaration)[]): ImportCodeAction[] { const actions: ImportCodeAction[] = []; @@ -262,7 +268,7 @@ namespace ts.codefix { actions.push(getCodeActionForNamespaceImport(namespaceImportDeclaration)); } - if (namedImportDeclaration && namedImportDeclaration.importClause && + if (!isNamespaceImport && namedImportDeclaration && namedImportDeclaration.importClause && (namedImportDeclaration.importClause.name || namedImportDeclaration.importClause.namedBindings)) { /** * If the existing import declaration already has a named import list, just @@ -386,7 +392,9 @@ namespace ts.codefix { const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier || getModuleSpecifierForNewImport()); const importStatementText = isDefault ? `import ${name} from "${moduleSpecifierWithoutQuotes}"` - : `import { ${name} } from "${moduleSpecifierWithoutQuotes}"`; + : isNamespaceImport + ? `import * as ${name} from "${moduleSpecifierWithoutQuotes}"` + : `import { ${name} } from "${moduleSpecifierWithoutQuotes}"`; // if this file doesn't have any import statements, insert an import statement and then insert a new line // between the only import statement and user code. Otherwise just insert the statement because chances diff --git a/tests/cases/fourslash/importNameCodeFixUMDGlobal0.ts b/tests/cases/fourslash/importNameCodeFixUMDGlobal0.ts new file mode 100644 index 00000000000..3c780dc0af6 --- /dev/null +++ b/tests/cases/fourslash/importNameCodeFixUMDGlobal0.ts @@ -0,0 +1,16 @@ +/// + +// @Filename: a/f1.ts +//// [|export function test() { }; +//// bar1/*0*/.bar;|] + +// @Filename: a/foo.d.ts +//// export declare function bar(): number; +//// export as namespace bar1; + +verify.importFixAtPosition([ +`import * as bar1 from "./foo"; + +export function test() { }; +bar1.bar;` +]); \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixUMDGlobal1.ts b/tests/cases/fourslash/importNameCodeFixUMDGlobal1.ts new file mode 100644 index 00000000000..96671ad6f91 --- /dev/null +++ b/tests/cases/fourslash/importNameCodeFixUMDGlobal1.ts @@ -0,0 +1,19 @@ +/// + +// @Filename: a/f1.ts +//// [|import { bar } from "./foo"; +//// +//// export function test() { }; +//// bar1/*0*/.bar();|] + +// @Filename: a/foo.d.ts +//// export declare function bar(): number; +//// export as namespace bar1; + +verify.importFixAtPosition([ +`import { bar } from "./foo"; +import * as bar1 from "./foo"; + +export function test() { }; +bar1.bar();` +]); \ No newline at end of file