Skip to content

Commit 99afd53

Browse files
jakebaileysnovader
authored andcommitted
Make JSDoc skipping public, plus more configurable (microsoft#55739)
1 parent 9549a9d commit 99afd53

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1741
-143
lines changed

src/compiler/parser.ts

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ import {
187187
JSDocOverloadTag,
188188
JSDocOverrideTag,
189189
JSDocParameterTag,
190+
JSDocParsingMode,
190191
JSDocPrivateTag,
191192
JSDocPropertyLikeTag,
192193
JSDocPropertyTag,
@@ -1318,16 +1319,14 @@ export interface CreateSourceFileOptions {
13181319
setExternalModuleIndicator?: (file: SourceFile) => void;
13191320
/** @internal */ packageJsonLocations?: readonly string[];
13201321
/** @internal */ packageJsonScope?: PackageJsonInfo;
1322+
jsDocParsingMode?: JSDocParsingMode;
13211323
}
13221324

13231325
function setExternalModuleIndicator(sourceFile: SourceFile) {
13241326
sourceFile.externalModuleIndicator = isFileProbablyExternalModule(sourceFile);
13251327
}
13261328

1327-
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile;
1328-
/** @internal */
1329-
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes?: boolean, scriptKind?: ScriptKind, skipNonSemanticJSDoc?: boolean): SourceFile;
1330-
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind, skipNonSemanticJSDoc?: boolean): SourceFile {
1329+
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind): SourceFile {
13311330
tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true);
13321331
performance.mark("beforeParse");
13331332
let result: SourceFile;
@@ -1337,16 +1336,17 @@ export function createSourceFile(fileName: string, sourceText: string, languageV
13371336
languageVersion,
13381337
setExternalModuleIndicator: overrideSetExternalModuleIndicator,
13391338
impliedNodeFormat: format,
1339+
jsDocParsingMode,
13401340
} = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions : ({ languageVersion: languageVersionOrOptions } as CreateSourceFileOptions);
13411341
if (languageVersion === ScriptTarget.JSON) {
1342-
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON, noop, skipNonSemanticJSDoc);
1342+
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON, noop, jsDocParsingMode);
13431343
}
13441344
else {
13451345
const setIndicator = format === undefined ? overrideSetExternalModuleIndicator : (file: SourceFile) => {
13461346
file.impliedNodeFormat = format;
13471347
return (overrideSetExternalModuleIndicator || setExternalModuleIndicator)(file);
13481348
};
1349-
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind, setIndicator, skipNonSemanticJSDoc);
1349+
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind, setIndicator, jsDocParsingMode);
13501350
}
13511351
perfLogger?.logStopParseSourceFile();
13521352

@@ -1586,7 +1586,7 @@ namespace Parser {
15861586
setParentNodes = false,
15871587
scriptKind?: ScriptKind,
15881588
setExternalModuleIndicatorOverride?: (file: SourceFile) => void,
1589-
skipNonSemanticJSDoc?: boolean,
1589+
jsDocParsingMode = JSDocParsingMode.ParseAll,
15901590
): SourceFile {
15911591
scriptKind = ensureScriptKind(fileName, scriptKind);
15921592
if (scriptKind === ScriptKind.JSON) {
@@ -1601,10 +1601,9 @@ namespace Parser {
16011601
return result;
16021602
}
16031603

1604-
skipNonSemanticJSDoc = !!skipNonSemanticJSDoc && scriptKind !== ScriptKind.JS && scriptKind !== ScriptKind.JSX;
1605-
initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind, skipNonSemanticJSDoc);
1604+
initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind, jsDocParsingMode);
16061605

1607-
const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind, setExternalModuleIndicatorOverride || setExternalModuleIndicator, skipNonSemanticJSDoc);
1606+
const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind, setExternalModuleIndicatorOverride || setExternalModuleIndicator, jsDocParsingMode);
16081607

16091608
clearState();
16101609

@@ -1613,7 +1612,7 @@ namespace Parser {
16131612

16141613
export function parseIsolatedEntityName(content: string, languageVersion: ScriptTarget): EntityName | undefined {
16151614
// Choice of `isDeclarationFile` should be arbitrary
1616-
initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS, /*skipNonSemanticJSDoc*/ false);
1615+
initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS, JSDocParsingMode.ParseAll);
16171616
// Prime the scanner.
16181617
nextToken();
16191618
const entityName = parseEntityName(/*allowReservedWords*/ true);
@@ -1623,7 +1622,7 @@ namespace Parser {
16231622
}
16241623

16251624
export function parseJsonText(fileName: string, sourceText: string, languageVersion: ScriptTarget = ScriptTarget.ES2015, syntaxCursor?: IncrementalParser.SyntaxCursor, setParentNodes = false): JsonSourceFile {
1626-
initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON, /*skipNonSemanticJSDoc*/ false);
1625+
initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON, JSDocParsingMode.ParseAll);
16271626
sourceFlags = contextFlags;
16281627

16291628
// Prime the scanner.
@@ -1711,7 +1710,7 @@ namespace Parser {
17111710
return result;
17121711
}
17131712

1714-
function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind, _skipNonSemanticJSDoc: boolean) {
1713+
function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind, _jsDocParsingMode: JSDocParsingMode) {
17151714
NodeConstructor = objectAllocator.getNodeConstructor();
17161715
TokenConstructor = objectAllocator.getTokenConstructor();
17171716
IdentifierConstructor = objectAllocator.getIdentifierConstructor();
@@ -1752,15 +1751,17 @@ namespace Parser {
17521751
scanner.setOnError(scanError);
17531752
scanner.setScriptTarget(languageVersion);
17541753
scanner.setLanguageVariant(languageVariant);
1755-
scanner.setSkipNonSemanticJSDoc(_skipNonSemanticJSDoc);
1754+
scanner.setScriptKind(scriptKind);
1755+
scanner.setJSDocParsingMode(_jsDocParsingMode);
17561756
}
17571757

17581758
function clearState() {
17591759
// Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily.
17601760
scanner.clearCommentDirectives();
17611761
scanner.setText("");
17621762
scanner.setOnError(undefined);
1763-
scanner.setSkipNonSemanticJSDoc(false);
1763+
scanner.setScriptKind(ScriptKind.Unknown);
1764+
scanner.setJSDocParsingMode(JSDocParsingMode.ParseAll);
17641765

17651766
// Clear any data. We don't want to accidentally hold onto it for too long.
17661767
sourceText = undefined!;
@@ -1777,7 +1778,7 @@ namespace Parser {
17771778
topLevel = true;
17781779
}
17791780

1780-
function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind, setExternalModuleIndicator: (file: SourceFile) => void, skipNonSemanticJSDoc: boolean): SourceFile {
1781+
function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind, setExternalModuleIndicator: (file: SourceFile) => void, jsDocParsingMode: JSDocParsingMode): SourceFile {
17811782
const isDeclarationFile = isDeclarationFileName(fileName);
17821783
if (isDeclarationFile) {
17831784
contextFlags |= NodeFlags.Ambient;
@@ -1804,7 +1805,7 @@ namespace Parser {
18041805
sourceFile.identifierCount = identifierCount;
18051806
sourceFile.identifiers = identifiers;
18061807
sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile);
1807-
sourceFile.skipNonSemanticJSDoc = skipNonSemanticJSDoc;
1808+
sourceFile.jsDocParsingMode = jsDocParsingMode;
18081809
if (jsDocDiagnostics) {
18091810
sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile);
18101811
}
@@ -8686,7 +8687,7 @@ namespace Parser {
86868687

86878688
export namespace JSDocParser {
86888689
export function parseJSDocTypeExpressionForTests(content: string, start: number | undefined, length: number | undefined): { jsDocTypeExpression: JSDocTypeExpression; diagnostics: Diagnostic[]; } | undefined {
8689-
initializeState("file.js", content, ScriptTarget.Latest, /*syntaxCursor*/ undefined, ScriptKind.JS, /*skipNonSemanticJSDoc*/ false);
8690+
initializeState("file.js", content, ScriptTarget.Latest, /*syntaxCursor*/ undefined, ScriptKind.JS, JSDocParsingMode.ParseAll);
86908691
scanner.setText(content, start, length);
86918692
currentToken = scanner.scan();
86928693
const jsDocTypeExpression = parseJSDocTypeExpression();
@@ -8736,7 +8737,7 @@ namespace Parser {
87368737
}
87378738

87388739
export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { jsDoc: JSDoc; diagnostics: Diagnostic[]; } | undefined {
8739-
initializeState("", content, ScriptTarget.Latest, /*syntaxCursor*/ undefined, ScriptKind.JS, /*skipNonSemanticJSDoc*/ false);
8740+
initializeState("", content, ScriptTarget.Latest, /*syntaxCursor*/ undefined, ScriptKind.JS, JSDocParsingMode.ParseAll);
87408741
const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length));
87418742

87428743
const sourceFile = { languageVariant: LanguageVariant.Standard, text: content } as SourceFile;
@@ -9804,7 +9805,7 @@ namespace IncrementalParser {
98049805
if (sourceFile.statements.length === 0) {
98059806
// If we don't have any statements in the current source file, then there's no real
98069807
// way to incrementally parse. So just do a full parse instead.
9807-
return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator, sourceFile.skipNonSemanticJSDoc);
9808+
return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator, sourceFile.jsDocParsingMode);
98089809
}
98099810

98109811
// Make sure we're not trying to incrementally update a source file more than once. Once
@@ -9867,7 +9868,7 @@ namespace IncrementalParser {
98679868
// inconsistent tree. Setting the parents on the new tree should be very fast. We
98689869
// will immediately bail out of walking any subtrees when we can see that their parents
98699870
// are already correct.
9870-
const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator, sourceFile.skipNonSemanticJSDoc);
9871+
const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator, sourceFile.jsDocParsingMode);
98719872
result.commentDirectives = getNewCommentDirectives(
98729873
sourceFile.commentDirectives,
98739874
result.commentDirectives,

src/compiler/program.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,6 @@ export function createGetSourceFile(
399399
readFile: ProgramHost<any>["readFile"],
400400
getCompilerOptions: () => CompilerOptions,
401401
setParentNodes: boolean | undefined,
402-
skipNonSemanticJSDocParsing: boolean | undefined,
403402
): CompilerHost["getSourceFile"] {
404403
return (fileName, languageVersionOrOptions, onError) => {
405404
let text: string | undefined;
@@ -415,7 +414,7 @@ export function createGetSourceFile(
415414
}
416415
text = "";
417416
}
418-
return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, setParentNodes, /*scriptKind*/ undefined, skipNonSemanticJSDocParsing) : undefined;
417+
return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, setParentNodes) : undefined;
419418
};
420419
}
421420

@@ -456,7 +455,6 @@ export function createWriteFileMeasuringIO(
456455
export function createCompilerHostWorker(
457456
options: CompilerOptions,
458457
setParentNodes?: boolean,
459-
skipNonSemanticJSDocParsing?: boolean,
460458
system: System = sys,
461459
): CompilerHost {
462460
const existingDirectories = new Map<string, boolean>();
@@ -479,7 +477,7 @@ export function createCompilerHostWorker(
479477
const newLine = getNewLineCharacter(options);
480478
const realpath = system.realpath && ((path: string) => system.realpath!(path));
481479
const compilerHost: CompilerHost = {
482-
getSourceFile: createGetSourceFile(fileName => compilerHost.readFile(fileName), () => options, setParentNodes, skipNonSemanticJSDocParsing),
480+
getSourceFile: createGetSourceFile(fileName => compilerHost.readFile(fileName), () => options, setParentNodes),
483481
getDefaultLibLocation,
484482
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
485483
writeFile: createWriteFileMeasuringIO(
@@ -3515,8 +3513,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
35153513
const languageVersion = getEmitScriptTarget(options);
35163514
const setExternalModuleIndicator = getSetExternalModuleIndicator(options);
35173515
return typeof result === "object" ?
3518-
{ ...result, languageVersion, setExternalModuleIndicator } :
3519-
{ languageVersion, impliedNodeFormat: result, setExternalModuleIndicator };
3516+
{ ...result, languageVersion, setExternalModuleIndicator, jsDocParsingMode: host.jsDocParsingMode } :
3517+
{ languageVersion, impliedNodeFormat: result, setExternalModuleIndicator, jsDocParsingMode: host.jsDocParsingMode };
35203518
}
35213519

35223520
function findSourceFileWorker(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined {

src/compiler/scanner.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
DiagnosticMessage,
1313
Diagnostics,
1414
identity,
15+
JSDocParsingMode,
1516
JSDocSyntaxKind,
1617
JsxTokenSyntaxKind,
1718
KeywordSyntaxKind,
@@ -21,6 +22,7 @@ import {
2122
parsePseudoBigInt,
2223
positionIsSynthesized,
2324
PunctuationOrKeywordSyntaxKind,
25+
ScriptKind,
2426
ScriptTarget,
2527
SourceFileLike,
2628
SyntaxKind,
@@ -95,6 +97,8 @@ export interface Scanner {
9597
setOnError(onError: ErrorCallback | undefined): void;
9698
setScriptTarget(scriptTarget: ScriptTarget): void;
9799
setLanguageVariant(variant: LanguageVariant): void;
100+
setScriptKind(scriptKind: ScriptKind): void;
101+
setJSDocParsingMode(kind: JSDocParsingMode): void;
98102
/** @deprecated use {@link resetTokenState} */
99103
setTextPos(textPos: number): void;
100104
resetTokenState(pos: number): void;
@@ -114,8 +118,6 @@ export interface Scanner {
114118
// callback returns something truthy, then the scanner state is not rolled back. The result
115119
// of invoking the callback is returned from this function.
116120
tryScan<T>(callback: () => T): T;
117-
/** @internal */
118-
setSkipNonSemanticJSDoc(skip: boolean): void;
119121
}
120122

121123
/** @internal */
@@ -345,10 +347,7 @@ const commentDirectiveRegExSingleLine = /^\/\/\/?\s*@(ts-expect-error|ts-ignore)
345347
*/
346348
const commentDirectiveRegExMultiLine = /^(?:\/|\*)*\s*@(ts-expect-error|ts-ignore)/;
347349

348-
/**
349-
* Test for whether a comment contains a JSDoc tag needed by the checker when run in tsc.
350-
*/
351-
const semanticJSDocTagRegEx = /@(?:see|link)/i;
350+
const jsDocSeeOrLink = /@(?:see|link)/i;
352351

353352
function lookupInUnicodeMap(code: number, map: readonly number[]): boolean {
354353
// Bail out quickly if it couldn't possibly be in the map.
@@ -1008,7 +1007,8 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
10081007
var commentDirectives: CommentDirective[] | undefined;
10091008
var inJSDocType = 0;
10101009

1011-
var skipNonSemanticJSDoc = false;
1010+
var scriptKind = ScriptKind.Unknown;
1011+
var jsDocParsingMode = JSDocParsingMode.ParseAll;
10121012

10131013
setText(text, start, length);
10141014

@@ -1054,14 +1054,15 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
10541054
setText,
10551055
setScriptTarget,
10561056
setLanguageVariant,
1057+
setScriptKind,
1058+
setJSDocParsingMode,
10571059
setOnError,
10581060
resetTokenState,
10591061
setTextPos: resetTokenState,
10601062
setInJSDocType,
10611063
tryScan,
10621064
lookAhead,
10631065
scanRange,
1064-
setSkipNonSemanticJSDoc,
10651066
};
10661067
/* eslint-enable no-var */
10671068

@@ -2003,7 +2004,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
20032004
}
20042005
}
20052006

2006-
if (isJSDoc && (!skipNonSemanticJSDoc || semanticJSDocTagRegEx.test(text.slice(fullStartPos, pos)))) {
2007+
if (isJSDoc && shouldParseJSDoc()) {
20072008
tokenFlags |= TokenFlags.PrecedingJSDocComment;
20082009
}
20092010

@@ -2288,6 +2289,28 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
22882289
}
22892290
}
22902291

2292+
function shouldParseJSDoc() {
2293+
switch (jsDocParsingMode) {
2294+
case JSDocParsingMode.ParseAll:
2295+
return true;
2296+
case JSDocParsingMode.ParseNone:
2297+
return false;
2298+
}
2299+
2300+
if (scriptKind !== ScriptKind.TS && scriptKind !== ScriptKind.TSX) {
2301+
// If outside of TS, we need JSDoc to get any type info.
2302+
return true;
2303+
}
2304+
2305+
if (jsDocParsingMode === JSDocParsingMode.ParseForTypeInfo) {
2306+
// If we're in TS, but we don't need to produce reliable errors,
2307+
// we don't need to parse to find @see or @link.
2308+
return false;
2309+
}
2310+
2311+
return jsDocSeeOrLink.test(text.slice(fullStartPos, pos));
2312+
}
2313+
22912314
function reScanInvalidIdentifier(): SyntaxKind {
22922315
Debug.assert(token === SyntaxKind.Unknown, "'reScanInvalidIdentifier' should only be called when the current token is 'SyntaxKind.Unknown'.");
22932316
pos = tokenStart = fullStartPos;
@@ -2788,8 +2811,12 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
27882811
languageVariant = variant;
27892812
}
27902813

2791-
function setSkipNonSemanticJSDoc(skip: boolean) {
2792-
skipNonSemanticJSDoc = skip;
2814+
function setScriptKind(kind: ScriptKind) {
2815+
scriptKind = kind;
2816+
}
2817+
2818+
function setJSDocParsingMode(kind: JSDocParsingMode) {
2819+
jsDocParsingMode = kind;
27932820
}
27942821

27952822
function resetTokenState(position: number) {

0 commit comments

Comments
 (0)