Skip to content

Skip parsing JSDoc in .ts files #52466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,13 @@ export const commonOptionsWithBuild: CommandLineOption[] = [
description: Diagnostics.Set_the_language_of_the_messaging_from_TypeScript_This_does_not_affect_emit,
defaultValueDescription: Diagnostics.Platform_specific
},
{
name: "skipJSDoc",
type: "boolean",
category: Diagnostics.Completeness,
description: Diagnostics.Skip_parsing_JSDoc_comments_in_ts_files,
defaultValueDescription: false,
},
];

/** @internal */
Expand Down Expand Up @@ -803,7 +810,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
type: "boolean",
// Though this affects semantic diagnostics, affectsSemanticDiagnostics is not set here
// The value of each strictFlag depends on own strictFlag value or this and never accessed directly.
// But we need to store `strict` in builf info, even though it won't be examined directly, so that the
// But we need to store `strict` in build info, even though it won't be examined directly, so that the
// flags it controls (e.g. `strictNullChecks`) will be retrieved correctly
affectsBuildInfo: true,
showInSimplifiedHelpView: true,
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5967,6 +5967,10 @@
"category": "Message",
"code": 6695
},
"Skip parsing JSDoc comments in .ts files": {
"category": "Message",
"code": 6696
},
"Check that the arguments for 'bind', 'call', and 'apply' methods match the original function.": {
"category": "Message",
"code": 6697
Expand Down
41 changes: 27 additions & 14 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1309,13 +1309,17 @@ export interface CreateSourceFileOptions {
setExternalModuleIndicator?: (file: SourceFile) => void;
/** @internal */ packageJsonLocations?: readonly string[];
/** @internal */ packageJsonScope?: PackageJsonInfo;
/**
* Skip parsing JSDoc in .ts files to speed up parsing.
*/
skipJSDoc?: boolean;
}

function setExternalModuleIndicator(sourceFile: SourceFile) {
sourceFile.externalModuleIndicator = isFileProbablyExternalModule(sourceFile);
}

export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind): SourceFile {
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind, skipJSDoc = false): SourceFile {
tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true);
performance.mark("beforeParse");
let result: SourceFile;
Expand All @@ -1324,17 +1328,17 @@ export function createSourceFile(fileName: string, sourceText: string, languageV
const {
languageVersion,
setExternalModuleIndicator: overrideSetExternalModuleIndicator,
impliedNodeFormat: format
impliedNodeFormat: format,
} = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions : ({ languageVersion: languageVersionOrOptions } as CreateSourceFileOptions);
if (languageVersion === ScriptTarget.JSON) {
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON, noop);
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, skipJSDoc, ScriptKind.JSON, noop);
}
else {
const setIndicator = format === undefined ? overrideSetExternalModuleIndicator : (file: SourceFile) => {
file.impliedNodeFormat = format;
return (overrideSetExternalModuleIndicator || setExternalModuleIndicator)(file);
};
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind, setIndicator);
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, skipJSDoc, scriptKind, setIndicator);
}
perfLogger.logStopParseSourceFile();

Expand Down Expand Up @@ -1436,6 +1440,7 @@ namespace Parser {
let sourceText: string;
let languageVersion: ScriptTarget;
let scriptKind: ScriptKind;
let skipJSDoc: boolean;
let languageVariant: LanguageVariant;
let parseDiagnostics: DiagnosticWithDetachedLocation[];
let jsDocDiagnostics: DiagnosticWithDetachedLocation[];
Expand Down Expand Up @@ -1530,7 +1535,7 @@ namespace Parser {
// attached to the EOF token.
let parseErrorBeforeNextFinishedNode = false;

export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind, setExternalModuleIndicatorOverride?: (file: SourceFile) => void): SourceFile {
export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, skipJSDoc = false, scriptKind?: ScriptKind, setExternalModuleIndicatorOverride?: (file: SourceFile) => void): SourceFile {
scriptKind = ensureScriptKind(fileName, scriptKind);
if (scriptKind === ScriptKind.JSON) {
const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes);
Expand All @@ -1544,7 +1549,7 @@ namespace Parser {
return result;
}

initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind);
initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind, skipJSDoc);

const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind, setExternalModuleIndicatorOverride || setExternalModuleIndicator);

Expand All @@ -1555,7 +1560,7 @@ namespace Parser {

export function parseIsolatedEntityName(content: string, languageVersion: ScriptTarget): EntityName | undefined {
// Choice of `isDeclarationFile` should be arbitrary
initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS);
initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS, false);
// Prime the scanner.
nextToken();
const entityName = parseEntityName(/*allowReservedWords*/ true);
Expand All @@ -1565,7 +1570,7 @@ namespace Parser {
}

export function parseJsonText(fileName: string, sourceText: string, languageVersion: ScriptTarget = ScriptTarget.ES2015, syntaxCursor?: IncrementalParser.SyntaxCursor, setParentNodes = false): JsonSourceFile {
initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON);
initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON, false);
sourceFlags = contextFlags;

// Prime the scanner.
Expand Down Expand Up @@ -1653,7 +1658,7 @@ namespace Parser {
return result;
}

function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind) {
function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind, _skipJSDoc: boolean) {
NodeConstructor = objectAllocator.getNodeConstructor();
TokenConstructor = objectAllocator.getTokenConstructor();
IdentifierConstructor = objectAllocator.getIdentifierConstructor();
Expand All @@ -1665,6 +1670,7 @@ namespace Parser {
languageVersion = _languageVersion;
syntaxCursor = _syntaxCursor;
scriptKind = _scriptKind;
skipJSDoc = _skipJSDoc;
languageVariant = getLanguageVariant(_scriptKind);

parseDiagnostics = [];
Expand Down Expand Up @@ -1762,10 +1768,17 @@ namespace Parser {
return hasJSDoc ? addJSDocComment(node) : node;
}

const seeLink = /@(?:see|link)/;
function shouldParseJSDoc<T extends HasJSDoc>(node: T, comment: ts.CommentRange) {
if (!skipJSDoc) return true;
if (node.flags & NodeFlags.JavaScriptFile) return true;
if (seeLink.test(sourceText.slice(comment.pos, comment.end))) return true;
}

let hasDeprecatedTag = false;
function addJSDocComment<T extends HasJSDoc>(node: T): T {
Debug.assert(!node.jsDoc); // Should only be called once per node
const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => shouldParseJSDoc(node, comment) && JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider having the scanner check for @link and @see in the code in scan() that already checks for JSDoc and sets the TokenFlags.PrecedingJSDocComment flag. This could either be a new flag, or you could simply say that in .ts files you only set the TokenFlags.PrecedingJSDocComment when there are @link and @see tags. Then withJSDoc would completely skip over this additional scanning which should save you even more time.

if (jsDoc.length) node.jsDoc = jsDoc;
if (hasDeprecatedTag) {
hasDeprecatedTag = false;
Expand Down Expand Up @@ -8409,7 +8422,7 @@ namespace Parser {

export namespace JSDocParser {
export function parseJSDocTypeExpressionForTests(content: string, start: number | undefined, length: number | undefined): { jsDocTypeExpression: JSDocTypeExpression, diagnostics: Diagnostic[] } | undefined {
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS);
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS, false);
scanner.setText(content, start, length);
currentToken = scanner.scan();
const jsDocTypeExpression = parseJSDocTypeExpression();
Expand Down Expand Up @@ -8459,7 +8472,7 @@ namespace Parser {
}

export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { jsDoc: JSDoc, diagnostics: Diagnostic[] } | undefined {
initializeState("", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS);
initializeState("", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS, false);
const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length));

const sourceFile = { languageVariant: LanguageVariant.Standard, text: content } as SourceFile;
Expand Down Expand Up @@ -9493,7 +9506,7 @@ namespace IncrementalParser {
if (sourceFile.statements.length === 0) {
// If we don't have any statements in the current source file, then there's no real
// way to incrementally parse. So just do a full parse instead.
return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator);
return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, /*skipJSDoc*/ false, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator);
}

// Make sure we're not trying to incrementally update a source file more than once. Once
Expand Down Expand Up @@ -9557,7 +9570,7 @@ namespace IncrementalParser {
// inconsistent tree. Setting the parents on the new tree should be very fast. We
// will immediately bail out of walking any subtrees when we can see that their parents
// are already correct.
const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator);
const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, /*skipJSDoc*/ false, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator);
result.commentDirectives = getNewCommentDirectives(
sourceFile.commentDirectives,
result.commentDirectives,
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ export function createGetSourceFile(
}
text = "";
}
return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, setParentNodes) : undefined;
return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, setParentNodes, /*scriptKind*/ undefined, getCompilerOptions().skipJSDoc) : undefined;
};
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export interface BuildOptions {
/** @internal */ locale?: string;
/** @internal */ generateCpuProfile?: string;
/** @internal */ generateTrace?: string;
skipJSDoc?: boolean;

[option: string]: CompilerOptionsValue | undefined;
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7095,6 +7095,7 @@ export interface CompilerOptions {
resolvePackageJsonImports?: boolean;
rootDir?: string;
rootDirs?: string[];
skipJSDoc?: boolean;
skipLibCheck?: boolean;
skipDefaultLibCheck?: boolean;
sourceMap?: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/tsc/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ if (ts.sys.setBlocking) {
ts.sys.setBlocking();
}

ts.sys.args.push("--skipJSDoc");
ts.executeCommandLine(ts.sys, ts.noop, ts.sys.args);