@@ -364,6 +364,7 @@ import {
364
364
tracing,
365
365
TransformFlags,
366
366
trimString,
367
+ trimStringEnd,
367
368
TryStatement,
368
369
TupleTypeNode,
369
370
TypeAliasDeclaration,
@@ -2165,6 +2166,10 @@ namespace Parser {
2165
2166
return currentToken = scanner.scanJsDocToken();
2166
2167
}
2167
2168
2169
+ function nextJSDocCommentTextToken(inBackticks: boolean): JSDocSyntaxKind | SyntaxKind.JSDocCommentTextToken {
2170
+ return currentToken = scanner.scanJSDocCommentTextToken(inBackticks);
2171
+ }
2172
+
2168
2173
function reScanGreaterToken(): SyntaxKind {
2169
2174
return currentToken = scanner.reScanGreaterToken();
2170
2175
}
@@ -8602,19 +8607,14 @@ namespace Parser {
8602
8607
loop: while (true) {
8603
8608
switch (token()) {
8604
8609
case SyntaxKind.AtToken:
8605
- if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) {
8606
- removeTrailingWhitespace(comments);
8607
- if (!commentsPos) commentsPos = getNodePos();
8608
- addTag(parseTag(indent));
8609
- // NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag.
8610
- // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning
8611
- // for malformed examples like `/** @param {string} x @returns {number} the length */`
8612
- state = JSDocState.BeginningOfLine;
8613
- margin = undefined;
8614
- }
8615
- else {
8616
- pushComment(scanner.getTokenText());
8617
- }
8610
+ removeTrailingWhitespace(comments);
8611
+ if (!commentsPos) commentsPos = getNodePos();
8612
+ addTag(parseTag(indent));
8613
+ // NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag.
8614
+ // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning
8615
+ // for malformed examples like `/** @param {string} x @returns {number} the length */`
8616
+ state = JSDocState.BeginningOfLine;
8617
+ margin = undefined;
8618
8618
break;
8619
8619
case SyntaxKind.NewLineTrivia:
8620
8620
comments.push(scanner.getTokenText());
@@ -8623,30 +8623,33 @@ namespace Parser {
8623
8623
break;
8624
8624
case SyntaxKind.AsteriskToken:
8625
8625
const asterisk = scanner.getTokenText();
8626
- if (state === JSDocState.SawAsterisk || state === JSDocState.SavingComments ) {
8626
+ if (state === JSDocState.SawAsterisk) {
8627
8627
// If we've already seen an asterisk, then we can no longer parse a tag on this line
8628
8628
state = JSDocState.SavingComments;
8629
8629
pushComment(asterisk);
8630
8630
}
8631
8631
else {
8632
+ Debug.assert(state === JSDocState.BeginningOfLine);
8632
8633
// Ignore the first asterisk on a line
8633
8634
state = JSDocState.SawAsterisk;
8634
8635
indent += asterisk.length;
8635
8636
}
8636
8637
break;
8637
8638
case SyntaxKind.WhitespaceTrivia:
8639
+ Debug.assert(state !== JSDocState.SavingComments, "whitespace shouldn't come from the scanner while saving top-level comment text");
8638
8640
// only collect whitespace if we're already saving comments or have just crossed the comment indent margin
8639
8641
const whitespace = scanner.getTokenText();
8640
- if (state === JSDocState.SavingComments) {
8641
- comments.push(whitespace);
8642
- }
8643
- else if (margin !== undefined && indent + whitespace.length > margin) {
8642
+ if (margin !== undefined && indent + whitespace.length > margin) {
8644
8643
comments.push(whitespace.slice(margin - indent));
8645
8644
}
8646
8645
indent += whitespace.length;
8647
8646
break;
8648
8647
case SyntaxKind.EndOfFileToken:
8649
8648
break loop;
8649
+ case SyntaxKind.JSDocCommentTextToken:
8650
+ state = JSDocState.SavingComments;
8651
+ pushComment(scanner.getTokenValue());
8652
+ break;
8650
8653
case SyntaxKind.OpenBraceToken:
8651
8654
state = JSDocState.SavingComments;
8652
8655
const commentEnd = scanner.getTokenFullStart();
@@ -8671,15 +8674,20 @@ namespace Parser {
8671
8674
pushComment(scanner.getTokenText());
8672
8675
break;
8673
8676
}
8674
- nextTokenJSDoc();
8677
+ if (state === JSDocState.SavingComments) {
8678
+ nextJSDocCommentTextToken(/*inBackticks*/ false);
8679
+ }
8680
+ else {
8681
+ nextTokenJSDoc();
8682
+ }
8675
8683
}
8676
- removeTrailingWhitespace (comments);
8677
- if (parts.length && comments .length) {
8678
- parts.push(finishNode(factory.createJSDocText(comments.join("") ), linkEnd ?? start, commentsPos));
8684
+ const trimmedComments = trimStringEnd (comments.join("") );
8685
+ if (parts.length && trimmedComments .length) {
8686
+ parts.push(finishNode(factory.createJSDocText(trimmedComments ), linkEnd ?? start, commentsPos));
8679
8687
}
8680
8688
if (parts.length && tags) Debug.assertIsDefined(commentsPos, "having parsed tags implies that the end of the comment span should be set");
8681
8689
const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd);
8682
- return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : comments .length ? comments.join("") : undefined, tagsArray), start, end);
8690
+ return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : trimmedComments .length ? trimmedComments : undefined, tagsArray), start, end);
8683
8691
});
8684
8692
8685
8693
function removeLeadingNewlines(comments: string[]) {
@@ -8689,8 +8697,18 @@ namespace Parser {
8689
8697
}
8690
8698
8691
8699
function removeTrailingWhitespace(comments: string[]) {
8692
- while (comments.length && comments[comments.length - 1].trim() === "") {
8693
- comments.pop();
8700
+ while (comments.length) {
8701
+ const trimmed = trimStringEnd(comments[comments.length - 1]);
8702
+ if (trimmed === "") {
8703
+ comments.pop();
8704
+ }
8705
+ else if (trimmed.length < comments[comments.length - 1].length) {
8706
+ comments[comments.length - 1] = trimmed;
8707
+ break;
8708
+ }
8709
+ else {
8710
+ break;
8711
+ }
8694
8712
}
8695
8713
}
8696
8714
@@ -8846,7 +8864,6 @@ namespace Parser {
8846
8864
const parts: JSDocComment[] = [];
8847
8865
let linkEnd;
8848
8866
let state = JSDocState.BeginningOfLine;
8849
- let previousWhitespace = true;
8850
8867
let margin: number | undefined;
8851
8868
function pushComment(text: string) {
8852
8869
if (!margin) {
@@ -8862,7 +8879,7 @@ namespace Parser {
8862
8879
}
8863
8880
state = JSDocState.SawAsterisk;
8864
8881
}
8865
- let tok = token() as JSDocSyntaxKind;
8882
+ let tok = token() as JSDocSyntaxKind | SyntaxKind.JSDocCommentTextToken ;
8866
8883
loop: while (true) {
8867
8884
switch (tok) {
8868
8885
case SyntaxKind.NewLineTrivia:
@@ -8872,29 +8889,20 @@ namespace Parser {
8872
8889
indent = 0;
8873
8890
break;
8874
8891
case SyntaxKind.AtToken:
8875
- if (state === JSDocState.SavingBackticks
8876
- || state === JSDocState.SavingComments && (!previousWhitespace || lookAhead(isNextJSDocTokenWhitespace))) {
8877
- // @ doesn't start a new tag inside ``, and inside a comment, only after whitespace or not before whitespace
8878
- comments.push(scanner.getTokenText());
8879
- break;
8880
- }
8881
8892
scanner.resetTokenState(scanner.getTokenEnd() - 1);
8882
- // falls through
8893
+ break loop;
8883
8894
case SyntaxKind.EndOfFileToken:
8884
8895
// Done
8885
8896
break loop;
8886
8897
case SyntaxKind.WhitespaceTrivia:
8887
- if (state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) {
8888
- pushComment(scanner.getTokenText());
8889
- }
8890
- else {
8891
- const whitespace = scanner.getTokenText();
8892
- // if the whitespace crosses the margin, take only the whitespace that passes the margin
8893
- if (margin !== undefined && indent + whitespace.length > margin) {
8894
- comments.push(whitespace.slice(margin - indent));
8895
- }
8896
- indent += whitespace.length;
8898
+ Debug.assert(state !== JSDocState.SavingComments && state !== JSDocState.SavingBackticks, "whitespace shouldn't come from the scanner while saving comment text");
8899
+ const whitespace = scanner.getTokenText();
8900
+ // if the whitespace crosses the margin, take only the whitespace that passes the margin
8901
+ if (margin !== undefined && indent + whitespace.length > margin) {
8902
+ comments.push(whitespace.slice(margin - indent));
8903
+ state = JSDocState.SavingComments;
8897
8904
}
8905
+ indent += whitespace.length;
8898
8906
break;
8899
8907
case SyntaxKind.OpenBraceToken:
8900
8908
state = JSDocState.SavingComments;
@@ -8920,6 +8928,12 @@ namespace Parser {
8920
8928
}
8921
8929
pushComment(scanner.getTokenText());
8922
8930
break;
8931
+ case SyntaxKind.JSDocCommentTextToken:
8932
+ if (state !== JSDocState.SavingBackticks) {
8933
+ state = JSDocState.SavingComments; // leading identifiers start recording as well
8934
+ }
8935
+ pushComment(scanner.getTokenValue());
8936
+ break;
8923
8937
case SyntaxKind.AsteriskToken:
8924
8938
if (state === JSDocState.BeginningOfLine) {
8925
8939
// leading asterisks start recording on the *next* (non-whitespace) token
@@ -8936,28 +8950,27 @@ namespace Parser {
8936
8950
pushComment(scanner.getTokenText());
8937
8951
break;
8938
8952
}
8939
- previousWhitespace = token() === SyntaxKind.WhitespaceTrivia;
8940
- tok = nextTokenJSDoc();
8953
+ if (state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) {
8954
+ tok = nextJSDocCommentTextToken(state === JSDocState.SavingBackticks);
8955
+ }
8956
+ else {
8957
+ tok = nextTokenJSDoc();
8958
+ }
8941
8959
}
8942
8960
8943
8961
removeLeadingNewlines(comments);
8944
- removeTrailingWhitespace (comments);
8962
+ const trimmedComments = trimStringEnd (comments.join("") );
8945
8963
if (parts.length) {
8946
- if (comments .length) {
8947
- parts.push(finishNode(factory.createJSDocText(comments.join("") ), linkEnd ?? commentsPos));
8964
+ if (trimmedComments .length) {
8965
+ parts.push(finishNode(factory.createJSDocText(trimmedComments ), linkEnd ?? commentsPos));
8948
8966
}
8949
8967
return createNodeArray(parts, commentsPos, scanner.getTokenEnd());
8950
8968
}
8951
- else if (comments .length) {
8952
- return comments.join("") ;
8969
+ else if (trimmedComments .length) {
8970
+ return trimmedComments ;
8953
8971
}
8954
8972
}
8955
8973
8956
- function isNextJSDocTokenWhitespace() {
8957
- const next = nextTokenJSDoc();
8958
- return next === SyntaxKind.WhitespaceTrivia || next === SyntaxKind.NewLineTrivia;
8959
- }
8960
-
8961
8974
function parseJSDocLink(start: number) {
8962
8975
const linkType = tryParse(parseJSDocLinkPrefix);
8963
8976
if (!linkType) {
0 commit comments