Skip to content

Commit d0170d1

Browse files
authored
Support dotted name in @typedef tag (#11695)
* Support dotted name in @typedef tag * Use a new node flag to get rid of the extra logics * replace jsdoc namespace node flag with optional property * Fix linting error
1 parent 66c1178 commit d0170d1

File tree

7 files changed

+99
-5
lines changed

7 files changed

+99
-5
lines changed

src/compiler/binder.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ namespace ts {
5454
const body = (<ModuleDeclaration>node).body;
5555
return body ? getModuleInstanceState(body) : ModuleInstanceState.Instantiated;
5656
}
57+
// Only jsdoc typedef definition can exist in jsdoc namespace, and it should
58+
// be considered the same as type alias
59+
else if (node.kind === SyntaxKind.Identifier && (<Identifier>node).isInJSDocNamespace) {
60+
return ModuleInstanceState.NonInstantiated;
61+
}
5762
else {
5863
return ModuleInstanceState.Instantiated;
5964
}
@@ -429,7 +434,11 @@ namespace ts {
429434
// during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation
430435
// and this case is specially handled. Module augmentations should only be merged with original module definition
431436
// and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed.
432-
if (!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) {
437+
const isJSDocTypedefInJSDocNamespace = node.kind === SyntaxKind.JSDocTypedefTag &&
438+
node.name &&
439+
node.name.kind === SyntaxKind.Identifier &&
440+
(<Identifier>node.name).isInJSDocNamespace;
441+
if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypedefInJSDocNamespace) {
433442
const exportKind =
434443
(symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0) |
435444
(symbolFlags & SymbolFlags.Type ? SymbolFlags.ExportType : 0) |
@@ -1828,6 +1837,17 @@ namespace ts {
18281837
switch (node.kind) {
18291838
/* Strict mode checks */
18301839
case SyntaxKind.Identifier:
1840+
// for typedef type names with namespaces, bind the new jsdoc type symbol here
1841+
// because it requires all containing namespaces to be in effect, namely the
1842+
// current "blockScopeContainer" needs to be set to its immediate namespace parent.
1843+
if ((<Identifier>node).isInJSDocNamespace) {
1844+
let parentNode = node.parent;
1845+
while (parentNode && parentNode.kind !== SyntaxKind.JSDocTypedefTag) {
1846+
parentNode = parentNode.parent;
1847+
}
1848+
bindBlockScopedDeclaration(<Declaration>parentNode, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
1849+
break;
1850+
}
18311851
case SyntaxKind.ThisKeyword:
18321852
if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) {
18331853
node.flowNode = currentFlow;
@@ -1951,6 +1971,10 @@ namespace ts {
19511971
case SyntaxKind.InterfaceDeclaration:
19521972
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes);
19531973
case SyntaxKind.JSDocTypedefTag:
1974+
if (!(<JSDocTypedefTag>node).fullName || (<JSDocTypedefTag>node).fullName.kind === SyntaxKind.Identifier) {
1975+
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
1976+
}
1977+
break;
19541978
case SyntaxKind.TypeAliasDeclaration:
19551979
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
19561980
case SyntaxKind.EnumDeclaration:

src/compiler/parser.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ namespace ts {
411411
return visitNodes(cbNodes, (<JSDocTemplateTag>node).typeParameters);
412412
case SyntaxKind.JSDocTypedefTag:
413413
return visitNode(cbNode, (<JSDocTypedefTag>node).typeExpression) ||
414+
visitNode(cbNode, (<JSDocTypedefTag>node).fullName) ||
414415
visitNode(cbNode, (<JSDocTypedefTag>node).name) ||
415416
visitNode(cbNode, (<JSDocTypedefTag>node).jsDocTypeLiteral);
416417
case SyntaxKind.JSDocTypeLiteral:
@@ -6552,7 +6553,14 @@ namespace ts {
65526553
const typedefTag = <JSDocTypedefTag>createNode(SyntaxKind.JSDocTypedefTag, atToken.pos);
65536554
typedefTag.atToken = atToken;
65546555
typedefTag.tagName = tagName;
6555-
typedefTag.name = parseJSDocIdentifierName();
6556+
typedefTag.fullName = parseJSDocTypeNameWithNamespace(/*flags*/ 0);
6557+
if (typedefTag.fullName) {
6558+
let rightNode = typedefTag.fullName;
6559+
while (rightNode.kind !== SyntaxKind.Identifier) {
6560+
rightNode = rightNode.body;
6561+
}
6562+
typedefTag.name = rightNode;
6563+
}
65566564
typedefTag.typeExpression = typeExpression;
65576565
skipWhitespace();
65586566

@@ -6615,8 +6623,27 @@ namespace ts {
66156623
scanner.setTextPos(resumePos);
66166624
return finishNode(jsDocTypeLiteral);
66176625
}
6626+
6627+
function parseJSDocTypeNameWithNamespace(flags: NodeFlags) {
6628+
const pos = scanner.getTokenPos();
6629+
const typeNameOrNamespaceName = parseJSDocIdentifierName();
6630+
6631+
if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) {
6632+
const jsDocNamespaceNode = <JSDocNamespaceDeclaration>createNode(SyntaxKind.ModuleDeclaration, pos);
6633+
jsDocNamespaceNode.flags |= flags;
6634+
jsDocNamespaceNode.name = typeNameOrNamespaceName;
6635+
jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(NodeFlags.NestedNamespace);
6636+
return jsDocNamespaceNode;
6637+
}
6638+
6639+
if (typeNameOrNamespaceName && flags & NodeFlags.NestedNamespace) {
6640+
typeNameOrNamespaceName.isInJSDocNamespace = true;
6641+
}
6642+
return typeNameOrNamespaceName;
6643+
}
66186644
}
66196645

6646+
66206647
function tryParseChildTag(parentTag: JSDocTypeLiteral): boolean {
66216648
Debug.assert(token() === SyntaxKind.AtToken);
66226649
const atToken = <AtToken>createNode(SyntaxKind.AtToken, scanner.getStartPos());

src/compiler/scanner.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,9 @@ namespace ts {
17991799
case CharacterCodes.comma:
18001800
pos++;
18011801
return token = SyntaxKind.CommaToken;
1802+
case CharacterCodes.dot:
1803+
pos++;
1804+
return token = SyntaxKind.DotToken;
18021805
}
18031806

18041807
if (isIdentifierStart(ch, ScriptTarget.Latest)) {

src/compiler/types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ namespace ts {
421421
ThisNodeHasError = 1 << 19, // If the parser encountered an error when parsing the code that created this node
422422
JavaScriptFile = 1 << 20, // If node was parsed in a JavaScript
423423
ThisNodeOrAnySubNodesHasError = 1 << 21, // If this node or any of its children had an error
424-
HasAggregatedChildData = 1 << 22, // If we've computed data from children and cached it in this node
424+
HasAggregatedChildData = 1 << 22, // If we've computed data from children and cached it in this node
425425

426426
BlockScoped = Let | Const,
427427

@@ -545,6 +545,7 @@ namespace ts {
545545
originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later
546546
/*@internal*/ autoGenerateKind?: GeneratedIdentifierKind; // Specifies whether to auto-generate the text for an identifier.
547547
/*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name.
548+
isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace
548549
}
549550

550551
// Transient identifier node (marked by id === -1)
@@ -1634,14 +1635,19 @@ namespace ts {
16341635
export interface ModuleDeclaration extends DeclarationStatement {
16351636
kind: SyntaxKind.ModuleDeclaration;
16361637
name: Identifier | LiteralExpression;
1637-
body?: ModuleBlock | NamespaceDeclaration;
1638+
body?: ModuleBlock | NamespaceDeclaration | JSDocNamespaceDeclaration | Identifier;
16381639
}
16391640

16401641
export interface NamespaceDeclaration extends ModuleDeclaration {
16411642
name: Identifier;
16421643
body: ModuleBlock | NamespaceDeclaration;
16431644
}
16441645

1646+
export interface JSDocNamespaceDeclaration extends ModuleDeclaration {
1647+
name: Identifier;
1648+
body: JSDocNamespaceDeclaration | Identifier;
1649+
}
1650+
16451651
export interface ModuleBlock extends Node, Statement {
16461652
kind: SyntaxKind.ModuleBlock;
16471653
statements: NodeArray<Statement>;
@@ -1871,6 +1877,7 @@ namespace ts {
18711877

18721878
export interface JSDocTypedefTag extends JSDocTag, Declaration {
18731879
kind: SyntaxKind.JSDocTypedefTag;
1880+
fullName?: JSDocNamespaceDeclaration | Identifier;
18741881
name?: Identifier;
18751882
typeExpression?: JSDocTypeExpression;
18761883
jsDocTypeLiteral?: JSDocTypeLiteral;

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3036,7 +3036,7 @@ namespace ts {
30363036
}
30373037
}
30383038

3039-
if (node.flags & NodeFlags.NestedNamespace) {
3039+
if (node.flags & NodeFlags.NestedNamespace || (node.kind === SyntaxKind.Identifier && (<Identifier>node).isInJSDocNamespace)) {
30403040
flags |= ModifierFlags.Export;
30413041
}
30423042

tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
"end": 16,
1919
"text": "typedef"
2020
},
21+
"fullName": {
22+
"kind": "Identifier",
23+
"pos": 17,
24+
"end": 23,
25+
"text": "People"
26+
},
2127
"name": {
2228
"kind": "Identifier",
2329
"pos": 17,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path="../fourslash.ts"/>
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: jsdocCompletion_typedef.js
5+
6+
//// /**
7+
//// * @typedef {string | number} T.NumberLike
8+
//// * @typedef {{age: number}} T.People
9+
//// * @typedef {string | number} T.O.Q.NumberLike
10+
//// * @type {T.NumberLike}
11+
//// */
12+
//// var x; x./*1*/;
13+
//// /** @type {T.O.Q.NumberLike} */
14+
//// var x1; x1./*2*/;
15+
//// /** @type {T.People} */
16+
//// var x1; x1./*3*/;
17+
18+
goTo.marker("1");
19+
verify.memberListContains('charAt');
20+
verify.memberListContains('toExponential');
21+
22+
goTo.marker("2");
23+
verify.memberListContains('age');
24+
25+
goTo.marker("3");
26+
verify.memberListContains('charAt');
27+
verify.memberListContains('toExponential');

0 commit comments

Comments
 (0)