Skip to content

Commit 20cc428

Browse files
committed
Fix jsdoc typedef symbol scope + avoid bind twice if the tag has a jsdoc namespace
1 parent b5c59c6 commit 20cc428

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
@@ -669,6 +669,12 @@ namespace ts {
669669
case SyntaxKind.CallExpression:
670670
bindCallExpressionFlow(<CallExpression>node);
671671
break;
672+
case SyntaxKind.JSDocComment:
673+
bindJSDocComment(<JSDoc>node);
674+
break;
675+
case SyntaxKind.JSDocTypedefTag:
676+
bindJSDocTypedefTag(<JSDocTypedefTag>node);
677+
break;
672678
default:
673679
bindEachChild(node);
674680
break;
@@ -1298,6 +1304,26 @@ namespace ts {
12981304
}
12991305
}
13001306

1307+
function bindJSDocComment(node: JSDoc) {
1308+
forEachChild(node, n => {
1309+
if (n.kind !== SyntaxKind.JSDocTypedefTag) {
1310+
bind(n);
1311+
}
1312+
});
1313+
}
1314+
1315+
function bindJSDocTypedefTag(node: JSDocTypedefTag) {
1316+
forEachChild(node, n => {
1317+
// if the node has a fullName "A.B.C", that means symbol "C" was already bond
1318+
// when we visit "fullName"; so when we visit the name "C" as the next child of
1319+
// the jsDocTypedefTag, we should skip binding it.
1320+
if (n === node.name && node.fullName.kind !== SyntaxKind.Identifier) {
1321+
return;
1322+
}
1323+
bind(n);
1324+
});
1325+
}
1326+
13011327
function bindCallExpressionFlow(node: CallExpression) {
13021328
// If the target of the call expression is a function expression or arrow function we have
13031329
// an immediately invoked function expression (IIFE). Initialize the flowNode property to
@@ -1827,6 +1853,18 @@ namespace ts {
18271853
}
18281854
node.parent = parent;
18291855
const saveInStrictMode = inStrictMode;
1856+
1857+
// Even though in the AST the jsdoc @typedef node belongs to the current node,
1858+
// its symbol might be in the same scope with the current node's symbol. Consider:
1859+
//
1860+
// /** @typedef {string | number} MyType */
1861+
// function foo();
1862+
//
1863+
// Here the current node is "foo", which is a container, but the scope of "MyType" should
1864+
// not be inside "foo". Therefore we always bind @typedef before bind the parent node,
1865+
// and skip binding this tag later when binding all the other jsdoc tags.
1866+
bindJSDocTypedefTagIfAny(node);
1867+
18301868
// First we bind declaration nodes to a symbol if possible. We'll both create a symbol
18311869
// and then potentially add the symbol to an appropriate symbol table. Possible
18321870
// destination symbol tables are:
@@ -1861,6 +1899,27 @@ namespace ts {
18611899
inStrictMode = saveInStrictMode;
18621900
}
18631901

1902+
function bindJSDocTypedefTagIfAny(node: Node) {
1903+
if (!node.jsDoc) {
1904+
return;
1905+
}
1906+
1907+
for (const jsDoc of node.jsDoc) {
1908+
if (!jsDoc.tags) {
1909+
continue;
1910+
}
1911+
1912+
for (const tag of jsDoc.tags) {
1913+
if (tag.kind === SyntaxKind.JSDocTypedefTag) {
1914+
const savedParent = parent;
1915+
parent = jsDoc;
1916+
bind(tag);
1917+
parent = savedParent;
1918+
}
1919+
}
1920+
}
1921+
}
1922+
18641923
function updateStrictModeStatementList(statements: NodeArray<Statement>) {
18651924
if (!inStrictMode) {
18661925
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)