Skip to content

Commit 64bbf5e

Browse files
committed
Skip asterisks after newline when parsing JSDoc types
1 parent 0ac3a0a commit 64bbf5e

File tree

8 files changed

+642
-12
lines changed

8 files changed

+642
-12
lines changed

src/compiler/parser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,8 +2377,10 @@ namespace ts {
23772377
}
23782378

23792379
function parseJSDocType(): TypeNode {
2380+
scanner.setInJSDocType(true);
23802381
const dotdotdot = parseOptionalToken(SyntaxKind.DotDotDotToken);
23812382
let type = parseTypeOrTypePredicate();
2383+
scanner.setInJSDocType(false);
23822384
if (dotdotdot) {
23832385
const variadic = createNode(SyntaxKind.JSDocVariadicType, dotdotdot.pos) as JSDocVariadicType;
23842386
variadic.type = type;

src/compiler/scanner.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ namespace ts {
4242
setScriptTarget(scriptTarget: ScriptTarget): void;
4343
setLanguageVariant(variant: LanguageVariant): void;
4444
setTextPos(textPos: number): void;
45+
/* @internal */
46+
setInJSDocType(inType: boolean): void;
4547
// Invokes the provided callback then unconditionally restores the scanner to the state it
4648
// was in immediately prior to invoking the callback. The result of invoking the callback
4749
// is returned from this function.
@@ -824,6 +826,8 @@ namespace ts {
824826
let tokenValue!: string;
825827
let tokenFlags: TokenFlags;
826828

829+
let inJSDocType = 0;
830+
827831
setText(text, start, length);
828832

829833
return {
@@ -854,6 +858,7 @@ namespace ts {
854858
setLanguageVariant,
855859
setOnError,
856860
setTextPos,
861+
setInJSDocType,
857862
tryScan,
858863
lookAhead,
859864
scanRange,
@@ -1350,6 +1355,7 @@ namespace ts {
13501355
function scan(): SyntaxKind {
13511356
startPos = pos;
13521357
tokenFlags = 0;
1358+
let asteriskSeen = false;
13531359
while (true) {
13541360
tokenPos = pos;
13551361
if (pos >= end) {
@@ -1447,6 +1453,11 @@ namespace ts {
14471453
return pos += 2, token = SyntaxKind.AsteriskAsteriskToken;
14481454
}
14491455
pos++;
1456+
if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) {
1457+
// decoration at the start of a JSDoc comment line
1458+
asteriskSeen = true;
1459+
continue;
1460+
}
14501461
return token = SyntaxKind.AsteriskToken;
14511462
case CharacterCodes.plus:
14521463
if (text.charCodeAt(pos + 1) === CharacterCodes.plus) {
@@ -2078,5 +2089,9 @@ namespace ts {
20782089
tokenValue = undefined!;
20792090
tokenFlags = 0;
20802091
}
2092+
2093+
function setInJSDocType(inType: boolean) {
2094+
inJSDocType += inType ? 1 : -1;
2095+
}
20812096
}
20822097
}

src/compiler/utilities.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,12 +490,29 @@ namespace ts {
490490
return getTextOfNodeFromSourceText(sourceFile.text, node, includeTrivia);
491491
}
492492

493+
function isJSDocTypeExpressionOrChild(node: Node): boolean {
494+
if (node.kind === SyntaxKind.JSDocTypeExpression) {
495+
return true;
496+
}
497+
if (node.parent) {
498+
return isJSDocTypeExpressionOrChild(node.parent);
499+
}
500+
return false;
501+
}
502+
493503
export function getTextOfNodeFromSourceText(sourceText: string, node: Node, includeTrivia = false): string {
494504
if (nodeIsMissing(node)) {
495505
return "";
496506
}
497507

498-
return sourceText.substring(includeTrivia ? node.pos : skipTrivia(sourceText, node.pos), node.end);
508+
let text = sourceText.substring(includeTrivia ? node.pos : skipTrivia(sourceText, node.pos), node.end);
509+
510+
if (isJSDocTypeExpressionOrChild(node)) {
511+
// strip space + asterisk at line start
512+
text = text.replace(/(^|\r?\n|\r)\s*\*\s*/g, "$1");
513+
}
514+
515+
return text;
499516
}
500517

501518
export function getTextOfNode(node: Node, includeTrivia = false): string {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
tests/cases/conformance/jsdoc/mod7.js(5,7): error TS1110: Type expected.
2+
tests/cases/conformance/jsdoc/mod7.js(8,4): error TS1110: Type expected.
3+
4+
5+
==== tests/cases/conformance/jsdoc/mod1.js (0 errors) ====
6+
/**
7+
* @typedef {function(string): boolean}
8+
* Type1
9+
*/
10+
11+
/**
12+
* Tries to use a type whose name is on a different
13+
* line than the typedef tag.
14+
* @param {Type1} func The function to call.
15+
* @param {string} arg The argument to call it with.
16+
* @returns {boolean} The return.
17+
*/
18+
function callIt(func, arg) {
19+
return func(arg);
20+
}
21+
22+
==== tests/cases/conformance/jsdoc/mod2.js (0 errors) ====
23+
/**
24+
* @typedef {{
25+
* num: number,
26+
* str: string,
27+
* boo: boolean
28+
* }} Type2
29+
*/
30+
31+
/**
32+
* Makes use of a type with a multiline type expression.
33+
* @param {Type2} obj The object.
34+
* @returns {string|number} The return.
35+
*/
36+
function check(obj) {
37+
return obj.boo ? obj.num : obj.str;
38+
}
39+
40+
==== tests/cases/conformance/jsdoc/mod3.js (0 errors) ====
41+
/**
42+
* A function whose signature is very long.
43+
*
44+
* @typedef {function(boolean, string, number):
45+
* (string|number)} StringOrNumber1
46+
*/
47+
48+
/**
49+
* Makes use of a function type with a long signature.
50+
* @param {StringOrNumber1} func The function.
51+
* @param {boolean} bool The condition.
52+
* @param {string} str The string.
53+
* @param {number} num The number.
54+
* @returns {string|number} The return.
55+
*/
56+
function use1(func, bool, str, num) {
57+
return func(bool, str, num)
58+
}
59+
60+
==== tests/cases/conformance/jsdoc/mod4.js (0 errors) ====
61+
/**
62+
* A function whose signature is very long.
63+
*
64+
* @typedef {function(boolean, string,
65+
* number):
66+
* (string|number)} StringOrNumber2
67+
*/
68+
69+
/**
70+
* Makes use of a function type with a long signature.
71+
* @param {StringOrNumber2} func The function.
72+
* @param {boolean} bool The condition.
73+
* @param {string} str The string.
74+
* @param {number} num The number.
75+
* @returns {string|number} The return.
76+
*/
77+
function use2(func, bool, str, num) {
78+
return func(bool, str, num)
79+
}
80+
81+
==== tests/cases/conformance/jsdoc/mod5.js (0 errors) ====
82+
/**
83+
* @typedef {{
84+
* num:
85+
* number,
86+
* str:
87+
* string,
88+
* boo:
89+
* boolean
90+
* }} Type5
91+
*/
92+
93+
/**
94+
* Makes use of a type with a multiline type expression.
95+
* @param {Type5} obj The object.
96+
* @returns {string|number} The return.
97+
*/
98+
function check5(obj) {
99+
return obj.boo ? obj.num : obj.str;
100+
}
101+
102+
==== tests/cases/conformance/jsdoc/mod6.js (0 errors) ====
103+
/**
104+
* @typedef {{
105+
* foo:
106+
* *,
107+
* bar:
108+
* *
109+
* }} Type6
110+
*/
111+
112+
/**
113+
* Makes use of a type with a multiline type expression.
114+
* @param {Type6} obj The object.
115+
* @returns {*} The return.
116+
*/
117+
function check6(obj) {
118+
return obj.foo;
119+
}
120+
121+
122+
==== tests/cases/conformance/jsdoc/mod7.js (2 errors) ====
123+
/**
124+
Multiline type expressions in comments without leading * are not supported.
125+
@typedef {{
126+
foo:
127+
*,
128+
~
129+
!!! error TS1110: Type expected.
130+
bar:
131+
*
132+
}} Type7
133+
~
134+
!!! error TS1110: Type expected.
135+
*/
136+

0 commit comments

Comments
 (0)