Skip to content

Commit f8e192a

Browse files
committed
Use a state enum to parse top-level jsdoc comments
Also use a `pushComments` function, which fixes a spacing bug.
1 parent 9e8e5ea commit f8e192a

File tree

2 files changed

+64
-76
lines changed

2 files changed

+64
-76
lines changed

src/compiler/parser.ts

Lines changed: 57 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6151,10 +6151,10 @@ namespace ts {
61516151
return comment;
61526152
}
61536153

6154-
const enum TagState {
6154+
const enum JSDocState {
61556155
BeginningOfLine,
61566156
SawAsterisk,
6157-
SavingComments
6157+
SavingComments,
61586158
}
61596159

61606160
export function parseJSDocCommentWorker(start: number, length: number): JSDoc {
@@ -6180,93 +6180,81 @@ namespace ts {
61806180
scanner.scanRange(start + 3, length - 5, () => {
61816181
// Initially we can parse out a tag. We also have seen a starting asterisk.
61826182
// This is so that /** * @type */ doesn't parse.
6183-
let canParseTag = true;
6184-
let seenAsterisk = true;
61856183
let advanceToken = true;
6184+
let state = JSDocState.SawAsterisk;
61866185
let margin: number | undefined = undefined;
61876186
let indent = start - Math.max(content.lastIndexOf("\n", start), 0) + 4;
6188-
let text: string;
6187+
function pushComment(text: string) {
6188+
if (!margin) {
6189+
margin = indent;
6190+
}
6191+
comments.push(text);
6192+
indent += text.length;
6193+
}
61896194

61906195
nextJSDocToken();
61916196
while (token() === SyntaxKind.WhitespaceTrivia) {
61926197
nextJSDocToken();
61936198
}
61946199
if (token() === SyntaxKind.NewLineTrivia) {
6195-
canParseTag = true;
6196-
seenAsterisk = false;
6200+
state = JSDocState.BeginningOfLine;
61976201
nextJSDocToken();
61986202
}
61996203
while (token() !== SyntaxKind.EndOfFileToken) {
62006204
switch (token()) {
62016205
case SyntaxKind.AtToken:
6202-
if (canParseTag) {
6206+
if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) {
62036207
removeTrailingNewlines(comments);
62046208
parseTag(indent);
6205-
// This will take us past the end of the line, so it's OK to parse a tag on the next pass through the loop
6206-
// NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. But real-world comments may break this rule.
6207-
seenAsterisk = false;
6209+
// NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag.
6210+
// Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning
6211+
// for malformed examples like `/** @param {string} x @returns {number} the length */`
6212+
state = JSDocState.BeginningOfLine;
62086213
advanceToken = false;
62096214
margin = undefined;
6215+
indent++;
62106216
}
62116217
else {
6212-
comments.push(scanner.getTokenText());
6218+
pushComment(scanner.getTokenText());
62136219
}
6214-
indent++;
62156220
break;
6216-
62176221
case SyntaxKind.NewLineTrivia:
6218-
// After a line break, we can parse a tag, and we haven't seen an asterisk on the next line yet
62196222
comments.push(scanner.getTokenText());
6220-
canParseTag = true;
6221-
seenAsterisk = false;
6223+
state = JSDocState.BeginningOfLine;
62226224
indent = 0;
62236225
break;
6224-
62256226
case SyntaxKind.AsteriskToken:
6226-
text = scanner.getTokenText();
6227-
if (seenAsterisk) {
6227+
const asterisk = scanner.getTokenText();
6228+
if (state === JSDocState.SawAsterisk) {
62286229
// If we've already seen an asterisk, then we can no longer parse a tag on this line
6229-
canParseTag = false;
6230-
comments.push(text);
6231-
if (!margin) {
6232-
margin = indent;
6233-
}
6230+
state = JSDocState.SavingComments;
6231+
pushComment(asterisk);
6232+
}
6233+
else {
6234+
// Ignore the first asterisk on a line
6235+
state = JSDocState.SawAsterisk;
6236+
indent += asterisk.length;
62346237
}
6235-
// Ignore the first asterisk on a line
6236-
seenAsterisk = true;
6237-
indent += text.length;
62386238
break;
6239-
62406239
case SyntaxKind.Identifier:
62416240
// Anything else is doc comment text. We just save it. Because it
62426241
// wasn't a tag, we can no longer parse a tag on this line until we hit the next
62436242
// line break.
6244-
text = scanner.getTokenText();
6245-
comments.push(text);
6246-
if (!margin) {
6247-
margin = indent;
6248-
}
6249-
canParseTag = false;
6250-
indent += text.length;
6243+
pushComment(scanner.getTokenText());
6244+
state = JSDocState.SavingComments;
62516245
break;
6252-
6253-
62546246
case SyntaxKind.WhitespaceTrivia:
6255-
// only collect whitespace if we *know* that we're just looking at comments, not a possible jsdoc tag
6256-
text = scanner.getTokenText();
6257-
if (!canParseTag || margin !== undefined && indent + text.length > margin) {
6258-
comments.push(text.slice(margin - indent - 1));
6247+
// only collect whitespace if we're already saving comments or have just crossed the comment indent margin
6248+
const whitespace = scanner.getTokenText();
6249+
if (state === JSDocState.SavingComments || margin !== undefined && indent + whitespace.length > margin) {
6250+
comments.push(whitespace.slice(margin - indent - 1));
62596251
}
6260-
indent += text.length;
6252+
indent += whitespace.length;
62616253
break;
6262-
62636254
case SyntaxKind.EndOfFileToken:
62646255
break;
6265-
62666256
default:
6267-
text = scanner.getTokenText();
6268-
comments.push(text);
6269-
indent += text.length;
6257+
pushComment(scanner.getTokenText());
62706258
break;
62716259
}
62726260
if (advanceToken) {
@@ -6365,58 +6353,58 @@ namespace ts {
63656353

63666354
function parseTagComments(indent: number) {
63676355
const comments: string[] = [];
6368-
let state = TagState.SawAsterisk;
6369-
let done = false;
6356+
let state = JSDocState.SawAsterisk;
63706357
let margin: number | undefined;
6371-
let text: string;
63726358
function pushComment(text: string) {
63736359
if (!margin) {
63746360
margin = indent;
63756361
}
63766362
comments.push(text);
63776363
indent += text.length;
63786364
}
6379-
while (!done && token() !== SyntaxKind.EndOfFileToken) {
6380-
text = scanner.getTokenText();
6365+
while (token() !== SyntaxKind.AtToken && token() !== SyntaxKind.EndOfFileToken) {
63816366
switch (token()) {
63826367
case SyntaxKind.NewLineTrivia:
6383-
if (state >= TagState.SawAsterisk) {
6384-
state = TagState.BeginningOfLine;
6385-
comments.push(text);
6368+
if (state >= JSDocState.SawAsterisk) {
6369+
state = JSDocState.BeginningOfLine;
6370+
comments.push(scanner.getTokenText());
63866371
}
63876372
indent = 0;
63886373
break;
63896374
case SyntaxKind.AtToken:
6390-
done = true;
6375+
// Done
63916376
break;
63926377
case SyntaxKind.WhitespaceTrivia:
6393-
if (state === TagState.SavingComments) {
6394-
pushComment(text);
6378+
if (state === JSDocState.SavingComments) {
6379+
pushComment(scanner.getTokenText());
63956380
}
63966381
else {
6382+
const whitespace = scanner.getTokenText();
63976383
// if the whitespace crosses the margin, take only the whitespace that passes the margin
6398-
if (margin !== undefined && indent + text.length > margin) {
6399-
comments.push(text.slice(margin - indent - 1));
6384+
if (margin !== undefined && indent + whitespace.length > margin) {
6385+
comments.push(whitespace.slice(margin - indent - 1));
64006386
}
6401-
indent += text.length;
6387+
indent += whitespace.length;
64026388
}
64036389
break;
64046390
case SyntaxKind.AsteriskToken:
6405-
if (state === TagState.BeginningOfLine) {
6391+
if (state === JSDocState.BeginningOfLine) {
64066392
// leading asterisks start recording on the *next* (non-whitespace) token
6407-
state = TagState.SawAsterisk;
6408-
indent += text.length;
6393+
state = JSDocState.SawAsterisk;
6394+
indent += scanner.getTokenText().length;
64096395
break;
64106396
}
64116397
// FALLTHROUGH otherwise to record the * as a comment
64126398
default:
6413-
state = TagState.SavingComments; // leading identifiers start recording as well
6414-
pushComment(text);
6399+
state = JSDocState.SavingComments; // leading identifiers start recording as well
6400+
pushComment(scanner.getTokenText());
64156401
break;
64166402
}
6417-
if (!done) {
6418-
nextJSDocToken();
6403+
if (token() === SyntaxKind.AtToken) {
6404+
// Done
6405+
break;
64196406
}
6407+
nextJSDocToken();
64206408
}
64216409

64226410
removeLeadingNewlines(comments);

tests/cases/fourslash/commentsCommentParsing.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -351,39 +351,39 @@ verify.completionListContains("multiply", "function multiply(a: number, b: numbe
351351
verify.completionListContains("f1", "function f1(a: number): any (+1 overload)", "fn f1 with number");
352352

353353
goTo.marker('28');
354-
verify.currentSignatureHelpDocCommentIs("This is subtract function{()=>string; } } f this is optional param f");
354+
verify.currentSignatureHelpDocCommentIs("This is subtract function{ () => string; } } f this is optional param f");
355355
verify.currentParameterHelpArgumentDocCommentIs("");
356356
goTo.marker('28q');
357-
verify.quickInfoIs("function subtract(a: number, b: number, c?: () => string, d?: () => string, e?: () => string, f?: () => string): void", "This is subtract function{()=>string; } } f this is optional param f");
357+
verify.quickInfoIs("function subtract(a: number, b: number, c?: () => string, d?: () => string, e?: () => string, f?: () => string): void", "This is subtract function{ () => string; } } f this is optional param f");
358358
goTo.marker('28aq');
359359
verify.quickInfoIs("(parameter) a: number", "");
360360

361361
goTo.marker('29');
362-
verify.currentSignatureHelpDocCommentIs("This is subtract function{()=>string; } } f this is optional param f");
362+
verify.currentSignatureHelpDocCommentIs("This is subtract function{ () => string; } } f this is optional param f");
363363
verify.currentParameterHelpArgumentDocCommentIs("this is about b");
364364
goTo.marker('29aq');
365365
verify.quickInfoIs("(parameter) b: number", "this is about b");
366366

367367
goTo.marker('30');
368-
verify.currentSignatureHelpDocCommentIs("This is subtract function{()=>string; } } f this is optional param f");
368+
verify.currentSignatureHelpDocCommentIs("This is subtract function{ () => string; } } f this is optional param f");
369369
verify.currentParameterHelpArgumentDocCommentIs("this is optional param c");
370370
goTo.marker('30aq');
371371
verify.quickInfoIs("(parameter) c: () => string", "this is optional param c");
372372

373373
goTo.marker('31');
374-
verify.currentSignatureHelpDocCommentIs("This is subtract function{()=>string; } } f this is optional param f");
374+
verify.currentSignatureHelpDocCommentIs("This is subtract function{ () => string; } } f this is optional param f");
375375
verify.currentParameterHelpArgumentDocCommentIs("this is optional param d");
376376
goTo.marker('31aq');
377377
verify.quickInfoIs("(parameter) d: () => string", "this is optional param d");
378378

379379
goTo.marker('32');
380-
verify.currentSignatureHelpDocCommentIs("This is subtract function{()=>string; } } f this is optional param f");
380+
verify.currentSignatureHelpDocCommentIs("This is subtract function{ () => string; } } f this is optional param f");
381381
verify.currentParameterHelpArgumentDocCommentIs("this is optional param e");
382382
goTo.marker('32aq');
383383
verify.quickInfoIs("(parameter) e: () => string", "this is optional param e");
384384

385385
goTo.marker('33');
386-
verify.currentSignatureHelpDocCommentIs("This is subtract function{()=>string; } } f this is optional param f");
386+
verify.currentSignatureHelpDocCommentIs("This is subtract function{ () => string; } } f this is optional param f");
387387
verify.currentParameterHelpArgumentDocCommentIs("");
388388
goTo.marker('33aq');
389389
verify.quickInfoIs("(parameter) f: () => string", "");

0 commit comments

Comments
 (0)