diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 629063de29808..4d942744ac481 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -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 */ @@ -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, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 838c4fd0afc3f..52baa7d932f25 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0baf8b65bc412..34e3210f8948a 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -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; @@ -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(); @@ -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[]; @@ -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); @@ -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); @@ -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); @@ -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. @@ -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(); @@ -1665,6 +1670,7 @@ namespace Parser { languageVersion = _languageVersion; syntaxCursor = _syntaxCursor; scriptKind = _scriptKind; + skipJSDoc = _skipJSDoc; languageVariant = getLanguageVariant(_scriptKind); parseDiagnostics = []; @@ -1762,10 +1768,17 @@ namespace Parser { return hasJSDoc ? addJSDocComment(node) : node; } + const seeLink = /@(?:see|link)/; + function shouldParseJSDoc(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(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)); if (jsDoc.length) node.jsDoc = jsDoc; if (hasDeprecatedTag) { hasDeprecatedTag = false; @@ -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(); @@ -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; @@ -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 @@ -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, diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 766d0292a42db..5becea0d2d5e0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -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; }; } diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index 572306483c438..56c33a444f909 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -158,6 +158,7 @@ export interface BuildOptions { /** @internal */ locale?: string; /** @internal */ generateCpuProfile?: string; /** @internal */ generateTrace?: string; + skipJSDoc?: boolean; [option: string]: CompilerOptionsValue | undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 87a72ea860f3b..9c908785e893b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7095,6 +7095,7 @@ export interface CompilerOptions { resolvePackageJsonImports?: boolean; rootDir?: string; rootDirs?: string[]; + skipJSDoc?: boolean; skipLibCheck?: boolean; skipDefaultLibCheck?: boolean; sourceMap?: boolean; diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index ce8b356b685d9..7e557ae9dd06d 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -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);