Skip to content

Commit 2d4b4c9

Browse files
authored
Merge pull request #14014 from zhengbli/12233
Fix jsdoc typedef symbol scope + avoid bind twice if the type has a namespace prefix
2 parents 65da012 + f6e9dad commit 2d4b4c9

File tree

4 files changed

+110
-0
lines changed

4 files changed

+110
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ internal/
5757
!tests/cases/projects/NodeModulesSearch/**/*
5858
!tests/baselines/reference/project/nodeModules*/**/*
5959
.idea
60+
yarn.lock

src/compiler/binder.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,12 @@ namespace ts {
670670
case SyntaxKind.CallExpression:
671671
bindCallExpressionFlow(<CallExpression>node);
672672
break;
673+
case SyntaxKind.JSDocComment:
674+
bindJSDocComment(<JSDoc>node);
675+
break;
676+
case SyntaxKind.JSDocTypedefTag:
677+
bindJSDocTypedefTag(<JSDocTypedefTag>node);
678+
break;
673679
default:
674680
bindEachChild(node);
675681
break;
@@ -1335,6 +1341,26 @@ namespace ts {
13351341
}
13361342
}
13371343

1344+
function bindJSDocComment(node: JSDoc) {
1345+
forEachChild(node, n => {
1346+
if (n.kind !== SyntaxKind.JSDocTypedefTag) {
1347+
bind(n);
1348+
}
1349+
});
1350+
}
1351+
1352+
function bindJSDocTypedefTag(node: JSDocTypedefTag) {
1353+
forEachChild(node, n => {
1354+
// if the node has a fullName "A.B.C", that means symbol "C" was already bound
1355+
// when we visit "fullName"; so when we visit the name "C" as the next child of
1356+
// the jsDocTypedefTag, we should skip binding it.
1357+
if (node.fullName && n === node.name && node.fullName.kind !== SyntaxKind.Identifier) {
1358+
return;
1359+
}
1360+
bind(n);
1361+
});
1362+
}
1363+
13381364
function bindCallExpressionFlow(node: CallExpression) {
13391365
// If the target of the call expression is a function expression or arrow function we have
13401366
// an immediately invoked function expression (IIFE). Initialize the flowNode property to
@@ -1874,6 +1900,18 @@ namespace ts {
18741900
}
18751901
node.parent = parent;
18761902
const saveInStrictMode = inStrictMode;
1903+
1904+
// Even though in the AST the jsdoc @typedef node belongs to the current node,
1905+
// its symbol might be in the same scope with the current node's symbol. Consider:
1906+
//
1907+
// /** @typedef {string | number} MyType */
1908+
// function foo();
1909+
//
1910+
// Here the current node is "foo", which is a container, but the scope of "MyType" should
1911+
// not be inside "foo". Therefore we always bind @typedef before bind the parent node,
1912+
// and skip binding this tag later when binding all the other jsdoc tags.
1913+
bindJSDocTypedefTagIfAny(node);
1914+
18771915
// First we bind declaration nodes to a symbol if possible. We'll both create a symbol
18781916
// and then potentially add the symbol to an appropriate symbol table. Possible
18791917
// destination symbol tables are:
@@ -1908,6 +1946,27 @@ namespace ts {
19081946
inStrictMode = saveInStrictMode;
19091947
}
19101948

1949+
function bindJSDocTypedefTagIfAny(node: Node) {
1950+
if (!node.jsDoc) {
1951+
return;
1952+
}
1953+
1954+
for (const jsDoc of node.jsDoc) {
1955+
if (!jsDoc.tags) {
1956+
continue;
1957+
}
1958+
1959+
for (const tag of jsDoc.tags) {
1960+
if (tag.kind === SyntaxKind.JSDocTypedefTag) {
1961+
const savedParent = parent;
1962+
parent = jsDoc;
1963+
bind(tag);
1964+
parent = savedParent;
1965+
}
1966+
}
1967+
}
1968+
}
1969+
19111970
function updateStrictModeStatementList(statements: NodeArray<Statement>) {
19121971
if (!inStrictMode) {
19131972
for (const statement of statements) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path="../fourslash.ts"/>
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: jsdocCompletion_typedef.js
5+
6+
//// /**
7+
//// * @typedef {Object} MyType
8+
//// * @property {string} yes
9+
//// */
10+
//// function foo() { }
11+
12+
//// /**
13+
//// * @param {MyType} my
14+
//// */
15+
//// function a(my) {
16+
//// my.yes./*1*/
17+
//// }
18+
19+
goTo.marker('1');
20+
verify.completionListContains('charAt');
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/// <reference path="../fourslash.ts"/>
2+
3+
// @allowNonTsExtensions: true
4+
// @Filename: jsdocCompletion_typedef.js
5+
6+
//// /**
7+
//// * @typedef {Object} A.B.MyType
8+
//// * @property {string} yes
9+
//// */
10+
//// function foo() {}
11+
12+
//// /**
13+
//// * @param {A.B.MyType} my2
14+
//// */
15+
//// function a(my2) {
16+
//// my2.yes./*1*/
17+
//// }
18+
19+
//// /**
20+
//// * @param {MyType} my2
21+
//// */
22+
//// function b(my2) {
23+
//// my2.yes./*2*/
24+
//// }
25+
26+
27+
goTo.marker('1');
28+
verify.completionListContains('charAt');
29+
goTo.marker('2');
30+
verify.not.completionListContains('charAt');

0 commit comments

Comments
 (0)