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

* Start enabling element access special assignment

* Treat element access assignment as special assignment in JS

* Make declarations for bindable element access expressions

* Fix navigationBar crash

* Add multi-level test for JS

* Propagate element access expressions to more code paths

* Fix property access on `this`

* Add quick info test

* Uhhh I guess this is fine

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

* Add test for this property assignment

* Add test for and fix prototype property assignment

* Fix teeeest???

* Update APIs

* Fix element access declarations on `this`

* Fix go-to-definition

* Add declaration emit to tests

* Reconcile with late-bound symbol element access assignment

* Fix baselines

* Add JS declaration back to tests

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

* Revert accidental auto-format

* Use `isAccessExpression`

* Add underscore escaping member to test

* Fix and test navBar changes
This commit is contained in:
Andrew Branch 2019-09-30 15:08:44 -05:00 committed by GitHub
parent 053388d6d2
commit f89de95955
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1078 additions and 129 deletions

View file

@ -2333,17 +2333,18 @@ namespace ts {
return checkStrictModeIdentifier(<Identifier>node);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
if (currentFlow && isNarrowableReference(<Expression>node)) {
node.flowNode = currentFlow;
const expr = node as PropertyAccessExpression | ElementAccessExpression;
if (currentFlow && isNarrowableReference(expr)) {
expr.flowNode = currentFlow;
}
if (isSpecialPropertyDeclaration(node as PropertyAccessExpression)) {
bindSpecialPropertyDeclaration(node as PropertyAccessExpression);
if (isSpecialPropertyDeclaration(expr)) {
bindSpecialPropertyDeclaration(expr);
}
if (isInJSFile(node) &&
if (isInJSFile(expr) &&
file.commonJsModuleIndicator &&
isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) &&
isModuleExportsAccessExpression(expr) &&
!lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) {
declareSymbol(file.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
declareSymbol(file.locals!, /*parent*/ undefined, expr.expression,
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
}
break;
@ -2351,22 +2352,22 @@ namespace ts {
const specialKind = getAssignmentDeclarationKind(node as BinaryExpression);
switch (specialKind) {
case AssignmentDeclarationKind.ExportsProperty:
bindExportsPropertyAssignment(node as BinaryExpression);
bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.ModuleExports:
bindModuleExportsAssignment(node as BinaryExpression);
bindModuleExportsAssignment(node as BindablePropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.PrototypeProperty:
bindPrototypePropertyAssignment((node as BinaryExpression).left as PropertyAccessEntityNameExpression, node);
bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node);
break;
case AssignmentDeclarationKind.Prototype:
bindPrototypeAssignment(node as BinaryExpression);
bindPrototypeAssignment(node as BindableStaticPropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.ThisProperty:
bindThisPropertyAssignment(node as BinaryExpression);
bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.Property:
bindSpecialPropertyAssignment(node as BinaryExpression);
bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.None:
// Nothing to do
@ -2641,14 +2642,13 @@ namespace ts {
}
}
function bindExportsPropertyAssignment(node: BinaryExpression) {
function bindExportsPropertyAssignment(node: BindableStaticPropertyAssignmentExpression) {
// When we create a property via 'exports.foo = bar', the 'exports.foo' property access
// expression is the declaration
if (!setCommonJsModuleIndicator(node)) {
return;
}
const lhs = node.left as PropertyAccessEntityNameExpression;
const symbol = forEachIdentifierInEntityName(lhs.expression, /*parent*/ undefined, (id, symbol) => {
const symbol = forEachIdentifierInEntityName(node.left.expression, /*parent*/ undefined, (id, symbol) => {
if (symbol) {
addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment);
}
@ -2658,11 +2658,11 @@ namespace ts {
const flags = isClassExpression(node.right) ?
SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.Class :
SymbolFlags.Property | SymbolFlags.ExportValue;
declareSymbol(symbol.exports!, symbol, lhs, flags, SymbolFlags.None);
declareSymbol(symbol.exports!, symbol, node.left, flags, SymbolFlags.None);
}
}
function bindModuleExportsAssignment(node: BinaryExpression) {
function bindModuleExportsAssignment(node: BindablePropertyAssignmentExpression) {
// A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports'
// is still pointing to 'module.exports'.
// We do not want to consider this as 'export=' since a module can have only one of these.
@ -2682,7 +2682,7 @@ namespace ts {
declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None);
}
function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {
function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) {
Debug.assert(isInJSFile(node));
const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false);
switch (thisContainer.kind) {
@ -2692,7 +2692,7 @@ namespace ts {
// For `f.prototype.m = function() { this.x = 0; }`, `this.x = 0` should modify `f`'s members, not the function expression.
if (isBinaryExpression(thisContainer.parent) && thisContainer.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
const l = thisContainer.parent.left;
if (isPropertyAccessEntityNameExpression(l) && isPrototypeAccess(l.expression)) {
if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) {
constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer);
}
}
@ -2754,11 +2754,11 @@ namespace ts {
}
}
function bindSpecialPropertyDeclaration(node: PropertyAccessExpression) {
function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) {
if (node.expression.kind === SyntaxKind.ThisKeyword) {
bindThisPropertyAssignment(node);
}
else if (isPropertyAccessEntityNameExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) {
else if (isBindableStaticAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) {
if (isPrototypeAccess(node.expression)) {
bindPrototypePropertyAssignment(node, node.parent);
}
@ -2769,11 +2769,10 @@ namespace ts {
}
/** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */
function bindPrototypeAssignment(node: BinaryExpression) {
function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) {
node.left.parent = node;
node.right.parent = node;
const lhs = node.left as PropertyAccessEntityNameExpression;
bindPropertyAssignment(lhs.expression, lhs, /*isPrototypeProperty*/ false, /*containerIsClass*/ true);
bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true);
}
function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) {
@ -2785,10 +2784,10 @@ namespace ts {
* For `x.prototype.y = z`, declare a member `y` on `x` if `x` is a function or class, or not declared.
* Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration.
*/
function bindPrototypePropertyAssignment(lhs: PropertyAccessEntityNameExpression, parent: Node) {
function bindPrototypePropertyAssignment(lhs: BindableStaticAccessExpression, parent: Node) {
// Look up the function in the local scope, since prototype assignments should
// follow the function declaration
const classPrototype = lhs.expression as PropertyAccessEntityNameExpression;
const classPrototype = lhs.expression as BindableStaticAccessExpression;
const constructorFunction = classPrototype.expression;
// Fix up parent pointers since we're going to use these nodes before we bind into them
@ -2806,30 +2805,29 @@ namespace ts {
bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false);
}
function bindSpecialPropertyAssignment(node: BinaryExpression) {
const lhs = node.left as PropertyAccessEntityNameExpression;
function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) {
// Class declarations in Typescript do not allow property declarations
const parentSymbol = lookupSymbolForPropertyAccess(lhs.expression);
const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression);
if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) {
return;
}
// Fix up parent pointers since we're going to use these nodes before we bind into them
node.left.parent = node;
node.right.parent = node;
if (isIdentifier(lhs.expression) && container === file && isExportsOrModuleExportsOrAlias(file, lhs.expression)) {
if (isIdentifier(node.left.expression) && container === file && isExportsOrModuleExportsOrAlias(file, node.left.expression)) {
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
// var util = module.exports;
// util.property = function ...
bindExportsPropertyAssignment(node);
bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression);
}
else {
if (hasDynamicName(node)) {
bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed);
const sym = bindPotentiallyMissingNamespaces(parentSymbol, lhs.expression, isTopLevelNamespaceAssignment(lhs), /*isPrototype*/ false, /*containerIsClass*/ false);
const sym = bindPotentiallyMissingNamespaces(parentSymbol, node.left.expression, isTopLevelNamespaceAssignment(node.left), /*isPrototype*/ false, /*containerIsClass*/ false);
addLateBoundAssignmentDeclarationToSymbol(node, sym);
}
else {
bindStaticPropertyAssignment(lhs);
bindStaticPropertyAssignment(cast(node.left, isBindableStaticAccessExpression));
}
}
}
@ -2838,12 +2836,12 @@ namespace ts {
* For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function (or IIFE) or class or {}, or not declared.
* Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y;
*/
function bindStaticPropertyAssignment(node: PropertyAccessEntityNameExpression) {
function bindStaticPropertyAssignment(node: BindableStaticAccessExpression) {
node.expression.parent = node;
bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false);
}
function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: EntityNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) {
function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) {
if (isToplevel && !isPrototypeProperty) {
// make symbols or add declarations for intermediate containers
const flags = SymbolFlags.Module | SymbolFlags.Assignment;
@ -2866,7 +2864,7 @@ namespace ts {
return namespaceSymbol;
}
function bindPotentiallyNewExpandoMemberToNamespace(declaration: PropertyAccessEntityNameExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) {
function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) {
if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) {
return;
}
@ -2911,13 +2909,13 @@ namespace ts {
declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment);
}
function isTopLevelNamespaceAssignment(propertyAccess: PropertyAccessEntityNameExpression) {
function isTopLevelNamespaceAssignment(propertyAccess: BindableAccessExpression) {
return isBinaryExpression(propertyAccess.parent)
? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile
: propertyAccess.parent.parent.kind === SyntaxKind.SourceFile;
}
function bindPropertyAssignment(name: EntityNameExpression, propertyAccess: PropertyAccessEntityNameExpression, isPrototypeProperty: boolean, containerIsClass: boolean) {
function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) {
let namespaceSymbol = lookupSymbolForPropertyAccess(name);
const isToplevel = isTopLevelNamespaceAssignment(propertyAccess);
namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass);
@ -2962,17 +2960,17 @@ namespace ts {
return expr.parent;
}
function lookupSymbolForPropertyAccess(node: EntityNameExpression, lookupContainer: Node = container): Symbol | undefined {
function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined {
if (isIdentifier(node)) {
return lookupSymbolForNameWorker(lookupContainer, node.escapedText);
}
else {
const symbol = lookupSymbolForPropertyAccess(node.expression);
return symbol && symbol.exports && symbol.exports.get(node.name.escapedText);
return symbol && symbol.exports && symbol.exports.get(escapeLeadingUnderscores(getElementOrPropertyAccessName(node)));
}
}
function forEachIdentifierInEntityName(e: EntityNameExpression, parent: Symbol | undefined, action: (e: Identifier, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined {
function forEachIdentifierInEntityName(e: BindableStaticNameExpression, parent: Symbol | undefined, action: (e: Declaration, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined {
if (isExportsOrModuleExportsOrAlias(file, e)) {
return file.symbol;
}
@ -2981,7 +2979,7 @@ namespace ts {
}
else {
const s = forEachIdentifierInEntityName(e.expression, parent, action);
return action(e.name, s && s.exports && s.exports.get(e.name.escapedText), s);
return action(getNameOrArgument(e), s && s.exports && s.exports.get(escapeLeadingUnderscores(getElementOrPropertyAccessName(e))), s);
}
}
@ -3255,7 +3253,7 @@ namespace ts {
while (q.length && i < 100) {
i++;
node = q.shift()!;
if (isExportsIdentifier(node) || isModuleExportsPropertyAccessExpression(node)) {
if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) {
return true;
}
else if (isIdentifier(node)) {

View file

@ -3041,7 +3041,7 @@ namespace ts {
return getSymbolOfNode(d.parent);
}
if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) {
if (isModuleExportsPropertyAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) {
if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) {
return getSymbolOfNode(getSourceFileOfNode(d));
}
checkExpressionCached(d.parent.left.expression);
@ -4877,6 +4877,15 @@ namespace ts {
}
}
function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) {
const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context);
if (fromNameType) {
return fromNameType;
}
const rawName = unescapeLeadingUnderscores(symbol.escapedName);
return createPropertyNameNodeForIdentifierOrLiteral(rawName);
}
// See getNameForSymbolFromNameType for a stringy equivalent
function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext) {
const nameType = symbol.nameType;
@ -4889,7 +4898,7 @@ namespace ts {
if (isNumericLiteralName(name) && startsWith(name, "-")) {
return createComputedPropertyName(createLiteral(+name));
}
return isIdentifierText(name, compilerOptions.target) ? createIdentifier(name) : createLiteral(isNumericLiteralName(name) ? +name : name) as StringLiteral | NumericLiteral;
return createPropertyNameNodeForIdentifierOrLiteral(name);
}
if (nameType.flags & TypeFlags.UniqueESSymbol) {
return createComputedPropertyName(symbolToExpression((<UniqueESSymbolType>nameType).symbol, context, SymbolFlags.Value));
@ -4897,6 +4906,10 @@ namespace ts {
}
}
function createPropertyNameNodeForIdentifierOrLiteral(name: string) {
return isIdentifierText(name, compilerOptions.target) ? createIdentifier(name) : createLiteral(isNumericLiteralName(name) ? +name : name) as StringLiteral | NumericLiteral;
}
function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext {
const initial: NodeBuilderContext = { ...context };
// Make type parameters created within this context not consume the name outside this context
@ -5772,8 +5785,7 @@ namespace ts {
return [];
}
const staticFlag = isStatic ? ModifierFlags.Static : 0;
const rawName = unescapeLeadingUnderscores(p.escapedName);
const name = getPropertyNameNodeForSymbolFromNameType(p, context) || createIdentifier(rawName);
const name = getPropertyNameNodeForSymbol(p, context);
const firstPropertyLikeDecl = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression));
if (p.flags & SymbolFlags.Accessor && useAccessors) {
const result: AccessorDeclaration[] = [];
@ -6869,13 +6881,15 @@ namespace ts {
let types: Type[] | undefined;
for (const declaration of symbol.declarations) {
const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration :
isPropertyAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration :
isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration :
undefined;
if (!expression) {
continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere
}
const kind = isPropertyAccessExpression(expression) ? getAssignmentDeclarationPropertyAccessKind(expression) : getAssignmentDeclarationKind(expression);
const kind = isAccessExpression(expression)
? getAssignmentDeclarationPropertyAccessKind(expression)
: getAssignmentDeclarationKind(expression);
if (kind === AssignmentDeclarationKind.ThisProperty) {
if (isDeclarationInConstructor(expression)) {
definedInConstructor = true;
@ -7250,12 +7264,15 @@ namespace ts {
else if (
isBinaryExpression(declaration) ||
(isInJSFile(declaration) &&
(isCallExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent)))) {
(isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) {
type = getWidenedTypeForAssignmentDeclaration(symbol);
}
else if (isJSDocPropertyLikeTag(declaration)
|| isPropertyAccessExpression(declaration)
|| isElementAccessExpression(declaration)
|| isIdentifier(declaration)
|| isStringLiteralLike(declaration)
|| isNumericLiteral(declaration)
|| isClassDeclaration(declaration)
|| isFunctionDeclaration(declaration)
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
@ -7436,7 +7453,8 @@ namespace ts {
return anyType;
}
else if (declaration.kind === SyntaxKind.BinaryExpression ||
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
(declaration.kind === SyntaxKind.PropertyAccessExpression || declaration.kind === SyntaxKind.ElementAccessExpression) &&
declaration.parent.kind === SyntaxKind.BinaryExpression) {
return getWidenedTypeForAssignmentDeclaration(symbol);
}
else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) {
@ -32134,7 +32152,7 @@ namespace ts {
return node;
case SyntaxKind.PropertyAccessExpression:
do {
if (isModuleExportsPropertyAccessExpression(node.expression)) {
if (isModuleExportsAccessExpression(node.expression)) {
return node.name;
}
node = node.expression;

View file

@ -2280,7 +2280,7 @@ namespace ts {
// if the expression doesn't have any comments that will be emitted.
return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!);
}
else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) {
else if (isAccessExpression(expression)) {
// check if constant enum value is integer
const constantValue = getConstantValue(expression);
// isFinite handles cases when constantValue is undefined

View file

@ -1306,7 +1306,7 @@ namespace ts {
literal: BooleanLiteral | LiteralExpression | PrefixUnaryExpression;
}
export interface StringLiteral extends LiteralExpression {
export interface StringLiteral extends LiteralExpression, Declaration {
kind: SyntaxKind.StringLiteral;
/* @internal */ textSourceNode?: Identifier | StringLiteralLike | NumericLiteral; // Allows a StringLiteral to get its text from another node (used by transforms).
/** Note: this is only set when synthesizing a node, not during parsing. */
@ -1693,7 +1693,7 @@ namespace ts {
kind: SyntaxKind.RegularExpressionLiteral;
}
export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode {
export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode, Declaration {
kind: SyntaxKind.NoSubstitutionTemplateLiteral;
}
@ -1722,7 +1722,7 @@ namespace ts {
NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinaryOrOctalSpecifier | ContainsSeparator
}
export interface NumericLiteral extends LiteralExpression {
export interface NumericLiteral extends LiteralExpression, Declaration {
kind: SyntaxKind.NumericLiteral;
/* @internal */
numericLiteralFlags: TokenFlags;
@ -1885,7 +1885,33 @@ namespace ts {
;
/** @internal */
export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } };
export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: BindableStaticNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } };
/** @internal */
export type BindableStaticNameExpression = EntityNameExpression | BindableStaticElementAccessExpression;
/** @internal */
export type LiteralLikeElementAccessExpression = ElementAccessExpression & Declaration & {
argumentExpression: StringLiteralLike | NumericLiteral;
};
/** @internal */
export type BindableStaticElementAccessExpression = LiteralLikeElementAccessExpression & {
expression: BindableStaticNameExpression;
};
/** @internal */
export type BindableElementAccessExpression = ElementAccessExpression & {
expression: BindableStaticNameExpression;
};
/** @internal */
export type BindableStaticAccessExpression = PropertyAccessEntityNameExpression | BindableStaticElementAccessExpression;
/** @internal */
export type BindableAccessExpression = PropertyAccessEntityNameExpression | BindableElementAccessExpression;
/** @internal */
export interface BindableStaticPropertyAssignmentExpression extends BinaryExpression {
left: BindableStaticAccessExpression;
}
/** @internal */
export interface BindablePropertyAssignmentExpression extends BinaryExpression {
left: BindableAccessExpression;
}
// see: https://tc39.github.io/ecma262/#prod-SuperCall
export interface SuperCall extends CallExpression {

View file

@ -1875,7 +1875,7 @@ namespace ts {
decl = name;
}
if (!name || !isEntityNameExpression(name) || !isSameEntityName(name, node.parent.left)) {
if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, node.parent.left)) {
return undefined;
}
}
@ -1887,7 +1887,7 @@ namespace ts {
}
export function isAssignmentDeclaration(decl: Declaration) {
return isBinaryExpression(decl) || isPropertyAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl);
return isBinaryExpression(decl) || isAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl);
}
/** Get the initializer, taking into account defaulted Javascript initializers */
@ -1907,18 +1907,23 @@ namespace ts {
}
function hasExpandoValueProperty(node: ObjectLiteralExpression, isPrototypeAssignment: boolean) {
return forEach(node.properties, p => isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === "value" && p.initializer && getExpandoInitializer(p.initializer, isPrototypeAssignment));
return forEach(node.properties, p =>
isPropertyAssignment(p) &&
isIdentifier(p.name) &&
p.name.escapedText === "value" &&
p.initializer &&
getExpandoInitializer(p.initializer, isPrototypeAssignment));
}
/**
* Get the assignment 'initializer' -- the righthand side-- when the initializer is container-like (See getExpandoInitializer).
* We treat the right hand side of assignments with container-like initalizers as declarations.
*/
export function getAssignedExpandoInitializer(node: Node | undefined) {
export function getAssignedExpandoInitializer(node: Node | undefined): Expression | undefined {
if (node && node.parent && isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
const isPrototypeAssignment = isPrototypeAccess(node.parent.left);
return getExpandoInitializer(node.parent.right, isPrototypeAssignment) ||
getDefaultedExpandoInitializer(node.parent.left as EntityNameExpression, node.parent.right, isPrototypeAssignment);
getDefaultedExpandoInitializer(node.parent.left, node.parent.right, isPrototypeAssignment);
}
if (node && isCallExpression(node) && isBindableObjectDefinePropertyCall(node)) {
const result = hasExpandoValueProperty(node.arguments[2], node.arguments[1].text === "prototype");
@ -1961,9 +1966,9 @@ namespace ts {
* The second Lhs is required to be the same as the first except that it may be prefixed with
* 'window.', 'global.' or 'self.' The second Lhs is otherwise ignored by the binder and checker.
*/
function getDefaultedExpandoInitializer(name: EntityNameExpression, initializer: Expression, isPrototypeAssignment: boolean) {
function getDefaultedExpandoInitializer(name: Expression, initializer: Expression, isPrototypeAssignment: boolean) {
const e = isBinaryExpression(initializer) && initializer.operatorToken.kind === SyntaxKind.BarBarToken && getExpandoInitializer(initializer.right, isPrototypeAssignment);
if (e && isSameEntityName(name, (initializer as BinaryExpression).left as EntityNameExpression)) {
if (e && isSameEntityName(name, (initializer as BinaryExpression).left)) {
return e;
}
}
@ -1998,19 +2003,20 @@ namespace ts {
* my.app = self.my.app || class { }
*/
function isSameEntityName(name: Expression, initializer: Expression): boolean {
if (isIdentifier(name) && isIdentifier(initializer)) {
return name.escapedText === initializer.escapedText;
if (isPropertyNameLiteral(name) && isPropertyNameLiteral(initializer)) {
return getTextOfIdentifierOrLiteral(name) === getTextOfIdentifierOrLiteral(name);
}
if (isIdentifier(name) && isPropertyAccessExpression(initializer)) {
return (initializer.expression.kind as SyntaxKind.ThisKeyword === SyntaxKind.ThisKeyword ||
if (isIdentifier(name) && (isLiteralLikeAccess(initializer))) {
return (initializer.expression.kind === SyntaxKind.ThisKeyword ||
isIdentifier(initializer.expression) &&
(initializer.expression.escapedText === "window" as __String ||
initializer.expression.escapedText === "self" as __String ||
initializer.expression.escapedText === "global" as __String)) &&
isSameEntityName(name, initializer.name);
(initializer.expression.escapedText === "window" ||
initializer.expression.escapedText === "self" ||
initializer.expression.escapedText === "global")) &&
isSameEntityName(name, getNameOrArgument(initializer));
}
if (isPropertyAccessExpression(name) && isPropertyAccessExpression(initializer)) {
return name.name.escapedText === initializer.name.escapedText && isSameEntityName(name.expression, initializer.expression);
if (isLiteralLikeAccess(name) && isLiteralLikeAccess(initializer)) {
return getElementOrPropertyAccessName(name) === getElementOrPropertyAccessName(initializer)
&& isSameEntityName(name.expression, initializer.expression);
}
return false;
}
@ -2026,8 +2032,11 @@ namespace ts {
return isIdentifier(node) && node.escapedText === "exports";
}
export function isModuleExportsPropertyAccessExpression(node: Node) {
return isPropertyAccessExpression(node) && isIdentifier(node.expression) && node.expression.escapedText === "module" && node.name.escapedText === "exports";
export function isModuleExportsAccessExpression(node: Node): node is LiteralLikeElementAccessExpression & { expression: Identifier } {
return (isPropertyAccessExpression(node) || isLiteralLikeElementAccess(node))
&& isIdentifier(node.expression)
&& node.expression.escapedText === "module"
&& getElementOrPropertyAccessName(node) === "exports";
}
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
@ -2044,7 +2053,38 @@ namespace ts {
idText(expr.expression.expression) === "Object" &&
idText(expr.expression.name) === "defineProperty" &&
isStringOrNumericLiteralLike(expr.arguments[1]) &&
isEntityNameExpression(expr.arguments[0]);
isBindableStaticNameExpression(expr.arguments[0]);
}
export function isBindableStaticElementAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticElementAccessExpression {
return isLiteralLikeElementAccess(node)
&& ((!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword) ||
isEntityNameExpression(node.expression) ||
isBindableStaticElementAccessExpression(node.expression, /*excludeThisKeyword*/ true));
}
export function isLiteralLikeAccess(node: Node): node is LiteralLikeElementAccessExpression | PropertyAccessExpression {
return isPropertyAccessExpression(node) || isLiteralLikeElementAccess(node);
}
export function isLiteralLikeElementAccess(node: Node): node is LiteralLikeElementAccessExpression {
return isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression);
}
export function isBindableStaticAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticAccessExpression {
return isPropertyAccessExpression(node) && (!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword || isBindableStaticNameExpression(node.expression, /*excludeThisKeyword*/ true))
|| isBindableStaticElementAccessExpression(node, excludeThisKeyword);
}
export function isBindableStaticNameExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticNameExpression {
return isEntityNameExpression(node) || isBindableStaticAccessExpression(node, excludeThisKeyword);
}
export function getNameOrArgument(expr: PropertyAccessExpression | LiteralLikeElementAccessExpression) {
if (isPropertyAccessExpression(expr)) {
return expr.name;
}
return expr.argumentExpression;
}
function getAssignmentDeclarationKindWorker(expr: BinaryExpression | CallExpression): AssignmentDeclarationKind {
@ -2053,26 +2093,22 @@ namespace ts {
return AssignmentDeclarationKind.None;
}
const entityName = expr.arguments[0];
if (isExportsIdentifier(entityName) || isModuleExportsPropertyAccessExpression(entityName)) {
if (isExportsIdentifier(entityName) || isModuleExportsAccessExpression(entityName)) {
return AssignmentDeclarationKind.ObjectDefinePropertyExports;
}
if (isPropertyAccessExpression(entityName) && entityName.name.escapedText === "prototype" && isEntityNameExpression(entityName.expression)) {
if (isBindableStaticAccessExpression(entityName) && getElementOrPropertyAccessName(entityName) === "prototype") {
return AssignmentDeclarationKind.ObjectDefinePrototypeProperty;
}
return AssignmentDeclarationKind.ObjectDefinePropertyValue;
}
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken) {
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || !isAccessExpression(expr.left)) {
return AssignmentDeclarationKind.None;
}
const lhs = expr.left;
if (isAccessExpression(lhs)) {
if (isEntityNameExpression(lhs.expression) && getElementOrPropertyAccessName(lhs) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) {
// F.prototype = { ... }
return AssignmentDeclarationKind.Prototype;
}
return getAssignmentDeclarationPropertyAccessKind(lhs);
if (isBindableStaticNameExpression(expr.left.expression) && getElementOrPropertyAccessName(expr.left) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) {
// F.prototype = { ... }
return AssignmentDeclarationKind.Prototype;
}
return AssignmentDeclarationKind.None;
return getAssignmentDeclarationPropertyAccessKind(expr.left);
}
/**
@ -2092,6 +2128,8 @@ namespace ts {
}
/* @internal */
export function getElementOrPropertyAccessName(node: LiteralLikeElementAccessExpression | PropertyAccessExpression): string;
export function getElementOrPropertyAccessName(node: AccessExpression): string | undefined;
export function getElementOrPropertyAccessName(node: AccessExpression): string | undefined {
const name = getElementOrPropertyAccessArgumentExpressionOrName(node);
if (name) {
@ -2109,22 +2147,21 @@ namespace ts {
if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
return AssignmentDeclarationKind.ThisProperty;
}
else if (isModuleExportsPropertyAccessExpression(lhs)) {
else if (isModuleExportsAccessExpression(lhs)) {
// module.exports = expr
return AssignmentDeclarationKind.ModuleExports;
}
else if (isEntityNameExpression(lhs.expression)) {
else if (isBindableStaticNameExpression(lhs.expression, /*excludeThisKeyword*/ true)) {
if (isPrototypeAccess(lhs.expression)) {
// F.G....prototype.x = expr
return AssignmentDeclarationKind.PrototypeProperty;
}
let nextToLast = lhs;
while (isAccessExpression(nextToLast.expression)) {
nextToLast = nextToLast.expression;
while (!isIdentifier(nextToLast.expression)) {
nextToLast = nextToLast.expression as Exclude<BindableStaticNameExpression, Identifier>;
}
Debug.assert(isIdentifier(nextToLast.expression));
const id = nextToLast.expression as Identifier;
const id = nextToLast.expression;
if (id.escapedText === "exports" ||
id.escapedText === "module" && getElementOrPropertyAccessName(nextToLast) === "exports") {
// exports.name = expr OR module.exports.name = expr
@ -2148,9 +2185,10 @@ namespace ts {
return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty;
}
export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression): boolean {
export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression | ElementAccessExpression): expr is PropertyAccessExpression | LiteralLikeElementAccessExpression {
return isInJSFile(expr) &&
expr.parent && expr.parent.kind === SyntaxKind.ExpressionStatement &&
(!isElementAccessExpression(expr) || isLiteralLikeElementAccess(expr)) &&
!!getJSDocTypeTag(expr.parent);
}
@ -4147,8 +4185,8 @@ namespace ts {
return undefined;
}
export function isPrototypeAccess(node: Node): node is PropertyAccessExpression {
return isPropertyAccessExpression(node) && node.name.escapedText === "prototype";
export function isPrototypeAccess(node: Node): node is BindableStaticAccessExpression {
return isBindableStaticAccessExpression(node) && getElementOrPropertyAccessName(node) === "prototype";
}
export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) {
@ -5378,6 +5416,11 @@ namespace ts {
const { expression } = declaration as ExportAssignment;
return isIdentifier(expression) ? expression : undefined;
}
case SyntaxKind.ElementAccessExpression:
const expr = declaration as ElementAccessExpression;
if (isBindableStaticElementAccessExpression(expr)) {
return expr.argumentExpression;
}
}
return (declaration as NamedDeclaration).name;
}
@ -5399,8 +5442,8 @@ namespace ts {
if (isIdentifier(node.parent.left)) {
return node.parent.left;
}
else if (isPropertyAccessExpression(node.parent.left)) {
return node.parent.left.name;
else if (isAccessExpression(node.parent.left)) {
return getElementOrPropertyAccessArgumentExpressionOrName(node.parent.left);
}
}
else if (isVariableDeclaration(node.parent) && isIdentifier(node.parent.name)) {

View file

@ -135,12 +135,13 @@ namespace ts.NavigationBar {
function endNestedNodes(depth: number): void {
for (let i = 0; i < depth; i++) endNode();
}
function startNestedNodes(targetNode: Node, entityName: EntityNameExpression) {
const names: Identifier[] = [];
while (!isIdentifier(entityName)) {
const name = entityName.name;
function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) {
const names: PropertyNameLiteral[] = [];
while (!isPropertyNameLiteral(entityName)) {
const name = getNameOrArgument(entityName);
const nameText = getElementOrPropertyAccessName(entityName);
entityName = entityName.expression;
if (name.escapedText === "prototype") continue;
if (nameText === "prototype") continue;
names.push(name);
}
names.push(entityName);
@ -333,7 +334,7 @@ namespace ts.NavigationBar {
assignmentTarget;
let depth = 0;
let className: Identifier;
let className: PropertyNameLiteral;
// If we see a prototype assignment, start tracking the target as a class
// This is only done for simple classes not nested assignments.
if (isIdentifier(prototypeAccess.expression)) {
@ -384,16 +385,16 @@ namespace ts.NavigationBar {
}
case AssignmentDeclarationKind.Property: {
const binaryExpression = (node as BinaryExpression);
const assignmentTarget = binaryExpression.left as PropertyAccessExpression;
const assignmentTarget = binaryExpression.left as PropertyAccessExpression | BindableElementAccessExpression;
const targetFunction = assignmentTarget.expression;
if (isIdentifier(targetFunction) && assignmentTarget.name.escapedText !== "prototype" &&
if (isIdentifier(targetFunction) && getElementOrPropertyAccessName(assignmentTarget) !== "prototype" &&
trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) {
if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) {
addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction);
}
else {
else if (isBindableStaticAccessExpression(assignmentTarget)) {
startNode(binaryExpression, targetFunction);
addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, assignmentTarget.name);
addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, getNameOrArgument(assignmentTarget));
endNode();
}
return;

View file

@ -852,7 +852,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;
@ -1016,7 +1016,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 {
@ -1027,7 +1027,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 {

View file

@ -852,7 +852,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;
@ -1016,7 +1016,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 {
@ -1027,7 +1027,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 {

View file

@ -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 {};

View file

@ -0,0 +1,74 @@
=== 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.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))
>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))
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))

View file

@ -0,0 +1,98 @@
=== 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.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")
>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"
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

View file

@ -0,0 +1,43 @@
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 '"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 (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. Im 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["y"] = {};
this["y"]["z"] = {};
~~~~~~~~~~~~~~
!!! 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;

View file

@ -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. Im 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;

View file

@ -0,0 +1,63 @@
=== 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. Im 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["y"] = {};
>this : Symbol(globalThis)
>"y" : Symbol("y", Decl(a.js, 8, 14))
this["y"]["z"] = {};
>this : Symbol(globalThis)
>"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))

View file

@ -0,0 +1,92 @@
=== 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. Im 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["y"] = {};
>this["y"] = {} : {}
>this["y"] : {} | undefined
>this : typeof globalThis
>"y" : "y"
>{} : {}
this["y"]["z"] = {};
>this["y"]["z"] = {} : {}
>this["y"]["z"] : any
>this["y"] : {}
>this : typeof globalThis
>"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 : {}

View file

@ -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))
F["prop"] = 3;
>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), 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))

View file

@ -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; "prop": number; }
>function () {} : { (): void; "prop": number; }
f["prop"] = 3;
>f["prop"] = 3 : 3
>f["prop"] : number
>f : { (): void; "prop": number; }
>"prop" : "prop"
>3 : 3

View file

@ -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;
}
}

View file

@ -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))

View file

@ -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

View file

@ -0,0 +1,42 @@
//// [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() {};
Multimap4["prototype"]["__underscores__"] = function() {};
const map4 = new Multimap4();
map4.get("");
map4["add-on"]();
map4.addon();
map4.__underscores__();
//// [a.d.ts]
declare function Multimap4(): void;
declare class Multimap4 {
_map: {};
"add-on"(): void;
addon(): void;
__underscores__(): void;
/**
* @param {string} key
* @returns {number} the value ok
*/
get(key: string): number;
}
declare const map4: Multimap4;

View file

@ -0,0 +1,64 @@
=== 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"]["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))
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, 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, 18, 5))
>get : Symbol(get, Decl(a.js, 4, 26))
map4["add-on"]();
>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, 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))

View file

@ -0,0 +1,96 @@
=== 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"]["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
>Multimap4["prototype"] : { get(key: string): number; }
>Multimap4 : typeof Multimap4
>"prototype" : "prototype"
>"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
>Multimap4 : typeof Multimap4
map4.get("");
>map4.get("") : number
>map4.get : (key: string) => number
>map4 : Multimap4
>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
>map4 : Multimap4
>addon : () => void
map4.__underscores__();
>map4.__underscores__() : void
>map4.__underscores__ : () => void
>map4 : Multimap4
>__underscores__ : () => void

View file

@ -0,0 +1,21 @@
// @allowJs: true
// @strict: true
// @checkJs: true
// @emitDeclarationOnly: true
// @declaration: true
// @filename: 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;
// @filename: mod2.js
const mod1 = require("./mod1");
mod1.a;
mod1.b;
mod1.c;
mod1.d;
mod1.d.e;
mod1.default;

View file

@ -0,0 +1,30 @@
// @checkJs: true
// @allowJs: true
// @strict: true
// @emitDeclarationOnly: true
// @declaration: 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. Im 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;

View file

@ -0,0 +1,9 @@
// @noEmit: true
// @strict: true
// @declaration: true
function F() {}
F["prop"] = 3;
const f = function () {};
f["prop"] = 3;

View file

@ -0,0 +1,10 @@
// @allowJs: true
// @checkJs: true
// @strict: true
// @emitDeclarationOnly: true
// @declaration: true
// @Filename: a.js
const foo = {};
foo["baz"] = {};
foo["baz"]["blah"] = 3;

View file

@ -0,0 +1,30 @@
// @allowJs: true
// @checkJs: true
// @emitDeclarationOnly: true
// @declaration: true
// @outDir: out
// @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"]["add-on"] = function() {};
Multimap4["prototype"]["addon"] = function() {};
Multimap4["prototype"]["__underscores__"] = function() {};
const map4 = new Multimap4();
map4.get("");
map4["add-on"]();
map4.addon();
map4.__underscores__();

View file

@ -0,0 +1,7 @@
/// <reference path="fourslash.ts" />
////function f() {}
////f[/*0*/"x"] = 0;
////f[[|/*1*/"x"|]] = 1;
verify.goToDefinition("1", "0");

View file

@ -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": "<global>",
"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": "<global>",
"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
}
}))
]);

View file

@ -0,0 +1,12 @@
/// <reference path="fourslash.ts" />
// @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`);