Type query can refer to block-scoped vars before declaration

This commit is contained in:
Nathan Shively-Sanders
2017-04-17 15:19:57 -07:00
parent e5416b5dcc
commit 03b12e88af
2 changed files with 86 additions and 167 deletions

View File

@@ -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)
@@ -763,8 +763,10 @@ namespace ts {
// 1. inside a function
// 2. inside an instance property initializer, a reference to a non-instance property
// 3. 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)
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);
@@ -794,12 +796,7 @@ namespace ts {
}
function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean {
let current = usage;
while (current) {
if (current === container) {
return false;
}
return !!findAncestor(usage, current => {
if (isFunctionLike(current)) {
return true;
}
@@ -821,20 +818,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;
}, n => n === container);
}
}
@@ -1256,15 +1240,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 === parent, n => n === stopAt || isFunctionLike(n));
}
function getAnyImportSyntax(node: Node): AnyImportSyntax {
@@ -1273,10 +1249,7 @@ namespace ts {
return <ImportEqualsDeclaration>node;
}
while (node && node.kind !== SyntaxKind.ImportDeclaration) {
node = node.parent;
}
return <ImportDeclaration>node;
return findAncestor(node, n => n.kind === SyntaxKind.ImportDeclaration) as ImportDeclaration;
}
}
@@ -2137,11 +2110,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);
}
}
@@ -2885,10 +2855,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);
}
@@ -3831,8 +3798,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:
@@ -3840,13 +3806,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 {
@@ -7918,8 +7883,7 @@ 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 => {
switch (node.kind) {
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
@@ -7966,13 +7930,9 @@ namespace ts {
}
}
break;
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.SourceFile:
return false;
}
node = node.parent;
}
return false;
},
node => node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile);
}
function isTopLevelTypeAlias(symbol: Symbol) {
@@ -10416,19 +10376,10 @@ 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,
n => n.kind !== SyntaxKind.TypeQuery && n.kind !== SyntaxKind.Identifier && n.kind !== SyntaxKind.QualifiedName);
}
// Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers
@@ -11651,15 +11602,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.
@@ -11676,15 +11623,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) {
@@ -11856,15 +11795,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, isFunctionLike, n => n === threshold);
}
function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void {
@@ -11916,8 +11847,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;
}
@@ -11938,15 +11869,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.statement, n => n === container);
}
function captureLexicalThis(node: Node, container: Node): void {
@@ -12128,12 +12051,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.kind === SyntaxKind.Parameter, n => n === constructorDecl);
}
function checkSuperExpression(node: Node): Type {
@@ -12159,10 +12077,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.kind === SyntaxKind.ComputedPropertyName, n => n === container);
if (current && current.kind === SyntaxKind.ComputedPropertyName) {
error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name);
}
@@ -12766,13 +12681,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
@@ -19010,8 +18920,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) {
@@ -19020,15 +18929,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) {
@@ -19037,10 +18944,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) {
@@ -19224,19 +19130,17 @@ 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 =>
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)),
n => n === node.initializer)) {
return;
}
// fall through to report error
}
@@ -20034,18 +19938,15 @@ 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 && (<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 (current.kind === SyntaxKind.LabeledStatement && (<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;
}
},
isFunctionLike);
}
// ensure that label is unique
@@ -22298,11 +22199,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 <ModuleDeclaration | EnumDeclaration>n;
}
}
return findAncestor(node.parent, n => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol) as ModuleDeclaration | EnumDeclaration;
}
}
}

View File

@@ -225,6 +225,28 @@ 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 'done' returns true
* At that point findAncestor returns undefined.
*/
export function findAncestor<T>(node: Node, callback: (element: Node) => T | undefined, done?: (element: Node) => boolean): Node {
if (node) {
while (node) {
if (done && done(node)) {
break;
}
if (callback(node)) {
return node;
}
node = node.parent;
}
}
return undefined;
}
export function zipWith<T, U>(arrayA: T[], arrayB: U[], callback: (a: T, b: U, index: number) => void): void {
Debug.assert(arrayA.length === arrayB.length);
for (let i = 0; i < arrayA.length; i++) {