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(utils): removed TRuleListener generic from the createRule #5036

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
27 changes: 27 additions & 0 deletions docs/linting/typed-linting/MONOREPOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,33 @@ module.exports = {
};
```

### Wide globs in `parserOptions.project`

Using wide globs `**` in your `parserOptions.project` may degrade linting performance.
Instead of globs that use `**` to recursively check all folders, prefer paths that use a single `*` at a time.

```js title=".eslintrc.js"
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
// Remove this line
project: ['./tsconfig.eslint.json', './**/tsconfig.json'],
// Add this line
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
},
plugins: ['@typescript-eslint'],
root: true,
};
```

See [Glob pattern in parser's option "project" slows down linting](https://github.com/typescript-eslint/typescript-eslint/issues/2611) for more details.

### Important note regarding large (> 10) multi-package monorepos

We've had reports that for sufficiently large and/or interdependent projects, you may run into OOMs using this approach.
Expand Down
2 changes: 2 additions & 0 deletions packages/parser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ This option allows you to provide a path to your project's `tsconfig.json`. **Th

- If you use project references, TypeScript will not automatically use project references to resolve files. This means that you will have to add each referenced tsconfig to the `project` field either separately, or via a glob.

- Note that using wide globs `**` in your `parserOptions.project` may cause performance implications. Instead of globs that use `**` to recursively check all folders, prefer paths that use a single `*` at a time. For more info see [#2611](https://github.com/typescript-eslint/typescript-eslint/issues/2611).

- TypeScript will ignore files with duplicate filenames in the same folder (for example, `src/file.ts` and `src/file.js`). TypeScript purposely ignore all but one of the files, only keeping the one file with the highest priority extension (the extension priority order (from highest to lowest) is `.ts`, `.tsx`, `.d.ts`, `.js`, `.jsx`). For more info see #955.

- Note that if this setting is specified and `createDefaultProgram` is not, you must only lint files that are included in the projects as defined by the provided `tsconfig.json` files. If your existing configuration does not include all of the files you would like to lint, you can create a separate `tsconfig.eslint.json` as follows:
Expand Down
18 changes: 9 additions & 9 deletions packages/typescript-estree/src/ast-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import type { ASTMaps } from './convert';
import { Converter, convertError } from './convert';
import { convertComments } from './convert-comments';
import { convertTokens } from './node-utils';
import type { Extra } from './parser-options';
import type { ParseSettings } from './parseSettings';
import { simpleTraverse } from './simple-traverse';
import type { TSESTree } from './ts-estree';

export function astConverter(
ast: SourceFile,
extra: Extra,
parseSettings: ParseSettings,
shouldPreserveNodeMaps: boolean,
): { estree: TSESTree.Program; astMaps: ASTMaps } {
/**
Expand All @@ -26,7 +26,7 @@ export function astConverter(
* Recursively convert the TypeScript AST into an ESTree-compatible AST
*/
const instance = new Converter(ast, {
errorOnUnknownASTType: extra.errorOnUnknownASTType || false,
errorOnUnknownASTType: parseSettings.errorOnUnknownASTType || false,
shouldPreserveNodeMaps,
});

Expand All @@ -35,15 +35,15 @@ export function astConverter(
/**
* Optionally remove range and loc if specified
*/
if (!extra.range || !extra.loc) {
if (!parseSettings.range || !parseSettings.loc) {
simpleTraverse(estree, {
enter: node => {
if (!extra.range) {
if (!parseSettings.range) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- TS 4.0 made this an error because the types aren't optional
// @ts-expect-error
delete node.range;
}
if (!extra.loc) {
if (!parseSettings.loc) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- TS 4.0 made this an error because the types aren't optional
// @ts-expect-error
delete node.loc;
Expand All @@ -55,15 +55,15 @@ export function astConverter(
/**
* Optionally convert and include all tokens in the AST
*/
if (extra.tokens) {
if (parseSettings.tokens) {
estree.tokens = convertTokens(ast);
}

/**
* Optionally convert and include all comments in the AST
*/
if (extra.comment) {
estree.comments = convertComments(ast, extra.code);
if (parseSettings.comment) {
estree.comments = convertComments(ast, parseSettings.code);
}

const astMaps = instance.getASTMaps();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import debug from 'debug';
import path from 'path';
import * as ts from 'typescript';

import type { Extra } from '../parser-options';
import type { ASTAndProgram, CanonicalPath } from './shared';
import type { ParseSettings } from '../parseSettings';
import type { ASTAndProgram } from './shared';
import {
createDefaultCompilerOptionsFromExtra,
getModuleResolver,
Expand All @@ -12,27 +12,26 @@ import {
const log = debug('typescript-eslint:typescript-estree:createDefaultProgram');

/**
* @param code The code of the file being linted
* @param extra The config object
* @param extra.tsconfigRootDir The root directory for relative tsconfig paths
* @param extra.projects Provided tsconfig paths
* @param parseSettings Internal settings for parsing the file
* @returns If found, returns the source file corresponding to the code and the containing program
*/
function createDefaultProgram(
code: string,
extra: Extra,
parseSettings: ParseSettings,
): ASTAndProgram | undefined {
log('Getting default program for: %s', extra.filePath || 'unnamed file');
log(
'Getting default program for: %s',
parseSettings.filePath || 'unnamed file',
);

if (!extra.projects || extra.projects.length !== 1) {
if (parseSettings.projects?.length !== 1) {
return undefined;
}

const tsconfigPath: CanonicalPath = extra.projects[0];
const tsconfigPath = parseSettings.projects[0];

const commandLine = ts.getParsedCommandLineOfConfigFile(
tsconfigPath,
createDefaultCompilerOptionsFromExtra(extra),
createDefaultCompilerOptionsFromExtra(parseSettings),
{ ...ts.sys, onUnRecoverableConfigFileDiagnostic: () => {} },
);

Expand All @@ -45,24 +44,24 @@ function createDefaultProgram(
/* setParentNodes */ true,
);

if (extra.moduleResolver) {
if (parseSettings.moduleResolver) {
compilerHost.resolveModuleNames = getModuleResolver(
extra.moduleResolver,
parseSettings.moduleResolver,
).resolveModuleNames;
}

const oldReadFile = compilerHost.readFile;
compilerHost.readFile = (fileName: string): string | undefined =>
path.normalize(fileName) === path.normalize(extra.filePath)
? code
path.normalize(fileName) === path.normalize(parseSettings.filePath)
? parseSettings.code
: oldReadFile(fileName);

const program = ts.createProgram(
[extra.filePath],
[parseSettings.filePath],
commandLine.options,
compilerHost,
);
const ast = program.getSourceFile(extra.filePath);
const ast = program.getSourceFile(parseSettings.filePath);

return ast && { ast, program };
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import debug from 'debug';
import * as ts from 'typescript';

import type { Extra } from '../parser-options';
import type { ParseSettings } from '../parseSettings';
import { getScriptKind } from './getScriptKind';
import type { ASTAndProgram } from './shared';
import { createDefaultCompilerOptionsFromExtra } from './shared';
Expand All @@ -12,19 +12,19 @@ const log = debug('typescript-eslint:typescript-estree:createIsolatedProgram');
* @param code The code of the file being linted
* @returns Returns a new source file and program corresponding to the linted code
*/
function createIsolatedProgram(code: string, extra: Extra): ASTAndProgram {
function createIsolatedProgram(parseSettings: ParseSettings): ASTAndProgram {
log(
'Getting isolated program in %s mode for: %s',
extra.jsx ? 'TSX' : 'TS',
extra.filePath,
parseSettings.jsx ? 'TSX' : 'TS',
parseSettings.filePath,
);

const compilerHost: ts.CompilerHost = {
fileExists() {
return true;
},
getCanonicalFileName() {
return extra.filePath;
return parseSettings.filePath;
},
getCurrentDirectory() {
return '';
Expand All @@ -43,10 +43,10 @@ function createIsolatedProgram(code: string, extra: Extra): ASTAndProgram {
getSourceFile(filename: string) {
return ts.createSourceFile(
filename,
code,
parseSettings.code,
ts.ScriptTarget.Latest,
/* setParentNodes */ true,
getScriptKind(extra.filePath, extra.jsx),
getScriptKind(parseSettings.filePath, parseSettings.jsx),
);
},
readFile() {
Expand All @@ -61,17 +61,17 @@ function createIsolatedProgram(code: string, extra: Extra): ASTAndProgram {
};

const program = ts.createProgram(
[extra.filePath],
[parseSettings.filePath],
{
noResolve: true,
target: ts.ScriptTarget.Latest,
jsx: extra.jsx ? ts.JsxEmit.Preserve : undefined,
...createDefaultCompilerOptionsFromExtra(extra),
jsx: parseSettings.jsx ? ts.JsxEmit.Preserve : undefined,
...createDefaultCompilerOptionsFromExtra(parseSettings),
},
compilerHost,
);

const ast = program.getSourceFile(extra.filePath);
const ast = program.getSourceFile(parseSettings.filePath);
if (!ast) {
throw new Error(
'Expected an ast to be returned for the single-file isolated program.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path';
import * as ts from 'typescript';

import { firstDefined } from '../node-utils';
import type { Extra } from '../parser-options';
import type { ParseSettings } from '../parseSettings';
import { getProgramsForProjects } from './createWatchProgram';
import type { ASTAndProgram } from './shared';
import { getAstFromProgram } from './shared';
Expand All @@ -22,45 +22,37 @@ const DEFAULT_EXTRA_FILE_EXTENSIONS = [
] as readonly string[];

/**
* @param code The code of the file being linted
* @param createDefaultProgram True if the default program should be created
* @param extra The config object
* @returns If found, returns the source file corresponding to the code and the containing program
* @param parseSettings Internal settings for parsing the file
* @returns If found, the source file corresponding to the code and the containing program
*/
function createProjectProgram(
code: string,
createDefaultProgram: boolean,
extra: Extra,
parseSettings: ParseSettings,
): ASTAndProgram | undefined {
log('Creating project program for: %s', extra.filePath);
log('Creating project program for: %s', parseSettings.filePath);

const programsForProjects = getProgramsForProjects(
code,
extra.filePath,
extra,
);
const programsForProjects = getProgramsForProjects(parseSettings);
const astAndProgram = firstDefined(programsForProjects, currentProgram =>
getAstFromProgram(currentProgram, extra),
getAstFromProgram(currentProgram, parseSettings),
);

// The file was either matched within the tsconfig, or we allow creating a default program
if (astAndProgram || createDefaultProgram) {
if (astAndProgram || parseSettings.createDefaultProgram) {
return astAndProgram;
}

const describeFilePath = (filePath: string): string => {
const relative = path.relative(
extra.tsconfigRootDir || process.cwd(),
parseSettings.tsconfigRootDir || process.cwd(),
filePath,
);
if (extra.tsconfigRootDir) {
if (parseSettings.tsconfigRootDir) {
return `<tsconfigRootDir>/${relative}`;
}
return `<cwd>/${relative}`;
};

const describedFilePath = describeFilePath(extra.filePath);
const relativeProjects = extra.projects.map(describeFilePath);
const describedFilePath = describeFilePath(parseSettings.filePath);
const relativeProjects = parseSettings.projects.map(describeFilePath);
const describedPrograms =
relativeProjects.length === 1
? relativeProjects[0]
Expand All @@ -70,7 +62,7 @@ function createProjectProgram(
];
let hasMatchedAnError = false;

const extraFileExtensions = extra.extraFileExtensions || [];
const extraFileExtensions = parseSettings.extraFileExtensions || [];

extraFileExtensions.forEach(extraExtension => {
if (!extraExtension.startsWith('.')) {
Expand All @@ -85,7 +77,7 @@ function createProjectProgram(
}
});

const fileExtension = path.extname(extra.filePath);
const fileExtension = path.extname(parseSettings.filePath);
if (!DEFAULT_EXTRA_FILE_EXTENSIONS.includes(fileExtension)) {
const nonStandardExt = `The extension for the file (\`${fileExtension}\`) is non-standard`;
if (extraFileExtensions.length > 0) {
Expand All @@ -105,7 +97,7 @@ function createProjectProgram(

if (!hasMatchedAnError) {
const [describedInclusions, describedSpecifiers] =
extra.projects.length === 1
parseSettings.projects.length === 1
? ['that TSConfig does not', 'that TSConfig']
: ['none of those TSConfigs', 'one of those TSConfigs'];
errorLines.push(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import debug from 'debug';
import * as ts from 'typescript';

import type { Extra } from '../parser-options';
import type { ParseSettings } from '../parseSettings';
import { getScriptKind } from './getScriptKind';

const log = debug('typescript-eslint:typescript-estree:createSourceFile');

function createSourceFile(code: string, extra: Extra): ts.SourceFile {
function createSourceFile(parseSettings: ParseSettings): ts.SourceFile {
log(
'Getting AST without type information in %s mode for: %s',
extra.jsx ? 'TSX' : 'TS',
extra.filePath,
parseSettings.jsx ? 'TSX' : 'TS',
parseSettings.filePath,
);

return ts.createSourceFile(
extra.filePath,
code,
parseSettings.filePath,
parseSettings.code,
ts.ScriptTarget.Latest,
/* setParentNodes */ true,
getScriptKind(extra.filePath, extra.jsx),
getScriptKind(parseSettings.filePath, parseSettings.jsx),
);
}

Expand Down