From 7f5ce606ae44f0795a54c2a4f7e0d468e6ef44ca Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 24 Jul 2019 17:36:36 -0700 Subject: [PATCH 01/25] Start enabling element access special assignment --- src/compiler/utilities.ts | 28 +++++++++++++------ .../moduleExportsElementAccessAssignment.ts | 18 ++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1645b9339ae5f..fdf216173fc6b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2046,6 +2046,18 @@ namespace ts { isEntityNameExpression(expr.arguments[0]); } + type BindableElementAccessExpression = ElementAccessExpression & { argumentExpression: StringLiteralLike | NumericLiteral }; + function isBindableElementAccessExpression(expr: Expression): expr is BindableElementAccessExpression { + return isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression); + } + + function getNameOrArgumentText(expr: PropertyAccessExpression | BindableElementAccessExpression) { + if (isPropertyAccessExpression(expr)) { + return expr.name.escapedText; + } + return expr.argumentExpression.text; + } + function getAssignmentDeclarationKindWorker(expr: BinaryExpression | CallExpression): AssignmentDeclarationKind { if (isCallExpression(expr)) { if (!isBindableObjectDefinePropertyCall(expr)) { @@ -2061,18 +2073,18 @@ namespace ts { return AssignmentDeclarationKind.ObjectDefinePropertyValue; } if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || - !isPropertyAccessExpression(expr.left)) { + !(isPropertyAccessExpression(expr.left) || isBindableElementAccessExpression(expr.left))) { return AssignmentDeclarationKind.None; } const lhs = expr.left; - if (isEntityNameExpression(lhs.expression) && lhs.name.escapedText === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { + if (isEntityNameExpression(lhs.expression) && getNameOrArgumentText(lhs) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { // F.prototype = { ... } return AssignmentDeclarationKind.Prototype; } return getAssignmentDeclarationPropertyAccessKind(lhs); } - export function getAssignmentDeclarationPropertyAccessKind(lhs: PropertyAccessExpression): AssignmentDeclarationKind { + export function getAssignmentDeclarationPropertyAccessKind(lhs: PropertyAccessExpression | BindableElementAccessExpression): AssignmentDeclarationKind { if (lhs.expression.kind === SyntaxKind.ThisKeyword) { return AssignmentDeclarationKind.ThisProperty; } @@ -2087,13 +2099,13 @@ namespace ts { } let nextToLast = lhs; - while (isPropertyAccessExpression(nextToLast.expression)) { + while (isBindableElementAccessExpression(nextToLast.expression)) { nextToLast = nextToLast.expression; } - Debug.assert(isIdentifier(nextToLast.expression)); - const id = nextToLast.expression as Identifier; - if (id.escapedText === "exports" || - id.escapedText === "module" && nextToLast.name.escapedText === "exports") { + const twoFromLast = tryCast(nextToLast.expression, isIdentifier) || cast(nextToLast.expression, isStringOrNumericLiteralLike); + const twoFromLastText = getTextOfIdentifierOrLiteral(twoFromLast); + if (twoFromLastText === "exports" || + twoFromLastText === "module" && getNameOrArgumentText(nextToLast) === "exports") { // exports.name = expr OR module.exports.name = expr return AssignmentDeclarationKind.ExportsProperty; } diff --git a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts new file mode 100644 index 0000000000000..ef75df7872267 --- /dev/null +++ b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts @@ -0,0 +1,18 @@ +// @allowJs: true +// @noEmit: true +// @strict: true +// @checkJs: true +// @filename: mod1.js +exports.a = {}; +exports['b'] = {}; +exports['default'] = {}; +module['exports'].c = {}; +module['exports']['d'] = {}; + +// @filename: mod2.js +const mod1 = require('./mod1'); +mod1.a; +mod1.b; +mod1.c; +mod1.d; +mod1.default; From d784e57111448edd36b21281671f84905e1e25ad Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 25 Jul 2019 14:08:18 -0700 Subject: [PATCH 02/25] Treat element access assignment as special assignment in JS --- src/compiler/binder.ts | 4 +- src/compiler/checker.ts | 10 +-- src/compiler/types.ts | 5 ++ src/compiler/utilities.ts | 38 ++++++++--- ...duleExportsElementAccessAssignment.symbols | 50 ++++++++++++++ ...moduleExportsElementAccessAssignment.types | 66 +++++++++++++++++++ .../moduleExportsElementAccessAssignment.ts | 12 ++-- 7 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 tests/baselines/reference/moduleExportsElementAccessAssignment.symbols create mode 100644 tests/baselines/reference/moduleExportsElementAccessAssignment.types diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 705ec3b75f4cc..2ce2fd5ece1ba 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2483,7 +2483,7 @@ namespace ts { if (!setCommonJsModuleIndicator(node)) { return; } - const lhs = node.left as PropertyAccessEntityNameExpression; + const lhs = node.left as PropertyAccessEntityNameExpression | BindableElementAccessExpression; const symbol = forEachIdentifierInEntityName(lhs.expression, /*parent*/ undefined, (id, symbol) => { if (symbol) { addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); @@ -2494,7 +2494,7 @@ 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, lhs as any, flags, SymbolFlags.None); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 463d00c13e267..e16c16a007ff7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5455,13 +5455,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 : + (isPropertyAccessExpression(declaration) || isElementAccessExpression(declaration)) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : undefined; if (!expression) { return errorType; } - const kind = isPropertyAccessExpression(expression) ? getAssignmentDeclarationPropertyAccessKind(expression) : getAssignmentDeclarationKind(expression); + const kind = (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) + ? getAssignmentDeclarationPropertyAccessKind(expression) + : getAssignmentDeclarationKind(expression); if (kind === AssignmentDeclarationKind.ThisProperty) { if (isDeclarationInConstructor(expression)) { definedInConstructor = true; @@ -5831,8 +5833,8 @@ namespace ts { type = widenTypeForVariableLikeDeclaration(checkExpressionCached((declaration).expression), declaration); } else if (isInJSFile(declaration) && - (isCallExpression(declaration) || isBinaryExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent))) { - type = getWidenedTypeForAssignmentDeclaration(symbol); + (isCallExpression(declaration) || isBinaryExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent))) { + type = getWidenedTypeFromAssignmentDeclaration(symbol); } else if (isJSDocPropertyLikeTag(declaration) || isPropertyAccessExpression(declaration) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1828d056405c6..b45bb4413c031 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1812,6 +1812,11 @@ namespace ts { /** @internal */ export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; + /** @internal */ + export type BindableElementAccessExpression = ElementAccessExpression & { + expression: EntityNameExpression; + argumentExpression: StringLiteralLike | NumericLiteral; + }; // see: https://tc39.github.io/ecma262/#prod-SuperCall export interface SuperCall extends CallExpression { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fdf216173fc6b..fd3868ce44d20 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2046,9 +2046,10 @@ namespace ts { isEntityNameExpression(expr.arguments[0]); } - type BindableElementAccessExpression = ElementAccessExpression & { argumentExpression: StringLiteralLike | NumericLiteral }; - function isBindableElementAccessExpression(expr: Expression): expr is BindableElementAccessExpression { - return isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression); + export function isBindableElementAccessExpression(node: Node): node is BindableElementAccessExpression { + return isElementAccessExpression(node) + && isStringOrNumericLiteralLike(node.argumentExpression) + && isEntityNameExpression(node.expression); } function getNameOrArgumentText(expr: PropertyAccessExpression | BindableElementAccessExpression) { @@ -2084,7 +2085,7 @@ namespace ts { return getAssignmentDeclarationPropertyAccessKind(lhs); } - export function getAssignmentDeclarationPropertyAccessKind(lhs: PropertyAccessExpression | BindableElementAccessExpression): AssignmentDeclarationKind { + export function getAssignmentDeclarationPropertyAccessKind(lhs: PropertyAccessExpression | ElementAccessExpression): AssignmentDeclarationKind { if (lhs.expression.kind === SyntaxKind.ThisKeyword) { return AssignmentDeclarationKind.ThisProperty; } @@ -2092,20 +2093,25 @@ namespace ts { // module.exports = expr return AssignmentDeclarationKind.ModuleExports; } + // This check for EntityNameExpression prohibits multiple levels of + // element access from being treated as special assignment, e.g. + // `F["prototype"]["X"]` is not special, but `F.prototype["x"]` is. else if (isEntityNameExpression(lhs.expression)) { + if (isElementAccessExpression(lhs) && !isBindableElementAccessExpression(lhs)) { + return AssignmentDeclarationKind.None; + } if (isPrototypeAccess(lhs.expression)) { // F.G....prototype.x = expr return AssignmentDeclarationKind.PrototypeProperty; } let nextToLast = lhs; - while (isBindableElementAccessExpression(nextToLast.expression)) { + while (isPropertyAccessExpression(nextToLast.expression)) { nextToLast = nextToLast.expression; } - const twoFromLast = tryCast(nextToLast.expression, isIdentifier) || cast(nextToLast.expression, isStringOrNumericLiteralLike); - const twoFromLastText = getTextOfIdentifierOrLiteral(twoFromLast); - if (twoFromLastText === "exports" || - twoFromLastText === "module" && getNameOrArgumentText(nextToLast) === "exports") { + const id = cast(nextToLast.expression, isIdentifier); + if (id.escapedText === "exports" || + id.escapedText === "module" && getNameOrArgumentText(nextToLast) === "exports") { // exports.name = expr OR module.exports.name = expr return AssignmentDeclarationKind.ExportsProperty; } @@ -5285,7 +5291,7 @@ namespace ts { case AssignmentDeclarationKind.ThisProperty: case AssignmentDeclarationKind.Property: case AssignmentDeclarationKind.PrototypeProperty: - return ((expr as BinaryExpression).left as PropertyAccessExpression).name; + return getNameOrArgument((expr as BinaryExpression).left as PropertyAccessExpression | BindableElementAccessExpression); case AssignmentDeclarationKind.ObjectDefinePropertyValue: case AssignmentDeclarationKind.ObjectDefinePropertyExports: case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: @@ -5302,6 +5308,11 @@ namespace ts { const { expression } = declaration as ExportAssignment; return isIdentifier(expression) ? expression : undefined; } + case SyntaxKind.ElementAccessExpression: + const expr = declaration as ElementAccessExpression; + if (isBindableElementAccessExpression(expr)) { + return expr.argumentExpression; + } } return (declaration as NamedDeclaration).name; } @@ -5312,6 +5323,13 @@ namespace ts { (isFunctionExpression(declaration) || isClassExpression(declaration) ? getAssignedName(declaration) : undefined); } + function getNameOrArgument(expr: PropertyAccessExpression | BindableElementAccessExpression) { + if (isPropertyAccessExpression(expr)) { + return expr.name; + } + return expr.argumentExpression; + } + function getAssignedName(node: Node): DeclarationName | undefined { if (!node.parent) { return undefined; diff --git a/tests/baselines/reference/moduleExportsElementAccessAssignment.symbols b/tests/baselines/reference/moduleExportsElementAccessAssignment.symbols new file mode 100644 index 0000000000000..122bd93c6526e --- /dev/null +++ b/tests/baselines/reference/moduleExportsElementAccessAssignment.symbols @@ -0,0 +1,50 @@ +=== tests/cases/conformance/jsdoc/mod2.js === +const mod1 = require("./mod1"); +>mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) +>require : Symbol(require) +>"./mod1" : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) + +mod1.a; +>mod1.a : Symbol(a, Decl(mod1.js, 0, 0)) +>mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) +>a : Symbol(a, Decl(mod1.js, 0, 0)) + +mod1.b; +>mod1.b : Symbol("b", Decl(mod1.js, 0, 23)) +>mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) +>b : Symbol("b", Decl(mod1.js, 0, 23)) + +mod1.c; +>mod1.c : Symbol("c", Decl(mod1.js, 2, 32)) +>mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) +>c : Symbol("c", Decl(mod1.js, 2, 32)) + +mod1.default; +>mod1.default : Symbol(default, Decl(mod1.js, 1, 26)) +>mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) +>default : Symbol(default, Decl(mod1.js, 1, 26)) + +=== tests/cases/conformance/jsdoc/mod1.js === +exports.a = { x: "x" }; +>exports.a : Symbol(a, Decl(mod1.js, 0, 0)) +>exports : Symbol(a, Decl(mod1.js, 0, 0)) +>a : Symbol(a, Decl(mod1.js, 0, 0)) +>x : Symbol(x, Decl(mod1.js, 0, 13)) + +exports["b"] = { x: "x" }; +>exports : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) +>"b" : Symbol("b", Decl(mod1.js, 0, 23)) +>x : Symbol(x, Decl(mod1.js, 1, 16)) + +exports["default"] = { x: "x" }; +>exports : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) +>"default" : Symbol("default", Decl(mod1.js, 1, 26)) +>x : Symbol(x, Decl(mod1.js, 2, 22)) + +module.exports["c"] = { x: "x" }; +>module.exports : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) +>module : Symbol(module, Decl(mod1.js, 2, 32)) +>exports : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) +>"c" : Symbol("c", Decl(mod1.js, 2, 32)) +>x : Symbol(x, Decl(mod1.js, 3, 23)) + diff --git a/tests/baselines/reference/moduleExportsElementAccessAssignment.types b/tests/baselines/reference/moduleExportsElementAccessAssignment.types new file mode 100644 index 0000000000000..3fbb805be45dc --- /dev/null +++ b/tests/baselines/reference/moduleExportsElementAccessAssignment.types @@ -0,0 +1,66 @@ +=== tests/cases/conformance/jsdoc/mod2.js === +const mod1 = require("./mod1"); +>mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") +>require("./mod1") : typeof import("tests/cases/conformance/jsdoc/mod1") +>require : any +>"./mod1" : "./mod1" + +mod1.a; +>mod1.a : { x: string; } +>mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") +>a : { x: string; } + +mod1.b; +>mod1.b : { x: string; } +>mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") +>b : { x: string; } + +mod1.c; +>mod1.c : { x: string; } +>mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") +>c : { x: string; } + +mod1.default; +>mod1.default : { x: string; } +>mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") +>default : { x: string; } + +=== tests/cases/conformance/jsdoc/mod1.js === +exports.a = { x: "x" }; +>exports.a = { x: "x" } : { x: string; } +>exports.a : { x: string; } +>exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>a : { x: string; } +>{ x: "x" } : { x: string; } +>x : string +>"x" : "x" + +exports["b"] = { x: "x" }; +>exports["b"] = { x: "x" } : { x: string; } +>exports["b"] : { x: string; } +>exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>"b" : "b" +>{ x: "x" } : { x: string; } +>x : string +>"x" : "x" + +exports["default"] = { x: "x" }; +>exports["default"] = { x: "x" } : { x: string; } +>exports["default"] : { x: string; } +>exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>"default" : "default" +>{ x: "x" } : { x: string; } +>x : string +>"x" : "x" + +module.exports["c"] = { x: "x" }; +>module.exports["c"] = { x: "x" } : { x: string; } +>module.exports["c"] : { x: string; } +>module.exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>module : { "tests/cases/conformance/jsdoc/mod1": typeof import("tests/cases/conformance/jsdoc/mod1"); } +>exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>"c" : "c" +>{ x: "x" } : { x: string; } +>x : string +>"x" : "x" + diff --git a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts index ef75df7872267..57f61ff7ac810 100644 --- a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts +++ b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts @@ -3,16 +3,14 @@ // @strict: true // @checkJs: true // @filename: mod1.js -exports.a = {}; -exports['b'] = {}; -exports['default'] = {}; -module['exports'].c = {}; -module['exports']['d'] = {}; +exports.a = { x: "x" }; +exports["b"] = { x: "x" }; +exports["default"] = { x: "x" }; +module.exports["c"] = { x: "x" }; // @filename: mod2.js -const mod1 = require('./mod1'); +const mod1 = require("./mod1"); mod1.a; mod1.b; mod1.c; -mod1.d; mod1.default; From 7a925b9ebcbca1855c3ae61e555046656777bc2e Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 17 Sep 2019 16:57:38 -0700 Subject: [PATCH 03/25] Make declarations for bindable element access expressions --- src/compiler/binder.ts | 6 ++--- src/compiler/checker.ts | 3 ++- src/compiler/types.ts | 12 +++++----- src/compiler/utilities.ts | 13 ++++++++--- .../reference/typeFromPropertyAssignment38.js | 14 ++++++++++++ .../typeFromPropertyAssignment38.symbols | 15 +++++++++++++ .../typeFromPropertyAssignment38.types | 22 +++++++++++++++++++ .../salsa/typeFromPropertyAssignment38.ts | 7 ++++++ ...eferencesForStringLiteralPropertyNames6.ts | 2 +- ...eferencesForStringLiteralPropertyNames7.ts | 2 +- 10 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 tests/baselines/reference/typeFromPropertyAssignment38.js create mode 100644 tests/baselines/reference/typeFromPropertyAssignment38.symbols create mode 100644 tests/baselines/reference/typeFromPropertyAssignment38.types create mode 100644 tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2ce2fd5ece1ba..66e3c38b0530a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2494,7 +2494,7 @@ namespace ts { const flags = isClassExpression(node.right) ? SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.Class : SymbolFlags.Property | SymbolFlags.ExportValue; - declareSymbol(symbol.exports!, symbol, lhs as any, flags, SymbolFlags.None); + declareSymbol(symbol.exports!, symbol, lhs, flags, SymbolFlags.None); } } @@ -2750,7 +2750,7 @@ namespace ts { } } - function forEachIdentifierInEntityName(e: EntityNameExpression, parent: Symbol | undefined, action: (e: Identifier, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined { + function forEachIdentifierInEntityName(e: BindableNameExpression, parent: Symbol | undefined, action: (e: Declaration, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined { if (isExportsOrModuleExportsOrAlias(file, e)) { return file.symbol; } @@ -2759,7 +2759,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(getNameOrArgumentText(e)), s); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e16c16a007ff7..1c02686d641b2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5834,10 +5834,11 @@ namespace ts { } else if (isInJSFile(declaration) && (isCallExpression(declaration) || isBinaryExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent))) { - type = getWidenedTypeFromAssignmentDeclaration(symbol); + type = getWidenedTypeForAssignmentDeclaration(symbol); } else if (isJSDocPropertyLikeTag(declaration) || isPropertyAccessExpression(declaration) + || isElementAccessExpression(declaration) || isIdentifier(declaration) || isClassDeclaration(declaration) || isFunctionDeclaration(declaration) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b45bb4413c031..9e4bc6e5c40bb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1275,7 +1275,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. */ @@ -1662,7 +1662,7 @@ namespace ts { kind: SyntaxKind.RegularExpressionLiteral; } - export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode { + export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode, Declaration { kind: SyntaxKind.NoSubstitutionTemplateLiteral; } @@ -1691,7 +1691,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; @@ -1813,8 +1813,10 @@ namespace ts { /** @internal */ export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; /** @internal */ - export type BindableElementAccessExpression = ElementAccessExpression & { - expression: EntityNameExpression; + export type BindableNameExpression = EntityNameExpression | BindableElementAccessExpression; + /** @internal */ + export type BindableElementAccessExpression = ElementAccessExpression & Declaration & { + expression: BindableNameExpression; argumentExpression: StringLiteralLike | NumericLiteral; }; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fd3868ce44d20..bb7efbc5cde94 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2049,14 +2049,21 @@ namespace ts { export function isBindableElementAccessExpression(node: Node): node is BindableElementAccessExpression { return isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression) - && isEntityNameExpression(node.expression); + && (isEntityNameExpression(node.expression) || isBindableElementAccessExpression(node.expression)); } - function getNameOrArgumentText(expr: PropertyAccessExpression | BindableElementAccessExpression) { + export function getNameOrArgument(expr: PropertyAccessExpression | BindableElementAccessExpression) { + if (isPropertyAccessExpression(expr)) { + return expr.name; + } + return expr.argumentExpression; + } + + export function getNameOrArgumentText(expr: PropertyAccessExpression | BindableElementAccessExpression): __String { if (isPropertyAccessExpression(expr)) { return expr.name.escapedText; } - return expr.argumentExpression.text; + return escapeLeadingUnderscores(expr.argumentExpression.text); } function getAssignmentDeclarationKindWorker(expr: BinaryExpression | CallExpression): AssignmentDeclarationKind { diff --git a/tests/baselines/reference/typeFromPropertyAssignment38.js b/tests/baselines/reference/typeFromPropertyAssignment38.js new file mode 100644 index 0000000000000..2d73e8f60c1fb --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment38.js @@ -0,0 +1,14 @@ +//// [typeFromPropertyAssignment38.ts] +function F() {} +F["prop"] = 3; + +const f = function () {}; +F["prop"] = 3; + + +//// [typeFromPropertyAssignment38.js] +"use strict"; +function F() { } +F["prop"] = 3; +var f = function () { }; +F["prop"] = 3; diff --git a/tests/baselines/reference/typeFromPropertyAssignment38.symbols b/tests/baselines/reference/typeFromPropertyAssignment38.symbols new file mode 100644 index 0000000000000..eb95592564527 --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment38.symbols @@ -0,0 +1,15 @@ +=== tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts === +function F() {} +>F : Symbol(F, Decl(typeFromPropertyAssignment38.ts, 0, 0), Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) + +F["prop"] = 3; +>F : Symbol(F, Decl(typeFromPropertyAssignment38.ts, 0, 0), Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) +>"prop" : Symbol(F["prop"], Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) + +const f = function () {}; +>f : Symbol(f, Decl(typeFromPropertyAssignment38.ts, 3, 5)) + +F["prop"] = 3; +>F : Symbol(F, Decl(typeFromPropertyAssignment38.ts, 0, 0), Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) +>"prop" : Symbol(F["prop"], Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) + diff --git a/tests/baselines/reference/typeFromPropertyAssignment38.types b/tests/baselines/reference/typeFromPropertyAssignment38.types new file mode 100644 index 0000000000000..1000141408c5a --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment38.types @@ -0,0 +1,22 @@ +=== tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts === +function F() {} +>F : typeof F + +F["prop"] = 3; +>F["prop"] = 3 : 3 +>F["prop"] : number +>F : typeof F +>"prop" : "prop" +>3 : 3 + +const f = function () {}; +>f : () => void +>function () {} : () => void + +F["prop"] = 3; +>F["prop"] = 3 : 3 +>F["prop"] : number +>F : typeof F +>"prop" : "prop" +>3 : 3 + diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts new file mode 100644 index 0000000000000..81c8b44818543 --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts @@ -0,0 +1,7 @@ +// @strict: true + +function F() {} +F["prop"] = 3; + +const f = function () {}; +F["prop"] = 3; diff --git a/tests/cases/fourslash/referencesForStringLiteralPropertyNames6.ts b/tests/cases/fourslash/referencesForStringLiteralPropertyNames6.ts index 84b330482fc95..3faf6514fcf73 100644 --- a/tests/cases/fourslash/referencesForStringLiteralPropertyNames6.ts +++ b/tests/cases/fourslash/referencesForStringLiteralPropertyNames6.ts @@ -2,7 +2,7 @@ ////const x = function () { return 111111; } ////x.[|{| "isWriteAccess": true, "isDefinition": true |}someProperty|] = 5; -////x["[|someProperty|]"] = 3; +////x["[|{| "isWriteAccess": true, "isDefinition": true |}someProperty|]"] = 3; const ranges = test.ranges(); const [r0, r1] = ranges; diff --git a/tests/cases/fourslash/referencesForStringLiteralPropertyNames7.ts b/tests/cases/fourslash/referencesForStringLiteralPropertyNames7.ts index eac206f90de05..3013d4f6d0d80 100644 --- a/tests/cases/fourslash/referencesForStringLiteralPropertyNames7.ts +++ b/tests/cases/fourslash/referencesForStringLiteralPropertyNames7.ts @@ -5,7 +5,7 @@ // @checkJs: true ////var x = { [|"[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 0 |}someProperty|]": 0|] } -////x["[|someProperty|]"] = 3; +////x["[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 3 |}someProperty|]"] = 3; ////[|x.[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 3 |}someProperty|] = 5;|] const [r0Def, r0, r1, r2Def, r2] = test.ranges(); From 3e206c429a4a0cdbe17539a1418025656849a996 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 17 Sep 2019 17:03:40 -0700 Subject: [PATCH 04/25] Fix navigationBar crash --- src/services/navigationBar.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index dd3bdcffd3248..9314779d6147e 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -384,16 +384,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) && getNameOrArgumentText(assignmentTarget) !== "prototype" && trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) { if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction); } else { startNode(binaryExpression, targetFunction); - addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, assignmentTarget.name); + addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, getNameOrArgument(assignmentTarget)); endNode(); } return; From 6218fe0aef0c80714470ec226dbcf7e1aff21097 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 17 Sep 2019 17:12:51 -0700 Subject: [PATCH 05/25] Add multi-level test for JS --- .../conformance/salsa/typeFromPropertyAssignment38.ts | 3 ++- .../conformance/salsa/typeFromPropertyAssignment39.ts | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts index 81c8b44818543..b43aed92607b7 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts @@ -1,7 +1,8 @@ +// @noEmit: true // @strict: true function F() {} -F["prop"] = 3; +F["prop"] = function() {}; const f = function () {}; F["prop"] = 3; diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts new file mode 100644 index 0000000000000..7d32ae2b07231 --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts @@ -0,0 +1,9 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @strict: true +// @Filename: mod.js + +const foo = {}; +foo["baz"] = {}; +foo["baz"]["blah"] = 3; From 7b57acabe57f4b7cfe6953995343802c7fa50a1d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 18 Sep 2019 13:42:51 -0700 Subject: [PATCH 06/25] Propagate element access expressions to more code paths --- src/compiler/binder.ts | 69 +++++++++--------- src/compiler/types.ts | 14 +++- src/compiler/utilities.ts | 71 +++++++++++-------- src/services/navigationBar.ts | 13 ++-- .../salsa/typeFromPropertyAssignment2.ts | 1 + .../salsa/typeFromPropertyAssignment39.ts | 2 +- 6 files changed, 94 insertions(+), 76 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 66e3c38b0530a..03900d2e164ae 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2172,14 +2172,14 @@ namespace ts { if (currentFlow && isNarrowableReference(node)) { node.flowNode = currentFlow; } - if (isSpecialPropertyDeclaration(node as PropertyAccessExpression)) { - bindSpecialPropertyDeclaration(node as PropertyAccessExpression); + if (isSpecialPropertyDeclaration(node as PropertyAccessExpression | ElementAccessExpression)) { + bindSpecialPropertyDeclaration(node as PropertyAccessExpression | ElementAccessExpression); } if (isInJSFile(node) && file.commonJsModuleIndicator && - isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) && + isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression | ElementAccessExpression) && !lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) { - declareSymbol(file.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier, + declareSymbol(file.locals!, /*parent*/ undefined, (node as PropertyAccessExpression | ElementAccessExpression).expression as Identifier, SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); } break; @@ -2187,22 +2187,22 @@ namespace ts { const specialKind = getAssignmentDeclarationKind(node as BinaryExpression); switch (specialKind) { case AssignmentDeclarationKind.ExportsProperty: - bindExportsPropertyAssignment(node as BinaryExpression); + bindExportsPropertyAssignment(node as BindablePropertyAssignmentExpression); 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 BindablePropertyAssignmentExpression).left, node); break; case AssignmentDeclarationKind.Prototype: - bindPrototypeAssignment(node as BinaryExpression); + bindPrototypeAssignment(node as BindablePropertyAssignmentExpression); 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 @@ -2477,14 +2477,13 @@ namespace ts { } } - function bindExportsPropertyAssignment(node: BinaryExpression) { + function bindExportsPropertyAssignment(node: BindablePropertyAssignmentExpression) { // 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 | BindableElementAccessExpression; - 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); } @@ -2494,11 +2493,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. @@ -2518,7 +2517,7 @@ namespace ts { declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); } - function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) { + function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression | ElementAccessExpression) { Debug.assert(isInJSFile(node)); const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false); switch (thisContainer.kind) { @@ -2528,7 +2527,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 (isBindableAccessExpression(l) && isPrototypeAccess(l.expression)) { constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer); } } @@ -2568,11 +2567,11 @@ namespace ts { } } - function bindSpecialPropertyDeclaration(node: PropertyAccessExpression) { + function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | ElementAccessExpression) { if (node.expression.kind === SyntaxKind.ThisKeyword) { bindThisPropertyAssignment(node); } - else if (isPropertyAccessEntityNameExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { + else if (isBindableAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { if (isPrototypeAccess(node.expression)) { bindPrototypePropertyAssignment(node, node.parent); } @@ -2583,11 +2582,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: BindablePropertyAssignmentExpression) { 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) { @@ -2599,10 +2597,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: BindableAccessExpression, 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 BindableAccessExpression; const constructorFunction = classPrototype.expression; // Fix up parent pointers since we're going to use these nodes before we bind into them @@ -2620,24 +2618,23 @@ 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); } else { - bindStaticPropertyAssignment(lhs); + bindStaticPropertyAssignment(node.left); } } @@ -2645,12 +2642,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: BindableAccessExpression) { 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: BindableNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { if (isToplevel && !isPrototypeProperty) { // make symbols or add declarations for intermediate containers const flags = SymbolFlags.Module | SymbolFlags.Assignment; @@ -2673,7 +2670,7 @@ namespace ts { return namespaceSymbol; } - function bindPotentiallyNewExpandoMemberToNamespace(declaration: PropertyAccessEntityNameExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { + function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) { return; } @@ -2689,13 +2686,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: BindableNameExpression, propertyAccess: BindableAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) { let namespaceSymbol = lookupSymbolForPropertyAccess(name); const isToplevel = isTopLevelNamespaceAssignment(propertyAccess); namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass); @@ -2740,13 +2737,13 @@ namespace ts { return expr.parent; } - function lookupSymbolForPropertyAccess(node: EntityNameExpression, lookupContainer: Node = container): Symbol | undefined { + function lookupSymbolForPropertyAccess(node: BindableNameExpression, 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(getNameOrArgumentText(node)); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9e4bc6e5c40bb..2304f0e46627e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1790,7 +1790,7 @@ namespace ts { expression: EntityNameExpression; } - export interface ElementAccessExpression extends MemberExpression { + export interface ElementAccessExpression extends MemberExpression, Declaration { kind: SyntaxKind.ElementAccessExpression; expression: LeftHandSideExpression; argumentExpression: Expression; @@ -1811,14 +1811,24 @@ namespace ts { } /** @internal */ - export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; + export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: BindableNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; /** @internal */ export type BindableNameExpression = EntityNameExpression | BindableElementAccessExpression; /** @internal */ + export type BindableAccessExpression = PropertyAccessEntityNameExpression | BindableElementAccessExpression; + /** @internal */ export type BindableElementAccessExpression = ElementAccessExpression & Declaration & { expression: BindableNameExpression; argumentExpression: StringLiteralLike | NumericLiteral; }; + /** @internal */ + export interface BindableAssignmentExpression extends BinaryExpression { + left: BindableNameExpression; + } + /** @internal */ + export interface BindablePropertyAssignmentExpression extends BindableAssignmentExpression { + left: BindableAccessExpression; + } // see: https://tc39.github.io/ecma262/#prod-SuperCall export interface SuperCall extends CallExpression { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index bb7efbc5cde94..c49678c1b9d71 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1906,18 +1906,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"); @@ -1960,9 +1965,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; } } @@ -1997,19 +2002,19 @@ 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) && isBindableAccessExpression(initializer)) { + return (initializer.expression.kind as SyntaxKind === 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); + isSameEntityName(name, getNameOrArgument(initializer)); } - if (isPropertyAccessExpression(name) && isPropertyAccessExpression(initializer)) { - return name.name.escapedText === initializer.name.escapedText && isSameEntityName(name.expression, initializer.expression); + if (isBindableAccessExpression(name) && isBindableAccessExpression(initializer)) { + return getNameOrArgumentText(name) === getNameOrArgumentText(initializer) && isSameEntityName(name.expression, initializer.expression); } return false; } @@ -2043,7 +2048,7 @@ namespace ts { idText(expr.expression.expression) === "Object" && idText(expr.expression.name) === "defineProperty" && isStringOrNumericLiteralLike(expr.arguments[1]) && - isEntityNameExpression(expr.arguments[0]); + isBindableNameExpression(expr.arguments[0]); } export function isBindableElementAccessExpression(node: Node): node is BindableElementAccessExpression { @@ -2052,6 +2057,18 @@ namespace ts { && (isEntityNameExpression(node.expression) || isBindableElementAccessExpression(node.expression)); } + export function isBindableAccessExpression(node: Node): node is BindableAccessExpression { + return isPropertyAccessEntityNameExpression(node) || isBindableElementAccessExpression(node); + } + + export function isBindableNameExpression(node: Node): node is BindableNameExpression { + return isEntityNameExpression(node) || isBindableAccessExpression(node); + } + + export function isBindableAssignmentExpression(node: Node): node is BindableAssignmentExpression { + return isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.EqualsToken && isBindableNameExpression(node.left); + } + export function getNameOrArgument(expr: PropertyAccessExpression | BindableElementAccessExpression) { if (isPropertyAccessExpression(expr)) { return expr.name; @@ -2075,21 +2092,19 @@ namespace ts { if (isExportsIdentifier(entityName) || isModuleExportsPropertyAccessExpression(entityName)) { return AssignmentDeclarationKind.ObjectDefinePropertyExports; } - if (isPropertyAccessExpression(entityName) && entityName.name.escapedText === "prototype" && isEntityNameExpression(entityName.expression)) { + if (isBindableAccessExpression(entityName) && getNameOrArgumentText(entityName) === "prototype") { return AssignmentDeclarationKind.ObjectDefinePrototypeProperty; } return AssignmentDeclarationKind.ObjectDefinePropertyValue; } - if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || - !(isPropertyAccessExpression(expr.left) || isBindableElementAccessExpression(expr.left))) { + if (!isBindableAssignmentExpression(expr) || !isBindableAccessExpression(expr.left)) { return AssignmentDeclarationKind.None; } - const lhs = expr.left; - if (isEntityNameExpression(lhs.expression) && getNameOrArgumentText(lhs) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { + if (isBindableNameExpression(expr.left.expression) && getNameOrArgumentText(expr.left) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { // F.prototype = { ... } return AssignmentDeclarationKind.Prototype; } - return getAssignmentDeclarationPropertyAccessKind(lhs); + return getAssignmentDeclarationPropertyAccessKind(expr.left); } export function getAssignmentDeclarationPropertyAccessKind(lhs: PropertyAccessExpression | ElementAccessExpression): AssignmentDeclarationKind { @@ -2100,23 +2115,17 @@ namespace ts { // module.exports = expr return AssignmentDeclarationKind.ModuleExports; } - // This check for EntityNameExpression prohibits multiple levels of - // element access from being treated as special assignment, e.g. - // `F["prototype"]["X"]` is not special, but `F.prototype["x"]` is. - else if (isEntityNameExpression(lhs.expression)) { - if (isElementAccessExpression(lhs) && !isBindableElementAccessExpression(lhs)) { - return AssignmentDeclarationKind.None; - } + else if (isBindableAccessExpression(lhs)) { if (isPrototypeAccess(lhs.expression)) { // F.G....prototype.x = expr return AssignmentDeclarationKind.PrototypeProperty; } let nextToLast = lhs; - while (isPropertyAccessExpression(nextToLast.expression)) { + while (!isIdentifier(nextToLast.expression)) { nextToLast = nextToLast.expression; } - const id = cast(nextToLast.expression, isIdentifier); + const id = nextToLast.expression; if (id.escapedText === "exports" || id.escapedText === "module" && getNameOrArgumentText(nextToLast) === "exports") { // exports.name = expr OR module.exports.name = expr @@ -2140,7 +2149,7 @@ namespace ts { return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty; } - export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression): boolean { + export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression | ElementAccessExpression): boolean { return isInJSFile(expr) && expr.parent && expr.parent.kind === SyntaxKind.ExpressionStatement && !!getJSDocTypeTag(expr.parent); @@ -4101,8 +4110,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 BindableAccessExpression { + return isBindableAccessExpression(node) && getNameOrArgumentText(node) === "prototype"; } export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) { diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 9314779d6147e..f94a90c953de3 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -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: BindableNameExpression) { + const names: PropertyNameLiteral[] = []; + while (!isPropertyNameLiteral(entityName)) { + const name = getNameOrArgument(entityName); + const nameText = getNameOrArgumentText(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)) { diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment2.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment2.ts index 5931a9960fdc4..c813d272649f7 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment2.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment2.ts @@ -1,6 +1,7 @@ // @noEmit: true // @allowJs: true // @checkJs: true +// @noLib: true // @Filename: a.js function Outer() { this.y = 2 diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts index 7d32ae2b07231..3eff15070ded6 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts @@ -2,7 +2,7 @@ // @allowJs: true // @checkJs: true // @strict: true -// @Filename: mod.js +// @Filename: a.js const foo = {}; foo["baz"] = {}; From 020508b0bf0375073600073a32a3a9eb8b71f8f3 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 18 Sep 2019 15:27:01 -0700 Subject: [PATCH 07/25] Fix property access on `this` --- src/compiler/types.ts | 7 +++-- src/compiler/utilities.ts | 31 ++++++++++++------- .../reference/typeFromPropertyAssignment38.js | 14 --------- .../typeFromPropertyAssignment38.symbols | 14 ++++----- .../typeFromPropertyAssignment38.types | 12 +++---- .../typeFromPropertyAssignment39.symbols | 13 ++++++++ .../typeFromPropertyAssignment39.types | 21 +++++++++++++ .../salsa/typeFromPropertyAssignment2.ts | 1 - .../salsa/typeFromPropertyAssignment38.ts | 4 +-- 9 files changed, 74 insertions(+), 43 deletions(-) delete mode 100644 tests/baselines/reference/typeFromPropertyAssignment38.js create mode 100644 tests/baselines/reference/typeFromPropertyAssignment39.symbols create mode 100644 tests/baselines/reference/typeFromPropertyAssignment39.types diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2304f0e46627e..d88befc3ea86b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1817,11 +1817,14 @@ namespace ts { /** @internal */ export type BindableAccessExpression = PropertyAccessEntityNameExpression | BindableElementAccessExpression; /** @internal */ - export type BindableElementAccessExpression = ElementAccessExpression & Declaration & { - expression: BindableNameExpression; + export interface LiteralLikeElementAccessExpression extends ElementAccessExpression { argumentExpression: StringLiteralLike | NumericLiteral; }; /** @internal */ + export type BindableElementAccessExpression = LiteralLikeElementAccessExpression & Declaration & { + expression: BindableNameExpression; + }; + /** @internal */ export interface BindableAssignmentExpression extends BinaryExpression { left: BindableNameExpression; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c49678c1b9d71..065357e7cf5b2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2005,15 +2005,15 @@ namespace ts { if (isPropertyNameLiteral(name) && isPropertyNameLiteral(initializer)) { return getTextOfIdentifierOrLiteral(name) === getTextOfIdentifierOrLiteral(name); } - if (isIdentifier(name) && isBindableAccessExpression(initializer)) { - return (initializer.expression.kind as SyntaxKind === 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)) && + (initializer.expression.escapedText === "window" || + initializer.expression.escapedText === "self" || + initializer.expression.escapedText === "global")) && isSameEntityName(name, getNameOrArgument(initializer)); } - if (isBindableAccessExpression(name) && isBindableAccessExpression(initializer)) { + if (isLiteralLikeAccess(name) && isLiteralLikeAccess(initializer)) { return getNameOrArgumentText(name) === getNameOrArgumentText(initializer) && isSameEntityName(name.expression, initializer.expression); } return false; @@ -2052,13 +2052,22 @@ namespace ts { } export function isBindableElementAccessExpression(node: Node): node is BindableElementAccessExpression { - return isElementAccessExpression(node) - && isStringOrNumericLiteralLike(node.argumentExpression) + return isLiteralLikeElementAccess(node) && (isEntityNameExpression(node.expression) || isBindableElementAccessExpression(node.expression)); } + 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 isBindableAccessExpression(node: Node): node is BindableAccessExpression { - return isPropertyAccessEntityNameExpression(node) || isBindableElementAccessExpression(node); + return isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword + || isPropertyAccessEntityNameExpression(node) + || isBindableElementAccessExpression(node); } export function isBindableNameExpression(node: Node): node is BindableNameExpression { @@ -2069,14 +2078,14 @@ namespace ts { return isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.EqualsToken && isBindableNameExpression(node.left); } - export function getNameOrArgument(expr: PropertyAccessExpression | BindableElementAccessExpression) { + export function getNameOrArgument(expr: PropertyAccessExpression | LiteralLikeElementAccessExpression) { if (isPropertyAccessExpression(expr)) { return expr.name; } return expr.argumentExpression; } - export function getNameOrArgumentText(expr: PropertyAccessExpression | BindableElementAccessExpression): __String { + export function getNameOrArgumentText(expr: PropertyAccessExpression | LiteralLikeElementAccessExpression): __String { if (isPropertyAccessExpression(expr)) { return expr.name.escapedText; } diff --git a/tests/baselines/reference/typeFromPropertyAssignment38.js b/tests/baselines/reference/typeFromPropertyAssignment38.js deleted file mode 100644 index 2d73e8f60c1fb..0000000000000 --- a/tests/baselines/reference/typeFromPropertyAssignment38.js +++ /dev/null @@ -1,14 +0,0 @@ -//// [typeFromPropertyAssignment38.ts] -function F() {} -F["prop"] = 3; - -const f = function () {}; -F["prop"] = 3; - - -//// [typeFromPropertyAssignment38.js] -"use strict"; -function F() { } -F["prop"] = 3; -var f = function () { }; -F["prop"] = 3; diff --git a/tests/baselines/reference/typeFromPropertyAssignment38.symbols b/tests/baselines/reference/typeFromPropertyAssignment38.symbols index eb95592564527..657fcd97006e0 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment38.symbols +++ b/tests/baselines/reference/typeFromPropertyAssignment38.symbols @@ -1,15 +1,15 @@ === tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts === function F() {} ->F : Symbol(F, Decl(typeFromPropertyAssignment38.ts, 0, 0), Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) +>F : Symbol(F, Decl(typeFromPropertyAssignment38.ts, 0, 0), Decl(typeFromPropertyAssignment38.ts, 0, 15)) F["prop"] = 3; ->F : Symbol(F, Decl(typeFromPropertyAssignment38.ts, 0, 0), Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) ->"prop" : Symbol(F["prop"], Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) +>F : Symbol(F, Decl(typeFromPropertyAssignment38.ts, 0, 0), Decl(typeFromPropertyAssignment38.ts, 0, 15)) +>"prop" : Symbol(F["prop"], Decl(typeFromPropertyAssignment38.ts, 0, 15)) const f = function () {}; ->f : Symbol(f, Decl(typeFromPropertyAssignment38.ts, 3, 5)) +>f : Symbol(f, Decl(typeFromPropertyAssignment38.ts, 3, 5), Decl(typeFromPropertyAssignment38.ts, 3, 25)) -F["prop"] = 3; ->F : Symbol(F, Decl(typeFromPropertyAssignment38.ts, 0, 0), Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) ->"prop" : Symbol(F["prop"], Decl(typeFromPropertyAssignment38.ts, 0, 15), Decl(typeFromPropertyAssignment38.ts, 3, 25)) +f["prop"] = 3; +>f : Symbol(f, Decl(typeFromPropertyAssignment38.ts, 3, 5), Decl(typeFromPropertyAssignment38.ts, 3, 25)) +>"prop" : Symbol(f["prop"], Decl(typeFromPropertyAssignment38.ts, 3, 25)) diff --git a/tests/baselines/reference/typeFromPropertyAssignment38.types b/tests/baselines/reference/typeFromPropertyAssignment38.types index 1000141408c5a..ce392aa9762d8 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment38.types +++ b/tests/baselines/reference/typeFromPropertyAssignment38.types @@ -10,13 +10,13 @@ F["prop"] = 3; >3 : 3 const f = function () {}; ->f : () => void ->function () {} : () => void +>f : { (): void; "prop": number; } +>function () {} : { (): void; "prop": number; } -F["prop"] = 3; ->F["prop"] = 3 : 3 ->F["prop"] : number ->F : typeof F +f["prop"] = 3; +>f["prop"] = 3 : 3 +>f["prop"] : number +>f : { (): void; "prop": number; } >"prop" : "prop" >3 : 3 diff --git a/tests/baselines/reference/typeFromPropertyAssignment39.symbols b/tests/baselines/reference/typeFromPropertyAssignment39.symbols new file mode 100644 index 0000000000000..622b77ed008dd --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment39.symbols @@ -0,0 +1,13 @@ +=== tests/cases/conformance/salsa/a.js === +const foo = {}; +>foo : Symbol(foo, Decl(a.js, 0, 5), Decl(a.js, 0, 15), Decl(a.js, 1, 16)) + +foo["baz"] = {}; +>foo : Symbol(foo, Decl(a.js, 0, 5), Decl(a.js, 0, 15), Decl(a.js, 1, 16)) +>"baz" : Symbol(foo["baz"], Decl(a.js, 0, 15), Decl(a.js, 2, 4)) + +foo["baz"]["blah"] = 3; +>foo : Symbol(foo, Decl(a.js, 0, 5), Decl(a.js, 0, 15), Decl(a.js, 1, 16)) +>"baz" : Symbol(foo["baz"], Decl(a.js, 0, 15), Decl(a.js, 2, 4)) +>"blah" : Symbol(foo["baz"]["blah"], Decl(a.js, 1, 16)) + diff --git a/tests/baselines/reference/typeFromPropertyAssignment39.types b/tests/baselines/reference/typeFromPropertyAssignment39.types new file mode 100644 index 0000000000000..ec8cdfb071c42 --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment39.types @@ -0,0 +1,21 @@ +=== tests/cases/conformance/salsa/a.js === +const foo = {}; +>foo : typeof foo +>{} : {} + +foo["baz"] = {}; +>foo["baz"] = {} : typeof foo."baz" +>foo["baz"] : typeof foo."baz" +>foo : typeof foo +>"baz" : "baz" +>{} : {} + +foo["baz"]["blah"] = 3; +>foo["baz"]["blah"] = 3 : 3 +>foo["baz"]["blah"] : number +>foo["baz"] : typeof foo."baz" +>foo : typeof foo +>"baz" : "baz" +>"blah" : "blah" +>3 : 3 + diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment2.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment2.ts index c813d272649f7..5931a9960fdc4 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment2.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment2.ts @@ -1,7 +1,6 @@ // @noEmit: true // @allowJs: true // @checkJs: true -// @noLib: true // @Filename: a.js function Outer() { this.y = 2 diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts index b43aed92607b7..f307185f37e9b 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts @@ -2,7 +2,7 @@ // @strict: true function F() {} -F["prop"] = function() {}; +F["prop"] = 3; const f = function () {}; -F["prop"] = 3; +f["prop"] = 3; From cba5fba644ab5bd00a6fb429efcd573ae258bea1 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 19 Sep 2019 14:07:08 -0700 Subject: [PATCH 08/25] Add quick info test --- .../fourslash/quickInfoElementAccessDeclaration.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/cases/fourslash/quickInfoElementAccessDeclaration.ts diff --git a/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts b/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts new file mode 100644 index 0000000000000..76d3a62d6e9ff --- /dev/null +++ b/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts @@ -0,0 +1,12 @@ +/// + +// @checkJs: true +// @allowJs: true +// @Filename: a.js +////const mod = {}; +////mod["@@thing1"] = {}; +////mod["/**/@@thing1"]["@@thing2"] = 0; + +goTo.marker(); +verify.quickInfoIs(`module mod["@@thing1"] +(property) mod["@@thing1"]: typeof mod["@@thing1"]`); From e148c605fc35c95b642676b18243a16b34377d54 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 20 Sep 2019 14:07:16 -0700 Subject: [PATCH 09/25] Uhhh I guess this is fine --- tests/cases/fourslash/quickInfoElementAccessDeclaration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts b/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts index 76d3a62d6e9ff..b286fd446aa01 100644 --- a/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts +++ b/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts @@ -9,4 +9,4 @@ goTo.marker(); verify.quickInfoIs(`module mod["@@thing1"] -(property) mod["@@thing1"]: typeof mod["@@thing1"]`); +(property) mod["@@thing1"]: typeof mod."@@thing1"`); From 65895231f5a5bb827017040aa918b3383655a422 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 20 Sep 2019 15:56:53 -0700 Subject: [PATCH 10/25] Fix module["exports"] and property access chained off element access --- src/compiler/binder.ts | 21 ++++++------ src/compiler/checker.ts | 2 ++ src/compiler/types.ts | 6 ++-- src/compiler/utilities.ts | 23 +++++++------ ...duleExportsElementAccessAssignment.symbols | 24 ++++++++++++++ ...moduleExportsElementAccessAssignment.types | 32 +++++++++++++++++++ .../moduleExportsElementAccessAssignment.ts | 6 +++- 7 files changed, 90 insertions(+), 24 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 03900d2e164ae..d8d0abe9275ee 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2169,17 +2169,18 @@ namespace ts { return checkStrictModeIdentifier(node); case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: - if (currentFlow && isNarrowableReference(node)) { - node.flowNode = currentFlow; + const expr = node as PropertyAccessExpression | ElementAccessExpression; + if (currentFlow && isNarrowableReference(expr)) { + expr.flowNode = currentFlow; } - if (isSpecialPropertyDeclaration(node as PropertyAccessExpression | ElementAccessExpression)) { - bindSpecialPropertyDeclaration(node as PropertyAccessExpression | ElementAccessExpression); + if (isSpecialPropertyDeclaration(expr)) { + bindSpecialPropertyDeclaration(expr); } - if (isInJSFile(node) && + if (isInJSFile(expr) && file.commonJsModuleIndicator && - isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression | ElementAccessExpression) && + isModuleExportsAccessExpression(expr) && !lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) { - declareSymbol(file.locals!, /*parent*/ undefined, (node as PropertyAccessExpression | ElementAccessExpression).expression as Identifier, + declareSymbol(file.locals!, /*parent*/ undefined, expr.expression, SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); } break; @@ -2517,7 +2518,7 @@ namespace ts { declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); } - function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression | ElementAccessExpression) { + function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) { Debug.assert(isInJSFile(node)); const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false); switch (thisContainer.kind) { @@ -2567,7 +2568,7 @@ namespace ts { } } - function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | ElementAccessExpression) { + function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) { if (node.expression.kind === SyntaxKind.ThisKeyword) { bindThisPropertyAssignment(node); } @@ -3030,7 +3031,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)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1c02686d641b2..7fcda0f8fe100 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5840,6 +5840,8 @@ namespace ts { || isPropertyAccessExpression(declaration) || isElementAccessExpression(declaration) || isIdentifier(declaration) + || isStringLiteralLike(declaration) + || isNumericLiteral(declaration) || isClassDeclaration(declaration) || isFunctionDeclaration(declaration) || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d88befc3ea86b..f76b52589d42e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1790,7 +1790,7 @@ namespace ts { expression: EntityNameExpression; } - export interface ElementAccessExpression extends MemberExpression, Declaration { + export interface ElementAccessExpression extends MemberExpression { kind: SyntaxKind.ElementAccessExpression; expression: LeftHandSideExpression; argumentExpression: Expression; @@ -1817,11 +1817,11 @@ namespace ts { /** @internal */ export type BindableAccessExpression = PropertyAccessEntityNameExpression | BindableElementAccessExpression; /** @internal */ - export interface LiteralLikeElementAccessExpression extends ElementAccessExpression { + export type LiteralLikeElementAccessExpression = ElementAccessExpression & Declaration & { argumentExpression: StringLiteralLike | NumericLiteral; }; /** @internal */ - export type BindableElementAccessExpression = LiteralLikeElementAccessExpression & Declaration & { + export type BindableElementAccessExpression = LiteralLikeElementAccessExpression & { expression: BindableNameExpression; }; /** @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 065357e7cf5b2..269c53b2c4a49 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2030,8 +2030,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" + && getNameOrArgumentText(node) === "exports"; } /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property @@ -2064,14 +2067,13 @@ namespace ts { return isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression); } - export function isBindableAccessExpression(node: Node): node is BindableAccessExpression { - return isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword - || isPropertyAccessEntityNameExpression(node) + export function isBindableAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableAccessExpression { + return isPropertyAccessExpression(node) && (!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword || isBindableNameExpression(node.expression, /*excludeThisKeyword*/ true)) || isBindableElementAccessExpression(node); } - export function isBindableNameExpression(node: Node): node is BindableNameExpression { - return isEntityNameExpression(node) || isBindableAccessExpression(node); + export function isBindableNameExpression(node: Node, excludeThisKeyword?: boolean): node is BindableNameExpression { + return isEntityNameExpression(node) || isBindableAccessExpression(node, excludeThisKeyword); } export function isBindableAssignmentExpression(node: Node): node is BindableAssignmentExpression { @@ -2098,7 +2100,7 @@ namespace ts { return AssignmentDeclarationKind.None; } const entityName = expr.arguments[0]; - if (isExportsIdentifier(entityName) || isModuleExportsPropertyAccessExpression(entityName)) { + if (isExportsIdentifier(entityName) || isModuleExportsAccessExpression(entityName)) { return AssignmentDeclarationKind.ObjectDefinePropertyExports; } if (isBindableAccessExpression(entityName) && getNameOrArgumentText(entityName) === "prototype") { @@ -2120,7 +2122,7 @@ 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; } @@ -2158,9 +2160,10 @@ namespace ts { return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty; } - export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression | ElementAccessExpression): 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); } diff --git a/tests/baselines/reference/moduleExportsElementAccessAssignment.symbols b/tests/baselines/reference/moduleExportsElementAccessAssignment.symbols index 122bd93c6526e..8032c898c934f 100644 --- a/tests/baselines/reference/moduleExportsElementAccessAssignment.symbols +++ b/tests/baselines/reference/moduleExportsElementAccessAssignment.symbols @@ -19,6 +19,18 @@ mod1.c; >mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) >c : Symbol("c", Decl(mod1.js, 2, 32)) +mod1.d; +>mod1.d : Symbol("d", Decl(mod1.js, 3, 33), Decl(mod1.js, 5, 18)) +>mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) +>d : Symbol("d", Decl(mod1.js, 3, 33), Decl(mod1.js, 5, 18)) + +mod1.d.e; +>mod1.d.e : Symbol("d".e, Decl(mod1.js, 4, 28)) +>mod1.d : Symbol("d", Decl(mod1.js, 3, 33), Decl(mod1.js, 5, 18)) +>mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) +>d : Symbol("d", Decl(mod1.js, 3, 33), Decl(mod1.js, 5, 18)) +>e : Symbol("d".e, Decl(mod1.js, 4, 28)) + mod1.default; >mod1.default : Symbol(default, Decl(mod1.js, 1, 26)) >mod1 : Symbol(mod1, Decl(mod2.js, 0, 5)) @@ -48,3 +60,15 @@ module.exports["c"] = { x: "x" }; >"c" : Symbol("c", Decl(mod1.js, 2, 32)) >x : Symbol(x, Decl(mod1.js, 3, 23)) +module["exports"]["d"] = {}; +>module : Symbol(module, Decl(mod1.js, 2, 32)) +>"exports" : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) +>"d" : Symbol("d", Decl(mod1.js, 3, 33), Decl(mod1.js, 5, 18)) + +module["exports"]["d"].e = 0; +>module["exports"]["d"].e : Symbol("d".e, Decl(mod1.js, 4, 28)) +>module : Symbol(module, Decl(mod1.js, 2, 32)) +>"exports" : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) +>"d" : Symbol("d", Decl(mod1.js, 3, 33), Decl(mod1.js, 5, 18)) +>e : Symbol("d".e, Decl(mod1.js, 4, 28)) + diff --git a/tests/baselines/reference/moduleExportsElementAccessAssignment.types b/tests/baselines/reference/moduleExportsElementAccessAssignment.types index 3fbb805be45dc..c9bd0a44d91f3 100644 --- a/tests/baselines/reference/moduleExportsElementAccessAssignment.types +++ b/tests/baselines/reference/moduleExportsElementAccessAssignment.types @@ -20,6 +20,18 @@ mod1.c; >mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") >c : { x: string; } +mod1.d; +>mod1.d : typeof import("tests/cases/conformance/jsdoc/mod1")."d" +>mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") +>d : typeof import("tests/cases/conformance/jsdoc/mod1")."d" + +mod1.d.e; +>mod1.d.e : number +>mod1.d : typeof import("tests/cases/conformance/jsdoc/mod1")."d" +>mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") +>d : typeof import("tests/cases/conformance/jsdoc/mod1")."d" +>e : number + mod1.default; >mod1.default : { x: string; } >mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") @@ -64,3 +76,23 @@ module.exports["c"] = { x: "x" }; >x : string >"x" : "x" +module["exports"]["d"] = {}; +>module["exports"]["d"] = {} : typeof "d" +>module["exports"]["d"] : typeof "d" +>module["exports"] : typeof import("tests/cases/conformance/jsdoc/mod1") +>module : { "tests/cases/conformance/jsdoc/mod1": typeof import("tests/cases/conformance/jsdoc/mod1"); } +>"exports" : "exports" +>"d" : "d" +>{} : {} + +module["exports"]["d"].e = 0; +>module["exports"]["d"].e = 0 : 0 +>module["exports"]["d"].e : number +>module["exports"]["d"] : typeof "d" +>module["exports"] : typeof import("tests/cases/conformance/jsdoc/mod1") +>module : { "tests/cases/conformance/jsdoc/mod1": typeof import("tests/cases/conformance/jsdoc/mod1"); } +>"exports" : "exports" +>"d" : "d" +>e : number +>0 : 0 + diff --git a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts index 57f61ff7ac810..5cf826facc1d0 100644 --- a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts +++ b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts @@ -7,10 +7,14 @@ exports.a = { x: "x" }; exports["b"] = { x: "x" }; exports["default"] = { x: "x" }; module.exports["c"] = { x: "x" }; +module["exports"]["d"] = {}; +module["exports"]["d"].e = 0; // @filename: mod2.js const mod1 = require("./mod1"); mod1.a; mod1.b; mod1.c; -mod1.default; +mod1.d; +mod1.d.e; +mod1.default; \ No newline at end of file From bd6602c002183c921a24f58e9946c2bcc427af3f Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 20 Sep 2019 16:11:25 -0700 Subject: [PATCH 11/25] Add test for this property assignment --- .../thisPropertyAssignment.errors.txt | 23 +++++++++++ .../reference/thisPropertyAssignment.symbols | 26 ++++++++++++ .../reference/thisPropertyAssignment.types | 40 +++++++++++++++++++ .../salsa/thisPropertyAssignment.ts | 17 ++++++++ 4 files changed, 106 insertions(+) create mode 100644 tests/baselines/reference/thisPropertyAssignment.errors.txt create mode 100644 tests/baselines/reference/thisPropertyAssignment.symbols create mode 100644 tests/baselines/reference/thisPropertyAssignment.types create mode 100644 tests/cases/conformance/salsa/thisPropertyAssignment.ts diff --git a/tests/baselines/reference/thisPropertyAssignment.errors.txt b/tests/baselines/reference/thisPropertyAssignment.errors.txt new file mode 100644 index 0000000000000..c4b8370c74864 --- /dev/null +++ b/tests/baselines/reference/thisPropertyAssignment.errors.txt @@ -0,0 +1,23 @@ +tests/cases/conformance/salsa/a.js(9,8): error TS2339: Property 'y' does not exist on type '{}'. +tests/cases/conformance/salsa/a.js(11,1): error TS7053: Element implicitly has an 'any' type because expression of type '"y"' can't be used to index type '{}'. + Property 'y' does not exist on type '{}'. + + +==== tests/cases/conformance/salsa/a.js (2 errors) ==== + // This test is asserting that a single property/element access + // on `this` is a special assignment declaration, but chaining + // off that does not create additional declarations. I’m not sure + // if it needs to be that way in JavaScript; the test simply + // ensures no accidental changes were introduced while allowing + // element access assignments to create declarations. + + this.x = {}; + this.x.y = {}; + ~ +!!! error TS2339: Property 'y' does not exist on type '{}'. + this["x"] = {}; + this["x"]["y"] = {}; + ~~~~~~~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"y"' can't be used to index type '{}'. +!!! error TS7053: Property 'y' does not exist on type '{}'. + \ No newline at end of file diff --git a/tests/baselines/reference/thisPropertyAssignment.symbols b/tests/baselines/reference/thisPropertyAssignment.symbols new file mode 100644 index 0000000000000..b8b299f104403 --- /dev/null +++ b/tests/baselines/reference/thisPropertyAssignment.symbols @@ -0,0 +1,26 @@ +=== tests/cases/conformance/salsa/a.js === +// This test is asserting that a single property/element access +// on `this` is a special assignment declaration, but chaining +// off that does not create additional declarations. I’m not sure +// if it needs to be that way in JavaScript; the test simply +// ensures no accidental changes were introduced while allowing +// element access assignments to create declarations. + +this.x = {}; +>this.x : Symbol(x, Decl(a.js, 0, 0)) +>this : Symbol(globalThis) +>x : Symbol(x, Decl(a.js, 0, 0)) + +this.x.y = {}; +>this.x : Symbol(x, Decl(a.js, 0, 0)) +>this : Symbol(globalThis) +>x : Symbol(x, Decl(a.js, 0, 0)) + +this["x"] = {}; +>this : Symbol(globalThis) +>"x" : Symbol(x, Decl(a.js, 0, 0)) + +this["x"]["y"] = {}; +>this : Symbol(globalThis) +>"x" : Symbol(x, Decl(a.js, 0, 0)) + diff --git a/tests/baselines/reference/thisPropertyAssignment.types b/tests/baselines/reference/thisPropertyAssignment.types new file mode 100644 index 0000000000000..cd9e95e6da373 --- /dev/null +++ b/tests/baselines/reference/thisPropertyAssignment.types @@ -0,0 +1,40 @@ +=== tests/cases/conformance/salsa/a.js === +// This test is asserting that a single property/element access +// on `this` is a special assignment declaration, but chaining +// off that does not create additional declarations. I’m not sure +// if it needs to be that way in JavaScript; the test simply +// ensures no accidental changes were introduced while allowing +// element access assignments to create declarations. + +this.x = {}; +>this.x = {} : {} +>this.x : {} | undefined +>this : typeof globalThis +>x : {} | undefined +>{} : {} + +this.x.y = {}; +>this.x.y = {} : {} +>this.x.y : any +>this.x : {} +>this : typeof globalThis +>x : {} +>y : any +>{} : {} + +this["x"] = {}; +>this["x"] = {} : {} +>this["x"] : {} | undefined +>this : typeof globalThis +>"x" : "x" +>{} : {} + +this["x"]["y"] = {}; +>this["x"]["y"] = {} : {} +>this["x"]["y"] : any +>this["x"] : {} +>this : typeof globalThis +>"x" : "x" +>"y" : "y" +>{} : {} + diff --git a/tests/cases/conformance/salsa/thisPropertyAssignment.ts b/tests/cases/conformance/salsa/thisPropertyAssignment.ts new file mode 100644 index 0000000000000..6ffc8dc71f887 --- /dev/null +++ b/tests/cases/conformance/salsa/thisPropertyAssignment.ts @@ -0,0 +1,17 @@ +// @noEmit: true +// @checkJs: true +// @allowJs: true +// @strict: true +// @Filename: a.js + +// This test is asserting that a single property/element access +// on `this` is a special assignment declaration, but chaining +// off that does not create additional declarations. I’m not sure +// if it needs to be that way in JavaScript; the test simply +// ensures no accidental changes were introduced while allowing +// element access assignments to create declarations. + +this.x = {}; +this.x.y = {}; +this["x"] = {}; +this["x"]["y"] = {}; From c04e30a414363d8b77a6277a4b7f54989f25760d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 20 Sep 2019 16:47:13 -0700 Subject: [PATCH 12/25] Add test for and fix prototype property assignment --- src/compiler/checker.ts | 3 +- src/compiler/utilities.ts | 2 +- .../typeFromPrototypeAssignment4.symbols | 47 +++++++++++++ .../typeFromPrototypeAssignment4.types | 66 +++++++++++++++++++ .../salsa/typeFromPrototypeAssignment4.ts | 24 +++++++ 5 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/typeFromPrototypeAssignment4.symbols create mode 100644 tests/baselines/reference/typeFromPrototypeAssignment4.types create mode 100644 tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7fcda0f8fe100..516a94207ca10 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6015,7 +6015,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) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 269c53b2c4a49..b423afa21d20d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1874,7 +1874,7 @@ namespace ts { decl = name; } - if (!name || !isEntityNameExpression(name) || !isSameEntityName(name, node.parent.left)) { + if (!name || !isBindableNameExpression(name) || !isSameEntityName(name, node.parent.left)) { return undefined; } } diff --git a/tests/baselines/reference/typeFromPrototypeAssignment4.symbols b/tests/baselines/reference/typeFromPrototypeAssignment4.symbols new file mode 100644 index 0000000000000..c8dc49661990a --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment4.symbols @@ -0,0 +1,47 @@ +=== tests/cases/conformance/salsa/a.js === +function Multimap4() { +>Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) + + this._map = {}; +>_map : Symbol(Multimap4._map, Decl(a.js, 0, 22)) + +}; + +Multimap4["prototype"] = { +>Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) +>"prototype" : Symbol(Multimap4["prototype"], Decl(a.js, 2, 2)) + + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key) { +>get : Symbol(get, Decl(a.js, 4, 26)) +>key : Symbol(key, Decl(a.js, 9, 6)) + + return this._map[key + '']; +>this._map : Symbol(Multimap4._map, Decl(a.js, 0, 22)) +>this : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) +>_map : Symbol(Multimap4._map, Decl(a.js, 0, 22)) +>key : Symbol(key, Decl(a.js, 9, 6)) + } +}; + +Multimap4["prototype"]["addon"] = function() {}; +>Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) +>"prototype" : Symbol(Multimap4["prototype"], Decl(a.js, 2, 2)) + +const map4 = new Multimap4(); +>map4 : Symbol(map4, Decl(a.js, 16, 5)) +>Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) + +map4.get(""); +>map4.get : Symbol(get, Decl(a.js, 4, 26)) +>map4 : Symbol(map4, Decl(a.js, 16, 5)) +>get : Symbol(get, Decl(a.js, 4, 26)) + +map4.addon(); +>map4.addon : Symbol(Multimap4["addon"], Decl(a.js, 12, 2)) +>map4 : Symbol(map4, Decl(a.js, 16, 5)) +>addon : Symbol(Multimap4["addon"], Decl(a.js, 12, 2)) + diff --git a/tests/baselines/reference/typeFromPrototypeAssignment4.types b/tests/baselines/reference/typeFromPrototypeAssignment4.types new file mode 100644 index 0000000000000..1121340110256 --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment4.types @@ -0,0 +1,66 @@ +=== tests/cases/conformance/salsa/a.js === +function Multimap4() { +>Multimap4 : typeof Multimap4 + + this._map = {}; +>this._map = {} : {} +>this._map : any +>this : any +>_map : any +>{} : {} + +}; + +Multimap4["prototype"] = { +>Multimap4["prototype"] = { /** * @param {string} key * @returns {number} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: string): number; } +>Multimap4["prototype"] : { get(key: string): number; } +>Multimap4 : typeof Multimap4 +>"prototype" : "prototype" +>{ /** * @param {string} key * @returns {number} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: string): number; } + + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key) { +>get : (key: string) => number +>key : string + + return this._map[key + '']; +>this._map[key + ''] : any +>this._map : {} +>this : this +>_map : {} +>key + '' : string +>key : string +>'' : "" + } +}; + +Multimap4["prototype"]["addon"] = function() {}; +>Multimap4["prototype"]["addon"] = function() {} : () => void +>Multimap4["prototype"]["addon"] : any +>Multimap4["prototype"] : { get(key: string): number; } +>Multimap4 : typeof Multimap4 +>"prototype" : "prototype" +>"addon" : "addon" +>function() {} : () => void + +const map4 = new Multimap4(); +>map4 : Multimap4 +>new Multimap4() : Multimap4 +>Multimap4 : typeof Multimap4 + +map4.get(""); +>map4.get("") : number +>map4.get : (key: string) => number +>map4 : Multimap4 +>get : (key: string) => number +>"" : "" + +map4.addon(); +>map4.addon() : void +>map4.addon : () => void +>map4 : Multimap4 +>addon : () => void + diff --git a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts new file mode 100644 index 0000000000000..9e77133286faa --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts @@ -0,0 +1,24 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: a.js + +function Multimap4() { + this._map = {}; +}; + +Multimap4["prototype"] = { + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key) { + return this._map[key + '']; + } +}; + +Multimap4["prototype"]["addon"] = function() {}; + +const map4 = new Multimap4(); +map4.get(""); +map4.addon(); From a357d311344d39328c89b680c5d8cb7f779ef3f9 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 20 Sep 2019 16:53:24 -0700 Subject: [PATCH 13/25] Fix teeeest??? --- .../cases/fourslash/referencesForStringLiteralPropertyNames7.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/fourslash/referencesForStringLiteralPropertyNames7.ts b/tests/cases/fourslash/referencesForStringLiteralPropertyNames7.ts index 3013d4f6d0d80..9bc641e0787fe 100644 --- a/tests/cases/fourslash/referencesForStringLiteralPropertyNames7.ts +++ b/tests/cases/fourslash/referencesForStringLiteralPropertyNames7.ts @@ -5,7 +5,7 @@ // @checkJs: true ////var x = { [|"[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 0 |}someProperty|]": 0|] } -////x["[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 3 |}someProperty|]"] = 3; +////x["[|{| "isWriteAccess": true, "isDefinition": true |}someProperty|]"] = 3; ////[|x.[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 3 |}someProperty|] = 5;|] const [r0Def, r0, r1, r2Def, r2] = test.ranges(); From 31460661e883662c20ef063a0bb7436d4ce01977 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 20 Sep 2019 16:54:32 -0700 Subject: [PATCH 14/25] Update APIs --- tests/baselines/reference/api/tsserverlibrary.d.ts | 6 +++--- tests/baselines/reference/api/typescript.d.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 79450ca83c9d3..0e2cc18170722 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -842,7 +842,7 @@ declare namespace ts { kind: SyntaxKind.LiteralType; literal: BooleanLiteral | LiteralExpression | PrefixUnaryExpression; } - export interface StringLiteral extends LiteralExpression { + export interface StringLiteral extends LiteralExpression, Declaration { kind: SyntaxKind.StringLiteral; } export type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral; @@ -1006,7 +1006,7 @@ declare namespace ts { export interface RegularExpressionLiteral extends LiteralExpression { kind: SyntaxKind.RegularExpressionLiteral; } - export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode { + export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode, Declaration { kind: SyntaxKind.NoSubstitutionTemplateLiteral; } export enum TokenFlags { @@ -1017,7 +1017,7 @@ declare namespace ts { BinarySpecifier = 128, OctalSpecifier = 256, } - export interface NumericLiteral extends LiteralExpression { + export interface NumericLiteral extends LiteralExpression, Declaration { kind: SyntaxKind.NumericLiteral; } export interface BigIntLiteral extends LiteralExpression { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index f0ae718d57001..5be30c674a4e3 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -842,7 +842,7 @@ declare namespace ts { kind: SyntaxKind.LiteralType; literal: BooleanLiteral | LiteralExpression | PrefixUnaryExpression; } - export interface StringLiteral extends LiteralExpression { + export interface StringLiteral extends LiteralExpression, Declaration { kind: SyntaxKind.StringLiteral; } export type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral; @@ -1006,7 +1006,7 @@ declare namespace ts { export interface RegularExpressionLiteral extends LiteralExpression { kind: SyntaxKind.RegularExpressionLiteral; } - export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode { + export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode, Declaration { kind: SyntaxKind.NoSubstitutionTemplateLiteral; } export enum TokenFlags { @@ -1017,7 +1017,7 @@ declare namespace ts { BinarySpecifier = 128, OctalSpecifier = 256, } - export interface NumericLiteral extends LiteralExpression { + export interface NumericLiteral extends LiteralExpression, Declaration { kind: SyntaxKind.NumericLiteral; } export interface BigIntLiteral extends LiteralExpression { From 862b8220ce7585d9512621b944c3f395edd4f341 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 25 Sep 2019 13:22:09 -0700 Subject: [PATCH 15/25] Fix element access declarations on `this` --- src/compiler/utilities.ts | 48 +++++++------ .../thisPropertyAssignment.errors.txt | 34 +++++++-- .../reference/thisPropertyAssignment.symbols | 45 ++++++++++-- .../reference/thisPropertyAssignment.types | 70 ++++++++++++++++--- .../salsa/thisPropertyAssignment.ts | 16 ++++- 5 files changed, 168 insertions(+), 45 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b423afa21d20d..54865eb30f1f8 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1104,7 +1104,7 @@ namespace ts { // At this point, node is either a qualified name or an identifier Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression, "'node' was expected to be a qualified name, identifier or property access in 'isPartOfTypeNode'."); - // falls through + // falls through case SyntaxKind.QualifiedName: case SyntaxKind.PropertyAccessExpression: @@ -1305,7 +1305,7 @@ namespace ts { export function isValidESSymbolDeclaration(node: Node): node is VariableDeclaration | PropertyDeclaration | SignatureDeclaration { return isVariableDeclaration(node) ? isVarConst(node) && isIdentifier(node.name) && isVariableDeclarationInVariableStatement(node) : isPropertyDeclaration(node) ? hasReadonlyModifier(node) && hasStaticModifier(node) : - isPropertySignature(node) && hasReadonlyModifier(node); + isPropertySignature(node) && hasReadonlyModifier(node); } export function introducesArgumentsExoticObject(node: Node) { @@ -1437,7 +1437,7 @@ namespace ts { if (!includeArrowFunctions) { continue; } - // falls through + // falls through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -1497,7 +1497,7 @@ namespace ts { if (!stopOnFunctions) { continue; } - // falls through + // falls through case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: @@ -1710,7 +1710,7 @@ namespace ts { if (node.parent.kind === SyntaxKind.TypeQuery || isJSXTagName(node)) { return true; } - // falls through + // falls through case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: @@ -1975,7 +1975,7 @@ namespace ts { export function isDefaultedExpandoInitializer(node: BinaryExpression) { const name = isVariableDeclaration(node.parent) ? node.parent.name : isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken ? node.parent.left : - undefined; + undefined; return name && getExpandoInitializer(node.right, isPrototypeAccess(name)) && isEntityNameExpression(name) && isSameEntityName(name, node.left); } @@ -2054,9 +2054,11 @@ namespace ts { isBindableNameExpression(expr.arguments[0]); } - export function isBindableElementAccessExpression(node: Node): node is BindableElementAccessExpression { + export function isBindableElementAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableElementAccessExpression { return isLiteralLikeElementAccess(node) - && (isEntityNameExpression(node.expression) || isBindableElementAccessExpression(node.expression)); + && ((!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword) || + isEntityNameExpression(node.expression) || + isBindableElementAccessExpression(node.expression, /*excludeThisKeyword*/ true)); } export function isLiteralLikeAccess(node: Node): node is LiteralLikeElementAccessExpression | PropertyAccessExpression { @@ -2344,13 +2346,13 @@ namespace ts { // var x = function(name) { return name.length; } else if (parent.parent && (getSingleVariableOfVariableStatement(parent.parent) === node || - isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken)) { + isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken)) { return parent.parent; } else if (parent.parent && parent.parent.parent && (getSingleVariableOfVariableStatement(parent.parent.parent) || - getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.parent.parent) === node || - getSourceOfDefaultedAssignment(parent.parent.parent))) { + getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.parent.parent) === node || + getSourceOfDefaultedAssignment(parent.parent.parent))) { return parent.parent.parent; } } @@ -2577,7 +2579,7 @@ namespace ts { case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: if (isComputedPropertyName(parent)) return parent.parent; - // falls through + // falls through case SyntaxKind.Identifier: if (isDeclaration(parent)) { @@ -2694,7 +2696,7 @@ namespace ts { export function getAllSuperTypeNodes(node: Node): readonly TypeNode[] { return isInterfaceDeclaration(node) ? getInterfaceBaseTypeNodes(node) || emptyArray : isClassLike(node) ? concatenate(singleElementArray(getEffectiveBaseTypeNode(node)), getClassImplementsHeritageClauseElements(node)) || emptyArray : - emptyArray; + emptyArray; } export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration) { @@ -2781,7 +2783,7 @@ namespace ts { if (node.asteriskToken) { flags |= FunctionFlags.Generator; } - // falls through + // falls through case SyntaxKind.ArrowFunction: if (hasModifier(node, ModifierFlags.Async)) { @@ -3260,8 +3262,8 @@ namespace ts { export function escapeString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string { const escapedCharsRegExp = quoteChar === CharacterCodes.backtick ? backtickQuoteEscapedCharsRegExp : - quoteChar === CharacterCodes.singleQuote ? singleQuoteEscapedCharsRegExp : - doubleQuoteEscapedCharsRegExp; + quoteChar === CharacterCodes.singleQuote ? singleQuoteEscapedCharsRegExp : + doubleQuoteEscapedCharsRegExp; return s.replace(escapedCharsRegExp, getReplacement); } @@ -4485,7 +4487,7 @@ namespace ts { const checkFlags = (s).checkFlags; const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : - ModifierFlags.Protected; + ModifierFlags.Protected; const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; return accessModifier | staticModifier; } @@ -5572,7 +5574,7 @@ namespace ts { export function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined { return node.constraint ? node.constraint : isJSDocTemplateTag(node.parent) && node === node.parent.typeParameters[0] ? node.parent.constraint : - undefined; + undefined; } } @@ -7195,7 +7197,7 @@ namespace ts { } } - function Signature() {} + function Signature() { } function Node(this: Node, kind: SyntaxKind, pos: number, end: number) { this.pos = pos; @@ -7409,7 +7411,7 @@ namespace ts { return compilerOptions.target || ScriptTarget.ES3; } - export function getEmitModuleKind(compilerOptions: {module?: CompilerOptions["module"], target?: CompilerOptions["target"]}) { + export function getEmitModuleKind(compilerOptions: { module?: CompilerOptions["module"], target?: CompilerOptions["target"] }) { return typeof compilerOptions.module === "number" ? compilerOptions.module : getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 ? ModuleKind.ES2015 : ModuleKind.CommonJS; @@ -8831,14 +8833,14 @@ namespace ts { // using Uint16 instead of Uint32 so combining steps can use bitwise operators const segments = new Uint16Array((bitsNeeded >>> 4) + (bitsNeeded & 15 ? 1 : 0)); // Add the digits, one at a time - for (let i = endIndex - 1, bitOffset = 0; i >= startIndex; i--, bitOffset += log2Base) { + for (let i = endIndex - 1, bitOffset = 0; i >= startIndex; i-- , bitOffset += log2Base) { const segment = bitOffset >>> 4; const digitChar = stringValue.charCodeAt(i); // Find character range: 0-9 < A-F < a-f const digit = digitChar <= CharacterCodes._9 ? digitChar - CharacterCodes._0 : 10 + digitChar - - (digitChar <= CharacterCodes.F ? CharacterCodes.A : CharacterCodes.a); + (digitChar <= CharacterCodes.F ? CharacterCodes.A : CharacterCodes.a); const shiftedDigit = digit << (bitOffset & 15); segments[segment] |= shiftedDigit; const residual = shiftedDigit >>> 16; @@ -8866,7 +8868,7 @@ namespace ts { return base10Value; } - export function pseudoBigIntToString({negative, base10Value}: PseudoBigInt): string { + export function pseudoBigIntToString({ negative, base10Value }: PseudoBigInt): string { return (negative && base10Value !== "0" ? "-" : "") + base10Value; } } diff --git a/tests/baselines/reference/thisPropertyAssignment.errors.txt b/tests/baselines/reference/thisPropertyAssignment.errors.txt index c4b8370c74864..77754631a799e 100644 --- a/tests/baselines/reference/thisPropertyAssignment.errors.txt +++ b/tests/baselines/reference/thisPropertyAssignment.errors.txt @@ -1,9 +1,12 @@ tests/cases/conformance/salsa/a.js(9,8): error TS2339: Property 'y' does not exist on type '{}'. -tests/cases/conformance/salsa/a.js(11,1): error TS7053: Element implicitly has an 'any' type because expression of type '"y"' can't be used to index type '{}'. - Property 'y' does not exist on type '{}'. +tests/cases/conformance/salsa/a.js(11,1): error TS7053: Element implicitly has an 'any' type because expression of type '"z"' can't be used to index type '{}'. + Property 'z' does not exist on type '{}'. +tests/cases/conformance/salsa/a.js(16,10): error TS2339: Property 'b' does not exist on type '{}'. +tests/cases/conformance/salsa/a.js(18,3): error TS7053: Element implicitly has an 'any' type because expression of type '"c"' can't be used to index type '{}'. + Property 'c' does not exist on type '{}'. -==== tests/cases/conformance/salsa/a.js (2 errors) ==== +==== tests/cases/conformance/salsa/a.js (4 errors) ==== // This test is asserting that a single property/element access // on `this` is a special assignment declaration, but chaining // off that does not create additional declarations. I’m not sure @@ -15,9 +18,26 @@ tests/cases/conformance/salsa/a.js(11,1): error TS7053: Element implicitly has a this.x.y = {}; ~ !!! error TS2339: Property 'y' does not exist on type '{}'. - this["x"] = {}; - this["x"]["y"] = {}; + this["y"] = {}; + this["y"]["z"] = {}; ~~~~~~~~~~~~~~ -!!! error TS7053: Element implicitly has an 'any' type because expression of type '"y"' can't be used to index type '{}'. -!!! error TS7053: Property 'y' does not exist on type '{}'. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"z"' can't be used to index type '{}'. +!!! error TS7053: Property 'z' does not exist on type '{}'. + + /** @constructor */ + function F() { + this.a = {}; + this.a.b = {}; + ~ +!!! error TS2339: Property 'b' does not exist on type '{}'. + this["b"] = {}; + this["b"]["c"] = {}; + ~~~~~~~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"c"' can't be used to index type '{}'. +!!! error TS7053: Property 'c' does not exist on type '{}'. + } + + const f = new F(); + f.a; + f.b; \ No newline at end of file diff --git a/tests/baselines/reference/thisPropertyAssignment.symbols b/tests/baselines/reference/thisPropertyAssignment.symbols index b8b299f104403..2517ae0f1425f 100644 --- a/tests/baselines/reference/thisPropertyAssignment.symbols +++ b/tests/baselines/reference/thisPropertyAssignment.symbols @@ -16,11 +16,48 @@ this.x.y = {}; >this : Symbol(globalThis) >x : Symbol(x, Decl(a.js, 0, 0)) -this["x"] = {}; +this["y"] = {}; >this : Symbol(globalThis) ->"x" : Symbol(x, Decl(a.js, 0, 0)) +>"y" : Symbol("y", Decl(a.js, 8, 14)) -this["x"]["y"] = {}; +this["y"]["z"] = {}; >this : Symbol(globalThis) ->"x" : Symbol(x, Decl(a.js, 0, 0)) +>"y" : Symbol("y", Decl(a.js, 8, 14)) + +/** @constructor */ +function F() { +>F : Symbol(F, Decl(a.js, 10, 20)) + + this.a = {}; +>this.a : Symbol(F.a, Decl(a.js, 13, 14)) +>this : Symbol(F, Decl(a.js, 10, 20)) +>a : Symbol(F.a, Decl(a.js, 13, 14)) + + this.a.b = {}; +>this.a : Symbol(F.a, Decl(a.js, 13, 14)) +>this : Symbol(F, Decl(a.js, 10, 20)) +>a : Symbol(F.a, Decl(a.js, 13, 14)) + + this["b"] = {}; +>this : Symbol(F, Decl(a.js, 10, 20)) +>"b" : Symbol(F["b"], Decl(a.js, 15, 16)) + + this["b"]["c"] = {}; +>this : Symbol(F, Decl(a.js, 10, 20)) +>"b" : Symbol(F["b"], Decl(a.js, 15, 16)) +} + +const f = new F(); +>f : Symbol(f, Decl(a.js, 20, 5)) +>F : Symbol(F, Decl(a.js, 10, 20)) + +f.a; +>f.a : Symbol(F.a, Decl(a.js, 13, 14)) +>f : Symbol(f, Decl(a.js, 20, 5)) +>a : Symbol(F.a, Decl(a.js, 13, 14)) + +f.b; +>f.b : Symbol(F["b"], Decl(a.js, 15, 16)) +>f : Symbol(f, Decl(a.js, 20, 5)) +>b : Symbol(F["b"], Decl(a.js, 15, 16)) diff --git a/tests/baselines/reference/thisPropertyAssignment.types b/tests/baselines/reference/thisPropertyAssignment.types index cd9e95e6da373..c919f47db1eec 100644 --- a/tests/baselines/reference/thisPropertyAssignment.types +++ b/tests/baselines/reference/thisPropertyAssignment.types @@ -22,19 +22,71 @@ this.x.y = {}; >y : any >{} : {} -this["x"] = {}; ->this["x"] = {} : {} ->this["x"] : {} | undefined +this["y"] = {}; +>this["y"] = {} : {} +>this["y"] : {} | undefined >this : typeof globalThis ->"x" : "x" +>"y" : "y" >{} : {} -this["x"]["y"] = {}; ->this["x"]["y"] = {} : {} ->this["x"]["y"] : any ->this["x"] : {} +this["y"]["z"] = {}; +>this["y"]["z"] = {} : {} +>this["y"]["z"] : any +>this["y"] : {} >this : typeof globalThis ->"x" : "x" >"y" : "y" +>"z" : "z" +>{} : {} + +/** @constructor */ +function F() { +>F : typeof F + + this.a = {}; +>this.a = {} : {} +>this.a : {} +>this : this +>a : {} >{} : {} + this.a.b = {}; +>this.a.b = {} : {} +>this.a.b : any +>this.a : {} +>this : this +>a : {} +>b : any +>{} : {} + + this["b"] = {}; +>this["b"] = {} : {} +>this["b"] : {} +>this : this +>"b" : "b" +>{} : {} + + this["b"]["c"] = {}; +>this["b"]["c"] = {} : {} +>this["b"]["c"] : any +>this["b"] : {} +>this : this +>"b" : "b" +>"c" : "c" +>{} : {} +} + +const f = new F(); +>f : F +>new F() : F +>F : typeof F + +f.a; +>f.a : {} +>f : F +>a : {} + +f.b; +>f.b : {} +>f : F +>b : {} + diff --git a/tests/cases/conformance/salsa/thisPropertyAssignment.ts b/tests/cases/conformance/salsa/thisPropertyAssignment.ts index 6ffc8dc71f887..e33375fc8661b 100644 --- a/tests/cases/conformance/salsa/thisPropertyAssignment.ts +++ b/tests/cases/conformance/salsa/thisPropertyAssignment.ts @@ -13,5 +13,17 @@ this.x = {}; this.x.y = {}; -this["x"] = {}; -this["x"]["y"] = {}; +this["y"] = {}; +this["y"]["z"] = {}; + +/** @constructor */ +function F() { + this.a = {}; + this.a.b = {}; + this["b"] = {}; + this["b"]["c"] = {}; +} + +const f = new F(); +f.a; +f.b; From 7436c2ddb0773f1194bc91322d4956576ba89f15 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 25 Sep 2019 15:10:33 -0700 Subject: [PATCH 16/25] Fix go-to-definition --- src/compiler/utilities.ts | 2 +- .../cases/fourslash/goToDefinitionExpandoElementAccess.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 54865eb30f1f8..c3df095cf9573 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1886,7 +1886,7 @@ namespace ts { } export function isAssignmentDeclaration(decl: Declaration) { - return isBinaryExpression(decl) || isPropertyAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl); + return isBinaryExpression(decl) || isPropertyAccessExpression(decl) || isElementAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl); } /** Get the initializer, taking into account defaulted Javascript initializers */ diff --git a/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts b/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts new file mode 100644 index 0000000000000..5deeb4c02532e --- /dev/null +++ b/tests/cases/fourslash/goToDefinitionExpandoElementAccess.ts @@ -0,0 +1,7 @@ +/// + +////function f() {} +////f[/*0*/"x"] = 0; +////f[[|/*1*/"x"|]] = 1; + +verify.goToDefinition("1", "0"); From f25848aab7dda6ee10a2b375fce46dd9380ddebe Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 26 Sep 2019 17:13:19 -0700 Subject: [PATCH 17/25] Add declaration emit to tests --- src/compiler/checker.ts | 4 ++-- .../jsdoc/moduleExportsElementAccessAssignment.ts | 3 ++- tests/cases/conformance/salsa/thisPropertyAssignment.ts | 3 ++- .../conformance/salsa/typeFromPropertyAssignment38.ts | 1 + .../conformance/salsa/typeFromPropertyAssignment39.ts | 3 ++- .../conformance/salsa/typeFromPrototypeAssignment4.ts | 8 +++++--- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 99ad606d333a3..8ec0e202a5667 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3033,7 +3033,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); @@ -31929,7 +31929,7 @@ namespace ts { return node; case SyntaxKind.PropertyAccessExpression: do { - if (isModuleExportsPropertyAccessExpression(node.expression)) { + if (isModuleExportsAccessExpression(node.expression)) { return node.name; } node = node.expression; diff --git a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts index 5cf826facc1d0..1608c199b2cfe 100644 --- a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts +++ b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts @@ -1,7 +1,8 @@ // @allowJs: true -// @noEmit: true // @strict: true // @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true // @filename: mod1.js exports.a = { x: "x" }; exports["b"] = { x: "x" }; diff --git a/tests/cases/conformance/salsa/thisPropertyAssignment.ts b/tests/cases/conformance/salsa/thisPropertyAssignment.ts index e33375fc8661b..df4bfa71ef14c 100644 --- a/tests/cases/conformance/salsa/thisPropertyAssignment.ts +++ b/tests/cases/conformance/salsa/thisPropertyAssignment.ts @@ -1,7 +1,8 @@ -// @noEmit: true // @checkJs: true // @allowJs: true // @strict: true +// @declaration: true +// @emitDeclarationOnly: true // @Filename: a.js // This test is asserting that a single property/element access diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts index f307185f37e9b..86104cfb0b62e 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment38.ts @@ -1,5 +1,6 @@ // @noEmit: true // @strict: true +// @declaration: true function F() {} F["prop"] = 3; diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts index 3eff15070ded6..cd170ae0e04a7 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts @@ -1,7 +1,8 @@ -// @noEmit: true // @allowJs: true // @checkJs: true // @strict: true +// @declaration: true +// @emitDeclarationOnly: true // @Filename: a.js const foo = {}; diff --git a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts index 9e77133286faa..acc397ada1fd9 100644 --- a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts +++ b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts @@ -1,6 +1,8 @@ -// @noEmit: true // @allowJs: true // @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true +// @outDir: out // @Filename: a.js function Multimap4() { @@ -17,8 +19,8 @@ Multimap4["prototype"] = { } }; -Multimap4["prototype"]["addon"] = function() {}; +Multimap4["prototype"]["add-on"] = function() {}; const map4 = new Multimap4(); map4.get(""); -map4.addon(); +map4["add-on"](); From fe3a57487653566ff607385356ec78b1ce12fb8c Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 27 Sep 2019 16:21:07 -0700 Subject: [PATCH 18/25] Reconcile with late-bound symbol element access assignment --- src/compiler/binder.ts | 42 ++++++++++++------------ src/compiler/checker.ts | 2 +- src/compiler/types.ts | 24 +++++++++----- src/compiler/utilities.ts | 62 +++++++++++++---------------------- src/services/navigationBar.ts | 8 ++--- 5 files changed, 64 insertions(+), 74 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a6cf66f931d28..451cd5b3ba7e1 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2273,16 +2273,16 @@ namespace ts { const specialKind = getAssignmentDeclarationKind(node as BinaryExpression); switch (specialKind) { case AssignmentDeclarationKind.ExportsProperty: - bindExportsPropertyAssignment(node as BindablePropertyAssignmentExpression); + bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); break; case AssignmentDeclarationKind.ModuleExports: bindModuleExportsAssignment(node as BindablePropertyAssignmentExpression); break; case AssignmentDeclarationKind.PrototypeProperty: - bindPrototypePropertyAssignment((node as BindablePropertyAssignmentExpression).left, node); + bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node); break; case AssignmentDeclarationKind.Prototype: - bindPrototypeAssignment(node as BindablePropertyAssignmentExpression); + bindPrototypeAssignment(node as BindableStaticPropertyAssignmentExpression); break; case AssignmentDeclarationKind.ThisProperty: bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); @@ -2563,7 +2563,7 @@ namespace ts { } } - function bindExportsPropertyAssignment(node: BindablePropertyAssignmentExpression) { + 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)) { @@ -2613,7 +2613,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 (isBindableAccessExpression(l) && isPrototypeAccess(l.expression)) { + if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) { constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer); } } @@ -2679,7 +2679,7 @@ namespace ts { if (node.expression.kind === SyntaxKind.ThisKeyword) { bindThisPropertyAssignment(node); } - else if (isBindableAccessExpression(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); } @@ -2690,7 +2690,7 @@ namespace ts { } /** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */ - function bindPrototypeAssignment(node: BindablePropertyAssignmentExpression) { + function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) { node.left.parent = node; node.right.parent = node; bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true); @@ -2705,10 +2705,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: BindableAccessExpression, 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 BindableAccessExpression; + 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 @@ -2726,7 +2726,7 @@ namespace ts { bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false); } - function bindSpecialPropertyAssignment(node: BindableAssignmentExpression) { + function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { // Class declarations in Typescript do not allow property declarations const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression); if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { @@ -2739,16 +2739,16 @@ namespace ts { // 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(node.left); + bindStaticPropertyAssignment(cast(node.left, isBindableStaticAccessExpression)); } } } @@ -2757,12 +2757,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: BindableAccessExpression) { + function bindStaticPropertyAssignment(node: BindableStaticAccessExpression) { node.expression.parent = node; bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); } - function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableNameExpression, 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; @@ -2785,7 +2785,7 @@ namespace ts { return namespaceSymbol; } - function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { + function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) { return; } @@ -2807,7 +2807,7 @@ namespace ts { : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; } - function bindPropertyAssignment(name: BindableNameExpression, propertyAccess: BindableAccessExpression, 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); @@ -2852,17 +2852,17 @@ namespace ts { return expr.parent; } - function lookupSymbolForPropertyAccess(node: BindableNameExpression, 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(getNameOrArgumentText(node)); + return symbol && symbol.exports && symbol.exports.get(escapeLeadingUnderscores(getElementOrPropertyAccessName(node))); } } - function forEachIdentifierInEntityName(e: BindableNameExpression, parent: Symbol | undefined, action: (e: Declaration, 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; } @@ -2871,7 +2871,7 @@ namespace ts { } else { const s = forEachIdentifierInEntityName(e.expression, parent, action); - return action(getNameOrArgument(e), s && s.exports && s.exports.get(getNameOrArgumentText(e)), s); + return action(getNameOrArgument(e), s && s.exports && s.exports.get(escapeLeadingUnderscores(getElementOrPropertyAccessName(e))), s); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 02baa49aced60..64ebe94296052 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7195,7 +7195,7 @@ namespace ts { else if ( isBinaryExpression(declaration) || (isInJSFile(declaration) && - (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) { + (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) { type = getWidenedTypeForAssignmentDeclaration(symbol); } else if (isJSDocPropertyLikeTag(declaration) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 28ab947a9d726..919a0a873f812 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1837,25 +1837,31 @@ namespace ts { } /** @internal */ - export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: BindableNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; + export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: BindableStaticNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; /** @internal */ - export type BindableNameExpression = EntityNameExpression | BindableElementAccessExpression; - /** @internal */ - export type BindableAccessExpression = PropertyAccessEntityNameExpression | BindableElementAccessExpression; + export type BindableStaticNameExpression = EntityNameExpression | BindableStaticElementAccessExpression; /** @internal */ export type LiteralLikeElementAccessExpression = ElementAccessExpression & Declaration & { argumentExpression: StringLiteralLike | NumericLiteral; }; /** @internal */ - export type BindableElementAccessExpression = LiteralLikeElementAccessExpression & { - expression: BindableNameExpression; + export type BindableStaticElementAccessExpression = LiteralLikeElementAccessExpression & { + expression: BindableStaticNameExpression; + }; + /** @internal */ + export type BindableElementAccessExpression = ElementAccessExpression & { + expression: BindableStaticNameExpression; }; /** @internal */ - export interface BindableAssignmentExpression extends BinaryExpression { - left: BindableNameExpression; + export type BindableStaticAccessExpression = PropertyAccessEntityNameExpression | BindableStaticElementAccessExpression; + /** @internal */ + export type BindableAccessExpression = PropertyAccessEntityNameExpression | BindableElementAccessExpression; + /** @internal */ + export interface BindableStaticPropertyAssignmentExpression extends BinaryExpression { + left: BindableStaticAccessExpression; } /** @internal */ - export interface BindablePropertyAssignmentExpression extends BindableAssignmentExpression { + export interface BindablePropertyAssignmentExpression extends BinaryExpression { left: BindableAccessExpression; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d4ab025b046f7..300b4dbf25f34 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1874,7 +1874,7 @@ namespace ts { decl = name; } - if (!name || !isBindableNameExpression(name) || !isSameEntityName(name, node.parent.left)) { + if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, node.parent.left)) { return undefined; } } @@ -2014,7 +2014,8 @@ namespace ts { isSameEntityName(name, getNameOrArgument(initializer)); } if (isLiteralLikeAccess(name) && isLiteralLikeAccess(initializer)) { - return getNameOrArgumentText(name) === getNameOrArgumentText(initializer) && isSameEntityName(name.expression, initializer.expression); + return getElementOrPropertyAccessName(name) === getElementOrPropertyAccessName(initializer) + && isSameEntityName(name.expression, initializer.expression); } return false; } @@ -2034,7 +2035,7 @@ namespace ts { return (isPropertyAccessExpression(node) || isLiteralLikeElementAccess(node)) && isIdentifier(node.expression) && node.expression.escapedText === "module" - && getNameOrArgumentText(node) === "exports"; + && getElementOrPropertyAccessName(node) === "exports"; } /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property @@ -2051,14 +2052,14 @@ namespace ts { idText(expr.expression.expression) === "Object" && idText(expr.expression.name) === "defineProperty" && isStringOrNumericLiteralLike(expr.arguments[1]) && - isBindableNameExpression(expr.arguments[0]); + isBindableStaticNameExpression(expr.arguments[0]); } - export function isBindableElementAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableElementAccessExpression { + export function isBindableStaticElementAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticElementAccessExpression { return isLiteralLikeElementAccess(node) && ((!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword) || isEntityNameExpression(node.expression) || - isBindableElementAccessExpression(node.expression, /*excludeThisKeyword*/ true)); + isBindableStaticElementAccessExpression(node.expression, /*excludeThisKeyword*/ true)); } export function isLiteralLikeAccess(node: Node): node is LiteralLikeElementAccessExpression | PropertyAccessExpression { @@ -2069,17 +2070,13 @@ namespace ts { return isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression); } - export function isBindableAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableAccessExpression { - return isPropertyAccessExpression(node) && (!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword || isBindableNameExpression(node.expression, /*excludeThisKeyword*/ true)) - || isBindableElementAccessExpression(node); + 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 isBindableNameExpression(node: Node, excludeThisKeyword?: boolean): node is BindableNameExpression { - return isEntityNameExpression(node) || isBindableAccessExpression(node, excludeThisKeyword); - } - - export function isBindableAssignmentExpression(node: Node): node is BindableAssignmentExpression { - return isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.EqualsToken && isBindableNameExpression(node.left); + export function isBindableStaticNameExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticNameExpression { + return isEntityNameExpression(node) || isBindableStaticAccessExpression(node, excludeThisKeyword); } export function getNameOrArgument(expr: PropertyAccessExpression | LiteralLikeElementAccessExpression) { @@ -2089,13 +2086,6 @@ namespace ts { return expr.argumentExpression; } - export function getNameOrArgumentText(expr: PropertyAccessExpression | LiteralLikeElementAccessExpression): __String { - if (isPropertyAccessExpression(expr)) { - return expr.name.escapedText; - } - return escapeLeadingUnderscores(expr.argumentExpression.text); - } - function getAssignmentDeclarationKindWorker(expr: BinaryExpression | CallExpression): AssignmentDeclarationKind { if (isCallExpression(expr)) { if (!isBindableObjectDefinePropertyCall(expr)) { @@ -2105,7 +2095,7 @@ namespace ts { if (isExportsIdentifier(entityName) || isModuleExportsAccessExpression(entityName)) { return AssignmentDeclarationKind.ObjectDefinePropertyExports; } - if (isBindableAccessExpression(entityName) && getNameOrArgumentText(entityName) === "prototype") { + if (isBindableStaticAccessExpression(entityName) && getElementOrPropertyAccessName(entityName) === "prototype") { return AssignmentDeclarationKind.ObjectDefinePrototypeProperty; } return AssignmentDeclarationKind.ObjectDefinePropertyValue; @@ -2113,7 +2103,7 @@ namespace ts { if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || !isAccessExpression(expr.left)) { return AssignmentDeclarationKind.None; } - if (isBindableNameExpression(expr.left.expression) && getNameOrArgumentText(expr.left) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { + if (isBindableStaticNameExpression(expr.left.expression) && getElementOrPropertyAccessName(expr.left) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { // F.prototype = { ... } return AssignmentDeclarationKind.Prototype; } @@ -2137,6 +2127,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) { @@ -2158,20 +2150,19 @@ namespace ts { // module.exports = expr return AssignmentDeclarationKind.ModuleExports; } - else if (isBindableAccessExpression(lhs)) { + else if (isBindableStaticNameExpression(lhs.expression, /*excludeThisKeyword*/ true)) { if (isPrototypeAccess(lhs.expression)) { // F.G....prototype.x = expr return AssignmentDeclarationKind.PrototypeProperty; } let nextToLast = lhs; - // while (!isIdentifier(nextToLast.expression)) { - while (isAccessExpression(nextToLast.expression)) { - nextToLast = nextToLast.expression; + while (!isIdentifier(nextToLast.expression)) { + nextToLast = nextToLast.expression as Exclude; } const id = nextToLast.expression; if (id.escapedText === "exports" || - id.escapedText === "module" && getNameOrArgumentText(nextToLast) === "exports") { + id.escapedText === "module" && getElementOrPropertyAccessName(nextToLast) === "exports") { // exports.name = expr OR module.exports.name = expr return AssignmentDeclarationKind.ExportsProperty; } @@ -4171,8 +4162,8 @@ namespace ts { return undefined; } - export function isPrototypeAccess(node: Node): node is BindableAccessExpression { - return isBindableAccessExpression(node) && getNameOrArgumentText(node) === "prototype"; + export function isPrototypeAccess(node: Node): node is BindableStaticAccessExpression { + return isBindableStaticAccessExpression(node) && getElementOrPropertyAccessName(node) === "prototype"; } export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) { @@ -5400,7 +5391,7 @@ namespace ts { } case SyntaxKind.ElementAccessExpression: const expr = declaration as ElementAccessExpression; - if (isBindableElementAccessExpression(expr)) { + if (isBindableStaticElementAccessExpression(expr)) { return expr.argumentExpression; } } @@ -5413,13 +5404,6 @@ namespace ts { (isFunctionExpression(declaration) || isClassExpression(declaration) ? getAssignedName(declaration) : undefined); } - function getNameOrArgument(expr: PropertyAccessExpression | BindableElementAccessExpression) { - if (isPropertyAccessExpression(expr)) { - return expr.name; - } - return expr.argumentExpression; - } - function getAssignedName(node: Node): DeclarationName | undefined { if (!node.parent) { return undefined; diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 17f155c95e605..6de1de99cd759 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -135,11 +135,11 @@ namespace ts.NavigationBar { function endNestedNodes(depth: number): void { for (let i = 0; i < depth; i++) endNode(); } - function startNestedNodes(targetNode: Node, entityName: BindableNameExpression) { + function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) { const names: PropertyNameLiteral[] = []; while (!isPropertyNameLiteral(entityName)) { const name = getNameOrArgument(entityName); - const nameText = getNameOrArgumentText(entityName); + const nameText = getElementOrPropertyAccessName(entityName); entityName = entityName.expression; if (nameText === "prototype") continue; names.push(name); @@ -387,12 +387,12 @@ namespace ts.NavigationBar { const binaryExpression = (node as BinaryExpression); const assignmentTarget = binaryExpression.left as PropertyAccessExpression | BindableElementAccessExpression; const targetFunction = assignmentTarget.expression; - if (isIdentifier(targetFunction) && getNameOrArgumentText(assignmentTarget) !== "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, getNameOrArgument(assignmentTarget)); endNode(); From 34967b8899c1b6adf2b2e8abf65ec118aefd804d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 27 Sep 2019 16:30:22 -0700 Subject: [PATCH 19/25] Fix baselines --- .../reference/moduleExportsElementAccessAssignment.types | 8 ++++---- .../reference/typeFromPropertyAssignment39.types | 6 +++--- .../jsdoc/moduleExportsElementAccessAssignment.ts | 3 +-- tests/cases/conformance/salsa/thisPropertyAssignment.ts | 3 +-- .../conformance/salsa/typeFromPropertyAssignment39.ts | 3 +-- .../conformance/salsa/typeFromPrototypeAssignment4.ts | 7 +++---- .../cases/fourslash/quickInfoElementAccessDeclaration.ts | 2 +- 7 files changed, 14 insertions(+), 18 deletions(-) diff --git a/tests/baselines/reference/moduleExportsElementAccessAssignment.types b/tests/baselines/reference/moduleExportsElementAccessAssignment.types index c9bd0a44d91f3..576bc227317e0 100644 --- a/tests/baselines/reference/moduleExportsElementAccessAssignment.types +++ b/tests/baselines/reference/moduleExportsElementAccessAssignment.types @@ -21,15 +21,15 @@ mod1.c; >c : { x: string; } mod1.d; ->mod1.d : typeof import("tests/cases/conformance/jsdoc/mod1")."d" +>mod1.d : typeof import("tests/cases/conformance/jsdoc/mod1").d >mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") ->d : typeof import("tests/cases/conformance/jsdoc/mod1")."d" +>d : typeof import("tests/cases/conformance/jsdoc/mod1").d mod1.d.e; >mod1.d.e : number ->mod1.d : typeof import("tests/cases/conformance/jsdoc/mod1")."d" +>mod1.d : typeof import("tests/cases/conformance/jsdoc/mod1").d >mod1 : typeof import("tests/cases/conformance/jsdoc/mod1") ->d : typeof import("tests/cases/conformance/jsdoc/mod1")."d" +>d : typeof import("tests/cases/conformance/jsdoc/mod1").d >e : number mod1.default; diff --git a/tests/baselines/reference/typeFromPropertyAssignment39.types b/tests/baselines/reference/typeFromPropertyAssignment39.types index ec8cdfb071c42..ce2d028524261 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment39.types +++ b/tests/baselines/reference/typeFromPropertyAssignment39.types @@ -4,8 +4,8 @@ const foo = {}; >{} : {} foo["baz"] = {}; ->foo["baz"] = {} : typeof foo."baz" ->foo["baz"] : typeof foo."baz" +>foo["baz"] = {} : typeof foo.baz +>foo["baz"] : typeof foo.baz >foo : typeof foo >"baz" : "baz" >{} : {} @@ -13,7 +13,7 @@ foo["baz"] = {}; foo["baz"]["blah"] = 3; >foo["baz"]["blah"] = 3 : 3 >foo["baz"]["blah"] : number ->foo["baz"] : typeof foo."baz" +>foo["baz"] : typeof foo.baz >foo : typeof foo >"baz" : "baz" >"blah" : "blah" diff --git a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts index 1608c199b2cfe..127d7db6642f2 100644 --- a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts +++ b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts @@ -1,8 +1,7 @@ // @allowJs: true // @strict: true // @checkJs: true -// @declaration: true -// @emitDeclarationOnly: true +// @noEmit: true // @filename: mod1.js exports.a = { x: "x" }; exports["b"] = { x: "x" }; diff --git a/tests/cases/conformance/salsa/thisPropertyAssignment.ts b/tests/cases/conformance/salsa/thisPropertyAssignment.ts index df4bfa71ef14c..22219607aaa8b 100644 --- a/tests/cases/conformance/salsa/thisPropertyAssignment.ts +++ b/tests/cases/conformance/salsa/thisPropertyAssignment.ts @@ -1,8 +1,7 @@ // @checkJs: true // @allowJs: true // @strict: true -// @declaration: true -// @emitDeclarationOnly: true +// @noEmit: true // @Filename: a.js // This test is asserting that a single property/element access diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts index cd170ae0e04a7..5d6b1fb892a7d 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts @@ -1,8 +1,7 @@ // @allowJs: true // @checkJs: true // @strict: true -// @declaration: true -// @emitDeclarationOnly: true +// @noEmit: true // @Filename: a.js const foo = {}; diff --git a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts index acc397ada1fd9..909244fa1be44 100644 --- a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts +++ b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts @@ -1,7 +1,6 @@ // @allowJs: true // @checkJs: true -// @declaration: true -// @emitDeclarationOnly: true +// @noEmit: true // @outDir: out // @Filename: a.js @@ -19,8 +18,8 @@ Multimap4["prototype"] = { } }; -Multimap4["prototype"]["add-on"] = function() {}; +Multimap4["prototype"]["addon"] = function() {}; const map4 = new Multimap4(); map4.get(""); -map4["add-on"](); +map4.addon(); diff --git a/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts b/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts index b286fd446aa01..4f36146887490 100644 --- a/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts +++ b/tests/cases/fourslash/quickInfoElementAccessDeclaration.ts @@ -9,4 +9,4 @@ goTo.marker(); verify.quickInfoIs(`module mod["@@thing1"] -(property) mod["@@thing1"]: typeof mod."@@thing1"`); +(property) mod["@@thing1"]: typeof mod.@@thing1`); From 72272d97c5036299e642f98afb254cae8e8001c0 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 27 Sep 2019 16:40:03 -0700 Subject: [PATCH 20/25] Add JS declaration back to tests --- .../jsdoc/moduleExportsElementAccessAssignment.ts | 3 ++- tests/cases/conformance/salsa/thisPropertyAssignment.ts | 3 ++- .../cases/conformance/salsa/typeFromPropertyAssignment39.ts | 3 ++- .../cases/conformance/salsa/typeFromPrototypeAssignment4.ts | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts index 127d7db6642f2..ed28f107a4045 100644 --- a/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts +++ b/tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts @@ -1,7 +1,8 @@ // @allowJs: true // @strict: true // @checkJs: true -// @noEmit: true +// @emitDeclarationOnly: true +// @declaration: true // @filename: mod1.js exports.a = { x: "x" }; exports["b"] = { x: "x" }; diff --git a/tests/cases/conformance/salsa/thisPropertyAssignment.ts b/tests/cases/conformance/salsa/thisPropertyAssignment.ts index 22219607aaa8b..44716cb7016be 100644 --- a/tests/cases/conformance/salsa/thisPropertyAssignment.ts +++ b/tests/cases/conformance/salsa/thisPropertyAssignment.ts @@ -1,7 +1,8 @@ // @checkJs: true // @allowJs: true // @strict: true -// @noEmit: true +// @emitDeclarationOnly: true +// @declaration: true // @Filename: a.js // This test is asserting that a single property/element access diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts index 5d6b1fb892a7d..17fbde3010e06 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment39.ts @@ -1,7 +1,8 @@ // @allowJs: true // @checkJs: true // @strict: true -// @noEmit: true +// @emitDeclarationOnly: true +// @declaration: true // @Filename: a.js const foo = {}; diff --git a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts index 909244fa1be44..f7ec7973b66b7 100644 --- a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts +++ b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts @@ -1,6 +1,7 @@ // @allowJs: true // @checkJs: true -// @noEmit: true +// @emitDeclarationOnly: true +// @declaration: true // @outDir: out // @Filename: a.js @@ -18,8 +19,10 @@ Multimap4["prototype"] = { } }; +Multimap4["prototype"]["add-on"] = function() {}; Multimap4["prototype"]["addon"] = function() {}; const map4 = new Multimap4(); map4.get(""); +map4["add-on"](); map4.addon(); From 98baa617742a709f2dba15b7bb719761d4b4ea30 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 30 Sep 2019 10:06:10 -0500 Subject: [PATCH 21/25] Fix JS declaration emit of non-late-bound string literal property names --- src/compiler/checker.ts | 18 ++++++-- .../moduleExportsElementAccessAssignment.js | 43 +++++++++++++++++++ .../reference/thisPropertyAssignment.js | 38 ++++++++++++++++ .../reference/typeFromPropertyAssignment39.js | 14 ++++++ .../reference/typeFromPrototypeAssignment4.js | 39 +++++++++++++++++ .../typeFromPrototypeAssignment4.symbols | 18 +++++--- .../typeFromPrototypeAssignment4.types | 15 +++++++ 7 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/moduleExportsElementAccessAssignment.js create mode 100644 tests/baselines/reference/thisPropertyAssignment.js create mode 100644 tests/baselines/reference/typeFromPropertyAssignment39.js create mode 100644 tests/baselines/reference/typeFromPrototypeAssignment4.js diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 64ebe94296052..fd5f46590e13b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4869,6 +4869,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; @@ -4881,7 +4890,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((nameType).symbol, context, SymbolFlags.Value)); @@ -4889,6 +4898,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 @@ -5748,8 +5761,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); if (p.flags & (SymbolFlags.Property | SymbolFlags.Accessor | SymbolFlags.Variable)) { return setTextRange(createProperty( /*decorators*/ undefined, diff --git a/tests/baselines/reference/moduleExportsElementAccessAssignment.js b/tests/baselines/reference/moduleExportsElementAccessAssignment.js new file mode 100644 index 0000000000000..daae7593d103d --- /dev/null +++ b/tests/baselines/reference/moduleExportsElementAccessAssignment.js @@ -0,0 +1,43 @@ +//// [tests/cases/conformance/jsdoc/moduleExportsElementAccessAssignment.ts] //// + +//// [mod1.js] +exports.a = { x: "x" }; +exports["b"] = { x: "x" }; +exports["default"] = { x: "x" }; +module.exports["c"] = { x: "x" }; +module["exports"]["d"] = {}; +module["exports"]["d"].e = 0; + +//// [mod2.js] +const mod1 = require("./mod1"); +mod1.a; +mod1.b; +mod1.c; +mod1.d; +mod1.d.e; +mod1.default; + + + +//// [mod1.d.ts] +export namespace a { + export const x: string; +} +export namespace b { + const x_1: string; + export { x_1 as x }; +} +declare namespace _default { + const x_2: string; + export { x_2 as x }; +} +export default _default; +export namespace c { + const x_3: string; + export { x_3 as x }; +} +export namespace d { + export const e: number; +} +//// [mod2.d.ts] +export {}; diff --git a/tests/baselines/reference/thisPropertyAssignment.js b/tests/baselines/reference/thisPropertyAssignment.js new file mode 100644 index 0000000000000..bd183fd622f81 --- /dev/null +++ b/tests/baselines/reference/thisPropertyAssignment.js @@ -0,0 +1,38 @@ +//// [a.js] +// This test is asserting that a single property/element access +// on `this` is a special assignment declaration, but chaining +// off that does not create additional declarations. I’m not sure +// if it needs to be that way in JavaScript; the test simply +// ensures no accidental changes were introduced while allowing +// element access assignments to create declarations. + +this.x = {}; +this.x.y = {}; +this["y"] = {}; +this["y"]["z"] = {}; + +/** @constructor */ +function F() { + this.a = {}; + this.a.b = {}; + this["b"] = {}; + this["b"]["c"] = {}; +} + +const f = new F(); +f.a; +f.b; + + + + +//// [a.d.ts] +/** @constructor */ +declare function F(): void; +declare class F { + a: {}; + b: {}; +} +declare var x: {} | undefined; +declare var y: {} | undefined; +declare const f: F; diff --git a/tests/baselines/reference/typeFromPropertyAssignment39.js b/tests/baselines/reference/typeFromPropertyAssignment39.js new file mode 100644 index 0000000000000..fb477aca063b9 --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment39.js @@ -0,0 +1,14 @@ +//// [a.js] +const foo = {}; +foo["baz"] = {}; +foo["baz"]["blah"] = 3; + + + + +//// [a.d.ts] +declare namespace foo { + namespace baz { + const blah: number; + } +} diff --git a/tests/baselines/reference/typeFromPrototypeAssignment4.js b/tests/baselines/reference/typeFromPrototypeAssignment4.js new file mode 100644 index 0000000000000..d240cda55d93d --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment4.js @@ -0,0 +1,39 @@ +//// [a.js] +function Multimap4() { + this._map = {}; +}; + +Multimap4["prototype"] = { + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key) { + return this._map[key + '']; + } +}; + +Multimap4["prototype"]["add-on"] = function() {}; +Multimap4["prototype"]["addon"] = function() {}; + +const map4 = new Multimap4(); +map4.get(""); +map4["add-on"](); +map4.addon(); + + + + +//// [a.d.ts] +declare function Multimap4(): void; +declare class Multimap4 { + _map: {}; + "add-on"(): void; + addon(): void; + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key: string): number; +} +declare const map4: Multimap4; diff --git a/tests/baselines/reference/typeFromPrototypeAssignment4.symbols b/tests/baselines/reference/typeFromPrototypeAssignment4.symbols index c8dc49661990a..64ede53df4dd4 100644 --- a/tests/baselines/reference/typeFromPrototypeAssignment4.symbols +++ b/tests/baselines/reference/typeFromPrototypeAssignment4.symbols @@ -27,21 +27,29 @@ Multimap4["prototype"] = { } }; +Multimap4["prototype"]["add-on"] = function() {}; +>Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) +>"prototype" : Symbol(Multimap4["prototype"], Decl(a.js, 2, 2)) + Multimap4["prototype"]["addon"] = function() {}; >Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) >"prototype" : Symbol(Multimap4["prototype"], Decl(a.js, 2, 2)) const map4 = new Multimap4(); ->map4 : Symbol(map4, Decl(a.js, 16, 5)) +>map4 : Symbol(map4, Decl(a.js, 17, 5)) >Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) map4.get(""); >map4.get : Symbol(get, Decl(a.js, 4, 26)) ->map4 : Symbol(map4, Decl(a.js, 16, 5)) +>map4 : Symbol(map4, Decl(a.js, 17, 5)) >get : Symbol(get, Decl(a.js, 4, 26)) +map4["add-on"](); +>map4 : Symbol(map4, Decl(a.js, 17, 5)) +>"add-on" : Symbol(Multimap4["add-on"], Decl(a.js, 12, 2)) + map4.addon(); ->map4.addon : Symbol(Multimap4["addon"], Decl(a.js, 12, 2)) ->map4 : Symbol(map4, Decl(a.js, 16, 5)) ->addon : Symbol(Multimap4["addon"], Decl(a.js, 12, 2)) +>map4.addon : Symbol(Multimap4["addon"], Decl(a.js, 14, 49)) +>map4 : Symbol(map4, Decl(a.js, 17, 5)) +>addon : Symbol(Multimap4["addon"], Decl(a.js, 14, 49)) diff --git a/tests/baselines/reference/typeFromPrototypeAssignment4.types b/tests/baselines/reference/typeFromPrototypeAssignment4.types index 1121340110256..892a5191ccfe0 100644 --- a/tests/baselines/reference/typeFromPrototypeAssignment4.types +++ b/tests/baselines/reference/typeFromPrototypeAssignment4.types @@ -37,6 +37,15 @@ Multimap4["prototype"] = { } }; +Multimap4["prototype"]["add-on"] = function() {}; +>Multimap4["prototype"]["add-on"] = function() {} : () => void +>Multimap4["prototype"]["add-on"] : any +>Multimap4["prototype"] : { get(key: string): number; } +>Multimap4 : typeof Multimap4 +>"prototype" : "prototype" +>"add-on" : "add-on" +>function() {} : () => void + Multimap4["prototype"]["addon"] = function() {}; >Multimap4["prototype"]["addon"] = function() {} : () => void >Multimap4["prototype"]["addon"] : any @@ -58,6 +67,12 @@ map4.get(""); >get : (key: string) => number >"" : "" +map4["add-on"](); +>map4["add-on"]() : void +>map4["add-on"] : () => void +>map4 : Multimap4 +>"add-on" : "add-on" + map4.addon(); >map4.addon() : void >map4.addon : () => void From 389902f29dabeafa4d804847a6b6eef4c61a7df0 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 30 Sep 2019 10:15:36 -0500 Subject: [PATCH 22/25] Revert accidental auto-format --- src/compiler/utilities.ts | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 300b4dbf25f34..20481058aec9a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1104,7 +1104,7 @@ namespace ts { // At this point, node is either a qualified name or an identifier Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression, "'node' was expected to be a qualified name, identifier or property access in 'isPartOfTypeNode'."); - // falls through + // falls through case SyntaxKind.QualifiedName: case SyntaxKind.PropertyAccessExpression: @@ -1305,7 +1305,7 @@ namespace ts { export function isValidESSymbolDeclaration(node: Node): node is VariableDeclaration | PropertyDeclaration | SignatureDeclaration { return isVariableDeclaration(node) ? isVarConst(node) && isIdentifier(node.name) && isVariableDeclarationInVariableStatement(node) : isPropertyDeclaration(node) ? hasReadonlyModifier(node) && hasStaticModifier(node) : - isPropertySignature(node) && hasReadonlyModifier(node); + isPropertySignature(node) && hasReadonlyModifier(node); } export function introducesArgumentsExoticObject(node: Node) { @@ -1437,7 +1437,7 @@ namespace ts { if (!includeArrowFunctions) { continue; } - // falls through + // falls through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -1497,7 +1497,7 @@ namespace ts { if (!stopOnFunctions) { continue; } - // falls through + // falls through case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: @@ -1709,7 +1709,7 @@ namespace ts { if (node.parent.kind === SyntaxKind.TypeQuery || isJSXTagName(node)) { return true; } - // falls through + // falls through case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: @@ -1975,7 +1975,7 @@ namespace ts { export function isDefaultedExpandoInitializer(node: BinaryExpression) { const name = isVariableDeclaration(node.parent) ? node.parent.name : isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken ? node.parent.left : - undefined; + undefined; return name && getExpandoInitializer(node.right, isPrototypeAccess(name)) && isEntityNameExpression(name) && isSameEntityName(name, node.left); } @@ -2368,13 +2368,13 @@ namespace ts { // var x = function(name) { return name.length; } else if (parent.parent && (getSingleVariableOfVariableStatement(parent.parent) === node || - isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken)) { + isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken)) { return parent.parent; } else if (parent.parent && parent.parent.parent && (getSingleVariableOfVariableStatement(parent.parent.parent) || - getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.parent.parent) === node || - getSourceOfDefaultedAssignment(parent.parent.parent))) { + getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.parent.parent) === node || + getSourceOfDefaultedAssignment(parent.parent.parent))) { return parent.parent.parent; } } @@ -2602,7 +2602,7 @@ namespace ts { case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.NumericLiteral: if (isComputedPropertyName(parent)) return parent.parent; - // falls through + // falls through case SyntaxKind.Identifier: if (isDeclaration(parent)) { @@ -2737,7 +2737,7 @@ namespace ts { export function getAllSuperTypeNodes(node: Node): readonly TypeNode[] { return isInterfaceDeclaration(node) ? getInterfaceBaseTypeNodes(node) || emptyArray : isClassLike(node) ? concatenate(singleElementArray(getEffectiveBaseTypeNode(node)), getClassImplementsHeritageClauseElements(node)) || emptyArray : - emptyArray; + emptyArray; } export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration) { @@ -2829,7 +2829,7 @@ namespace ts { if (node.asteriskToken) { flags |= FunctionFlags.Generator; } - // falls through + // falls through case SyntaxKind.ArrowFunction: if (hasModifier(node, ModifierFlags.Async)) { @@ -3311,8 +3311,8 @@ namespace ts { export function escapeString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string { const escapedCharsRegExp = quoteChar === CharacterCodes.backtick ? backtickQuoteEscapedCharsRegExp : - quoteChar === CharacterCodes.singleQuote ? singleQuoteEscapedCharsRegExp : - doubleQuoteEscapedCharsRegExp; + quoteChar === CharacterCodes.singleQuote ? singleQuoteEscapedCharsRegExp : + doubleQuoteEscapedCharsRegExp; return s.replace(escapedCharsRegExp, getReplacement); } @@ -4525,7 +4525,7 @@ namespace ts { const checkFlags = (s).checkFlags; const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : - ModifierFlags.Protected; + ModifierFlags.Protected; const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; return accessModifier | staticModifier; } @@ -5618,7 +5618,7 @@ namespace ts { export function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined { return node.constraint ? node.constraint : isJSDocTemplateTag(node.parent) && node === node.parent.typeParameters[0] ? node.parent.constraint : - undefined; + undefined; } } @@ -7262,7 +7262,7 @@ namespace ts { } } - function Signature() { } + function Signature() {} function Node(this: Node, kind: SyntaxKind, pos: number, end: number) { this.pos = pos; @@ -7476,7 +7476,7 @@ namespace ts { return compilerOptions.target || ScriptTarget.ES3; } - export function getEmitModuleKind(compilerOptions: { module?: CompilerOptions["module"], target?: CompilerOptions["target"] }) { + export function getEmitModuleKind(compilerOptions: {module?: CompilerOptions["module"], target?: CompilerOptions["target"]}) { return typeof compilerOptions.module === "number" ? compilerOptions.module : getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 ? ModuleKind.ES2015 : ModuleKind.CommonJS; @@ -8933,14 +8933,14 @@ namespace ts { // using Uint16 instead of Uint32 so combining steps can use bitwise operators const segments = new Uint16Array((bitsNeeded >>> 4) + (bitsNeeded & 15 ? 1 : 0)); // Add the digits, one at a time - for (let i = endIndex - 1, bitOffset = 0; i >= startIndex; i-- , bitOffset += log2Base) { + for (let i = endIndex - 1, bitOffset = 0; i >= startIndex; i--, bitOffset += log2Base) { const segment = bitOffset >>> 4; const digitChar = stringValue.charCodeAt(i); // Find character range: 0-9 < A-F < a-f const digit = digitChar <= CharacterCodes._9 ? digitChar - CharacterCodes._0 : 10 + digitChar - - (digitChar <= CharacterCodes.F ? CharacterCodes.A : CharacterCodes.a); + (digitChar <= CharacterCodes.F ? CharacterCodes.A : CharacterCodes.a); const shiftedDigit = digit << (bitOffset & 15); segments[segment] |= shiftedDigit; const residual = shiftedDigit >>> 16; @@ -8968,7 +8968,7 @@ namespace ts { return base10Value; } - export function pseudoBigIntToString({ negative, base10Value }: PseudoBigInt): string { + export function pseudoBigIntToString({negative, base10Value}: PseudoBigInt): string { return (negative && base10Value !== "0" ? "-" : "") + base10Value; } } From ef30b1167dda9d9ea5d1adff767748c3fdf769cd Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 30 Sep 2019 13:32:21 -0500 Subject: [PATCH 23/25] Use `isAccessExpression` --- src/compiler/checker.ts | 4 ++-- src/compiler/emitter.ts | 2 +- src/compiler/utilities.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fd5f46590e13b..016cea0ceadee 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6824,13 +6824,13 @@ namespace ts { let types: Type[] | undefined; for (const declaration of symbol.declarations) { const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : - (isPropertyAccessExpression(declaration) || isElementAccessExpression(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) || isElementAccessExpression(expression)) + const kind = isAccessExpression(expression) ? getAssignmentDeclarationPropertyAccessKind(expression) : getAssignmentDeclarationKind(expression); if (kind === AssignmentDeclarationKind.ThisProperty) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 36a70ec7d2562..e9b07af5dc00d 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2260,7 +2260,7 @@ namespace ts { return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!) && (!dotHasTrivia || printerOptions.removeComments); } - 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 diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 20481058aec9a..a28905a27b613 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1886,7 +1886,7 @@ namespace ts { } export function isAssignmentDeclaration(decl: Declaration) { - return isBinaryExpression(decl) || isPropertyAccessExpression(decl) || isElementAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl); + return isBinaryExpression(decl) || isAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl); } /** Get the initializer, taking into account defaulted Javascript initializers */ From 96575df98ded42454f86e8fa1678c179c85d889c Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 30 Sep 2019 13:37:24 -0500 Subject: [PATCH 24/25] Add underscore escaping member to test --- .../reference/typeFromPrototypeAssignment4.js | 3 +++ .../typeFromPrototypeAssignment4.symbols | 17 +++++++++++++---- .../typeFromPrototypeAssignment4.types | 15 +++++++++++++++ .../salsa/typeFromPrototypeAssignment4.ts | 2 ++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/typeFromPrototypeAssignment4.js b/tests/baselines/reference/typeFromPrototypeAssignment4.js index d240cda55d93d..0bea8aa81e33e 100644 --- a/tests/baselines/reference/typeFromPrototypeAssignment4.js +++ b/tests/baselines/reference/typeFromPrototypeAssignment4.js @@ -15,11 +15,13 @@ Multimap4["prototype"] = { Multimap4["prototype"]["add-on"] = function() {}; Multimap4["prototype"]["addon"] = function() {}; +Multimap4["prototype"]["__underscores__"] = function() {}; const map4 = new Multimap4(); map4.get(""); map4["add-on"](); map4.addon(); +map4.__underscores__(); @@ -30,6 +32,7 @@ declare class Multimap4 { _map: {}; "add-on"(): void; addon(): void; + __underscores__(): void; /** * @param {string} key * @returns {number} the value ok diff --git a/tests/baselines/reference/typeFromPrototypeAssignment4.symbols b/tests/baselines/reference/typeFromPrototypeAssignment4.symbols index 64ede53df4dd4..bc7e816cc941c 100644 --- a/tests/baselines/reference/typeFromPrototypeAssignment4.symbols +++ b/tests/baselines/reference/typeFromPrototypeAssignment4.symbols @@ -35,21 +35,30 @@ Multimap4["prototype"]["addon"] = function() {}; >Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) >"prototype" : Symbol(Multimap4["prototype"], Decl(a.js, 2, 2)) +Multimap4["prototype"]["__underscores__"] = function() {}; +>Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) +>"prototype" : Symbol(Multimap4["prototype"], Decl(a.js, 2, 2)) + const map4 = new Multimap4(); ->map4 : Symbol(map4, Decl(a.js, 17, 5)) +>map4 : Symbol(map4, Decl(a.js, 18, 5)) >Multimap4 : Symbol(Multimap4, Decl(a.js, 0, 0), Decl(a.js, 2, 2)) map4.get(""); >map4.get : Symbol(get, Decl(a.js, 4, 26)) ->map4 : Symbol(map4, Decl(a.js, 17, 5)) +>map4 : Symbol(map4, Decl(a.js, 18, 5)) >get : Symbol(get, Decl(a.js, 4, 26)) map4["add-on"](); ->map4 : Symbol(map4, Decl(a.js, 17, 5)) +>map4 : Symbol(map4, Decl(a.js, 18, 5)) >"add-on" : Symbol(Multimap4["add-on"], Decl(a.js, 12, 2)) map4.addon(); >map4.addon : Symbol(Multimap4["addon"], Decl(a.js, 14, 49)) ->map4 : Symbol(map4, Decl(a.js, 17, 5)) +>map4 : Symbol(map4, Decl(a.js, 18, 5)) >addon : Symbol(Multimap4["addon"], Decl(a.js, 14, 49)) +map4.__underscores__(); +>map4.__underscores__ : Symbol(Multimap4["__underscores__"], Decl(a.js, 15, 48)) +>map4 : Symbol(map4, Decl(a.js, 18, 5)) +>__underscores__ : Symbol(Multimap4["__underscores__"], Decl(a.js, 15, 48)) + diff --git a/tests/baselines/reference/typeFromPrototypeAssignment4.types b/tests/baselines/reference/typeFromPrototypeAssignment4.types index 892a5191ccfe0..42f340ac7a956 100644 --- a/tests/baselines/reference/typeFromPrototypeAssignment4.types +++ b/tests/baselines/reference/typeFromPrototypeAssignment4.types @@ -55,6 +55,15 @@ Multimap4["prototype"]["addon"] = function() {}; >"addon" : "addon" >function() {} : () => void +Multimap4["prototype"]["__underscores__"] = function() {}; +>Multimap4["prototype"]["__underscores__"] = function() {} : () => void +>Multimap4["prototype"]["__underscores__"] : any +>Multimap4["prototype"] : { get(key: string): number; } +>Multimap4 : typeof Multimap4 +>"prototype" : "prototype" +>"__underscores__" : "__underscores__" +>function() {} : () => void + const map4 = new Multimap4(); >map4 : Multimap4 >new Multimap4() : Multimap4 @@ -79,3 +88,9 @@ map4.addon(); >map4 : Multimap4 >addon : () => void +map4.__underscores__(); +>map4.__underscores__() : void +>map4.__underscores__ : () => void +>map4 : Multimap4 +>__underscores__ : () => void + diff --git a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts index f7ec7973b66b7..426e327981c6e 100644 --- a/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts +++ b/tests/cases/conformance/salsa/typeFromPrototypeAssignment4.ts @@ -21,8 +21,10 @@ Multimap4["prototype"] = { Multimap4["prototype"]["add-on"] = function() {}; Multimap4["prototype"]["addon"] = function() {}; +Multimap4["prototype"]["__underscores__"] = function() {}; const map4 = new Multimap4(); map4.get(""); map4["add-on"](); map4.addon(); +map4.__underscores__(); From d8fa7d6ea4cc1b703ddb1ff36e7ddd101a722515 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 30 Sep 2019 14:12:48 -0500 Subject: [PATCH 25/25] Fix and test navBar changes --- src/compiler/utilities.ts | 4 +-- .../navigationBarFunctionPrototype4.ts | 32 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a28905a27b613..a281ebcf83a37 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5415,8 +5415,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)) { diff --git a/tests/cases/fourslash/navigationBarFunctionPrototype4.ts b/tests/cases/fourslash/navigationBarFunctionPrototype4.ts index c24261ee8230d..f12ec4ff7dba0 100644 --- a/tests/cases/fourslash/navigationBarFunctionPrototype4.ts +++ b/tests/cases/fourslash/navigationBarFunctionPrototype4.ts @@ -7,13 +7,19 @@ ////A.prototype = { m() {} }; ////A.prototype.a = function() { }; ////A.b = function() { }; +//// +////var B; +////B["prototype"] = { }; +////B["prototype"] = { m() {} }; +////B["prototype"]["a"] = function() { }; +////B["b"] = function() { }; verify.navigationTree({ "text": "", "kind": "script", - "childItems": [ + "childItems": [{ name: "A", quoted: false }, { name: "B", quoted: true }].map(({ name, quoted }) => ( { - "text": "A", + "text": name, "kind": "class", "childItems": [ { @@ -25,31 +31,31 @@ verify.navigationTree({ "kind": "method" }, { - "text": "a", + "text": quoted ? `"a"` : "a", "kind": "function" }, { - "text": "b", + "text": quoted ? `"b"` : "b", "kind": "function" } ] } - ] + )) }); verify.navigationBar([ { "text": "", "kind": "script", - "childItems": [ + "childItems": ["A", "B"].map(name => ( { - "text": "A", + "text": name, "kind": "class" } - ] + )) }, - { - "text": "A", + ...[{ name: "A", quoted: false }, { name: "B", quoted: true }].map(({ name, quoted }) => ({ + "text": name, "kind": "class", "childItems": [ { @@ -61,14 +67,14 @@ verify.navigationBar([ "kind": "method" }, { - "text": "a", + "text": quoted ? `"a"` : "a", "kind": "function" }, { - "text": "b", + "text": quoted ? `"b"` : "b", "kind": "function" } ], "indent": 1 - } + })) ]);