Skip to content

Commit 9f4daf5

Browse files
committed
Scanner:skip @ in backticks or with bad whitespace
Outside backticks, @ only starts a new tag after whitespace and before non-whitespace. The scanner now checks for this instead of the parser.
1 parent 3d96fa9 commit 9f4daf5

File tree

4 files changed

+25
-39
lines changed

4 files changed

+25
-39
lines changed

src/compiler/parser.ts

+5-20
Original file line numberDiff line numberDiff line change
@@ -2165,8 +2165,8 @@ namespace Parser {
21652165
return currentToken = scanner.scanJsDocToken();
21662166
}
21672167

2168-
function nextTokenJSDocBig(): JSDocSyntaxKind { // TODO: nextTokenJSDocCommentText
2169-
return currentToken = scanner.scanBigJsDocToken();
2168+
function nextTokenJSDocBig(inBackticks: boolean): JSDocSyntaxKind { // TODO: nextTokenJSDocCommentText
2169+
return currentToken = scanner.scanBigJsDocToken(inBackticks);
21702170
}
21712171

21722172
function reScanGreaterToken(): SyntaxKind {
@@ -8680,7 +8680,7 @@ namespace Parser {
86808680
break;
86818681
}
86828682
if (state === JSDocState.SavingComments) {
8683-
nextTokenJSDocBig();
8683+
nextTokenJSDocBig(/*inBackticks*/ false);
86848684
}
86858685
else {
86868686
nextTokenJSDoc();
@@ -8869,7 +8869,6 @@ namespace Parser {
88698869
const parts: JSDocComment[] = [];
88708870
let linkEnd;
88718871
let state = JSDocState.BeginningOfLine;
8872-
let previousWhitespace = true;
88738872
let margin: number | undefined;
88748873
function pushComment(text: string) {
88758874
if (!margin) {
@@ -8901,14 +8900,8 @@ namespace Parser {
89018900
indent = 0;
89028901
break;
89038902
case SyntaxKind.AtToken:
8904-
if (state === JSDocState.SavingBackticks // TODO: nextTokenJSDocBig should be able to skip @ inside backticks
8905-
|| state === JSDocState.SavingComments && (!previousWhitespace || lookAhead(isNextJSDocTokenWhitespace))) {
8906-
// @ doesn't start a new tag inside ``, and inside a comment, only after whitespace or not before whitespace
8907-
comments.push(scanner.getTokenText());
8908-
break;
8909-
}
89108903
scanner.setTextPos(scanner.getTextPos() - 1);
8911-
// falls through
8904+
break loop;
89128905
case SyntaxKind.EndOfFileToken:
89138906
// Done
89148907
break loop;
@@ -8965,11 +8958,8 @@ namespace Parser {
89658958
pushComment(scanner.getTokenText());
89668959
break;
89678960
}
8968-
// TODO: nextTokenJSDocBig always returns Identifier, even when that token ends with some whitespace.
8969-
// Make this hack less hacky: call a isWhitespace function, and importantly, the state *currently* being SavingComments doesn't mean that the previous call was for a big token
8970-
previousWhitespace = token() === SyntaxKind.WhitespaceTrivia || ((state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) && tok === SyntaxKind.Identifier && scanner.getTokenValue().at(-1) === " ");
89718961
if (state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) { // TODO: Add another scanner method for scanning over the introductory " *" after BeginningOfLine
8972-
tok = nextTokenJSDocBig(); // TODO: Maybe SawAsterisk could also call nextTokenJSDocBig?
8962+
tok = nextTokenJSDocBig(state === JSDocState.SavingBackticks); // TODO: Maybe SawAsterisk could also call nextTokenJSDocBig?
89738963
} // TODO: Maybe nextTokenJSDocBig is backward-compatible enough to just call all the time
89748964
else {
89758965
tok = nextTokenJSDoc();
@@ -8989,11 +8979,6 @@ namespace Parser {
89898979
}
89908980
}
89918981

8992-
function isNextJSDocTokenWhitespace() {
8993-
const next = nextTokenJSDoc();
8994-
return next === SyntaxKind.WhitespaceTrivia || next === SyntaxKind.NewLineTrivia;
8995-
}
8996-
89978982
function parseJSDocLink(start: number) {
89988983
const linkType = tryParse(parseJSDocLinkPrefix);
89998984
if (!linkType) {

src/compiler/scanner.ts

+18-17
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export interface Scanner {
7575
reScanInvalidIdentifier(): SyntaxKind;
7676
scanJsxToken(): JsxTokenSyntaxKind;
7777
scanJsDocToken(): JSDocSyntaxKind;
78-
scanBigJsDocToken(): JSDocSyntaxKind; // TODO: Should only be the Big Token kinds
78+
scanBigJsDocToken(inBackticks: boolean): JSDocSyntaxKind;
7979
scan(): SyntaxKind;
8080

8181
getText(): string;
@@ -2457,32 +2457,33 @@ export function createScanner(languageVersion: ScriptTarget,
24572457
return scanJsxAttributeValue();
24582458
}
24592459

2460-
/** TODO: might need to return WhitespaceTrivia if only whitespace was encountered? */
2461-
function scanBigJsDocToken(): JSDocSyntaxKind { // can be configurable to skip almost everything (except newline and backtick) if backticks is true
2460+
function scanBigJsDocToken(inBackticks: boolean): JSDocSyntaxKind {
24622461
startPos = tokenPos = pos;
24632462
tokenFlags = TokenFlags.None;
24642463
if (pos >= end) {
24652464
return token = SyntaxKind.EndOfFileToken;
24662465
}
2467-
2468-
let ch = codePointAt(text, pos);
2469-
while (pos < end) {
2470-
if (ch !== CharacterCodes.lineFeed && ch !== CharacterCodes.at && ch !== CharacterCodes.backtick && ch !== CharacterCodes.openBrace) {
2471-
// TODO: We can also be smarter about openBrace, backtick and at by looking at a tiny amount of context
2472-
pos++;
2473-
}
2474-
else {
2475-
break;
2466+
// TODO: Probably need to increment pos in the initial part to avoid a double read
2467+
// TODO: Need to increment `pos += charSize(ch)`,
2468+
for (let ch = codePointAt(text, pos);
2469+
pos < end && (ch !== CharacterCodes.lineFeed && ch !== CharacterCodes.carriageReturn && ch !== CharacterCodes.backtick);
2470+
ch = codePointAt(text, ++pos)) {
2471+
if (!inBackticks) {
2472+
if (ch === CharacterCodes.openBrace) {
2473+
break;
2474+
}
2475+
else if (ch === CharacterCodes.at
2476+
&& pos - 1 >= 0 && isWhiteSpaceSingleLine(codePointAt(text, pos - 1))
2477+
&& !(pos + 1 < end && isWhiteSpaceLike(codePointAt(text, pos + 1)))) {
2478+
// @ doesn't start a new tag inside ``, and elsewhere, only after whitespace and before non-whitespace
2479+
break;
2480+
}
24762481
}
2477-
ch = codePointAt(text, pos);
24782482
}
24792483
if (pos === tokenPos) {
24802484
return scanJsDocToken();
24812485
}
2482-
else {
2483-
// TODO: Make sure this is right (and in the right place)
2484-
tokenValue = text.substring(tokenPos, pos);
2485-
}
2486+
tokenValue = text.substring(tokenPos, pos);
24862487
return token = SyntaxKind.Identifier;
24872488
}
24882489

tests/baselines/reference/api/tsserverlibrary.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8350,7 +8350,7 @@ declare namespace ts {
83508350
reScanInvalidIdentifier(): SyntaxKind;
83518351
scanJsxToken(): JsxTokenSyntaxKind;
83528352
scanJsDocToken(): JSDocSyntaxKind;
8353-
scanBigJsDocToken(): JSDocSyntaxKind;
8353+
scanBigJsDocToken(inBackticks: boolean): JSDocSyntaxKind;
83548354
scan(): SyntaxKind;
83558355
getText(): string;
83568356
setText(text: string | undefined, start?: number, length?: number): void;

tests/baselines/reference/api/typescript.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4407,7 +4407,7 @@ declare namespace ts {
44074407
reScanInvalidIdentifier(): SyntaxKind;
44084408
scanJsxToken(): JsxTokenSyntaxKind;
44094409
scanJsDocToken(): JSDocSyntaxKind;
4410-
scanBigJsDocToken(): JSDocSyntaxKind;
4410+
scanBigJsDocToken(inBackticks: boolean): JSDocSyntaxKind;
44114411
scan(): SyntaxKind;
44124412
getText(): string;
44134413
setText(text: string | undefined, start?: number, length?: number): void;

0 commit comments

Comments
 (0)