mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-06 04:35:21 -05:00
Merge pull request #15239 from Microsoft/typeof-can-refer-class-before-declaration
Typeof can refer to block-scoped values before declaration
This commit is contained in:
@@ -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 <ImportEqualsDeclaration>node;
|
||||
}
|
||||
|
||||
while (node && node.kind !== SyntaxKind.ImportDeclaration) {
|
||||
node = node.parent;
|
||||
}
|
||||
return <ImportDeclaration>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 && (<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 && (<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 <ModuleDeclaration | EnumDeclaration>n;
|
||||
}
|
||||
}
|
||||
return findAncestor(node.parent, n => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol) as ModuleDeclaration | EnumDeclaration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T, U>(arrayA: T[], arrayB: U[], callback: (a: T, b: U, index: number) => void): void {
|
||||
Debug.assert(arrayA.length === arrayB.length);
|
||||
|
||||
Reference in New Issue
Block a user