diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a46a6ed74bf..a1a7e519758 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -727,7 +727,7 @@ namespace ts { if (declarationFile !== useFile) { if ((modulekind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || (!compilerOptions.outFile && !compilerOptions.out)) { - // nodes are in different files and order cannot be determines + // nodes are in different files and order cannot be determined return true; } // declaration is after usage @@ -745,7 +745,7 @@ namespace ts { // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; if (errorBindingElement) { - return getAncestorBindingPattern(errorBindingElement) !== getAncestorBindingPattern(declaration) || + return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || declaration.pos < errorBindingElement.pos; } // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) @@ -764,13 +764,15 @@ namespace ts { // 2. inside a function // 3. inside an instance property initializer, a reference to a non-instance property // 4. inside a static property initializer, a reference to a static method in the same class + // or if usage is in a type context: + // 1. inside a type query (typeof in type position) if (usage.parent.kind === SyntaxKind.ExportSpecifier) { // export specifiers do not use the variable, they only make it available for use return true; } const container = getEnclosingBlockScopeContainer(declaration); - return isUsedInFunctionOrInstanceProperty(usage, declaration, container); + return isInTypeQuery(usage) || isUsedInFunctionOrInstanceProperty(usage, declaration, container); function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { const container = getEnclosingBlockScopeContainer(declaration); @@ -800,12 +802,10 @@ namespace ts { } function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean { - let current = usage; - while (current) { + return !!findAncestor(usage, current => { if (current === container) { - return false; + return "quit"; } - if (isFunctionLike(current)) { return true; } @@ -827,20 +827,7 @@ namespace ts { } } } - - current = current.parent; - } - return false; - } - - function getAncestorBindingPattern(node: Node): BindingPattern { - while (node) { - if (isBindingPattern(node)) { - return node; - } - node = node.parent; - } - return undefined; + }); } } @@ -1263,15 +1250,7 @@ namespace ts { * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. */ function isSameScopeDescendentOf(initial: Node, parent: Node, stopAt: Node): boolean { - if (!parent) { - return false; - } - for (let current = initial; current && current !== stopAt && !isFunctionLike(current); current = current.parent) { - if (current === parent) { - return true; - } - } - return false; + return parent && !!findAncestor(initial, n => n === stopAt || isFunctionLike(n) ? "quit" : n === parent); } function getAnyImportSyntax(node: Node): AnyImportSyntax { @@ -1280,10 +1259,7 @@ namespace ts { return node; } - while (node && node.kind !== SyntaxKind.ImportDeclaration) { - node = node.parent; - } - return node; + return findAncestor(node, n => n.kind === SyntaxKind.ImportDeclaration) as ImportDeclaration; } } @@ -2145,11 +2121,8 @@ namespace ts { return { accessibility: SymbolAccessibility.Accessible }; function getExternalModuleContainer(declaration: Node) { - for (; declaration; declaration = declaration.parent) { - if (hasExternalModuleSymbol(declaration)) { - return getSymbolOfNode(declaration); - } - } + const node = findAncestor(declaration, hasExternalModuleSymbol); + return node && getSymbolOfNode(node); } } @@ -2893,10 +2866,7 @@ namespace ts { function getTypeAliasForTypeLiteral(type: Type): Symbol { if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) { - let node = type.symbol.declarations[0].parent; - while (node.kind === SyntaxKind.ParenthesizedType) { - node = node.parent; - } + const node = findAncestor(type.symbol.declarations[0].parent, n => n.kind !== SyntaxKind.ParenthesizedType); if (node.kind === SyntaxKind.TypeAliasDeclaration) { return getSymbolOfNode(node); } @@ -3840,8 +3810,7 @@ namespace ts { } function getDeclarationContainer(node: Node): Node { - node = getRootDeclaration(node); - while (node) { + node = findAncestor(getRootDeclaration(node), node => { switch (node.kind) { case SyntaxKind.VariableDeclaration: case SyntaxKind.VariableDeclarationList: @@ -3849,13 +3818,12 @@ namespace ts { case SyntaxKind.NamedImports: case SyntaxKind.NamespaceImport: case SyntaxKind.ImportClause: - node = node.parent; - break; - + return false; default: - return node.parent; + return true; } - } + }); + return node && node.parent; } function getTypeOfPrototypeProperty(prototype: Symbol): Type { @@ -7927,8 +7895,10 @@ namespace ts { // Starting with the parent of the symbol's declaration, check if the mapper maps any of // the type parameters introduced by enclosing declarations. We just pick the first // declaration since multiple declarations will all have the same parent anyway. - let node: Node = symbol.declarations[0]; - while (node) { + return !!findAncestor(symbol.declarations[0], node => { + if (node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile) { + return "quit"; + } switch (node.kind) { case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: @@ -7975,13 +7945,8 @@ namespace ts { } } break; - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.SourceFile: - return false; } - node = node.parent; - } - return false; + }); } function isTopLevelTypeAlias(symbol: Symbol) { @@ -10445,19 +10410,9 @@ namespace ts { // TypeScript 1.0 spec (April 2014): 3.6.3 // A type query consists of the keyword typeof followed by an expression. // The expression is restricted to a single identifier or a sequence of identifiers separated by periods - while (node) { - switch (node.kind) { - case SyntaxKind.TypeQuery: - return true; - case SyntaxKind.Identifier: - case SyntaxKind.QualifiedName: - node = node.parent; - continue; - default: - return false; - } - } - Debug.fail("should not get here"); + return !!findAncestor( + node, + n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); } // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers @@ -11680,15 +11635,11 @@ namespace ts { } function getControlFlowContainer(node: Node): Node { - while (true) { - node = node.parent; - if (isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || - node.kind === SyntaxKind.ModuleBlock || - node.kind === SyntaxKind.SourceFile || - node.kind === SyntaxKind.PropertyDeclaration) { - return node; - } - } + return findAncestor(node.parent, node => + isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || + node.kind === SyntaxKind.ModuleBlock || + node.kind === SyntaxKind.SourceFile || + node.kind === SyntaxKind.PropertyDeclaration); } // Check if a parameter is assigned anywhere within its declaring function. @@ -11705,15 +11656,7 @@ namespace ts { } function hasParentWithAssignmentsMarked(node: Node) { - while (true) { - node = node.parent; - if (!node) { - return false; - } - if (isFunctionLike(node) && getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked) { - return true; - } - } + return !!findAncestor(node.parent, node => isFunctionLike(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); } function markParameterAssignments(node: Node) { @@ -11885,15 +11828,7 @@ namespace ts { } function isInsideFunction(node: Node, threshold: Node): boolean { - let current = node; - while (current && current !== threshold) { - if (isFunctionLike(current)) { - return true; - } - current = current.parent; - } - - return false; + return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n)); } function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { @@ -11945,8 +11880,8 @@ namespace ts { } function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { - let current: Node = node; // skip parenthesized nodes + let current: Node = node; while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { current = current.parent; } @@ -11967,15 +11902,7 @@ namespace ts { // at this point we know that node is the target of assignment // now check that modification happens inside the statement part of the ForStatement - while (current !== container) { - if (current === container.statement) { - return true; - } - else { - current = current.parent; - } - } - return false; + return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); } function captureLexicalThis(node: Node, container: Node): void { @@ -12157,12 +12084,7 @@ namespace ts { } function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { - for (let n = node; n && n !== constructorDecl; n = n.parent) { - if (n.kind === SyntaxKind.Parameter) { - return true; - } - } - return false; + return !!findAncestor(node, n => n === constructorDecl ? "quit" : n.kind === SyntaxKind.Parameter); } function checkSuperExpression(node: Node): Type { @@ -12188,10 +12110,7 @@ namespace ts { // class B { // [super.foo()]() {} // } - let current = node; - while (current && current !== container && current.kind !== SyntaxKind.ComputedPropertyName) { - current = current.parent; - } + const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); if (current && current.kind === SyntaxKind.ComputedPropertyName) { error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); } @@ -12795,13 +12714,8 @@ namespace ts { } function getContextualMapper(node: Node) { - while (node) { - if (node.contextualMapper) { - return node.contextualMapper; - } - node = node.parent; - } - return identityMapper; + node = findAncestor(node, n => !!n.contextualMapper); + return node ? node.contextualMapper : identityMapper; } // If the given type is an object or union type, if that type has a single signature, and if @@ -19039,8 +18953,7 @@ namespace ts { // this function will run after checking the source file so 'CaptureThis' is correct for all nodes function checkIfThisIsCapturedInEnclosingScope(node: Node): void { - let current = node; - while (current) { + findAncestor(node, current => { if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { const isDeclaration = node.kind !== SyntaxKind.Identifier; if (isDeclaration) { @@ -19049,15 +18962,13 @@ namespace ts { else { error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); } - return; + return true; } - current = current.parent; - } + }); } function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { - let current = node; - while (current) { + findAncestor(node, current => { if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { const isDeclaration = node.kind !== SyntaxKind.Identifier; if (isDeclaration) { @@ -19066,10 +18977,9 @@ namespace ts { else { error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); } - return; + return true; } - current = current.parent; - } + }); } function checkCollisionWithCapturedSuperVariable(node: Node, name: Identifier) { @@ -19253,19 +19163,20 @@ namespace ts { return; } // - parameter is wrapped in function-like entity - let current = n; - while (current !== node.initializer) { - if (isFunctionLike(current.parent)) { - return; - } - // computed property names/initializers in instance property declaration of class like entities - // are executed in constructor and thus deferred - if (current.parent.kind === SyntaxKind.PropertyDeclaration && - !(hasModifier(current.parent, ModifierFlags.Static)) && - isClassLike(current.parent.parent)) { - return; - } - current = current.parent; + if (findAncestor( + n, + current => { + if (current === node.initializer) { + return "quit"; + } + return isFunctionLike(current.parent) || + // computed property names/initializers in instance property declaration of class like entities + // are executed in constructor and thus deferred + (current.parent.kind === SyntaxKind.PropertyDeclaration && + !(hasModifier(current.parent, ModifierFlags.Static)) && + isClassLike(current.parent.parent)); + })) { + return; } // fall through to report error } @@ -20063,18 +19974,17 @@ namespace ts { function checkLabeledStatement(node: LabeledStatement) { // Grammar checking if (!checkGrammarStatementInAmbientContext(node)) { - let current = node.parent; - while (current) { - if (isFunctionLike(current)) { - break; - } - if (current.kind === SyntaxKind.LabeledStatement && (current).label.text === node.label.text) { - const sourceFile = getSourceFileOfNode(node); - grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNodeFromSourceText(sourceFile.text, node.label)); - break; - } - current = current.parent; - } + findAncestor(node.parent, + current => { + if (isFunctionLike(current)) { + return "quit"; + } + if (current.kind === SyntaxKind.LabeledStatement && (current).label.text === node.label.text) { + const sourceFile = getSourceFileOfNode(node); + grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNodeFromSourceText(sourceFile.text, node.label)); + return true; + } + }); } // ensure that label is unique @@ -22329,11 +22239,7 @@ namespace ts { const symbolIsUmdExport = symbolFile !== referenceFile; return symbolIsUmdExport ? undefined : symbolFile; } - for (let n = node.parent; n; n = n.parent) { - if (isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol) { - return n; - } - } + return findAncestor(node.parent, n => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol) as ModuleDeclaration | EnumDeclaration; } } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 5296a25d6cf..3c867024485 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -224,6 +224,25 @@ namespace ts { } return undefined; } + /** + * Iterates through the parent chain of a node and performs the callback on each parent until the callback + * returns a truthy value, then returns that value. + * If no such value is found, it applies the callback until the parent pointer is undefined or the callback returns "quit" + * At that point findAncestor returns undefined. + */ + export function findAncestor(node: Node, callback: (element: Node) => boolean | "quit"): Node { + while (node) { + const result = callback(node); + if (result === "quit") { + return undefined; + } + else if (result) { + return node; + } + node = node.parent; + } + return undefined; + } export function zipWith(arrayA: T[], arrayB: U[], callback: (a: T, b: U, index: number) => void): void { Debug.assert(arrayA.length === arrayB.length); diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.js b/tests/baselines/reference/typeofUsedBeforeBlockScoped.js new file mode 100644 index 00000000000..eebf4beae67 --- /dev/null +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.js @@ -0,0 +1,19 @@ +//// [typeofUsedBeforeBlockScoped.ts] +type T = typeof C & typeof C.s & typeof o & typeof o.n; +class C { + static s = 2; +} +type W = typeof o.n; +let o2: typeof o; +let o = { n: 12 }; + + +//// [typeofUsedBeforeBlockScoped.js] +var C = (function () { + function C() { + } + return C; +}()); +C.s = 2; +var o2; +var o = { n: 12 }; diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols b/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols new file mode 100644 index 00000000000..5cf09a4d4d5 --- /dev/null +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols @@ -0,0 +1,32 @@ +=== tests/cases/compiler/typeofUsedBeforeBlockScoped.ts === +type T = typeof C & typeof C.s & typeof o & typeof o.n; +>T : Symbol(T, Decl(typeofUsedBeforeBlockScoped.ts, 0, 0)) +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 0, 55)) +>C.s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 1, 9)) +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 0, 55)) +>s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 1, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>o.n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) + +class C { +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 0, 55)) + + static s = 2; +>s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 1, 9)) +} +type W = typeof o.n; +>W : Symbol(W, Decl(typeofUsedBeforeBlockScoped.ts, 3, 1)) +>o.n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) + +let o2: typeof o; +>o2 : Symbol(o2, Decl(typeofUsedBeforeBlockScoped.ts, 5, 3)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) + +let o = { n: 12 }; +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) + diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.types b/tests/baselines/reference/typeofUsedBeforeBlockScoped.types new file mode 100644 index 00000000000..ec8e30bb135 --- /dev/null +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.types @@ -0,0 +1,35 @@ +=== tests/cases/compiler/typeofUsedBeforeBlockScoped.ts === +type T = typeof C & typeof C.s & typeof o & typeof o.n; +>T : T +>C : typeof C +>C.s : number +>C : typeof C +>s : number +>o : { n: number; } +>o.n : number +>o : { n: number; } +>n : number + +class C { +>C : C + + static s = 2; +>s : number +>2 : 2 +} +type W = typeof o.n; +>W : number +>o.n : number +>o : { n: number; } +>n : number + +let o2: typeof o; +>o2 : { n: number; } +>o : { n: number; } + +let o = { n: 12 }; +>o : { n: number; } +>{ n: 12 } : { n: number; } +>n : number +>12 : 12 + diff --git a/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts b/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts new file mode 100644 index 00000000000..3ab1534875b --- /dev/null +++ b/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts @@ -0,0 +1,7 @@ +type T = typeof C & typeof C.s & typeof o & typeof o.n; +class C { + static s = 2; +} +type W = typeof o.n; +let o2: typeof o; +let o = { n: 12 };