Skip to content

Commit 2ec2238

Browse files
authored
Merge pull request #20198 from Microsoft/jsdoc-values-as-namespaces
Jsdoc values as namespaces
2 parents 75e5b13 + 7208204 commit 2ec2238

28 files changed

+7031
-29942
lines changed

src/compiler/binder.ts

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,6 +2006,9 @@ namespace ts {
20062006
if (currentFlow && isNarrowableReference(<Expression>node)) {
20072007
node.flowNode = currentFlow;
20082008
}
2009+
if (isSpecialPropertyDeclaration(node as PropertyAccessExpression)) {
2010+
bindSpecialPropertyDeclaration(node as PropertyAccessExpression);
2011+
}
20092012
break;
20102013
case SyntaxKind.BinaryExpression:
20112014
const specialKind = getSpecialPropertyAssignmentKind(node as BinaryExpression);
@@ -2314,7 +2317,7 @@ namespace ts {
23142317
declareSymbol(file.symbol.exports, file.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule, SymbolFlags.None);
23152318
}
23162319

2317-
function bindThisPropertyAssignment(node: BinaryExpression) {
2320+
function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {
23182321
Debug.assert(isInJavaScriptFile(node));
23192322
const container = getThisContainer(node, /*includeArrowFunctions*/ false);
23202323
switch (container.kind) {
@@ -2340,8 +2343,19 @@ namespace ts {
23402343
}
23412344
}
23422345

2346+
function bindSpecialPropertyDeclaration(node: PropertyAccessExpression) {
2347+
Debug.assert(isInJavaScriptFile(node));
2348+
if (node.expression.kind === SyntaxKind.ThisKeyword) {
2349+
bindThisPropertyAssignment(node);
2350+
}
2351+
else if ((node.expression.kind === SyntaxKind.Identifier || node.expression.kind === SyntaxKind.PropertyAccessExpression) &&
2352+
node.parent.parent.kind === SyntaxKind.SourceFile) {
2353+
bindStaticPropertyAssignment(node);
2354+
}
2355+
}
2356+
23432357
function bindPrototypePropertyAssignment(node: BinaryExpression) {
2344-
// We saw a node of the form 'x.prototype.y = z'. Declare a 'member' y on x if x was a function.
2358+
// We saw a node of the form 'x.prototype.y = z'. Declare a 'member' y on x if x is a function or class, or not declared.
23452359

23462360
// Look up the function in the local scope, since prototype assignments should
23472361
// follow the function declaration
@@ -2357,24 +2371,27 @@ namespace ts {
23572371
bindPropertyAssignment(constructorFunction.escapedText, leftSideOfAssignment, /*isPrototypeProperty*/ true);
23582372
}
23592373

2360-
function bindStaticPropertyAssignment(node: BinaryExpression) {
2361-
// We saw a node of the form 'x.y = z'. Declare a 'member' y on x if x was a function.
2362-
2363-
// Look up the function in the local scope, since prototype assignments should
2374+
/**
2375+
* For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function or class, or not declared.
2376+
* Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y;
2377+
*/
2378+
function bindStaticPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {
2379+
// Look up the function in the local scope, since static assignments should
23642380
// follow the function declaration
2365-
const leftSideOfAssignment = node.left as PropertyAccessExpression;
2381+
const leftSideOfAssignment = node.kind === SyntaxKind.PropertyAccessExpression ? node : node.left as PropertyAccessExpression;
23662382
const target = leftSideOfAssignment.expression;
23672383

23682384
if (isIdentifier(target)) {
23692385
// Fix up parent pointers since we're going to use these nodes before we bind into them
2370-
leftSideOfAssignment.parent = node;
23712386
target.parent = leftSideOfAssignment;
2372-
2387+
if (node.kind === SyntaxKind.BinaryExpression) {
2388+
leftSideOfAssignment.parent = node;
2389+
}
23732390
if (isNameOfExportsOrModuleExportsAliasDeclaration(target)) {
23742391
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
23752392
// var util = module.exports;
23762393
// util.property = function ...
2377-
bindExportsPropertyAssignment(node);
2394+
bindExportsPropertyAssignment(node as BinaryExpression);
23782395
}
23792396
else {
23802397
bindPropertyAssignment(target.escapedText, leftSideOfAssignment, /*isPrototypeProperty*/ false);
@@ -2383,17 +2400,41 @@ namespace ts {
23832400
}
23842401

23852402
function lookupSymbolForName(name: __String) {
2386-
return (container.symbol && container.symbol.exports && container.symbol.exports.get(name)) || (container.locals && container.locals.get(name));
2403+
const local = container.locals && container.locals.get(name);
2404+
if (local) {
2405+
return local.exportSymbol || local;
2406+
}
2407+
return container.symbol && container.symbol.exports && container.symbol.exports.get(name);
23872408
}
23882409

2389-
function bindPropertyAssignment(functionName: __String, propertyAccessExpression: PropertyAccessExpression, isPrototypeProperty: boolean) {
2390-
let targetSymbol = lookupSymbolForName(functionName);
2391-
2392-
if (targetSymbol && isDeclarationOfFunctionOrClassExpression(targetSymbol)) {
2393-
targetSymbol = (targetSymbol.valueDeclaration as VariableDeclaration).initializer.symbol;
2410+
function bindPropertyAssignment(functionName: __String, propertyAccess: PropertyAccessExpression, isPrototypeProperty: boolean) {
2411+
const symbol = lookupSymbolForName(functionName);
2412+
let targetSymbol = symbol && isDeclarationOfFunctionOrClassExpression(symbol) ?
2413+
(symbol.valueDeclaration as VariableDeclaration).initializer.symbol :
2414+
symbol;
2415+
Debug.assert(propertyAccess.parent.kind === SyntaxKind.BinaryExpression || propertyAccess.parent.kind === SyntaxKind.ExpressionStatement);
2416+
let isLegalPosition: boolean;
2417+
if (propertyAccess.parent.kind === SyntaxKind.BinaryExpression) {
2418+
const initializerKind = (propertyAccess.parent as BinaryExpression).right.kind;
2419+
isLegalPosition = (initializerKind === SyntaxKind.ClassExpression || initializerKind === SyntaxKind.FunctionExpression) &&
2420+
propertyAccess.parent.parent.parent.kind === SyntaxKind.SourceFile;
23942421
}
2395-
2396-
if (!targetSymbol || !(targetSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class))) {
2422+
else {
2423+
isLegalPosition = propertyAccess.parent.parent.kind === SyntaxKind.SourceFile;
2424+
}
2425+
if (!isPrototypeProperty && (!targetSymbol || !(targetSymbol.flags & SymbolFlags.Namespace)) && isLegalPosition) {
2426+
Debug.assert(isIdentifier(propertyAccess.expression));
2427+
const identifier = propertyAccess.expression as Identifier;
2428+
const flags = SymbolFlags.Module | SymbolFlags.JSContainer;
2429+
const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.JSContainer;
2430+
if (targetSymbol) {
2431+
addDeclarationToSymbol(symbol, identifier, flags);
2432+
}
2433+
else {
2434+
targetSymbol = declareSymbol(container.locals, /*parent*/ undefined, identifier, flags, excludeFlags);
2435+
}
2436+
}
2437+
if (!targetSymbol || !(targetSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule))) {
23972438
return;
23982439
}
23992440

@@ -2403,7 +2444,7 @@ namespace ts {
24032444
(targetSymbol.exports || (targetSymbol.exports = createSymbolTable()));
24042445

24052446
// Declare the method/property
2406-
declareSymbol(symbolTable, targetSymbol, propertyAccessExpression, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
2447+
declareSymbol(symbolTable, targetSymbol, propertyAccess, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
24072448
}
24082449

24092450
function bindCallExpression(node: CallExpression) {

src/compiler/checker.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,9 @@ namespace ts {
632632
}
633633

634634
function mergeSymbol(target: Symbol, source: Symbol) {
635-
if (!(target.flags & getExcludedSymbolFlags(source.flags))) {
635+
if (!(target.flags & getExcludedSymbolFlags(source.flags)) ||
636+
source.flags & SymbolFlags.JSContainer || target.flags & SymbolFlags.JSContainer) {
637+
// Javascript static-property-assignment declarations always merge, even though they are also values
636638
if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) {
637639
// reset flag when merging instantiated module into value module that has only const enums
638640
target.constEnumOnlyModule = false;
@@ -1735,13 +1737,16 @@ namespace ts {
17351737
return undefined;
17361738
}
17371739
const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name;
1738-
const namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors, /*dontResolveAlias*/ false, location);
1740+
let namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors, /*dontResolveAlias*/ false, location);
17391741
if (!namespace || nodeIsMissing(right)) {
17401742
return undefined;
17411743
}
17421744
else if (namespace === unknownSymbol) {
17431745
return namespace;
17441746
}
1747+
if (isInJavaScriptFile(name) && isDeclarationOfFunctionOrClassExpression(namespace)) {
1748+
namespace = getSymbolOfNode((namespace.valueDeclaration as VariableDeclaration).initializer);
1749+
}
17451750
symbol = getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning);
17461751
if (!symbol) {
17471752
if (!ignoreErrors) {
@@ -4478,7 +4483,9 @@ namespace ts {
44784483
if (!jsDocType) {
44794484
jsDocType = declarationType;
44804485
}
4481-
else if (jsDocType !== unknownType && declarationType !== unknownType && !isTypeIdenticalTo(jsDocType, declarationType)) {
4486+
else if (jsDocType !== unknownType && declarationType !== unknownType &&
4487+
!isTypeIdenticalTo(jsDocType, declarationType) &&
4488+
!(symbol.flags & SymbolFlags.JSContainer)) {
44824489
errorNextVariableOrPropertyDeclarationMustHaveSameType(jsDocType, declaration, declarationType);
44834490
}
44844491
}
@@ -21443,7 +21450,10 @@ namespace ts {
2144321450
// Node is a secondary declaration, check that type is identical to primary declaration and check that
2144421451
// initializer is consistent with type associated with the node
2144521452
const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node));
21446-
if (type !== unknownType && declarationType !== unknownType && !isTypeIdenticalTo(type, declarationType)) {
21453+
21454+
if (type !== unknownType && declarationType !== unknownType &&
21455+
!isTypeIdenticalTo(type, declarationType) &&
21456+
!(symbol.flags & SymbolFlags.JSContainer)) {
2144721457
errorNextVariableOrPropertyDeclarationMustHaveSameType(type, node, declarationType);
2144821458
}
2144921459
if (node.initializer) {

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ namespace ts {
633633
Node, // Unique name based on the node in the 'original' property.
634634
}
635635

636-
export interface Identifier extends PrimaryExpression {
636+
export interface Identifier extends PrimaryExpression, Declaration {
637637
kind: SyntaxKind.Identifier;
638638
/**
639639
* Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.)
@@ -3062,6 +3062,7 @@ namespace ts {
30623062
ExportStar = 1 << 23, // Export * declaration
30633063
Optional = 1 << 24, // Optional property
30643064
Transient = 1 << 25, // Transient symbol (created during type check)
3065+
JSContainer = 1 << 26, // Contains Javascript special declarations
30653066

30663067
/* @internal */
30673068
All = FunctionScopedVariable | BlockScopedVariable | Property | EnumMember | Function | Class | Interface | ConstEnum | RegularEnum | ValueModule | NamespaceModule | TypeLiteral

src/compiler/utilities.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,7 @@ namespace ts {
14461446
}
14471447

14481448
/**
1449-
* Returns true if the node is a variable declaration whose initializer is a function expression.
1449+
* Returns true if the node is a variable declaration whose initializer is a function or class expression.
14501450
* This function does not test if the node is in a JavaScript file or not.
14511451
*/
14521452
export function isDeclarationOfFunctionOrClassExpression(s: Symbol) {
@@ -1519,6 +1519,12 @@ namespace ts {
15191519
return SpecialPropertyAssignmentKind.None;
15201520
}
15211521

1522+
export function isSpecialPropertyDeclaration(expr: ts.PropertyAccessExpression): boolean {
1523+
return isInJavaScriptFile(expr) &&
1524+
expr.parent && expr.parent.kind === SyntaxKind.ExpressionStatement &&
1525+
!!getJSDocTypeTag(expr.parent);
1526+
}
1527+
15221528
export function getExternalModuleName(node: Node): Expression {
15231529
if (node.kind === SyntaxKind.ImportDeclaration) {
15241530
return (<ImportDeclaration>node).moduleSpecifier;
@@ -1636,7 +1642,8 @@ namespace ts {
16361642
if (parent && parent.parent && parent.parent.parent && getSingleInitializerOfVariableStatement(parent.parent.parent, node)) {
16371643
getJSDocCommentsAndTagsWorker(parent.parent.parent);
16381644
}
1639-
if (isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) !== SpecialPropertyAssignmentKind.None) {
1645+
if (isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) !== SpecialPropertyAssignmentKind.None ||
1646+
node.kind === SyntaxKind.PropertyAccessExpression && node.parent && node.parent.kind === SyntaxKind.ExpressionStatement) {
16401647
getJSDocCommentsAndTagsWorker(parent);
16411648
}
16421649

@@ -4203,6 +4210,8 @@ namespace ts {
42034210
return undefined;
42044211
}
42054212
switch (declaration.kind) {
4213+
case SyntaxKind.Identifier:
4214+
return declaration as Identifier;
42064215
case SyntaxKind.JSDocPropertyTag:
42074216
case SyntaxKind.JSDocParameterTag: {
42084217
const { name } = declaration as JSDocPropertyLikeTag;

src/services/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ namespace ts {
416416
_updateExpressionBrand: any;
417417
_unaryExpressionBrand: any;
418418
_expressionBrand: any;
419+
_declarationBrand: any;
419420
/*@internal*/typeArguments: NodeArray<TypeNode>;
420421
constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) {
421422
super(pos, end);

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ declare namespace ts {
472472
type AwaitKeywordToken = Token<SyntaxKind.AwaitKeyword>;
473473
type Modifier = Token<SyntaxKind.AbstractKeyword> | Token<SyntaxKind.AsyncKeyword> | Token<SyntaxKind.ConstKeyword> | Token<SyntaxKind.DeclareKeyword> | Token<SyntaxKind.DefaultKeyword> | Token<SyntaxKind.ExportKeyword> | Token<SyntaxKind.PublicKeyword> | Token<SyntaxKind.PrivateKeyword> | Token<SyntaxKind.ProtectedKeyword> | Token<SyntaxKind.ReadonlyKeyword> | Token<SyntaxKind.StaticKeyword>;
474474
type ModifiersArray = NodeArray<Modifier>;
475-
interface Identifier extends PrimaryExpression {
475+
interface Identifier extends PrimaryExpression, Declaration {
476476
kind: SyntaxKind.Identifier;
477477
/**
478478
* Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.)
@@ -1903,6 +1903,7 @@ declare namespace ts {
19031903
ExportStar = 8388608,
19041904
Optional = 16777216,
19051905
Transient = 33554432,
1906+
JSContainer = 67108864,
19061907
Enum = 384,
19071908
Variable = 3,
19081909
Value = 107455,

tests/baselines/reference/api/typescript.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ declare namespace ts {
472472
type AwaitKeywordToken = Token<SyntaxKind.AwaitKeyword>;
473473
type Modifier = Token<SyntaxKind.AbstractKeyword> | Token<SyntaxKind.AsyncKeyword> | Token<SyntaxKind.ConstKeyword> | Token<SyntaxKind.DeclareKeyword> | Token<SyntaxKind.DefaultKeyword> | Token<SyntaxKind.ExportKeyword> | Token<SyntaxKind.PublicKeyword> | Token<SyntaxKind.PrivateKeyword> | Token<SyntaxKind.ProtectedKeyword> | Token<SyntaxKind.ReadonlyKeyword> | Token<SyntaxKind.StaticKeyword>;
474474
type ModifiersArray = NodeArray<Modifier>;
475-
interface Identifier extends PrimaryExpression {
475+
interface Identifier extends PrimaryExpression, Declaration {
476476
kind: SyntaxKind.Identifier;
477477
/**
478478
* Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.)
@@ -1903,6 +1903,7 @@ declare namespace ts {
19031903
ExportStar = 8388608,
19041904
Optional = 16777216,
19051905
Transient = 33554432,
1906+
JSContainer = 67108864,
19061907
Enum = 384,
19071908
Variable = 3,
19081909
Value = 107455,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/conformance/salsa/a.js ===
2+
var Outer = class O {
3+
>Outer : Symbol(Outer, Decl(a.js, 0, 3), Decl(a.js, 2, 1))
4+
>O : Symbol(O, Decl(a.js, 0, 11))
5+
6+
m(x, y) { }
7+
>m : Symbol(O.m, Decl(a.js, 0, 21))
8+
>x : Symbol(x, Decl(a.js, 1, 6))
9+
>y : Symbol(y, Decl(a.js, 1, 8))
10+
}
11+
Outer.Inner = class I {
12+
>Outer.Inner : Symbol(O.Inner, Decl(a.js, 2, 1))
13+
>Outer : Symbol(Outer, Decl(a.js, 0, 3), Decl(a.js, 2, 1))
14+
>Inner : Symbol(O.Inner, Decl(a.js, 2, 1))
15+
>I : Symbol(I, Decl(a.js, 3, 13))
16+
17+
n(a, b) { }
18+
>n : Symbol(I.n, Decl(a.js, 3, 23))
19+
>a : Symbol(a, Decl(a.js, 4, 6))
20+
>b : Symbol(b, Decl(a.js, 4, 8))
21+
22+
}
23+
/** @type {Outer} */
24+
var si
25+
>si : Symbol(si, Decl(a.js, 8, 3))
26+
27+
si.m
28+
>si.m : Symbol(O.m, Decl(a.js, 0, 21))
29+
>si : Symbol(si, Decl(a.js, 8, 3))
30+
>m : Symbol(O.m, Decl(a.js, 0, 21))
31+
32+
/** @type {Outer.Inner} */
33+
var oi
34+
>oi : Symbol(oi, Decl(a.js, 11, 3))
35+
36+
oi.n
37+
>oi.n : Symbol(I.n, Decl(a.js, 3, 23))
38+
>oi : Symbol(oi, Decl(a.js, 11, 3))
39+
>n : Symbol(I.n, Decl(a.js, 3, 23))
40+
41+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== tests/cases/conformance/salsa/a.js ===
2+
var Outer = class O {
3+
>Outer : typeof O
4+
>class O { m(x, y) { }} : typeof O
5+
>O : typeof O
6+
7+
m(x, y) { }
8+
>m : (x: any, y: any) => void
9+
>x : any
10+
>y : any
11+
}
12+
Outer.Inner = class I {
13+
>Outer.Inner = class I { n(a, b) { }} : typeof I
14+
>Outer.Inner : typeof I
15+
>Outer : typeof O
16+
>Inner : typeof I
17+
>class I { n(a, b) { }} : typeof I
18+
>I : typeof I
19+
20+
n(a, b) { }
21+
>n : (a: any, b: any) => void
22+
>a : any
23+
>b : any
24+
25+
}
26+
/** @type {Outer} */
27+
var si
28+
>si : O
29+
30+
si.m
31+
>si.m : (x: any, y: any) => void
32+
>si : O
33+
>m : (x: any, y: any) => void
34+
35+
/** @type {Outer.Inner} */
36+
var oi
37+
>oi : I
38+
39+
oi.n
40+
>oi.n : (a: any, b: any) => void
41+
>oi : I
42+
>n : (a: any, b: any) => void
43+
44+

0 commit comments

Comments
 (0)