feat(linter): do not set eslint parserOptions.project by default (#5798)

This commit is contained in:
James Henry 2021-05-28 17:35:34 +04:00 committed by GitHub
parent 1f93ebb47a
commit a3c08a9153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 1146 additions and 158 deletions

View File

@ -1,9 +1,6 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.*?.json"
},
"env": {
"node": true
},

View File

@ -58,6 +58,14 @@ Generate JavaScript files rather than TypeScript files
Type: `string`
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### style
Alias(es): s

View File

@ -84,6 +84,14 @@ Type: `string`
The server script path to be used with next.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -82,6 +82,14 @@ Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -126,6 +126,14 @@ Type: `boolean`
Generate application with routes.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -134,6 +134,14 @@ Type: `boolean`
Generate library with routes.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -98,6 +98,14 @@ Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipBabelrc
Default: `false`

View File

@ -1022,6 +1022,11 @@
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
},
{
"name": "Nx 7 => Nx 8",
"id": "nx7-to-nx8"
@ -2076,6 +2081,11 @@
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
},
{
"name": "JavaScript and TypeScript",
"id": "js-and-ts",
@ -3084,6 +3094,11 @@
"name": "Using Nx at Enterprises",
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
}
]
}

View File

@ -58,6 +58,14 @@ Generate JavaScript files rather than TypeScript files
Type: `string`
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### style
Alias(es): s

View File

@ -84,6 +84,14 @@ Type: `string`
The server script path to be used with next.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -82,6 +82,14 @@ Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -126,6 +126,14 @@ Type: `boolean`
Generate application with routes.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -134,6 +134,14 @@ Type: `boolean`
Generate library with routes.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -98,6 +98,14 @@ Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipBabelrc
Default: `false`

View File

@ -58,6 +58,14 @@ Generate JavaScript files rather than TypeScript files
Type: `string`
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### style
Alias(es): s

View File

@ -84,6 +84,14 @@ Type: `string`
The server script path to be used with next.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -82,6 +82,14 @@ Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -126,6 +126,14 @@ Type: `boolean`
Generate application with routes.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -134,6 +134,14 @@ Type: `boolean`
Generate library with routes.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipFormat
Default: `false`

View File

@ -98,6 +98,14 @@ Type: `boolean`
Use pascal case file names.
### setParserOptionsProject
Default: `false`
Type: `boolean`
Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons.
### skipBabelrc
Default: `false`

108
docs/shared/eslint.md Normal file
View File

@ -0,0 +1,108 @@
# Using ESLint in Nx Workspaces
## Rules requiring type information
ESLint is powerful linter by itself, able to work on the syntax of your source files and assert things about based on the rules you configure. It gets even more powerful, however, when TypeScript type-checker is layered on top of it when analyzing TypeScript files, which is something that `@typescript-eslint` allows us to do.
By default, Nx sets up your ESLint configs with performance in mind - we want your linting to run as fast as possible. Because creating the necessary so called TypeScript `Program`s required to create the type-checker behind the scenes is relatively expensive compared to pure syntax analysis, you should only configure the `parserOptions.project` option in your project's `.eslintrc.json` when you need to use rules requiring type information (and you should not configure `parserOptions.project` in your workspace's root `.eslintrc.json`).
Let's take an example of an ESLint config that Nx might generate for you out of the box for a Next.js project called `tuskdesk`:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
Here we do _not_ have `parserOptions.project`, which is appropriate because we are not leveraging any rules which require type information.
If we now come in and add a rule which does require type information, for example `@typescript-eslint/await-thenable`, our config will look as follows:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
// This rule requires the TypeScript type checker to be present when it runs
"@typescript-eslint/await-thenable": "error"
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
Now if we try and run `nx lint tuskdesk` we will get an error
```
> nx run tuskdesk:lint
Linting "tuskdesk"...
Error: You have attempted to use a lint rule which requires the full TypeScript type-checker to be available, but you do not have `parserOptions.project` configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your project ESLint config `apps/tuskdesk/.eslintrc.json`
```
The solution is to update our config once more, this time to set `parserOptions.project` to appropriately point at our various tsconfig.json files which belong to our project:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
// We set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting
"parserOptions": {
"project": ["apps/tuskdesk/tsconfig.*?.json"]
},
"rules": {
"@typescript-eslint/await-thenable": "error"
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
And that's it! Now any rules requiring type information will run correctly when we run `nx lint tuskdesk`.
> NOTE: As well as adapting the path to match your project's real path, please be aware that if you apply the above to a **Next.js** application, you should change the glob pattern at the end to be `tsconfig(.*)?.json`. E.g. if `tuskdesk` had been a Next.js app, we would have written: `"project": ["apps/tuskdesk/tsconfig(.*)?.json"]`

View File

@ -1003,6 +1003,11 @@
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
},
{
"name": "Nx 7 => Nx 8",
"id": "nx7-to-nx8"
@ -2033,6 +2038,11 @@
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
},
{
"name": "JavaScript and TypeScript",
"id": "js-and-ts",
@ -3017,6 +3027,11 @@
"name": "Using Nx at Enterprises",
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
}
]
}

View File

@ -0,0 +1,108 @@
# Using ESLint in Nx Workspaces
## Rules requiring type information
ESLint is powerful linter by itself, able to work on the syntax of your source files and assert things about based on the rules you configure. It gets even more powerful, however, when TypeScript type-checker is layered on top of it when analyzing TypeScript files, which is something that `@typescript-eslint` allows us to do.
By default, Nx sets up your ESLint configs with performance in mind - we want your linting to run as fast as possible. Because creating the necessary so called TypeScript `Program`s required to create the type-checker behind the scenes is relatively expensive compared to pure syntax analysis, you should only configure the `parserOptions.project` option in your project's `.eslintrc.json` when you need to use rules requiring type information (and you should not configure `parserOptions.project` in your workspace's root `.eslintrc.json`).
Let's take an example of an ESLint config that Nx might generate for you out of the box for a Next.js project called `tuskdesk`:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
Here we do _not_ have `parserOptions.project`, which is appropriate because we are not leveraging any rules which require type information.
If we now come in and add a rule which does require type information, for example `@typescript-eslint/await-thenable`, our config will look as follows:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
// This rule requires the TypeScript type checker to be present when it runs
"@typescript-eslint/await-thenable": "error"
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
Now if we try and run `nx lint tuskdesk` we will get an error
```
> nx run tuskdesk:lint
Linting "tuskdesk"...
Error: You have attempted to use a lint rule which requires the full TypeScript type-checker to be available, but you do not have `parserOptions.project` configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your project ESLint config `apps/tuskdesk/.eslintrc.json`
```
The solution is to update our config once more, this time to set `parserOptions.project` to appropriately point at our various tsconfig.json files which belong to our project:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
// We set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting
"parserOptions": {
"project": ["apps/tuskdesk/tsconfig.*?.json"]
},
"rules": {
"@typescript-eslint/await-thenable": "error"
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
And that's it! Now any rules requiring type information will run correctly when we run `nx lint tuskdesk`.
> NOTE: As well as adapting the path to match your project's real path, please be aware that if you apply the above to a **Next.js** application, you should change the glob pattern at the end to be `tsconfig(.*)?.json`. E.g. if `tuskdesk` had been a Next.js app, we would have written: `"project": ["apps/tuskdesk/tsconfig(.*)?.json"]`

View File

@ -1048,6 +1048,11 @@
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
},
{
"name": "Nx 7 => Nx 8",
"id": "nx7-to-nx8"
@ -2122,6 +2127,11 @@
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
},
{
"name": "JavaScript and TypeScript",
"id": "js-and-ts",
@ -3146,6 +3156,11 @@
"name": "Using Nx at Enterprises",
"id": "monorepo-nx-enterprise",
"file": "shared/monorepo-nx-enterprise"
},
{
"name": "Using ESLint in Nx Workspaces",
"id": "eslint",
"file": "shared/eslint"
}
]
}

View File

@ -0,0 +1,108 @@
# Using ESLint in Nx Workspaces
## Rules requiring type information
ESLint is powerful linter by itself, able to work on the syntax of your source files and assert things about based on the rules you configure. It gets even more powerful, however, when TypeScript type-checker is layered on top of it when analyzing TypeScript files, which is something that `@typescript-eslint` allows us to do.
By default, Nx sets up your ESLint configs with performance in mind - we want your linting to run as fast as possible. Because creating the necessary so called TypeScript `Program`s required to create the type-checker behind the scenes is relatively expensive compared to pure syntax analysis, you should only configure the `parserOptions.project` option in your project's `.eslintrc.json` when you need to use rules requiring type information (and you should not configure `parserOptions.project` in your workspace's root `.eslintrc.json`).
Let's take an example of an ESLint config that Nx might generate for you out of the box for a Next.js project called `tuskdesk`:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
Here we do _not_ have `parserOptions.project`, which is appropriate because we are not leveraging any rules which require type information.
If we now come in and add a rule which does require type information, for example `@typescript-eslint/await-thenable`, our config will look as follows:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
// This rule requires the TypeScript type checker to be present when it runs
"@typescript-eslint/await-thenable": "error"
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
Now if we try and run `nx lint tuskdesk` we will get an error
```
> nx run tuskdesk:lint
Linting "tuskdesk"...
Error: You have attempted to use a lint rule which requires the full TypeScript type-checker to be available, but you do not have `parserOptions.project` configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your project ESLint config `apps/tuskdesk/.eslintrc.json`
```
The solution is to update our config once more, this time to set `parserOptions.project` to appropriately point at our various tsconfig.json files which belong to our project:
**apps/tuskdesk/.eslintrc.json**
```jsonc
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
// We set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting
"parserOptions": {
"project": ["apps/tuskdesk/tsconfig.*?.json"]
},
"rules": {
"@typescript-eslint/await-thenable": "error"
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
```
And that's it! Now any rules requiring type information will run correctly when we run `nx lint tuskdesk`.
> NOTE: As well as adapting the path to match your project's real path, please be aware that if you apply the above to a **Next.js** application, you should change the glob pattern at the end to be `tsconfig(.*)?.json`. E.g. if `tuskdesk` had been a Next.js app, we would have written: `"project": ["apps/tuskdesk/tsconfig(.*)?.json"]`

View File

@ -17,11 +17,6 @@ Object {
"files": Array [
"*.ts",
],
"parserOptions": Object {
"project": Array [
"apps/ng-app1/tsconfig.*?.json",
],
},
"rules": Object {
"@angular-eslint/component-selector": Array [
"error",

View File

@ -21,9 +21,24 @@ export function createEsLintConfiguration(
'plugin:@nrwl/nx/angular',
'plugin:@angular-eslint/template/process-inline-templates',
],
parserOptions: {
project: [`${options.projectRoot}/tsconfig.*?.json`],
},
/**
* NOTE: We no longer set parserOptions.project by default when creating new projects.
*
* We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore
* do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project,
* typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple
* parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much
* less memory intensive.
*
* In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set
* parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you
* and provide feedback to the user.
*/
parserOptions: !options.setParserOptionsProject
? undefined
: {
project: [`${options.projectRoot}/tsconfig.*?.json`],
},
rules: {
'@angular-eslint/directive-selector': [
'error',

View File

@ -2,4 +2,5 @@ export interface AddLintingGeneratorSchema {
projectName: string;
projectRoot: string;
prefix: string;
setParserOptionsProject?: boolean;
}

View File

@ -17,6 +17,11 @@
"projectRoot": {
"type": "string",
"description": "The path to the root of the selected project."
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
}
},
"additionalProperties": false,

View File

@ -332,6 +332,7 @@ Object {
"type": "attribute",
},
],
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-empty-interface": "error",
},
},
@ -659,11 +660,6 @@ Object {
"files": Array [
"*.ts",
],
"parserOptions": Object {
"project": Array [
"libs/angular-lib-1/tsconfig.*?.json",
],
},
"plugins": Array [
"@angular-eslint/eslint-plugin",
"@typescript-eslint",

View File

@ -79,7 +79,20 @@ function mockFindReportedConfiguration(_, pathToTslintJson) {
case 'tslint.json':
return exampleRootTslintJson.tslintPrintConfigResult;
case appProjectTSLintJsonPath:
return projectTslintJsonData.tslintPrintConfigResult;
return {
/**
* Add in an example of rule which requires type-checking so we can test
* that parserOptions.project is appropriately preserved in the final
* config in this case.
*/
rules: {
...projectTslintJsonData.tslintPrintConfigResult.rules,
'await-promise': {
ruleArguments: [],
ruleSeverity: 'error',
},
},
};
case libProjectTSLintJsonPath:
return projectTslintJsonData.tslintPrintConfigResult;
default:
@ -167,11 +180,18 @@ describe('convert-tslint-to-eslint', () => {
/**
* Existing tslint.json file for the app project
*/
writeJson(
host,
'apps/angular-app-1/tslint.json',
projectTslintJsonData.raw
);
writeJson(host, 'apps/angular-app-1/tslint.json', {
...projectTslintJsonData.raw,
rules: {
...projectTslintJsonData.raw.rules,
/**
* Add in an example of rule which requires type-checking so we can test
* that parserOptions.project is appropriately preserved in the final
* config in this case.
*/
'await-promise': true,
},
});
/**
* Existing tslint.json file for the lib project
*/

View File

@ -33,6 +33,12 @@ export async function conversionGenerator(
projectName,
projectRoot: projectConfig.root,
prefix: (projectConfig as any).prefix || 'app',
/**
* We set the parserOptions.project config just in case the converted config uses
* rules which require type-checking. Later in the conversion we check if it actually
* does and remove the config again if it doesn't, so that it is most efficient.
*/
setParserOptionsProject: true,
});
},
});

View File

@ -404,11 +404,6 @@ describe('app', () => {
"files": Array [
"*.ts",
],
"parserOptions": Object {
"project": Array [
"apps/my-app/tsconfig.*?.json",
],
},
"rules": Object {
"@angular-eslint/component-selector": Array [
"error",

View File

@ -1249,11 +1249,6 @@ describe('lib', () => {
"files": Array [
"*.ts",
],
"parserOptions": Object {
"project": Array [
"libs/my-lib/tsconfig.*?.json",
],
},
"rules": Object {
"@angular-eslint/component-selector": Array [
"error",

View File

@ -262,9 +262,6 @@ Object {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": "apps/e2e-app-1/tsconfig.*?.json",
},
"rules": Object {},
},
Object {

View File

@ -34,6 +34,12 @@ export async function conversionGenerator(
linter: 'eslint',
projectName,
projectRoot: projectConfig.root,
/**
* We set the parserOptions.project config just in case the converted config uses
* rules which require type-checking. Later in the conversion we check if it actually
* does and remove the config again if it doesn't, so that it is most efficient.
*/
setParserOptionsProject: true,
} as CypressProjectSchema);
},
});

View File

@ -343,9 +343,6 @@ describe('schematic:cypress-project', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": "apps/my-app-e2e/tsconfig.*?.json",
},
"rules": Object {},
},
Object {

View File

@ -90,6 +90,7 @@ export async function addLinter(host: Tree, options: CypressProjectSchema) {
eslintFilePatterns: [
`${options.projectRoot}/**/*.${options.js ? 'js' : '{js,ts}'}`,
],
setParserOptionsProject: options.setParserOptionsProject,
});
if (!options.linter || options.linter !== Linter.EsLint) {
@ -112,9 +113,24 @@ export async function addLinter(host: Tree, options: CypressProjectSchema) {
*/
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
parserOptions: {
project: `${options.projectRoot}/tsconfig.*?.json`,
},
/**
* NOTE: We no longer set parserOptions.project by default when creating new projects.
*
* We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore
* do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project,
* typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple
* parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much
* less memory intensive.
*
* In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set
* parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you
* and provide feedback to the user.
*/
parserOptions: !options.setParserOptionsProject
? undefined
: {
project: `${options.projectRoot}/tsconfig.*?.json`,
},
/**
* Having an empty rules object present makes it more obvious to the user where they would
* extend things from if they needed to

View File

@ -7,4 +7,5 @@ export interface Schema {
linter?: Linter;
js?: boolean;
skipFormat?: boolean;
setParserOptionsProject?: boolean;
}

View File

@ -40,6 +40,11 @@
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
}
},
"required": ["name"]

View File

@ -33,5 +33,9 @@ export default {
? ['plugin:@angular-eslint/recommended--extra']
: []),
],
parserOptions: {
// Unset the default value for parserOptions.project that is found in earlier versions of @angular-eslint
project: [],
},
rules: {},
};

View File

@ -53,11 +53,6 @@ describe('app', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"apps/my-node-app/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -320,11 +320,6 @@ describe('app', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"apps/my-app/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -21,7 +21,10 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
skipFormat: true,
});
const reactEslintJson = createReactEslintJson(options.projectRoot);
const reactEslintJson = createReactEslintJson(
options.projectRoot,
options.setParserOptionsProject
);
updateJson(
host,

View File

@ -8,4 +8,5 @@ export interface Schema {
unitTestRunner?: 'jest' | 'none';
e2eTestRunner?: 'cypress' | 'none';
js?: boolean;
setParserOptionsProject?: boolean;
}

View File

@ -84,6 +84,11 @@
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files",
"default": false
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
}
},
"required": ["name"]

View File

@ -40,6 +40,12 @@
"version": "11.5.0-beta.0",
"description": "Update project .eslintrc.json files to always use project level tsconfigs",
"factory": "./src/migrations/update-11-5-0/always-use-project-level-tsconfigs-with-eslint"
},
"remove-eslint-project-config-if-no-type-checking-rules": {
"cli": "nx",
"version": "12.4.0-beta.0",
"description": "Remove ESLint parserOptions.project config if no rules requiring type-checking are in use",
"factory": "./src/migrations/update-12-4-0/remove-eslint-project-config-if-no-type-checking-rules"
}
},
"packageJsonUpdates": {

View File

@ -42,7 +42,7 @@ function createValidRunBuilderOptions(
): Schema {
return {
lintFilePatterns: [],
eslintConfig: './.eslintrc.json',
eslintConfig: null,
fix: true,
cache: true,
cacheLocation: 'cacheLocation1',
@ -73,16 +73,24 @@ describe('Linter Builder', () => {
beforeEach(() => {
MockESLint.version = VALID_ESLINT_VERSION;
mockReports = [{ results: [], usedDeprecatedRules: [] }];
const projectName = 'proj';
mockContext = {
projectName: 'proj',
projectName,
root: '/root',
cwd: '/root',
workspace: {
version: 2,
projects: {},
projects: {
[projectName]: {
root: `apps/${projectName}`,
sourceRoot: `apps/${projectName}/src`,
targets: {},
},
},
},
isVerbose: false,
};
mockLint.mockImplementation(() => mockReports);
});
afterAll(() => {
@ -224,6 +232,32 @@ describe('Linter Builder', () => {
);
});
it('should intercept the error from `@typescript-eslint` regarding missing parserServices and provide a more detailed user-facing message', async () => {
setupMocks();
mockLint.mockImplementation(() => {
throw new Error(
`Error while loading rule '@typescript-eslint/await-thenable': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.`
);
});
await lintExecutor(
createValidRunBuilderOptions({
lintFilePatterns: ['includedFile1'],
format: 'json',
silent: false,
}),
mockContext
);
expect(console.error).toHaveBeenCalledWith(
`
Error: You have attempted to use a lint rule which requires the full TypeScript type-checker to be available, but you do not have \`parserOptions.project\` configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your project ESLint config \`apps/proj/.eslintrc.json\`
Please see https://nx.dev/latest/guides/eslint for full guidance on how to resolve this issue.
`
);
});
it('should log if there are no warnings or errors', async () => {
mockReports = [
{

View File

@ -46,7 +46,37 @@ export default async function run(
? resolve(systemRoot, options.eslintConfig)
: undefined;
let lintResults: ESLint.LintResult[] = await lint(eslintConfigPath, options);
let lintResults: ESLint.LintResult[] = [];
try {
lintResults = await lint(eslintConfigPath, options);
} catch (err) {
if (
err.message.includes(
'You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser'
)
) {
let eslintConfigPathForError = `for ${projectName}`;
if (context.workspace?.projects?.[projectName]?.root) {
const { root } = context.workspace.projects[projectName];
eslintConfigPathForError = `\`${root}/.eslintrc.json\``;
}
console.error(`
Error: You have attempted to use a lint rule which requires the full TypeScript type-checker to be available, but you do not have \`parserOptions.project\` configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your project ESLint config ${
eslintConfigPath || eslintConfigPathForError
}
Please see https://nx.dev/latest/guides/eslint for full guidance on how to resolve this issue.
`);
return {
success: false,
};
}
// If some unexpected error, rethrow
throw err;
}
if (lintResults.length === 0) {
throw new Error('Invalid lint configuration. Nothing to lint.');

View File

@ -16,11 +16,6 @@ exports[`@nrwl/linter:lint-project --linter eslint should generate a eslint conf
\\"*.js\\",
\\"*.jsx\\"
],
\\"parserOptions\\": {
\\"project\\": [
\\"libs/test-lib/tsconfig.*?.json\\"
]
},
\\"rules\\": {}
},
{

View File

@ -31,6 +31,7 @@ describe('@nrwl/linter:lint-project', () => {
linter: Linter.EsLint,
eslintFilePatterns: ['**/*.ts'],
project: 'test-lib',
setParserOptionsProject: false,
});
expect(
@ -44,6 +45,7 @@ describe('@nrwl/linter:lint-project', () => {
linter: Linter.EsLint,
eslintFilePatterns: ['**/*.ts'],
project: 'test-lib',
setParserOptionsProject: false,
});
const projectConfig = readProjectConfiguration(tree, 'test-lib');
@ -67,6 +69,7 @@ describe('@nrwl/linter:lint-project', () => {
linter: Linter.TsLint,
tsConfigPaths: ['tsconfig.json'],
project: 'test-lib',
setParserOptionsProject: false,
});
expect(
@ -80,6 +83,7 @@ describe('@nrwl/linter:lint-project', () => {
linter: Linter.TsLint,
tsConfigPaths: ['tsconfig.json'],
project: 'test-lib',
setParserOptionsProject: false,
});
const projectConfig = readProjectConfiguration(tree, 'test-lib');

View File

@ -17,6 +17,7 @@ interface LintProjectOptions {
eslintFilePatterns?: string[];
tsConfigPaths?: string[];
skipFormat: boolean;
setParserOptionsProject?: boolean;
}
function createTsLintConfiguration(
@ -35,7 +36,8 @@ function createTsLintConfiguration(
function createEsLintConfiguration(
tree: Tree,
projectConfig: ProjectConfiguration
projectConfig: ProjectConfiguration,
setParserOptionsProject: boolean
) {
writeJson(tree, join(projectConfig.root, `.eslintrc.json`), {
extends: [`${offsetFromRoot(projectConfig.root)}.eslintrc.json`],
@ -44,14 +46,24 @@ function createEsLintConfiguration(
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
parserOptions: {
/**
* In order to ensure maximum efficiency when typescript-eslint generates TypeScript Programs
* behind the scenes during lint runs, we need to make sure the project is configured to use its
* own specific tsconfigs, and not fall back to the ones in the root of the workspace.
*/
project: [`${projectConfig.root}/tsconfig.*?.json`],
},
/**
* NOTE: We no longer set parserOptions.project by default when creating new projects.
*
* We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore
* do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project,
* typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple
* parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much
* less memory intensive.
*
* In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set
* parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you
* and provide feedback to the user.
*/
parserOptions: !setParserOptionsProject
? undefined
: {
project: [`${projectConfig.root}/tsconfig.*?.json`],
},
/**
* Having an empty rules object present makes it more obvious to the user where they would
* extend things from if they needed to
@ -86,7 +98,11 @@ export async function lintProjectGenerator(
lintFilePatterns: options.eslintFilePatterns,
},
};
createEsLintConfiguration(tree, projectConfig);
createEsLintConfiguration(
tree,
projectConfig,
options.setParserOptionsProject
);
} else {
projectConfig.targets['lint'] = {
executor: '@angular-devkit/build-angular:tslint',

View File

@ -0,0 +1,194 @@
import { addProjectConfiguration, readJson, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import remoteESLintProjectConfigIfNoTypeCheckingRules from './remove-eslint-project-config-if-no-type-checking-rules';
const KNOWN_RULE_REQUIRING_TYPE_CHECKING = '@typescript-eslint/await-thenable';
describe('Remove ESLint parserOptions.project config if no rules requiring type-checking are in use', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'react-app', {
root: 'apps/react-app',
sourceRoot: 'apps/react-app/src',
projectType: 'application',
targets: {},
});
addProjectConfiguration(tree, 'workspace-lib', {
root: 'libs/workspace-lib',
sourceRoot: 'libs/workspace-lib/src',
projectType: 'library',
targets: {},
});
addProjectConfiguration(tree, 'some-lib', {
root: 'libs/some-lib',
sourceRoot: 'libs/some-lib/src',
projectType: 'library',
targets: {},
});
});
it('should not update any configs if the root .eslintrc.json contains at least one rule requiring type-checking', async () => {
const rootEslintConfig = {
root: true,
ignorePatterns: ['**/*'],
plugins: ['@nrwl/nx'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {
'@nrwl/nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
],
},
],
},
},
{
files: ['*.ts', '*.tsx'],
extends: ['plugin:@nrwl/nx/typescript'],
rules: {
[KNOWN_RULE_REQUIRING_TYPE_CHECKING]: 'error',
},
},
{
files: ['*.js', '*.jsx'],
extends: ['plugin:@nrwl/nx/javascript'],
rules: {},
},
],
};
tree.write('.eslintrc.json', JSON.stringify(rootEslintConfig));
const projectEslintConfig1 = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {},
},
],
};
tree.write(
'apps/react-app/.eslintrc.json',
JSON.stringify(projectEslintConfig1)
);
const projectEslintConfig2 = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {},
},
],
};
tree.write(
'libs/workspace-lib/.eslintrc.json',
JSON.stringify(projectEslintConfig2)
);
await remoteESLintProjectConfigIfNoTypeCheckingRules(tree);
// No change
expect(readJson(tree, 'apps/react-app/.eslintrc.json')).toEqual(
projectEslintConfig1
);
// No change
expect(readJson(tree, 'libs/workspace-lib/.eslintrc.json')).toEqual(
projectEslintConfig1
);
});
it('should remove the parserOptions.project from any project .eslintrc.json files that do not contain any rules requiring type-checking', async () => {
// Root doesn't contain any rules requiring type-checking
const rootEslintConfig = {
root: true,
ignorePatterns: ['**/*'],
plugins: ['@nrwl/nx'],
overrides: [],
};
tree.write('.eslintrc.json', JSON.stringify(rootEslintConfig));
const projectEslintConfig1 = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {
[KNOWN_RULE_REQUIRING_TYPE_CHECKING]: 'error',
},
},
],
};
tree.write(
'apps/react-app/.eslintrc.json',
JSON.stringify(projectEslintConfig1)
);
const projectEslintConfig2 = {
extends: '../../../.eslintrc.json',
ignorePatterns: ['!**/*'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parserOptions: {
project: 'some-path-to-tsconfig.json',
},
rules: {
// No rules requiring type-checking
},
},
],
};
tree.write(
'libs/workspace-lib/.eslintrc.json',
JSON.stringify(projectEslintConfig2)
);
await remoteESLintProjectConfigIfNoTypeCheckingRules(tree);
// No change - uses rule requiring type-checking
expect(readJson(tree, 'apps/react-app/.eslintrc.json')).toEqual(
projectEslintConfig1
);
// Updated - no more parserOptions.project
expect(readJson(tree, 'libs/workspace-lib/.eslintrc.json'))
.toMatchInlineSnapshot(`
Object {
"extends": "../../../.eslintrc.json",
"ignorePatterns": Array [
"!**/*",
],
"overrides": Array [
Object {
"files": Array [
"*.ts",
"*.tsx",
],
"rules": Object {},
},
],
}
`);
});
});

View File

@ -0,0 +1,39 @@
import {
formatFiles,
getProjects,
readJson,
Tree,
updateJson,
} from '@nrwl/devkit';
import { join } from 'path';
import {
hasRulesRequiringTypeChecking,
removeParserOptionsProjectIfNotRequired,
} from '../../utils/rules-requiring-type-checking';
function updateProjectESLintConfigs(host: Tree) {
const projects = getProjects(host);
projects.forEach((p) => {
const eslintConfigPath = join(p.root, '.eslintrc.json');
if (!host.exists(eslintConfigPath)) {
return;
}
return updateJson(
host,
eslintConfigPath,
removeParserOptionsProjectIfNotRequired
);
});
}
export default async function removeESLintProjectConfigIfNoTypeCheckingRules(
host: Tree
) {
// If the root level config uses at least one rule requiring type-checking, do not migrate any project configs
const rootESLintConfig = readJson(host, '.eslintrc.json');
if (hasRulesRequiringTypeChecking(rootESLintConfig)) {
return;
}
updateProjectESLintConfigs(host);
await formatFiles(host);
}

View File

@ -17,6 +17,7 @@ import {
updateWorkspaceConfiguration,
} from '@nrwl/devkit';
import type { Linter } from 'eslint';
import { removeParserOptionsProjectIfNotRequired } from '../rules-requiring-type-checking';
import { convertTSLintDisableCommentsForProject } from './convert-to-eslint-config';
import {
convertTSLintConfig,
@ -226,7 +227,11 @@ export class ProjectConverter {
});
}
json.overrides = deduplicateOverrides(json.overrides);
return json;
/**
* Remove the parserOptions.project config if it is not required for the final config,
* so that lint runs can be as fast and efficient as possible.
*/
return removeParserOptionsProjectIfNotRequired(json);
});
/**
@ -332,7 +337,11 @@ export class ProjectConverter {
* updating the config file.
*/
const finalJson = applyPackageSpecificModifications(json);
return finalJson;
/**
* Remove the parserOptions.project config if it is not required for the final config,
* so that lint runs can be as fast and efficient as possible.
*/
return removeParserOptionsProjectIfNotRequired(finalJson);
});
/**

View File

@ -0,0 +1,86 @@
import type { Linter } from 'eslint';
// Cache the resolved rules from node_modules
let knownRulesRequiringTypeChecking: string[] | null = null;
function resolveKnownRulesRequiringTypeChecking(): string[] | null {
if (knownRulesRequiringTypeChecking) {
return knownRulesRequiringTypeChecking;
}
try {
const { rules } = require('@typescript-eslint/eslint-plugin');
const rulesRequiringTypeInfo = Object.entries(rules)
.map(([ruleName, config]) => {
if ((config as any).meta?.docs?.requiresTypeChecking) {
return `@typescript-eslint/${ruleName}`;
}
return null;
})
.filter(Boolean);
return rulesRequiringTypeInfo;
} catch (err) {
console.log(err);
return null;
}
}
export function hasRulesRequiringTypeChecking(
eslintConfig: Linter.Config
): boolean {
knownRulesRequiringTypeChecking = resolveKnownRulesRequiringTypeChecking();
if (!knownRulesRequiringTypeChecking) {
/**
* If (unexpectedly) known rules requiring type checking could not be resolved,
* default to assuming that the rules are in use to align most closely with Nx
* ESLint configs to date.
*/
return true;
}
const allRulesInConfig = getAllRulesInConfig(eslintConfig);
return allRulesInConfig.some((rule) =>
knownRulesRequiringTypeChecking.includes(rule)
);
}
export function removeParserOptionsProjectIfNotRequired(
json: Linter.Config
): Linter.Config {
// At least one rule requiring type-checking is in use, do not migrate the config
if (hasRulesRequiringTypeChecking(json)) {
return json;
}
removeProjectParserOptionFromConfig(json);
return json;
}
function getAllRulesInConfig(json: Linter.Config): string[] {
let allRules = json.rules ? Object.keys(json.rules) : [];
if (json.overrides?.length > 0) {
for (const override of json.overrides) {
if (override.rules) {
allRules = [...allRules, ...Object.keys(override.rules)];
}
}
}
return allRules;
}
function removeProjectParserOptionFromConfig(json: Linter.Config): void {
delete json.parserOptions?.project;
// If parserOptions is left empty by this removal, also clean up the whole object
if (json.parserOptions && Object.keys(json.parserOptions).length === 0) {
delete json.parserOptions;
}
if (json.overrides) {
for (const override of json.overrides) {
delete override.parserOptions?.project;
// If parserOptions is left empty by this removal, also clean up the whole object
if (
override.parserOptions &&
Object.keys(override.parserOptions).length === 0
) {
delete override.parserOptions;
}
}
}
}

View File

@ -260,11 +260,6 @@ Object {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"apps/nest-app-1/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {
@ -551,11 +546,6 @@ Object {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"libs/nest-lib-1/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -39,6 +39,12 @@ export async function conversionGenerator(
* delegating to the external (more generic) generators below.
*/
const js = false;
/**
* We set the parserOptions.project config just in case the converted config uses
* rules which require type-checking. Later in the conversion we check if it actually
* does and remove the config again if it doesn't, so that it is most efficient.
*/
const setParserOptionsProject = true;
if (projectConfig.projectType === 'application') {
await addLintingToApplication(host, {
@ -46,6 +52,7 @@ export async function conversionGenerator(
name: projectName,
appProjectRoot: projectConfig.root,
js,
setParserOptionsProject,
} as AddLintForApplicationSchema);
}
@ -55,6 +62,7 @@ export async function conversionGenerator(
name: projectName,
projectRoot: projectConfig.root,
js,
setParserOptionsProject,
} as AddLintForLibrarySchema);
}
},

View File

@ -38,11 +38,6 @@ describe('app', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"apps/my-node-app/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -222,11 +222,6 @@ describe('lib', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"libs/my-lib/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -283,11 +283,6 @@ describe('app', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"apps/my-app/tsconfig(.*)?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -25,11 +25,15 @@ export async function addLinting(
});
if (options.linter === Linter.EsLint) {
const reactEslintJson = createReactEslintJson(options.appProjectRoot);
const reactEslintJson = createReactEslintJson(
options.appProjectRoot,
options.setParserOptionsProject
);
updateJson(
host,
joinPathFragments(options.appProjectRoot, '.eslintrc.json'),
() => {
// Only set parserOptions.project if it already exists (defined by options.setParserOptionsProject)
if (reactEslintJson.overrides?.[0].parserOptions?.project) {
reactEslintJson.overrides[0].parserOptions.project = [
`${options.appProjectRoot}/tsconfig(.*)?.json`,

View File

@ -13,4 +13,5 @@ export interface Schema {
linter?: Linter;
skipWorkspaceJson?: boolean;
js?: boolean;
setParserOptionsProject?: boolean;
}

View File

@ -104,6 +104,11 @@
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files.",
"default": false
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
}
},
"required": []

View File

@ -132,11 +132,6 @@ describe('app', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"apps/my-node-app/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -174,6 +174,7 @@ export async function addLintingToApplication(
`${options.appProjectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
],
skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject,
});
return lintTask;

View File

@ -12,4 +12,5 @@ export interface Schema {
babelJest?: boolean;
js?: boolean;
pascalCaseFiles?: boolean;
setParserOptionsProject?: boolean;
}

View File

@ -63,6 +63,11 @@
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files.",
"default": false
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
}
},
"required": []

View File

@ -119,11 +119,6 @@ describe('lib', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"libs/my-lib/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -390,11 +390,6 @@ describe('app', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"apps/my-app/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -38,7 +38,10 @@ async function addLinting(host: Tree, options: NormalizedSchema) {
});
tasks.push(lintTask);
const reactEslintJson = createReactEslintJson(options.appProjectRoot);
const reactEslintJson = createReactEslintJson(
options.appProjectRoot,
options.setParserOptionsProject
);
updateJson(
host,

View File

@ -18,6 +18,7 @@ export interface Schema {
js?: boolean;
globalCss?: boolean;
strict?: boolean;
setParserOptionsProject?: boolean;
}
export interface NormalizedSchema extends Schema {

View File

@ -140,6 +140,11 @@
"type": "boolean",
"description": "Creates an application with stricter type checking and build optimization options.",
"default": true
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
}
},
"required": []

View File

@ -145,11 +145,6 @@ describe('lib', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"libs/my-lib/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -150,7 +150,10 @@ async function addLinting(host: Tree, options: NormalizedSchema) {
return;
}
const reactEslintJson = createReactEslintJson(options.projectRoot);
const reactEslintJson = createReactEslintJson(
options.projectRoot,
options.setParserOptionsProject
);
updateJson(
host,

View File

@ -20,4 +20,5 @@ export interface Schema {
js?: boolean;
globalCss?: boolean;
strict?: boolean;
setParserOptionsProject?: boolean;
}

View File

@ -145,6 +145,11 @@
"type": "boolean",
"description": "Whether to enable tsconfig strict mode or not.",
"default": false
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
}
},
"required": ["name"]

View File

@ -17,7 +17,10 @@ export const extraEslintDependencies = {
},
};
export const createReactEslintJson = (projectRoot: string): Linter.Config => ({
export const createReactEslintJson = (
projectRoot: string,
setParserOptionsProject: boolean
): Linter.Config => ({
extends: [
'plugin:@nrwl/nx/react',
`${offsetFromRoot(projectRoot)}.eslintrc.json`,
@ -26,14 +29,24 @@ export const createReactEslintJson = (projectRoot: string): Linter.Config => ({
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
parserOptions: {
/**
* In order to ensure maximum efficiency when typescript-eslint generates TypeScript Programs
* behind the scenes during lint runs, we need to make sure the project is configured to use its
* own specific tsconfigs, and not fall back to the ones in the root of the workspace.
*/
project: [`${projectRoot}/tsconfig.*?.json`],
},
/**
* NOTE: We no longer set parserOptions.project by default when creating new projects.
*
* We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore
* do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project,
* typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple
* parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much
* less memory intensive.
*
* In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set
* parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you
* and provide feedback to the user.
*/
parserOptions: !setParserOptionsProject
? undefined
: {
project: [`${projectRoot}/tsconfig.*?.json`],
},
/**
* Having an empty rules object present makes it more obvious to the user where they would
* extend things from if they needed to

View File

@ -83,11 +83,6 @@ describe('app', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"apps/my-app/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -321,11 +321,6 @@ describe('lib', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"libs/my-lib/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {
@ -394,11 +389,6 @@ describe('lib', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"libs/my-dir/my-lib/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {
@ -678,11 +668,6 @@ describe('lib', () => {
"*.js",
"*.jsx",
],
"parserOptions": Object {
"project": Array [
"libs/my-dir/my-lib/tsconfig.*?.json",
],
},
"rules": Object {},
},
Object {

View File

@ -73,6 +73,7 @@ export function addLint(
eslintFilePatterns: [
`${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
],
setParserOptionsProject: options.setParserOptionsProject,
});
}

View File

@ -18,4 +18,5 @@ export interface Schema {
strict?: boolean;
skipBabelrc?: boolean;
buildable?: boolean;
setParserOptionsProject?: boolean;
}

View File

@ -91,6 +91,11 @@
"type": "boolean",
"default": false,
"description": "Generate a buildable library."
},
"setParserOptionsProject": {
"type": "boolean",
"description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.",
"default": false
}
},
"required": ["name"]

View File

@ -65,6 +65,7 @@ describe('updateEslint', () => {
await libraryGenerator(tree, {
name: 'my-lib',
linter: Linter.EsLint,
setParserOptionsProject: true,
});
// This step is usually handled elsewhere

View File

@ -55,6 +55,7 @@ interface AddLintFileOptions {
dependencies: { [key: string]: string };
devDependencies: { [key: string]: string };
};
setParserOptionsProject?: boolean;
}
export function addLintFiles(
projectRoot: string,
@ -163,14 +164,24 @@ export function addLintFiles(
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
parserOptions: {
/**
* In order to ensure maximum efficiency when typescript-eslint generates TypeScript Programs
* behind the scenes during lint runs, we need to make sure the project is configured to use its
* own specific tsconfigs, and not fall back to the ones in the root of the workspace.
*/
project: [`${projectRoot}/tsconfig.*?.json`],
},
/**
* NOTE: We no longer set parserOptions.project by default when creating new projects.
*
* We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore
* do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project,
* typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple
* parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much
* less memory intensive.
*
* In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set
* parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you
* and provide feedback to the user.
*/
parserOptions: !options.setParserOptionsProject
? undefined
: {
project: [`${projectRoot}/tsconfig.*?.json`],
},
/**
* Having an empty rules object present makes it more obvious to the user where they would
* extend things from if they needed to

View File

@ -37,6 +37,8 @@ const IGNORE_MATCHES = {
'@angular-devkit/architect',
// Installed and uninstalled dynamically when the conversion generator runs
'tslint-to-eslint-config',
// Resolved from the end user's own workspace installation dynamically
'@typescript-eslint/eslint-plugin',
],
next: [
'@angular-devkit/architect',