Skip to content
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

fix: default to parse all JSDoc and provide options to configure it #7999

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 18 additions & 2 deletions docs/packages/Parser.mdx
Expand Up @@ -40,10 +40,11 @@ interface ParserOptions {
ecmaVersion?: number | 'latest';
emitDecoratorMetadata?: boolean;
extraFileExtensions?: string[];
jsDocParsingMode?: 'all' | 'none' | 'type-info';
jsxFragmentName?: string | null;
jsxPragma?: string | null;
lib?: string[];
program?: import('typescript').Program;
programs?: import('typescript').Program;
project?: string | string[] | true;
projectFolderIgnoreList?: string[];
tsconfigRootDir?: string;
Expand Down Expand Up @@ -118,6 +119,21 @@ This option allows you to provide one or more additional file extensions which s
The default extensions are `['.js', '.mjs', '.cjs', '.jsx', '.ts', '.mts', '.cts', '.tsx']`.
Add extensions starting with `.`, followed by the file extension. E.g. for a `.vue` file use `"extraFileExtensions": [".vue"]`.

### `jsDocParsingMode`

> Default if `parserOptions.project` is set, then `'all'`, otherwise `'none'`

When TS parses a file it will also parse JSDoc comments into the AST - which can then be consumed by lint rules.
If you are using TypeScript version >=5.3 then this option can be used as a performance optimization.

The valid values for this rule are:

- `'all'` - parse all JSDoc comments, always.
- `'none'` - parse no JSDoc comments, ever.
- `'type-info'` - parse just JSDoc comments that are required to provide correct type-info. TS will always parse JSDoc in non-TS files, but never in TS files.

If you do not use lint rules like `eslint-plugin-deprecation` that rely on TS's JSDoc tag representation, then you can set this to `'none'` to improve parser performance.

### `jsxFragmentName`

> Default `null`
Expand Down Expand Up @@ -149,7 +165,7 @@ Specifies the TypeScript `lib`s that are available. This is used by the scope an

If you provide `parserOptions.project`, you do not need to set this, as it will automatically detected from the compiler.

### `program`
### `programs`

> Default `undefined`.

Expand Down
13 changes: 13 additions & 0 deletions docs/packages/TypeScript_ESTree.mdx
Expand Up @@ -70,6 +70,18 @@ interface ParseOptions {
*/
filePath?: string;

/**
* If you are using TypeScript version >=5.3 then this option can be used as a performance optimization.
*
* The valid values for this rule are:
* - `'all'` - parse all JSDoc comments, always.
* - `'none'` - parse no JSDoc comments, ever.
* - `'type-info'` - parse just JSDoc comments that are required to provide correct type-info. TS will always parse JSDoc in non-TS files, but never in TS files.
*
* If you do not rely on JSDoc tags from the TypeScript AST, then you can safely set this to `'none'` to improve performance.
*/
jsDocParsingMode?: JSDocParsingMode;

/**
* Enable parsing of JSX.
* For more details, see https://www.typescriptlang.org/docs/handbook/jsx.html
Expand Down Expand Up @@ -111,6 +123,7 @@ interface ParseOptions {
const PARSE_DEFAULT_OPTIONS: ParseOptions = {
comment: false,
filePath: 'estree.ts', // or 'estree.tsx', if you pass jsx: true
jsDocParsingMode: 'all',
jsx: false,
loc: false,
loggerFn: undefined,
Expand Down
7 changes: 6 additions & 1 deletion packages/types/src/parser-options.ts
Expand Up @@ -32,6 +32,9 @@ type EcmaVersion =
type SourceTypeClassic = 'module' | 'script';
type SourceType = SourceTypeClassic | 'commonjs';

type JSDocParsingMode = 'all' | 'none' | 'type-info';

// If you add publicly visible options here, make sure they're also documented in `docs/packages/Parser.mdx`
interface ParserOptions {
ecmaFeatures?: {
globalReturn?: boolean;
Expand All @@ -57,8 +60,9 @@ interface ParserOptions {
EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; // purposely undocumented for now
extraFileExtensions?: string[];
filePath?: string;
jsDocParsingMode?: JSDocParsingMode;
loc?: boolean;
program?: Program | null;
programs?: Program | null;
project?: string[] | string | true | null;
projectFolderIgnoreList?: (RegExp | string)[];
range?: boolean;
Expand All @@ -77,6 +81,7 @@ export {
CacheDurationSeconds,
DebugLevel,
EcmaVersion,
JSDocParsingMode,
ParserOptions,
SourceType,
};
Expand Up @@ -59,7 +59,7 @@ function createDefaultProgram(
[parseSettings.filePath],
{
...commandLine.options,
jsDocParsingMode: ts.JSDocParsingMode?.ParseForTypeInfo,
jsDocParsingMode: parseSettings.jsDocParsingMode,
},
compilerHost,
);
Expand Down
Expand Up @@ -65,7 +65,7 @@ function createIsolatedProgram(
const program = ts.createProgram(
[parseSettings.filePath],
{
jsDocParsingMode: ts.JSDocParsingMode?.ParseForTypeInfo,
jsDocParsingMode: parseSettings.jsDocParsingMode,
noResolve: true,
target: ts.ScriptTarget.Latest,
jsx: parseSettings.jsx ? ts.JsxEmit.Preserve : undefined,
Expand Down
Expand Up @@ -9,7 +9,9 @@ const createStubFileWatcher = (): ts.FileWatcher => ({

export type TypeScriptProjectService = ts.server.ProjectService;

export function createProjectService(): TypeScriptProjectService {
export function createProjectService(
jsDocParsingMode?: ts.JSDocParsingMode,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Preemptive check; IIRC this is public. Should this be some sort of options bag rather than a parameter?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nah this isn't public - we don't export it from either index.ts or use-at-your-own-risk.ts.

): TypeScriptProjectService {
// We import this lazily to avoid its cost for users who don't use the service
// TODO: Once we drop support for TS<5.3 we can import from "typescript" directly
const tsserver = require('typescript/lib/tsserverlibrary') as typeof ts;
Expand Down Expand Up @@ -45,6 +47,6 @@ export function createProjectService(): TypeScriptProjectService {
startGroup: doNothing,
},
session: undefined,
jsDocParsingMode: tsserver.JSDocParsingMode?.ParseForTypeInfo,
jsDocParsingMode,
});
}
Expand Up @@ -22,7 +22,7 @@ function createSourceFile(parseSettings: ParseSettings): ts.SourceFile {
parseSettings.codeFullText,
{
languageVersion: ts.ScriptTarget.Latest,
jsDocParsingMode: ts.JSDocParsingMode?.ParseNone,
jsDocParsingMode: parseSettings.jsDocParsingMode,
},
/* setParentNodes */ true,
getScriptKind(parseSettings.filePath, parseSettings.jsx),
Expand Down
Expand Up @@ -267,7 +267,7 @@ function createWatchProgram(
// eslint-disable-next-line @typescript-eslint/no-empty-function
/*reportWatchStatus*/ () => {},
) as WatchCompilerHostOfConfigFile<ts.BuilderProgram>;
watchCompilerHost.jsDocParsingMode = ts.JSDocParsingMode?.ParseForTypeInfo;
watchCompilerHost.jsDocParsingMode = parseSettings.jsDocParsingMode;

// ensure readFile reads the code being linted instead of the copy on disk
const oldReadFile = watchCompilerHost.readFile;
Expand Down
@@ -1,5 +1,5 @@
import debug from 'debug';
import type * as ts from 'typescript';
import * as ts from 'typescript';

import type { TypeScriptProjectService } from '../create-program/createProjectService';
import { createProjectService } from '../create-program/createProjectService';
Expand All @@ -23,6 +23,15 @@ const log = debug(
let TSCONFIG_MATCH_CACHE: ExpiringCache<string, string> | null;
let TSSERVER_PROJECT_SERVICE: TypeScriptProjectService | null = null;

// NOTE - we intentionally use "unnecessary" `?.` here because in TS<5.3 this enum doesn't exist
// This object exists so we can centralize these for tracking and so we don't proliferate these across the file
export const JSDocParsingMode = {
ParseAll: ts.JSDocParsingMode?.ParseAll,
ParseNone: ts.JSDocParsingMode?.ParseNone,
ParseForTypeErrors: ts.JSDocParsingMode?.ParseForTypeErrors,
ParseForTypeInfo: ts.JSDocParsingMode?.ParseForTypeInfo,
} as const;

export function createParseSettings(
code: ts.SourceFile | string,
options: Partial<TSESTreeOptions> = {},
Expand All @@ -34,6 +43,22 @@ export function createParseSettings(
? options.tsconfigRootDir
: process.cwd();
const passedLoggerFn = typeof options.loggerFn === 'function';
const jsDocParsingMode = ((): ts.JSDocParsingMode => {
switch (options.jsDocParsingMode) {
case 'all':
return JSDocParsingMode.ParseAll;

case 'none':
return JSDocParsingMode.ParseNone;

case 'type-info':
return JSDocParsingMode.ParseForTypeInfo;

default:
return JSDocParsingMode.ParseAll;
}
})();

const parseSettings: MutableParseSettings = {
allowInvalidAST: options.allowInvalidAST === true,
code,
Expand All @@ -56,7 +81,7 @@ export function createParseSettings(
process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'false') ||
(process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true' &&
options.EXPERIMENTAL_useProjectService !== false)
? (TSSERVER_PROJECT_SERVICE ??= createProjectService())
? (TSSERVER_PROJECT_SERVICE ??= createProjectService(jsDocParsingMode))
: undefined,
EXPERIMENTAL_useSourceOfProjectReferenceRedirect:
options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect === true,
Expand All @@ -71,6 +96,7 @@ export function createParseSettings(
: getFileName(options.jsx),
tsconfigRootDir,
),
jsDocParsingMode,
jsx: options.jsx === true,
loc: options.loc === true,
log:
Expand Down Expand Up @@ -136,6 +162,17 @@ export function createParseSettings(
});
}

// No type-aware linting which means that cross-file (or even same-file) JSDoc is useless
// So in this specific case we default to 'none' if no value was provided
if (
options.jsDocParsingMode == null &&
parseSettings.projects.length === 0 &&
parseSettings.programs == null &&
parseSettings.EXPERIMENTAL_projectService
bradzacher marked this conversation as resolved.
Show resolved Hide resolved
) {
parseSettings.jsDocParsingMode = JSDocParsingMode.ParseNone;
}

warnAboutTSVersion(parseSettings, passedLoggerFn);

return parseSettings;
Expand Down
11 changes: 11 additions & 0 deletions packages/typescript-estree/src/parseSettings/index.ts
Expand Up @@ -7,6 +7,12 @@ import type { CacheLike } from './ExpiringCache';

type DebugModule = 'eslint' | 'typescript-eslint' | 'typescript';

// Workaround to support new TS version features for consumers on old TS versions
declare module 'typescript' {
// Added in TypeScript 5.3
enum JSDocParsingMode {}
}

/**
* Internal settings used by the parser to run on a file.
*/
Expand Down Expand Up @@ -84,6 +90,11 @@ export interface MutableParseSettings {
*/
filePath: string;

/**
* JSDoc parsing style to pass through to TypeScript
*/
jsDocParsingMode: ts.JSDocParsingMode;

/**
* Whether parsing of JSX is enabled.
*
Expand Down
13 changes: 13 additions & 0 deletions packages/typescript-estree/src/parser-options.ts
@@ -1,6 +1,7 @@
import type {
CacheDurationSeconds,
DebugLevel,
JSDocParsingMode,
} from '@typescript-eslint/types';
import type * as ts from 'typescript';

Expand Down Expand Up @@ -45,6 +46,18 @@ interface ParseOptions {
*/
filePath?: string;

/**
* If you are using TypeScript version >=5.3 then this option can be used as a performance optimization.
*
* The valid values for this rule are:
* - `'all'` - parse all JSDoc comments, always.
* - `'none'` - parse no JSDoc comments, ever.
* - `'type-info'` - parse just JSDoc comments that are required to provide correct type-info. TS will always parse JSDoc in non-TS files, but never in TS files.
*
* If you do not rely on JSDoc tags from the TypeScript AST, then you can safely set this to `'none'` to improve performance.
*/
jsDocParsingMode?: JSDocParsingMode;

/**
* Enable parsing of JSX.
* For more details, see https://www.typescriptlang.org/docs/handbook/jsx.html
Expand Down
1 change: 1 addition & 0 deletions packages/typescript-estree/src/use-at-your-own-risk.ts
Expand Up @@ -2,6 +2,7 @@
export * from './create-program/getScriptKind';
export * from './ast-converter';
export type { ParseSettings } from './parseSettings';
export { JSDocParsingMode } from './parseSettings/createParseSettings';

// required by packages/utils/src/ts-estree.ts
export * from './getModifiers';
Expand Down
2 changes: 2 additions & 0 deletions packages/website/src/components/linter/config.ts
@@ -1,4 +1,5 @@
import type { ParseSettings } from '@typescript-eslint/typescript-estree/use-at-your-own-risk';
import { JSDocParsingMode } from '@typescript-eslint/typescript-estree/use-at-your-own-risk';
import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint';

export const PARSER_NAME = '@typescript-eslint/parser';
Expand All @@ -17,6 +18,7 @@ export const defaultParseSettings: ParseSettings = {
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false,
extraFileExtensions: [],
filePath: '',
jsDocParsingMode: JSDocParsingMode.ParseAll,
jsx: true,
loc: true,
log: console.log,
Expand Down