Allow special element access assignments to create declarations (#33537)

* Start enabling element access special assignment

* Treat element access assignment as special assignment in JS

* Make declarations for bindable element access expressions

* Fix navigationBar crash

* Add multi-level test for JS

* Propagate element access expressions to more code paths

* Fix property access on `this`

* Add quick info test

* Uhhh I guess this is fine

* Fix module["exports"] and property access chained off element access

* Add test for this property assignment

* Add test for and fix prototype property assignment

* Fix teeeest???

* Update APIs

* Fix element access declarations on `this`

* Fix go-to-definition

* Add declaration emit to tests

* Reconcile with late-bound symbol element access assignment

* Fix baselines

* Add JS declaration back to tests

* Fix JS declaration emit of non-late-bound string literal property names

* Revert accidental auto-format

* Use `isAccessExpression`

* Add underscore escaping member to test

* Fix and test navBar changes
This commit is contained in:
Andrew Branch
2019-09-30 15:08:44 -05:00
committed by GitHub
parent 053388d6d2
commit f89de95955
31 changed files with 1078 additions and 129 deletions
+42 -44
View File
@@ -2333,17 +2333,18 @@ namespace ts {
return checkStrictModeIdentifier(<Identifier>node);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
if (currentFlow && isNarrowableReference(<Expression>node)) {
node.flowNode = currentFlow;
const expr = node as PropertyAccessExpression | ElementAccessExpression;
if (currentFlow && isNarrowableReference(expr)) {
expr.flowNode = currentFlow;
}
if (isSpecialPropertyDeclaration(node as PropertyAccessExpression)) {
bindSpecialPropertyDeclaration(node as PropertyAccessExpression);
if (isSpecialPropertyDeclaration(expr)) {
bindSpecialPropertyDeclaration(expr);
}
if (isInJSFile(node) &&
if (isInJSFile(expr) &&
file.commonJsModuleIndicator &&
isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) &&
isModuleExportsAccessExpression(expr) &&
!lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) {
declareSymbol(file.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
declareSymbol(file.locals!, /*parent*/ undefined, expr.expression,
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
}
break;
@@ -2351,22 +2352,22 @@ namespace ts {
const specialKind = getAssignmentDeclarationKind(node as BinaryExpression);
switch (specialKind) {
case AssignmentDeclarationKind.ExportsProperty:
bindExportsPropertyAssignment(node as BinaryExpression);
bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.ModuleExports:
bindModuleExportsAssignment(node as BinaryExpression);
bindModuleExportsAssignment(node as BindablePropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.PrototypeProperty:
bindPrototypePropertyAssignment((node as BinaryExpression).left as PropertyAccessEntityNameExpression, node);
bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node);
break;
case AssignmentDeclarationKind.Prototype:
bindPrototypeAssignment(node as BinaryExpression);
bindPrototypeAssignment(node as BindableStaticPropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.ThisProperty:
bindThisPropertyAssignment(node as BinaryExpression);
bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.Property:
bindSpecialPropertyAssignment(node as BinaryExpression);
bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.None:
// Nothing to do
@@ -2641,14 +2642,13 @@ namespace ts {
}
}
function bindExportsPropertyAssignment(node: BinaryExpression) {
function bindExportsPropertyAssignment(node: BindableStaticPropertyAssignmentExpression) {
// When we create a property via 'exports.foo = bar', the 'exports.foo' property access
// expression is the declaration
if (!setCommonJsModuleIndicator(node)) {
return;
}
const lhs = node.left as PropertyAccessEntityNameExpression;
const symbol = forEachIdentifierInEntityName(lhs.expression, /*parent*/ undefined, (id, symbol) => {
const symbol = forEachIdentifierInEntityName(node.left.expression, /*parent*/ undefined, (id, symbol) => {
if (symbol) {
addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment);
}
@@ -2658,11 +2658,11 @@ namespace ts {
const flags = isClassExpression(node.right) ?
SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.Class :
SymbolFlags.Property | SymbolFlags.ExportValue;
declareSymbol(symbol.exports!, symbol, lhs, flags, SymbolFlags.None);
declareSymbol(symbol.exports!, symbol, node.left, flags, SymbolFlags.None);
}
}
function bindModuleExportsAssignment(node: BinaryExpression) {
function bindModuleExportsAssignment(node: BindablePropertyAssignmentExpression) {
// A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports'
// is still pointing to 'module.exports'.
// We do not want to consider this as 'export=' since a module can have only one of these.
@@ -2682,7 +2682,7 @@ namespace ts {
declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None);
}
function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {
function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) {
Debug.assert(isInJSFile(node));
const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false);
switch (thisContainer.kind) {
@@ -2692,7 +2692,7 @@ namespace ts {
// For `f.prototype.m = function() { this.x = 0; }`, `this.x = 0` should modify `f`'s members, not the function expression.
if (isBinaryExpression(thisContainer.parent) && thisContainer.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
const l = thisContainer.parent.left;
if (isPropertyAccessEntityNameExpression(l) && isPrototypeAccess(l.expression)) {
if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) {
constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer);
}
}
@@ -2754,11 +2754,11 @@ namespace ts {
}
}
function bindSpecialPropertyDeclaration(node: PropertyAccessExpression) {
function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) {
if (node.expression.kind === SyntaxKind.ThisKeyword) {
bindThisPropertyAssignment(node);
}
else if (isPropertyAccessEntityNameExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) {
else if (isBindableStaticAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) {
if (isPrototypeAccess(node.expression)) {
bindPrototypePropertyAssignment(node, node.parent);
}
@@ -2769,11 +2769,10 @@ namespace ts {
}
/** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */
function bindPrototypeAssignment(node: BinaryExpression) {
function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) {
node.left.parent = node;
node.right.parent = node;
const lhs = node.left as PropertyAccessEntityNameExpression;
bindPropertyAssignment(lhs.expression, lhs, /*isPrototypeProperty*/ false, /*containerIsClass*/ true);
bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true);
}
function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) {
@@ -2785,10 +2784,10 @@ namespace ts {
* For `x.prototype.y = z`, declare a member `y` on `x` if `x` is a function or class, or not declared.
* Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration.
*/
function bindPrototypePropertyAssignment(lhs: PropertyAccessEntityNameExpression, parent: Node) {
function bindPrototypePropertyAssignment(lhs: BindableStaticAccessExpression, parent: Node) {
// Look up the function in the local scope, since prototype assignments should
// follow the function declaration
const classPrototype = lhs.expression as PropertyAccessEntityNameExpression;
const classPrototype = lhs.expression as BindableStaticAccessExpression;
const constructorFunction = classPrototype.expression;
// Fix up parent pointers since we're going to use these nodes before we bind into them
@@ -2806,30 +2805,29 @@ namespace ts {
bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false);
}
function bindSpecialPropertyAssignment(node: BinaryExpression) {
const lhs = node.left as PropertyAccessEntityNameExpression;
function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) {
// Class declarations in Typescript do not allow property declarations
const parentSymbol = lookupSymbolForPropertyAccess(lhs.expression);
const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression);
if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) {
return;
}
// Fix up parent pointers since we're going to use these nodes before we bind into them
node.left.parent = node;
node.right.parent = node;
if (isIdentifier(lhs.expression) && container === file && isExportsOrModuleExportsOrAlias(file, lhs.expression)) {
if (isIdentifier(node.left.expression) && container === file && isExportsOrModuleExportsOrAlias(file, node.left.expression)) {
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
// var util = module.exports;
// util.property = function ...
bindExportsPropertyAssignment(node);
bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression);
}
else {
if (hasDynamicName(node)) {
bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed);
const sym = bindPotentiallyMissingNamespaces(parentSymbol, lhs.expression, isTopLevelNamespaceAssignment(lhs), /*isPrototype*/ false, /*containerIsClass*/ false);
const sym = bindPotentiallyMissingNamespaces(parentSymbol, node.left.expression, isTopLevelNamespaceAssignment(node.left), /*isPrototype*/ false, /*containerIsClass*/ false);
addLateBoundAssignmentDeclarationToSymbol(node, sym);
}
else {
bindStaticPropertyAssignment(lhs);
bindStaticPropertyAssignment(cast(node.left, isBindableStaticAccessExpression));
}
}
}
@@ -2838,12 +2836,12 @@ namespace ts {
* For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function (or IIFE) or class or {}, or not declared.
* Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y;
*/
function bindStaticPropertyAssignment(node: PropertyAccessEntityNameExpression) {
function bindStaticPropertyAssignment(node: BindableStaticAccessExpression) {
node.expression.parent = node;
bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false);
}
function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: EntityNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) {
function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) {
if (isToplevel && !isPrototypeProperty) {
// make symbols or add declarations for intermediate containers
const flags = SymbolFlags.Module | SymbolFlags.Assignment;
@@ -2866,7 +2864,7 @@ namespace ts {
return namespaceSymbol;
}
function bindPotentiallyNewExpandoMemberToNamespace(declaration: PropertyAccessEntityNameExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) {
function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) {
if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) {
return;
}
@@ -2911,13 +2909,13 @@ namespace ts {
declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment);
}
function isTopLevelNamespaceAssignment(propertyAccess: PropertyAccessEntityNameExpression) {
function isTopLevelNamespaceAssignment(propertyAccess: BindableAccessExpression) {
return isBinaryExpression(propertyAccess.parent)
? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile
: propertyAccess.parent.parent.kind === SyntaxKind.SourceFile;
}
function bindPropertyAssignment(name: EntityNameExpression, propertyAccess: PropertyAccessEntityNameExpression, isPrototypeProperty: boolean, containerIsClass: boolean) {
function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) {
let namespaceSymbol = lookupSymbolForPropertyAccess(name);
const isToplevel = isTopLevelNamespaceAssignment(propertyAccess);
namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass);
@@ -2962,17 +2960,17 @@ namespace ts {
return expr.parent;
}
function lookupSymbolForPropertyAccess(node: EntityNameExpression, lookupContainer: Node = container): Symbol | undefined {
function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined {
if (isIdentifier(node)) {
return lookupSymbolForNameWorker(lookupContainer, node.escapedText);
}
else {
const symbol = lookupSymbolForPropertyAccess(node.expression);
return symbol && symbol.exports && symbol.exports.get(node.name.escapedText);
return symbol && symbol.exports && symbol.exports.get(escapeLeadingUnderscores(getElementOrPropertyAccessName(node)));
}
}
function forEachIdentifierInEntityName(e: EntityNameExpression, parent: Symbol | undefined, action: (e: Identifier, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined {
function forEachIdentifierInEntityName(e: BindableStaticNameExpression, parent: Symbol | undefined, action: (e: Declaration, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined {
if (isExportsOrModuleExportsOrAlias(file, e)) {
return file.symbol;
}
@@ -2981,7 +2979,7 @@ namespace ts {
}
else {
const s = forEachIdentifierInEntityName(e.expression, parent, action);
return action(e.name, s && s.exports && s.exports.get(e.name.escapedText), s);
return action(getNameOrArgument(e), s && s.exports && s.exports.get(escapeLeadingUnderscores(getElementOrPropertyAccessName(e))), s);
}
}
@@ -3255,7 +3253,7 @@ namespace ts {
while (q.length && i < 100) {
i++;
node = q.shift()!;
if (isExportsIdentifier(node) || isModuleExportsPropertyAccessExpression(node)) {
if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) {
return true;
}
else if (isIdentifier(node)) {
+27 -9
View File
@@ -3041,7 +3041,7 @@ namespace ts {
return getSymbolOfNode(d.parent);
}
if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) {
if (isModuleExportsPropertyAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) {
if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) {
return getSymbolOfNode(getSourceFileOfNode(d));
}
checkExpressionCached(d.parent.left.expression);
@@ -4877,6 +4877,15 @@ namespace ts {
}
}
function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) {
const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context);
if (fromNameType) {
return fromNameType;
}
const rawName = unescapeLeadingUnderscores(symbol.escapedName);
return createPropertyNameNodeForIdentifierOrLiteral(rawName);
}
// See getNameForSymbolFromNameType for a stringy equivalent
function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext) {
const nameType = symbol.nameType;
@@ -4889,7 +4898,7 @@ namespace ts {
if (isNumericLiteralName(name) && startsWith(name, "-")) {
return createComputedPropertyName(createLiteral(+name));
}
return isIdentifierText(name, compilerOptions.target) ? createIdentifier(name) : createLiteral(isNumericLiteralName(name) ? +name : name) as StringLiteral | NumericLiteral;
return createPropertyNameNodeForIdentifierOrLiteral(name);
}
if (nameType.flags & TypeFlags.UniqueESSymbol) {
return createComputedPropertyName(symbolToExpression((<UniqueESSymbolType>nameType).symbol, context, SymbolFlags.Value));
@@ -4897,6 +4906,10 @@ namespace ts {
}
}
function createPropertyNameNodeForIdentifierOrLiteral(name: string) {
return isIdentifierText(name, compilerOptions.target) ? createIdentifier(name) : createLiteral(isNumericLiteralName(name) ? +name : name) as StringLiteral | NumericLiteral;
}
function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext {
const initial: NodeBuilderContext = { ...context };
// Make type parameters created within this context not consume the name outside this context
@@ -5772,8 +5785,7 @@ namespace ts {
return [];
}
const staticFlag = isStatic ? ModifierFlags.Static : 0;
const rawName = unescapeLeadingUnderscores(p.escapedName);
const name = getPropertyNameNodeForSymbolFromNameType(p, context) || createIdentifier(rawName);
const name = getPropertyNameNodeForSymbol(p, context);
const firstPropertyLikeDecl = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression));
if (p.flags & SymbolFlags.Accessor && useAccessors) {
const result: AccessorDeclaration[] = [];
@@ -6869,13 +6881,15 @@ namespace ts {
let types: Type[] | undefined;
for (const declaration of symbol.declarations) {
const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration :
isPropertyAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration :
isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration :
undefined;
if (!expression) {
continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere
}
const kind = isPropertyAccessExpression(expression) ? getAssignmentDeclarationPropertyAccessKind(expression) : getAssignmentDeclarationKind(expression);
const kind = isAccessExpression(expression)
? getAssignmentDeclarationPropertyAccessKind(expression)
: getAssignmentDeclarationKind(expression);
if (kind === AssignmentDeclarationKind.ThisProperty) {
if (isDeclarationInConstructor(expression)) {
definedInConstructor = true;
@@ -7250,12 +7264,15 @@ namespace ts {
else if (
isBinaryExpression(declaration) ||
(isInJSFile(declaration) &&
(isCallExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent)))) {
(isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) {
type = getWidenedTypeForAssignmentDeclaration(symbol);
}
else if (isJSDocPropertyLikeTag(declaration)
|| isPropertyAccessExpression(declaration)
|| isElementAccessExpression(declaration)
|| isIdentifier(declaration)
|| isStringLiteralLike(declaration)
|| isNumericLiteral(declaration)
|| isClassDeclaration(declaration)
|| isFunctionDeclaration(declaration)
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
@@ -7436,7 +7453,8 @@ namespace ts {
return anyType;
}
else if (declaration.kind === SyntaxKind.BinaryExpression ||
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
(declaration.kind === SyntaxKind.PropertyAccessExpression || declaration.kind === SyntaxKind.ElementAccessExpression) &&
declaration.parent.kind === SyntaxKind.BinaryExpression) {
return getWidenedTypeForAssignmentDeclaration(symbol);
}
else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) {
@@ -32134,7 +32152,7 @@ namespace ts {
return node;
case SyntaxKind.PropertyAccessExpression:
do {
if (isModuleExportsPropertyAccessExpression(node.expression)) {
if (isModuleExportsAccessExpression(node.expression)) {
return node.name;
}
node = node.expression;
+1 -1
View File
@@ -2280,7 +2280,7 @@ namespace ts {
// if the expression doesn't have any comments that will be emitted.
return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!);
}
else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) {
else if (isAccessExpression(expression)) {
// check if constant enum value is integer
const constantValue = getConstantValue(expression);
// isFinite handles cases when constantValue is undefined
+30 -4
View File
@@ -1306,7 +1306,7 @@ namespace ts {
literal: BooleanLiteral | LiteralExpression | PrefixUnaryExpression;
}
export interface StringLiteral extends LiteralExpression {
export interface StringLiteral extends LiteralExpression, Declaration {
kind: SyntaxKind.StringLiteral;
/* @internal */ textSourceNode?: Identifier | StringLiteralLike | NumericLiteral; // Allows a StringLiteral to get its text from another node (used by transforms).
/** Note: this is only set when synthesizing a node, not during parsing. */
@@ -1693,7 +1693,7 @@ namespace ts {
kind: SyntaxKind.RegularExpressionLiteral;
}
export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode {
export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode, Declaration {
kind: SyntaxKind.NoSubstitutionTemplateLiteral;
}
@@ -1722,7 +1722,7 @@ namespace ts {
NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinaryOrOctalSpecifier | ContainsSeparator
}
export interface NumericLiteral extends LiteralExpression {
export interface NumericLiteral extends LiteralExpression, Declaration {
kind: SyntaxKind.NumericLiteral;
/* @internal */
numericLiteralFlags: TokenFlags;
@@ -1885,7 +1885,33 @@ namespace ts {
;
/** @internal */
export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } };
export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: BindableStaticNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } };
/** @internal */
export type BindableStaticNameExpression = EntityNameExpression | BindableStaticElementAccessExpression;
/** @internal */
export type LiteralLikeElementAccessExpression = ElementAccessExpression & Declaration & {
argumentExpression: StringLiteralLike | NumericLiteral;
};
/** @internal */
export type BindableStaticElementAccessExpression = LiteralLikeElementAccessExpression & {
expression: BindableStaticNameExpression;
};
/** @internal */
export type BindableElementAccessExpression = ElementAccessExpression & {
expression: BindableStaticNameExpression;
};
/** @internal */
export type BindableStaticAccessExpression = PropertyAccessEntityNameExpression | BindableStaticElementAccessExpression;
/** @internal */
export type BindableAccessExpression = PropertyAccessEntityNameExpression | BindableElementAccessExpression;
/** @internal */
export interface BindableStaticPropertyAssignmentExpression extends BinaryExpression {
left: BindableStaticAccessExpression;
}
/** @internal */
export interface BindablePropertyAssignmentExpression extends BinaryExpression {
left: BindableAccessExpression;
}
// see: https://tc39.github.io/ecma262/#prod-SuperCall
export interface SuperCall extends CallExpression {
+85 -42
View File
@@ -1875,7 +1875,7 @@ namespace ts {
decl = name;
}
if (!name || !isEntityNameExpression(name) || !isSameEntityName(name, node.parent.left)) {
if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, node.parent.left)) {
return undefined;
}
}
@@ -1887,7 +1887,7 @@ namespace ts {
}
export function isAssignmentDeclaration(decl: Declaration) {
return isBinaryExpression(decl) || isPropertyAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl);
return isBinaryExpression(decl) || isAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl);
}
/** Get the initializer, taking into account defaulted Javascript initializers */
@@ -1907,18 +1907,23 @@ namespace ts {
}
function hasExpandoValueProperty(node: ObjectLiteralExpression, isPrototypeAssignment: boolean) {
return forEach(node.properties, p => isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === "value" && p.initializer && getExpandoInitializer(p.initializer, isPrototypeAssignment));
return forEach(node.properties, p =>
isPropertyAssignment(p) &&
isIdentifier(p.name) &&
p.name.escapedText === "value" &&
p.initializer &&
getExpandoInitializer(p.initializer, isPrototypeAssignment));
}
/**
* Get the assignment 'initializer' -- the righthand side-- when the initializer is container-like (See getExpandoInitializer).
* We treat the right hand side of assignments with container-like initalizers as declarations.
*/
export function getAssignedExpandoInitializer(node: Node | undefined) {
export function getAssignedExpandoInitializer(node: Node | undefined): Expression | undefined {
if (node && node.parent && isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
const isPrototypeAssignment = isPrototypeAccess(node.parent.left);
return getExpandoInitializer(node.parent.right, isPrototypeAssignment) ||
getDefaultedExpandoInitializer(node.parent.left as EntityNameExpression, node.parent.right, isPrototypeAssignment);
getDefaultedExpandoInitializer(node.parent.left, node.parent.right, isPrototypeAssignment);
}
if (node && isCallExpression(node) && isBindableObjectDefinePropertyCall(node)) {
const result = hasExpandoValueProperty(node.arguments[2], node.arguments[1].text === "prototype");
@@ -1961,9 +1966,9 @@ namespace ts {
* The second Lhs is required to be the same as the first except that it may be prefixed with
* 'window.', 'global.' or 'self.' The second Lhs is otherwise ignored by the binder and checker.
*/
function getDefaultedExpandoInitializer(name: EntityNameExpression, initializer: Expression, isPrototypeAssignment: boolean) {
function getDefaultedExpandoInitializer(name: Expression, initializer: Expression, isPrototypeAssignment: boolean) {
const e = isBinaryExpression(initializer) && initializer.operatorToken.kind === SyntaxKind.BarBarToken && getExpandoInitializer(initializer.right, isPrototypeAssignment);
if (e && isSameEntityName(name, (initializer as BinaryExpression).left as EntityNameExpression)) {
if (e && isSameEntityName(name, (initializer as BinaryExpression).left)) {
return e;
}
}
@@ -1998,19 +2003,20 @@ namespace ts {
* my.app = self.my.app || class { }
*/
function isSameEntityName(name: Expression, initializer: Expression): boolean {
if (isIdentifier(name) && isIdentifier(initializer)) {
return name.escapedText === initializer.escapedText;
if (isPropertyNameLiteral(name) && isPropertyNameLiteral(initializer)) {
return getTextOfIdentifierOrLiteral(name) === getTextOfIdentifierOrLiteral(name);
}
if (isIdentifier(name) && isPropertyAccessExpression(initializer)) {
return (initializer.expression.kind as SyntaxKind.ThisKeyword === SyntaxKind.ThisKeyword ||
if (isIdentifier(name) && (isLiteralLikeAccess(initializer))) {
return (initializer.expression.kind === SyntaxKind.ThisKeyword ||
isIdentifier(initializer.expression) &&
(initializer.expression.escapedText === "window" as __String ||
initializer.expression.escapedText === "self" as __String ||
initializer.expression.escapedText === "global" as __String)) &&
isSameEntityName(name, initializer.name);
(initializer.expression.escapedText === "window" ||
initializer.expression.escapedText === "self" ||
initializer.expression.escapedText === "global")) &&
isSameEntityName(name, getNameOrArgument(initializer));
}
if (isPropertyAccessExpression(name) && isPropertyAccessExpression(initializer)) {
return name.name.escapedText === initializer.name.escapedText && isSameEntityName(name.expression, initializer.expression);
if (isLiteralLikeAccess(name) && isLiteralLikeAccess(initializer)) {
return getElementOrPropertyAccessName(name) === getElementOrPropertyAccessName(initializer)
&& isSameEntityName(name.expression, initializer.expression);
}
return false;
}
@@ -2026,8 +2032,11 @@ namespace ts {
return isIdentifier(node) && node.escapedText === "exports";
}
export function isModuleExportsPropertyAccessExpression(node: Node) {
return isPropertyAccessExpression(node) && isIdentifier(node.expression) && node.expression.escapedText === "module" && node.name.escapedText === "exports";
export function isModuleExportsAccessExpression(node: Node): node is LiteralLikeElementAccessExpression & { expression: Identifier } {
return (isPropertyAccessExpression(node) || isLiteralLikeElementAccess(node))
&& isIdentifier(node.expression)
&& node.expression.escapedText === "module"
&& getElementOrPropertyAccessName(node) === "exports";
}
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
@@ -2044,7 +2053,38 @@ namespace ts {
idText(expr.expression.expression) === "Object" &&
idText(expr.expression.name) === "defineProperty" &&
isStringOrNumericLiteralLike(expr.arguments[1]) &&
isEntityNameExpression(expr.arguments[0]);
isBindableStaticNameExpression(expr.arguments[0]);
}
export function isBindableStaticElementAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticElementAccessExpression {
return isLiteralLikeElementAccess(node)
&& ((!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword) ||
isEntityNameExpression(node.expression) ||
isBindableStaticElementAccessExpression(node.expression, /*excludeThisKeyword*/ true));
}
export function isLiteralLikeAccess(node: Node): node is LiteralLikeElementAccessExpression | PropertyAccessExpression {
return isPropertyAccessExpression(node) || isLiteralLikeElementAccess(node);
}
export function isLiteralLikeElementAccess(node: Node): node is LiteralLikeElementAccessExpression {
return isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression);
}
export function isBindableStaticAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticAccessExpression {
return isPropertyAccessExpression(node) && (!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword || isBindableStaticNameExpression(node.expression, /*excludeThisKeyword*/ true))
|| isBindableStaticElementAccessExpression(node, excludeThisKeyword);
}
export function isBindableStaticNameExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticNameExpression {
return isEntityNameExpression(node) || isBindableStaticAccessExpression(node, excludeThisKeyword);
}
export function getNameOrArgument(expr: PropertyAccessExpression | LiteralLikeElementAccessExpression) {
if (isPropertyAccessExpression(expr)) {
return expr.name;
}
return expr.argumentExpression;
}
function getAssignmentDeclarationKindWorker(expr: BinaryExpression | CallExpression): AssignmentDeclarationKind {
@@ -2053,26 +2093,22 @@ namespace ts {
return AssignmentDeclarationKind.None;
}
const entityName = expr.arguments[0];
if (isExportsIdentifier(entityName) || isModuleExportsPropertyAccessExpression(entityName)) {
if (isExportsIdentifier(entityName) || isModuleExportsAccessExpression(entityName)) {
return AssignmentDeclarationKind.ObjectDefinePropertyExports;
}
if (isPropertyAccessExpression(entityName) && entityName.name.escapedText === "prototype" && isEntityNameExpression(entityName.expression)) {
if (isBindableStaticAccessExpression(entityName) && getElementOrPropertyAccessName(entityName) === "prototype") {
return AssignmentDeclarationKind.ObjectDefinePrototypeProperty;
}
return AssignmentDeclarationKind.ObjectDefinePropertyValue;
}
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken) {
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || !isAccessExpression(expr.left)) {
return AssignmentDeclarationKind.None;
}
const lhs = expr.left;
if (isAccessExpression(lhs)) {
if (isEntityNameExpression(lhs.expression) && getElementOrPropertyAccessName(lhs) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) {
// F.prototype = { ... }
return AssignmentDeclarationKind.Prototype;
}
return getAssignmentDeclarationPropertyAccessKind(lhs);
if (isBindableStaticNameExpression(expr.left.expression) && getElementOrPropertyAccessName(expr.left) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) {
// F.prototype = { ... }
return AssignmentDeclarationKind.Prototype;
}
return AssignmentDeclarationKind.None;
return getAssignmentDeclarationPropertyAccessKind(expr.left);
}
/**
@@ -2092,6 +2128,8 @@ namespace ts {
}
/* @internal */
export function getElementOrPropertyAccessName(node: LiteralLikeElementAccessExpression | PropertyAccessExpression): string;
export function getElementOrPropertyAccessName(node: AccessExpression): string | undefined;
export function getElementOrPropertyAccessName(node: AccessExpression): string | undefined {
const name = getElementOrPropertyAccessArgumentExpressionOrName(node);
if (name) {
@@ -2109,22 +2147,21 @@ namespace ts {
if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
return AssignmentDeclarationKind.ThisProperty;
}
else if (isModuleExportsPropertyAccessExpression(lhs)) {
else if (isModuleExportsAccessExpression(lhs)) {
// module.exports = expr
return AssignmentDeclarationKind.ModuleExports;
}
else if (isEntityNameExpression(lhs.expression)) {
else if (isBindableStaticNameExpression(lhs.expression, /*excludeThisKeyword*/ true)) {
if (isPrototypeAccess(lhs.expression)) {
// F.G....prototype.x = expr
return AssignmentDeclarationKind.PrototypeProperty;
}
let nextToLast = lhs;
while (isAccessExpression(nextToLast.expression)) {
nextToLast = nextToLast.expression;
while (!isIdentifier(nextToLast.expression)) {
nextToLast = nextToLast.expression as Exclude<BindableStaticNameExpression, Identifier>;
}
Debug.assert(isIdentifier(nextToLast.expression));
const id = nextToLast.expression as Identifier;
const id = nextToLast.expression;
if (id.escapedText === "exports" ||
id.escapedText === "module" && getElementOrPropertyAccessName(nextToLast) === "exports") {
// exports.name = expr OR module.exports.name = expr
@@ -2148,9 +2185,10 @@ namespace ts {
return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty;
}
export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression): boolean {
export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression | ElementAccessExpression): expr is PropertyAccessExpression | LiteralLikeElementAccessExpression {
return isInJSFile(expr) &&
expr.parent && expr.parent.kind === SyntaxKind.ExpressionStatement &&
(!isElementAccessExpression(expr) || isLiteralLikeElementAccess(expr)) &&
!!getJSDocTypeTag(expr.parent);
}
@@ -4147,8 +4185,8 @@ namespace ts {
return undefined;
}
export function isPrototypeAccess(node: Node): node is PropertyAccessExpression {
return isPropertyAccessExpression(node) && node.name.escapedText === "prototype";
export function isPrototypeAccess(node: Node): node is BindableStaticAccessExpression {
return isBindableStaticAccessExpression(node) && getElementOrPropertyAccessName(node) === "prototype";
}
export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) {
@@ -5378,6 +5416,11 @@ namespace ts {
const { expression } = declaration as ExportAssignment;
return isIdentifier(expression) ? expression : undefined;
}
case SyntaxKind.ElementAccessExpression:
const expr = declaration as ElementAccessExpression;
if (isBindableStaticElementAccessExpression(expr)) {
return expr.argumentExpression;
}
}
return (declaration as NamedDeclaration).name;
}
@@ -5399,8 +5442,8 @@ namespace ts {
if (isIdentifier(node.parent.left)) {
return node.parent.left;
}
else if (isPropertyAccessExpression(node.parent.left)) {
return node.parent.left.name;
else if (isAccessExpression(node.parent.left)) {
return getElementOrPropertyAccessArgumentExpressionOrName(node.parent.left);
}
}
else if (isVariableDeclaration(node.parent) && isIdentifier(node.parent.name)) {
+11 -10
View File
@@ -135,12 +135,13 @@ namespace ts.NavigationBar {
function endNestedNodes(depth: number): void {
for (let i = 0; i < depth; i++) endNode();
}
function startNestedNodes(targetNode: Node, entityName: EntityNameExpression) {
const names: Identifier[] = [];
while (!isIdentifier(entityName)) {
const name = entityName.name;
function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) {
const names: PropertyNameLiteral[] = [];
while (!isPropertyNameLiteral(entityName)) {
const name = getNameOrArgument(entityName);
const nameText = getElementOrPropertyAccessName(entityName);
entityName = entityName.expression;
if (name.escapedText === "prototype") continue;
if (nameText === "prototype") continue;
names.push(name);
}
names.push(entityName);
@@ -333,7 +334,7 @@ namespace ts.NavigationBar {
assignmentTarget;
let depth = 0;
let className: Identifier;
let className: PropertyNameLiteral;
// If we see a prototype assignment, start tracking the target as a class
// This is only done for simple classes not nested assignments.
if (isIdentifier(prototypeAccess.expression)) {
@@ -384,16 +385,16 @@ namespace ts.NavigationBar {
}
case AssignmentDeclarationKind.Property: {
const binaryExpression = (node as BinaryExpression);
const assignmentTarget = binaryExpression.left as PropertyAccessExpression;
const assignmentTarget = binaryExpression.left as PropertyAccessExpression | BindableElementAccessExpression;
const targetFunction = assignmentTarget.expression;
if (isIdentifier(targetFunction) && assignmentTarget.name.escapedText !== "prototype" &&
if (isIdentifier(targetFunction) && getElementOrPropertyAccessName(assignmentTarget) !== "prototype" &&
trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) {
if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) {
addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction);
}
else {
else if (isBindableStaticAccessExpression(assignmentTarget)) {
startNode(binaryExpression, targetFunction);
addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, assignmentTarget.name);
addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, getNameOrArgument(assignmentTarget));
endNode();
}
return;