feat(linter): do not set eslint parserOptions.project by default (#5798)
This commit is contained in:
parent
1f93ebb47a
commit
a3c08a9153
@ -1,9 +1,6 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.*?.json"
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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
108
docs/shared/eslint.md
Normal 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"]`
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
108
nx-dev/nx-dev/public/documentation/latest/shared/eslint.md
Normal file
108
nx-dev/nx-dev/public/documentation/latest/shared/eslint.md
Normal 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"]`
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
108
nx-dev/nx-dev/public/documentation/previous/shared/eslint.md
Normal file
108
nx-dev/nx-dev/public/documentation/previous/shared/eslint.md
Normal 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"]`
|
||||
@ -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",
|
||||
|
||||
@ -21,7 +21,22 @@ export function createEsLintConfiguration(
|
||||
'plugin:@nrwl/nx/angular',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
],
|
||||
parserOptions: {
|
||||
/**
|
||||
* 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: {
|
||||
|
||||
@ -2,4 +2,5 @@ export interface AddLintingGeneratorSchema {
|
||||
projectName: string;
|
||||
projectRoot: string;
|
||||
prefix: string;
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -262,9 +262,6 @@ Object {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": "apps/e2e-app-1/tsconfig.*?.json",
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@ -343,9 +343,6 @@ describe('schematic:cypress-project', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": "apps/my-app-e2e/tsconfig.*?.json",
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -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,7 +113,22 @@ export async function addLinter(host: Tree, options: CypressProjectSchema) {
|
||||
*/
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
parserOptions: {
|
||||
/**
|
||||
* 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`,
|
||||
},
|
||||
/**
|
||||
|
||||
@ -7,4 +7,5 @@ export interface Schema {
|
||||
linter?: Linter;
|
||||
js?: boolean;
|
||||
skipFormat?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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: {},
|
||||
};
|
||||
|
||||
@ -53,11 +53,6 @@ describe('app', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/my-node-app/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -320,11 +320,6 @@ describe('app', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/my-app/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -8,4 +8,5 @@ export interface Schema {
|
||||
unitTestRunner?: 'jest' | 'none';
|
||||
e2eTestRunner?: 'cypress' | 'none';
|
||||
js?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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 = [
|
||||
{
|
||||
|
||||
@ -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.');
|
||||
|
||||
@ -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\\": {}
|
||||
},
|
||||
{
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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,12 +46,22 @@ 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.
|
||||
* 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`],
|
||||
},
|
||||
/**
|
||||
@ -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',
|
||||
|
||||
@ -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 {},
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
86
packages/linter/src/utils/rules-requiring-type-checking.ts
Normal file
86
packages/linter/src/utils/rules-requiring-type-checking.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@ -38,11 +38,6 @@ describe('app', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/my-node-app/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -222,11 +222,6 @@ describe('lib', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"libs/my-lib/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -283,11 +283,6 @@ describe('app', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/my-app/tsconfig(.*)?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -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`,
|
||||
|
||||
@ -13,4 +13,5 @@ export interface Schema {
|
||||
linter?: Linter;
|
||||
skipWorkspaceJson?: boolean;
|
||||
js?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
|
||||
@ -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": []
|
||||
|
||||
@ -132,11 +132,6 @@ describe('app', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/my-node-app/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -174,6 +174,7 @@ export async function addLintingToApplication(
|
||||
`${options.appProjectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
|
||||
],
|
||||
skipFormat: true,
|
||||
setParserOptionsProject: options.setParserOptionsProject,
|
||||
});
|
||||
|
||||
return lintTask;
|
||||
|
||||
@ -12,4 +12,5 @@ export interface Schema {
|
||||
babelJest?: boolean;
|
||||
js?: boolean;
|
||||
pascalCaseFiles?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
|
||||
@ -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": []
|
||||
|
||||
@ -119,11 +119,6 @@ describe('lib', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"libs/my-lib/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -390,11 +390,6 @@ describe('app', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/my-app/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -18,6 +18,7 @@ export interface Schema {
|
||||
js?: boolean;
|
||||
globalCss?: boolean;
|
||||
strict?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
|
||||
export interface NormalizedSchema extends Schema {
|
||||
|
||||
@ -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": []
|
||||
|
||||
@ -145,11 +145,6 @@ describe('lib', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"libs/my-lib/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -20,4 +20,5 @@ export interface Schema {
|
||||
js?: boolean;
|
||||
globalCss?: boolean;
|
||||
strict?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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,12 +29,22 @@ 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.
|
||||
* 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`],
|
||||
},
|
||||
/**
|
||||
|
||||
@ -83,11 +83,6 @@ describe('app', () => {
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/my-app/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {},
|
||||
},
|
||||
Object {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -73,6 +73,7 @@ export function addLint(
|
||||
eslintFilePatterns: [
|
||||
`${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
|
||||
],
|
||||
setParserOptionsProject: options.setParserOptionsProject,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -18,4 +18,5 @@ export interface Schema {
|
||||
strict?: boolean;
|
||||
skipBabelrc?: boolean;
|
||||
buildable?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -65,6 +65,7 @@ describe('updateEslint', () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
setParserOptionsProject: true,
|
||||
});
|
||||
|
||||
// This step is usually handled elsewhere
|
||||
|
||||
@ -55,6 +55,7 @@ interface AddLintFileOptions {
|
||||
dependencies: { [key: string]: string };
|
||||
devDependencies: { [key: string]: string };
|
||||
};
|
||||
setParserOptionsProject?: boolean;
|
||||
}
|
||||
export function addLintFiles(
|
||||
projectRoot: string,
|
||||
@ -163,12 +164,22 @@ 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.
|
||||
* 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`],
|
||||
},
|
||||
/**
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user