feat(linter): convert-tslint-to-eslint generators (#4943)
* feat(linter): convert-tslint-to-eslint generators * fix(core): remove generators in collection for ng and nest * fix(core): update tao to support mixed generators and schematics * fix(core): update tao to support mixed generators and schematics * fix(core): address some PR feedback * fix(core): fix snapshots after syncing up with master * feat(core): store user preference for removeTSLintIfNoMoreTSLintTargets * fix(linter): unit tests * feat(core): apply root tslint.json conversion to root .eslintrc.json
This commit is contained in:
parent
4eebad2a6d
commit
00dec221e2
@ -0,0 +1,47 @@
|
|||||||
|
# convert-tslint-to-eslint
|
||||||
|
|
||||||
|
Convert a project from TSLint to ESLint
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx generate convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Nx will search for `convert-tslint-to-eslint` in the default collection provisioned in `angular.json`.
|
||||||
|
|
||||||
|
You can specify the collection explicitly as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g @nrwl/angular:convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Show what will be generated without writing to disk:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint ... --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Convert the Angular project `myapp` from TSLint to ESLint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### project
|
||||||
|
|
||||||
|
Type: `string`
|
||||||
|
|
||||||
|
The name of the Angular project to convert.
|
||||||
|
|
||||||
|
### removeTSLintIfNoMoreTSLintTargets
|
||||||
|
|
||||||
|
Default: `true`
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration
|
||||||
47
docs/angular/api-nest/generators/convert-tslint-to-eslint.md
Normal file
47
docs/angular/api-nest/generators/convert-tslint-to-eslint.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# convert-tslint-to-eslint
|
||||||
|
|
||||||
|
Convert a project from TSLint to ESLint
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx generate convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Nx will search for `convert-tslint-to-eslint` in the default collection provisioned in `angular.json`.
|
||||||
|
|
||||||
|
You can specify the collection explicitly as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g @nrwl/nest:convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Show what will be generated without writing to disk:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint ... --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Convert the NestJS project `myapp` from TSLint to ESLint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### project
|
||||||
|
|
||||||
|
Type: `string`
|
||||||
|
|
||||||
|
The name of the NestJS project to convert.
|
||||||
|
|
||||||
|
### removeTSLintIfNoMoreTSLintTargets
|
||||||
|
|
||||||
|
Default: `true`
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration
|
||||||
@ -443,6 +443,11 @@
|
|||||||
"id": "upgrade-module",
|
"id": "upgrade-module",
|
||||||
"file": "angular/api-angular/generators/upgrade-module"
|
"file": "angular/api-angular/generators/upgrade-module"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "convert-tslint-to-eslint",
|
||||||
|
"id": "convert-tslint-to-eslint",
|
||||||
|
"file": "angular/api-angular/generators/convert-tslint-to-eslint"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "package executor",
|
"name": "package executor",
|
||||||
"id": "package",
|
"id": "package",
|
||||||
@ -747,6 +752,11 @@
|
|||||||
"name": "service generator",
|
"name": "service generator",
|
||||||
"id": "service",
|
"id": "service",
|
||||||
"file": "angular/api-nest/generators/service"
|
"file": "angular/api-nest/generators/service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "convert-tslint-to-eslint",
|
||||||
|
"id": "convert-tslint-to-eslint",
|
||||||
|
"file": "angular/api-nest/generators/convert-tslint-to-eslint"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1466,6 +1476,11 @@
|
|||||||
"id": "ngrx",
|
"id": "ngrx",
|
||||||
"file": "react/api-angular/generators/ngrx"
|
"file": "react/api-angular/generators/ngrx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "convert-tslint-to-eslint",
|
||||||
|
"id": "convert-tslint-to-eslint",
|
||||||
|
"file": "react/api-angular/generators/convert-tslint-to-eslint"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "stories generator",
|
"name": "stories generator",
|
||||||
"id": "stories",
|
"id": "stories",
|
||||||
@ -1796,6 +1811,11 @@
|
|||||||
"name": "service generator",
|
"name": "service generator",
|
||||||
"id": "service",
|
"id": "service",
|
||||||
"file": "react/api-nest/generators/service"
|
"file": "react/api-nest/generators/service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "convert-tslint-to-eslint",
|
||||||
|
"id": "convert-tslint-to-eslint",
|
||||||
|
"file": "react/api-nest/generators/convert-tslint-to-eslint"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2488,6 +2508,11 @@
|
|||||||
"id": "upgrade-module",
|
"id": "upgrade-module",
|
||||||
"file": "node/api-angular/generators/upgrade-module"
|
"file": "node/api-angular/generators/upgrade-module"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "convert-tslint-to-eslint",
|
||||||
|
"id": "convert-tslint-to-eslint",
|
||||||
|
"file": "node/api-angular/generators/convert-tslint-to-eslint"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "package executor",
|
"name": "package executor",
|
||||||
"id": "package",
|
"id": "package",
|
||||||
@ -2791,6 +2816,11 @@
|
|||||||
"name": "service generator",
|
"name": "service generator",
|
||||||
"id": "service",
|
"id": "service",
|
||||||
"file": "node/api-nest/generators/service"
|
"file": "node/api-nest/generators/service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "convert-tslint-to-eslint",
|
||||||
|
"id": "convert-tslint-to-eslint",
|
||||||
|
"file": "node/api-nest/generators/convert-tslint-to-eslint"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
47
docs/node/api-angular/generators/convert-tslint-to-eslint.md
Normal file
47
docs/node/api-angular/generators/convert-tslint-to-eslint.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# convert-tslint-to-eslint
|
||||||
|
|
||||||
|
Convert a project from TSLint to ESLint
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx generate convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Nx will search for `convert-tslint-to-eslint` in the default collection provisioned in `workspace.json`.
|
||||||
|
|
||||||
|
You can specify the collection explicitly as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g @nrwl/angular:convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Show what will be generated without writing to disk:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint ... --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Convert the Angular project `myapp` from TSLint to ESLint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### project
|
||||||
|
|
||||||
|
Type: `string`
|
||||||
|
|
||||||
|
The name of the Angular project to convert.
|
||||||
|
|
||||||
|
### removeTSLintIfNoMoreTSLintTargets
|
||||||
|
|
||||||
|
Default: `true`
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration
|
||||||
47
docs/node/api-nest/generators/convert-tslint-to-eslint.md
Normal file
47
docs/node/api-nest/generators/convert-tslint-to-eslint.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# convert-tslint-to-eslint
|
||||||
|
|
||||||
|
Convert a project from TSLint to ESLint
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx generate convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Nx will search for `convert-tslint-to-eslint` in the default collection provisioned in `workspace.json`.
|
||||||
|
|
||||||
|
You can specify the collection explicitly as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g @nrwl/nest:convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Show what will be generated without writing to disk:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint ... --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Convert the NestJS project `myapp` from TSLint to ESLint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### project
|
||||||
|
|
||||||
|
Type: `string`
|
||||||
|
|
||||||
|
The name of the NestJS project to convert.
|
||||||
|
|
||||||
|
### removeTSLintIfNoMoreTSLintTargets
|
||||||
|
|
||||||
|
Default: `true`
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
# convert-tslint-to-eslint
|
||||||
|
|
||||||
|
Convert a project from TSLint to ESLint
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx generate convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Nx will search for `convert-tslint-to-eslint` in the default collection provisioned in `workspace.json`.
|
||||||
|
|
||||||
|
You can specify the collection explicitly as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g @nrwl/angular:convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Show what will be generated without writing to disk:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint ... --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Convert the Angular project `myapp` from TSLint to ESLint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### project
|
||||||
|
|
||||||
|
Type: `string`
|
||||||
|
|
||||||
|
The name of the Angular project to convert.
|
||||||
|
|
||||||
|
### removeTSLintIfNoMoreTSLintTargets
|
||||||
|
|
||||||
|
Default: `true`
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration
|
||||||
47
docs/react/api-nest/generators/convert-tslint-to-eslint.md
Normal file
47
docs/react/api-nest/generators/convert-tslint-to-eslint.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# convert-tslint-to-eslint
|
||||||
|
|
||||||
|
Convert a project from TSLint to ESLint
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx generate convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Nx will search for `convert-tslint-to-eslint` in the default collection provisioned in `workspace.json`.
|
||||||
|
|
||||||
|
You can specify the collection explicitly as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g @nrwl/nest:convert-tslint-to-eslint ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Show what will be generated without writing to disk:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint ... --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Convert the NestJS project `myapp` from TSLint to ESLint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g convert-tslint-to-eslint myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### project
|
||||||
|
|
||||||
|
Type: `string`
|
||||||
|
|
||||||
|
The name of the NestJS project to convert.
|
||||||
|
|
||||||
|
### removeTSLintIfNoMoreTSLintTargets
|
||||||
|
|
||||||
|
Default: `true`
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration
|
||||||
2
nx.json
2
nx.json
@ -79,7 +79,7 @@
|
|||||||
},
|
},
|
||||||
"nest": {
|
"nest": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"implicitDependencies": ["node"]
|
"implicitDependencies": ["node", "linter"]
|
||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
|||||||
@ -249,6 +249,7 @@
|
|||||||
"tsickle": "^0.38.1",
|
"tsickle": "^0.38.1",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"tslint": "6.1.3",
|
"tslint": "6.1.3",
|
||||||
|
"tslint-to-eslint-config": "2.2.0",
|
||||||
"typescript": "4.0.5",
|
"typescript": "4.0.5",
|
||||||
"url-loader": "^3.0.0",
|
"url-loader": "^3.0.0",
|
||||||
"verdaccio": "^4.11.1",
|
"verdaccio": "^4.11.1",
|
||||||
|
|||||||
@ -95,6 +95,26 @@
|
|||||||
"schema": "./src/schematics/move/schema.json",
|
"schema": "./src/schematics/move/schema.json",
|
||||||
"aliases": ["mv"],
|
"aliases": ["mv"],
|
||||||
"description": "Move an Angular application or library to another folder"
|
"description": "Move an Angular application or library to another folder"
|
||||||
|
},
|
||||||
|
|
||||||
|
"add-linting": {
|
||||||
|
"factory": "./src/schematics/add-linting/add-linting",
|
||||||
|
"schema": "./src/schematics/add-linting/schema.json",
|
||||||
|
"description": "Add linting configuration to an Angular project",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"convert-tslint-to-eslint": {
|
||||||
|
"factory": "./src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint#conversionSchematic",
|
||||||
|
"schema": "./src/generators/convert-tslint-to-eslint/schema.json",
|
||||||
|
"description": "Convert a project from TSLint to ESLint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generators": {
|
||||||
|
"convert-tslint-to-eslint": {
|
||||||
|
"factory": "./src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint#conversionGenerator",
|
||||||
|
"schema": "./src/generators/convert-tslint-to-eslint/schema.json",
|
||||||
|
"description": "Convert a project from TSLint to ESLint"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './src/schematics/generators';
|
export * from './src/schematics/generators';
|
||||||
|
export * from './src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint';
|
||||||
|
|||||||
@ -0,0 +1,679 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Angular applications 1`] = `
|
||||||
|
Object {
|
||||||
|
"dependencies": Object {},
|
||||||
|
"devDependencies": Object {
|
||||||
|
"@angular-eslint/eslint-plugin": "~1.0.0",
|
||||||
|
"@angular-eslint/eslint-plugin-template": "~1.0.0",
|
||||||
|
"@angular-eslint/template-parser": "~1.0.0",
|
||||||
|
"@nrwl/eslint-plugin-nx": "*",
|
||||||
|
"@nrwl/linter": "*",
|
||||||
|
"@typescript-eslint/eslint-plugin": "4.3.0",
|
||||||
|
"@typescript-eslint/parser": "4.3.0",
|
||||||
|
"eslint": "7.10.0",
|
||||||
|
"eslint-config-prettier": "8.1.0",
|
||||||
|
"eslint-plugin-import": "latest",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Angular applications 2`] = `
|
||||||
|
Object {
|
||||||
|
"prefix": "angular-app",
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/angular-app-1",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": Object {
|
||||||
|
"lintFilePatterns": Array [
|
||||||
|
"apps/angular-app-1/src/**/*.ts",
|
||||||
|
"apps/angular-app-1/src/**/*.html",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Angular applications 3`] = `
|
||||||
|
Object {
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@nrwl/nx/enforce-module-boundaries": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"@nx-example/shared/product/data/testing",
|
||||||
|
],
|
||||||
|
"depConstraints": Array [
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:feature",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:app",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:ui",
|
||||||
|
"type:data",
|
||||||
|
"type:types",
|
||||||
|
"type:state",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:feature",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:types",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:state",
|
||||||
|
"type:types",
|
||||||
|
"type:data",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:state",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:data",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:e2e-utils",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:e2e",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:ui",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:products",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:products",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:cart",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:cart",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/typescript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/javascript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"eslint-plugin-import",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/component-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "kebab-case",
|
||||||
|
"type": "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/directive-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "camelCase",
|
||||||
|
"type": "attribute",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/no-conflicting-lifecycle": "error",
|
||||||
|
"@angular-eslint/no-host-metadata-property": "error",
|
||||||
|
"@angular-eslint/no-input-rename": "error",
|
||||||
|
"@angular-eslint/no-inputs-metadata-property": "error",
|
||||||
|
"@angular-eslint/no-output-native": "error",
|
||||||
|
"@angular-eslint/no-output-on-prefix": "error",
|
||||||
|
"@angular-eslint/no-output-rename": "error",
|
||||||
|
"@angular-eslint/no-outputs-metadata-property": "error",
|
||||||
|
"@angular-eslint/use-lifecycle-interface": "error",
|
||||||
|
"@angular-eslint/use-pipe-transform-interface": "error",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "error",
|
||||||
|
"@typescript-eslint/dot-notation": "off",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": Array [
|
||||||
|
"off",
|
||||||
|
Object {
|
||||||
|
"accessibility": "explicit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "error",
|
||||||
|
"@typescript-eslint/naming-convention": "error",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"ignoreParameters": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "error",
|
||||||
|
"@typescript-eslint/no-shadow": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"hoist": "all",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "error",
|
||||||
|
"@typescript-eslint/prefer-function-type": "error",
|
||||||
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
"arrow-body-style": "error",
|
||||||
|
"constructor-super": "error",
|
||||||
|
"eqeqeq": Array [
|
||||||
|
"error",
|
||||||
|
"smart",
|
||||||
|
],
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"id-blacklist": "off",
|
||||||
|
"id-match": "off",
|
||||||
|
"import/no-deprecated": "warn",
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-console": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"log",
|
||||||
|
"warn",
|
||||||
|
"dir",
|
||||||
|
"timeLog",
|
||||||
|
"assert",
|
||||||
|
"clear",
|
||||||
|
"count",
|
||||||
|
"countReset",
|
||||||
|
"group",
|
||||||
|
"groupEnd",
|
||||||
|
"table",
|
||||||
|
"dirxml",
|
||||||
|
"error",
|
||||||
|
"groupCollapsed",
|
||||||
|
"_buffer",
|
||||||
|
"_counters",
|
||||||
|
"_timers",
|
||||||
|
"_groupDepth",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-empty": "off",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-restricted-imports": Array [
|
||||||
|
"error",
|
||||||
|
"rxjs/Rx",
|
||||||
|
],
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"radix": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.html",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/template/banana-in-box": "error",
|
||||||
|
"@angular-eslint/template/no-negated-async": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"@nrwl/nx",
|
||||||
|
],
|
||||||
|
"root": true,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Angular applications 4`] = `
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"../../.eslintrc.json",
|
||||||
|
],
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"!**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/angular",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": Array [
|
||||||
|
"apps/angular-app-1/tsconfig.*?.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/component-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "angular-app",
|
||||||
|
"style": "kebab-case",
|
||||||
|
"type": "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/directive-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "angular-app",
|
||||||
|
"style": "camelCase",
|
||||||
|
"type": "attribute",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/angular-template",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.html",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/template/banana-in-box": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Angular libraries 1`] = `
|
||||||
|
Object {
|
||||||
|
"dependencies": Object {},
|
||||||
|
"devDependencies": Object {
|
||||||
|
"@angular-eslint/eslint-plugin": "~1.0.0",
|
||||||
|
"@angular-eslint/eslint-plugin-template": "~1.0.0",
|
||||||
|
"@angular-eslint/template-parser": "~1.0.0",
|
||||||
|
"@nrwl/eslint-plugin-nx": "*",
|
||||||
|
"@nrwl/linter": "*",
|
||||||
|
"@typescript-eslint/eslint-plugin": "4.3.0",
|
||||||
|
"@typescript-eslint/parser": "4.3.0",
|
||||||
|
"eslint": "7.10.0",
|
||||||
|
"eslint-config-prettier": "8.1.0",
|
||||||
|
"eslint-plugin-import": "latest",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Angular libraries 2`] = `
|
||||||
|
Object {
|
||||||
|
"prefix": "angular-app",
|
||||||
|
"projectType": "library",
|
||||||
|
"root": "libs/angular-lib-1",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": Object {
|
||||||
|
"lintFilePatterns": Array [
|
||||||
|
"libs/angular-lib-1/src/**/*.ts",
|
||||||
|
"libs/angular-lib-1/src/**/*.html",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Angular libraries 3`] = `
|
||||||
|
Object {
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@nrwl/nx/enforce-module-boundaries": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"@nx-example/shared/product/data/testing",
|
||||||
|
],
|
||||||
|
"depConstraints": Array [
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:feature",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:app",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:ui",
|
||||||
|
"type:data",
|
||||||
|
"type:types",
|
||||||
|
"type:state",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:feature",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:types",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:state",
|
||||||
|
"type:types",
|
||||||
|
"type:data",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:state",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:data",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:e2e-utils",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:e2e",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:ui",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:products",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:products",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:cart",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:cart",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/typescript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/javascript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"eslint-plugin-import",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/component-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "kebab-case",
|
||||||
|
"type": "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/directive-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "camelCase",
|
||||||
|
"type": "attribute",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/no-conflicting-lifecycle": "error",
|
||||||
|
"@angular-eslint/no-host-metadata-property": "error",
|
||||||
|
"@angular-eslint/no-input-rename": "error",
|
||||||
|
"@angular-eslint/no-inputs-metadata-property": "error",
|
||||||
|
"@angular-eslint/no-output-native": "error",
|
||||||
|
"@angular-eslint/no-output-on-prefix": "error",
|
||||||
|
"@angular-eslint/no-output-rename": "error",
|
||||||
|
"@angular-eslint/no-outputs-metadata-property": "error",
|
||||||
|
"@angular-eslint/use-lifecycle-interface": "error",
|
||||||
|
"@angular-eslint/use-pipe-transform-interface": "error",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "error",
|
||||||
|
"@typescript-eslint/dot-notation": "off",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": Array [
|
||||||
|
"off",
|
||||||
|
Object {
|
||||||
|
"accessibility": "explicit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "error",
|
||||||
|
"@typescript-eslint/naming-convention": "error",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"ignoreParameters": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "error",
|
||||||
|
"@typescript-eslint/no-shadow": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"hoist": "all",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "error",
|
||||||
|
"@typescript-eslint/prefer-function-type": "error",
|
||||||
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
"arrow-body-style": "error",
|
||||||
|
"constructor-super": "error",
|
||||||
|
"eqeqeq": Array [
|
||||||
|
"error",
|
||||||
|
"smart",
|
||||||
|
],
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"id-blacklist": "off",
|
||||||
|
"id-match": "off",
|
||||||
|
"import/no-deprecated": "warn",
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-console": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"log",
|
||||||
|
"warn",
|
||||||
|
"dir",
|
||||||
|
"timeLog",
|
||||||
|
"assert",
|
||||||
|
"clear",
|
||||||
|
"count",
|
||||||
|
"countReset",
|
||||||
|
"group",
|
||||||
|
"groupEnd",
|
||||||
|
"table",
|
||||||
|
"dirxml",
|
||||||
|
"error",
|
||||||
|
"groupCollapsed",
|
||||||
|
"_buffer",
|
||||||
|
"_counters",
|
||||||
|
"_timers",
|
||||||
|
"_groupDepth",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-empty": "off",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-restricted-imports": Array [
|
||||||
|
"error",
|
||||||
|
"rxjs/Rx",
|
||||||
|
],
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"radix": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.html",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/template/banana-in-box": "error",
|
||||||
|
"@angular-eslint/template/no-negated-async": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"@nrwl/nx",
|
||||||
|
],
|
||||||
|
"root": true,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Angular libraries 4`] = `
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"../../.eslintrc.json",
|
||||||
|
],
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"!**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/angular",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": Array [
|
||||||
|
"libs/angular-lib-1/tsconfig.*?.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/component-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "angular-app",
|
||||||
|
"style": "kebab-case",
|
||||||
|
"type": "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/directive-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "angular-app",
|
||||||
|
"style": "camelCase",
|
||||||
|
"type": "attribute",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/angular-template",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.html",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/template/banana-in-box": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,264 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
joinPathFragments,
|
||||||
|
ProjectConfiguration,
|
||||||
|
readJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
writeJson,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||||
|
import { exampleRootTslintJson } from '@nrwl/linter';
|
||||||
|
import { conversionGenerator } from './convert-tslint-to-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't run actual child_process implementation of installPackagesTask()
|
||||||
|
*/
|
||||||
|
jest.mock('child_process');
|
||||||
|
|
||||||
|
const appProjectName = 'angular-app-1';
|
||||||
|
const appProjectRoot = `apps/${appProjectName}`;
|
||||||
|
const appProjectTSLintJsonPath = joinPathFragments(
|
||||||
|
appProjectRoot,
|
||||||
|
'tslint.json'
|
||||||
|
);
|
||||||
|
const projectPrefix = 'angular-app';
|
||||||
|
const libProjectName = 'angular-lib-1';
|
||||||
|
const libProjectRoot = `libs/${libProjectName}`;
|
||||||
|
const libProjectTSLintJsonPath = joinPathFragments(
|
||||||
|
libProjectRoot,
|
||||||
|
'tslint.json'
|
||||||
|
);
|
||||||
|
// Used to configure the test Tree and stub the response from tslint-to-eslint-config util findReportedConfiguration()
|
||||||
|
const projectTslintJsonData = {
|
||||||
|
raw: {
|
||||||
|
extends: '../../tslint.json',
|
||||||
|
rules: {
|
||||||
|
// Standard Nx/Angular CLI generated rules
|
||||||
|
'directive-selector': [true, 'attribute', projectPrefix, 'camelCase'],
|
||||||
|
'component-selector': [true, 'element', projectPrefix, 'kebab-case'],
|
||||||
|
// User custom TS rule
|
||||||
|
'no-empty-interface': true,
|
||||||
|
// User custom template/HTML rule
|
||||||
|
'template-banana-in-box': true,
|
||||||
|
// User custom rule with no known automated converter
|
||||||
|
'some-super-custom-rule-with-no-converter': true,
|
||||||
|
},
|
||||||
|
linterOptions: {
|
||||||
|
exclude: ['!**/*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tslintPrintConfigResult: {
|
||||||
|
rules: {
|
||||||
|
'directive-selector': {
|
||||||
|
ruleArguments: ['attribute', projectPrefix, 'camelCase'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'component-selector': {
|
||||||
|
ruleArguments: ['element', projectPrefix, 'kebab-case'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'no-empty-interface': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'template-banana-in-box': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'some-super-custom-rule-with-no-converter': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function mockFindReportedConfiguration(_, pathToTslintJson) {
|
||||||
|
switch (pathToTslintJson) {
|
||||||
|
case 'tslint.json':
|
||||||
|
return exampleRootTslintJson.tslintPrintConfigResult;
|
||||||
|
case appProjectTSLintJsonPath:
|
||||||
|
return projectTslintJsonData.tslintPrintConfigResult;
|
||||||
|
case libProjectTSLintJsonPath:
|
||||||
|
return projectTslintJsonData.tslintPrintConfigResult;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`mockFindReportedConfiguration - Did not recognize path ${pathToTslintJson}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See ./mock-tslint-to-eslint-config.ts for why this is needed
|
||||||
|
*/
|
||||||
|
jest.mock('tslint-to-eslint-config', () => {
|
||||||
|
return {
|
||||||
|
// Since upgrading to (ts-)jest 26 this usage of this mock has caused issues...
|
||||||
|
// @ts-ignore
|
||||||
|
...jest.requireActual('tslint-to-eslint-config'),
|
||||||
|
findReportedConfiguration: jest.fn(mockFindReportedConfiguration),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock the the mutating fs utilities used within the conversion logic, they are not
|
||||||
|
* needed because of our stubbed response for findReportedConfiguration() above, and
|
||||||
|
* they would cause noise in the git data of the actual Nx repo when the tests run.
|
||||||
|
*/
|
||||||
|
jest.mock('fs', () => {
|
||||||
|
return {
|
||||||
|
// Since upgrading to (ts-)jest 26 this usage of this mock has caused issues...
|
||||||
|
// @ts-ignore
|
||||||
|
...jest.requireActual('fs'),
|
||||||
|
writeFileSync: jest.fn(),
|
||||||
|
mkdirSync: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('convert-tslint-to-eslint', () => {
|
||||||
|
let host: Tree;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
host = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
writeJson(host, 'tslint.json', exampleRootTslintJson.raw);
|
||||||
|
|
||||||
|
addProjectConfiguration(host, appProjectName, {
|
||||||
|
root: appProjectRoot,
|
||||||
|
prefix: projectPrefix,
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - BEFORE CONVERSION
|
||||||
|
*
|
||||||
|
* TSLint executor configured for the project
|
||||||
|
*/
|
||||||
|
lint: {
|
||||||
|
executor: '@angular-devkit/build-angular:tslint',
|
||||||
|
options: {
|
||||||
|
exclude: ['**/node_modules/**', '!apps/angular-app-1/**/*'],
|
||||||
|
tsConfig: ['apps/angular-app-1/tsconfig.app.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ProjectConfiguration);
|
||||||
|
|
||||||
|
addProjectConfiguration(host, libProjectName, {
|
||||||
|
root: libProjectRoot,
|
||||||
|
prefix: projectPrefix,
|
||||||
|
projectType: 'library',
|
||||||
|
targets: {
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - BEFORE CONVERSION
|
||||||
|
*
|
||||||
|
* TSLint executor configured for the project
|
||||||
|
*/
|
||||||
|
lint: {
|
||||||
|
executor: '@angular-devkit/build-angular:tslint',
|
||||||
|
options: {
|
||||||
|
exclude: ['**/node_modules/**', '!libs/angular-lib-1/**/*'],
|
||||||
|
tsConfig: ['libs/angular-lib-1/tsconfig.app.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ProjectConfiguration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Existing tslint.json file for the app project
|
||||||
|
*/
|
||||||
|
writeJson(
|
||||||
|
host,
|
||||||
|
'apps/angular-app-1/tslint.json',
|
||||||
|
projectTslintJsonData.raw
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Existing tslint.json file for the lib project
|
||||||
|
*/
|
||||||
|
writeJson(
|
||||||
|
host,
|
||||||
|
'libs/angular-lib-1/tslint.json',
|
||||||
|
projectTslintJsonData.raw
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for Angular applications', async () => {
|
||||||
|
await conversionGenerator(host, {
|
||||||
|
project: appProjectName,
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It should ensure the required Nx packages are installed and available
|
||||||
|
*
|
||||||
|
* NOTE: tslint-to-eslint-config should NOT be present
|
||||||
|
*/
|
||||||
|
expect(readJson(host, 'package.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - AFTER CONVERSION
|
||||||
|
*
|
||||||
|
* It should replace the TSLint executor with the ESLint one
|
||||||
|
*/
|
||||||
|
expect(readProjectConfiguration(host, appProjectName)).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root level .eslintrc.json should now have been generated
|
||||||
|
*/
|
||||||
|
expect(readJson(host, '.eslintrc.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project level .eslintrc.json should now have been generated
|
||||||
|
* and extend from the root, as well as applying any customizations
|
||||||
|
* which are specific to this projectType.
|
||||||
|
*/
|
||||||
|
expect(
|
||||||
|
readJson(host, joinPathFragments(appProjectRoot, '.eslintrc.json'))
|
||||||
|
).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project's TSLint file should have been deleted
|
||||||
|
*/
|
||||||
|
expect(host.exists(appProjectTSLintJsonPath)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for Angular libraries', async () => {
|
||||||
|
await conversionGenerator(host, {
|
||||||
|
project: libProjectName,
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It should ensure the required Nx packages are installed and available
|
||||||
|
*
|
||||||
|
* NOTE: tslint-to-eslint-config should NOT be present
|
||||||
|
*/
|
||||||
|
expect(readJson(host, 'package.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - AFTER CONVERSION
|
||||||
|
*
|
||||||
|
* It should replace the TSLint executor with the ESLint one
|
||||||
|
*/
|
||||||
|
expect(readProjectConfiguration(host, libProjectName)).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root level .eslintrc.json should now have been generated
|
||||||
|
*/
|
||||||
|
expect(readJson(host, '.eslintrc.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project level .eslintrc.json should now have been generated
|
||||||
|
* and extend from the root, as well as applying any customizations
|
||||||
|
* which are specific to this projectType.
|
||||||
|
*/
|
||||||
|
expect(
|
||||||
|
readJson(host, joinPathFragments(libProjectRoot, '.eslintrc.json'))
|
||||||
|
).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project's TSLint file should have been deleted
|
||||||
|
*/
|
||||||
|
expect(host.exists(libProjectTSLintJsonPath)).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,185 @@
|
|||||||
|
import { conversionGenerator as cypressConversionGenerator } from '@nrwl/cypress';
|
||||||
|
import {
|
||||||
|
convertNxGenerator,
|
||||||
|
formatFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
logger,
|
||||||
|
Tree,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { ConvertTSLintToESLintSchema, ProjectConverter } from '@nrwl/linter';
|
||||||
|
import type { Linter } from 'eslint';
|
||||||
|
import { addLintingGenerator } from '../../schematics/add-linting/add-linting';
|
||||||
|
|
||||||
|
export async function conversionGenerator(
|
||||||
|
host: Tree,
|
||||||
|
options: ConvertTSLintToESLintSchema
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The ProjectConverter instance encapsulates all the standard operations we need
|
||||||
|
* to perform in order to convert a project from TSLint to ESLint, as well as some
|
||||||
|
* extensibility points for adjusting the behavior on a per package basis.
|
||||||
|
*
|
||||||
|
* E.g. @nrwl/angular projects might need to make different changes to the final
|
||||||
|
* ESLint config when compared with @nrwl/next projects.
|
||||||
|
*
|
||||||
|
* See the ProjectConverter implementation for a full breakdown of what it does.
|
||||||
|
*/
|
||||||
|
const projectConverter = new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName: options.project,
|
||||||
|
eslintInitializer: async ({ projectName, projectConfig }) => {
|
||||||
|
await addLintingGenerator(host, {
|
||||||
|
linter: 'eslint',
|
||||||
|
projectType: projectConfig.projectType,
|
||||||
|
projectName,
|
||||||
|
projectRoot: projectConfig.root,
|
||||||
|
prefix: (projectConfig as any).prefix || 'app',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically install tslint-to-eslint-config to assist with the conversion.
|
||||||
|
*/
|
||||||
|
projectConverter.installTSLintToESLintConfigPackage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the standard (which is applicable to the current package) ESLint setup
|
||||||
|
* for converting the project.
|
||||||
|
*/
|
||||||
|
await projectConverter.initESLint();
|
||||||
|
/**
|
||||||
|
* Convert the root tslint.json and apply the converted rules to the root .eslintrc.json
|
||||||
|
*/
|
||||||
|
const rootConfigInstallTask = await projectConverter.convertRootTSLintConfig(
|
||||||
|
(json) => {
|
||||||
|
json.overrides = [
|
||||||
|
{ files: ['*.ts'], rules: {} },
|
||||||
|
{ files: ['*.html'], rules: {} },
|
||||||
|
];
|
||||||
|
return applyAngularRulesToCorrectOverrides(json);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Convert the project's tslint.json to an equivalent ESLint config.
|
||||||
|
*/
|
||||||
|
const projectConfigInstallTask = await projectConverter.convertProjectConfig(
|
||||||
|
(json) => applyAngularRulesToCorrectOverrides(json)
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Clean up the original TSLint configuration for the project.
|
||||||
|
*/
|
||||||
|
projectConverter.removeProjectTSLintFile();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store user preference regarding removeTSLintIfNoMoreTSLintTargets for the collection
|
||||||
|
*/
|
||||||
|
projectConverter.setDefaults(
|
||||||
|
'@nrwl/angular',
|
||||||
|
options.removeTSLintIfNoMoreTSLintTargets
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the Angular project is an app which has an e2e project, try and convert that as well.
|
||||||
|
*/
|
||||||
|
let cypressInstallTask: GeneratorCallback = () => Promise.resolve(undefined);
|
||||||
|
const e2eProjectName = projectConverter.getE2EProjectName();
|
||||||
|
if (e2eProjectName) {
|
||||||
|
try {
|
||||||
|
cypressInstallTask = await cypressConversionGenerator(host, {
|
||||||
|
project: e2eProjectName,
|
||||||
|
/**
|
||||||
|
* We can always set this to false, because it will already be handled by the next
|
||||||
|
* step of this parent generator, if applicable
|
||||||
|
*/
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: false,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
logger.warn(
|
||||||
|
'This Angular app has an e2e project, but it was not possible to convert it from TSLint to ESLint. This could be because the e2e project did not have a tslint.json file to begin with.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on user preference and remaining usage, remove TSLint from the workspace entirely.
|
||||||
|
*/
|
||||||
|
let uninstallTSLintTask: GeneratorCallback = () => Promise.resolve(undefined);
|
||||||
|
if (
|
||||||
|
options.removeTSLintIfNoMoreTSLintTargets &&
|
||||||
|
!projectConverter.isTSLintUsedInWorkspace()
|
||||||
|
) {
|
||||||
|
uninstallTSLintTask = projectConverter.removeTSLintFromWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
await formatFiles(host);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
projectConverter.uninstallTSLintToESLintConfigPackage();
|
||||||
|
await rootConfigInstallTask();
|
||||||
|
await projectConfigInstallTask();
|
||||||
|
await cypressInstallTask();
|
||||||
|
await uninstallTSLintTask();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const conversionSchematic = convertNxGenerator(conversionGenerator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In the case of Angular lint rules, we need to apply them to correct override depending upon whether
|
||||||
|
* or not they require @typescript-eslint/parser or @angular-eslint/template-parser in order to function.
|
||||||
|
*
|
||||||
|
* By this point, the applicable overrides have already been scaffolded for us by the Nx generators
|
||||||
|
* that ran earlier within this generator.
|
||||||
|
*/
|
||||||
|
function applyAngularRulesToCorrectOverrides(
|
||||||
|
json: Linter.Config
|
||||||
|
): Linter.Config {
|
||||||
|
const rules = json.rules;
|
||||||
|
|
||||||
|
if (rules && Object.keys(rules).length) {
|
||||||
|
for (const [ruleName, ruleConfig] of Object.entries(rules)) {
|
||||||
|
for (const override of json.overrides) {
|
||||||
|
if (
|
||||||
|
override.files.includes('*.html') &&
|
||||||
|
ruleName.startsWith('@angular-eslint/template')
|
||||||
|
) {
|
||||||
|
// Prioritize the converted rules over any base implementations from the original Nx generator
|
||||||
|
override.rules[ruleName] = ruleConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, tslint-to-eslint-config will try and apply any rules without known converters
|
||||||
|
* by using eslint-plugin-tslint. We instead explicitly warn the user about this missing converter,
|
||||||
|
* and therefore at this point we strip out any rules which start with @typescript-eslint/tslint/config
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
override.files.includes('*.ts') &&
|
||||||
|
!ruleName.startsWith('@angular-eslint/template') &&
|
||||||
|
!ruleName.startsWith('@typescript-eslint/tslint/config')
|
||||||
|
) {
|
||||||
|
// Prioritize the converted rules over any base implementations from the original Nx generator
|
||||||
|
override.rules[ruleName] = ruleConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's possible that there are plugins to apply to the TS override
|
||||||
|
if (json.plugins) {
|
||||||
|
for (const override of json.overrides) {
|
||||||
|
if (override.files.includes('*.ts')) {
|
||||||
|
override.plugins = override.plugins || [];
|
||||||
|
override.plugins = [...override.plugins, ...json.plugins];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete json.plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We now no longer need the flat list of rules at the root of the config
|
||||||
|
* because they have all been applied to an appropriate override.
|
||||||
|
*/
|
||||||
|
delete json.rules;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"id": "angular-convert-tslint-to-eslint",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Convert an Angular project from TSLint to ESLint",
|
||||||
|
"description": "NOTE: Does not work in --dry-run mode",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g convert-tslint-to-eslint myapp",
|
||||||
|
"description": "Convert the Angular project `myapp` from TSLint to ESLint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"description": "The name of the Angular project to convert.",
|
||||||
|
"type": "string",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "Which Angular project would you like to convert from TSLint to ESLint?"
|
||||||
|
},
|
||||||
|
"removeTSLintIfNoMoreTSLintTargets": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration",
|
||||||
|
"default": true,
|
||||||
|
"x-prompt": "Would you like to remove TSLint and its related config if there are no TSLint projects remaining after this conversion?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["project"]
|
||||||
|
}
|
||||||
131
packages/angular/src/schematics/add-linting/add-linting.ts
Executable file
131
packages/angular/src/schematics/add-linting/add-linting.ts
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
import { join, normalize } from '@angular-devkit/core';
|
||||||
|
import { chain, noop, Rule, Tree } from '@angular-devkit/schematics';
|
||||||
|
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
||||||
|
import {
|
||||||
|
addLintFiles,
|
||||||
|
getWorkspacePath,
|
||||||
|
Linter,
|
||||||
|
offsetFromRoot,
|
||||||
|
updateJsonInTree,
|
||||||
|
} from '@nrwl/workspace';
|
||||||
|
import {
|
||||||
|
createAngularEslintJson,
|
||||||
|
createAngularProjectESLintLintTarget,
|
||||||
|
extraEslintDependencies,
|
||||||
|
} from '../../utils/lint';
|
||||||
|
import { Schema } from './schema';
|
||||||
|
|
||||||
|
export default function addLinting(options: Schema): Rule {
|
||||||
|
return chain([
|
||||||
|
addLintFiles(options.projectRoot, options.linter, {
|
||||||
|
onlyGlobal: options.linter === Linter.TsLint, // local lint files are added differently when tslint
|
||||||
|
localConfig:
|
||||||
|
options.linter === Linter.TsLint
|
||||||
|
? undefined
|
||||||
|
: createAngularEslintJson(options.projectRoot, options.prefix),
|
||||||
|
extraPackageDeps:
|
||||||
|
options.linter === Linter.TsLint ? undefined : extraEslintDependencies,
|
||||||
|
}),
|
||||||
|
options.projectType === 'application' && options.linter === Linter.TsLint
|
||||||
|
? updateTsLintConfig(options)
|
||||||
|
: noop(),
|
||||||
|
options.projectType === 'library' && options.linter === Linter.TsLint
|
||||||
|
? updateJsonInTree(`${options.projectRoot}/tslint.json`, (json) => {
|
||||||
|
return {
|
||||||
|
...json,
|
||||||
|
extends: `${offsetFromRoot(options.projectRoot)}tslint.json`,
|
||||||
|
linterOptions: {
|
||||||
|
exclude: ['!**/*'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: noop(),
|
||||||
|
updateProject(options),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTsLintConfig(options: Schema): Rule {
|
||||||
|
return chain([
|
||||||
|
updateJsonInTree('tslint.json', (json) => {
|
||||||
|
if (
|
||||||
|
json.rulesDirectory &&
|
||||||
|
json.rulesDirectory.indexOf('node_modules/codelyzer') === -1
|
||||||
|
) {
|
||||||
|
json.rulesDirectory.push('node_modules/codelyzer');
|
||||||
|
json.rules = {
|
||||||
|
...json.rules,
|
||||||
|
|
||||||
|
'directive-selector': [true, 'attribute', 'app', 'camelCase'],
|
||||||
|
'component-selector': [true, 'element', 'app', 'kebab-case'],
|
||||||
|
'no-conflicting-lifecycle': true,
|
||||||
|
'no-host-metadata-property': true,
|
||||||
|
'no-input-rename': true,
|
||||||
|
'no-inputs-metadata-property': true,
|
||||||
|
'no-output-native': true,
|
||||||
|
'no-output-on-prefix': true,
|
||||||
|
'no-output-rename': true,
|
||||||
|
'no-outputs-metadata-property': true,
|
||||||
|
'template-banana-in-box': true,
|
||||||
|
'template-no-negated-async': true,
|
||||||
|
'use-lifecycle-interface': true,
|
||||||
|
'use-pipe-transform-interface': true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}),
|
||||||
|
updateJsonInTree(`${options.projectRoot}/tslint.json`, (json) => {
|
||||||
|
json.extends = `${offsetFromRoot(options.projectRoot)}tslint.json`;
|
||||||
|
json.linterOptions = {
|
||||||
|
exclude: ['!**/*'],
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProject(options: Schema): Rule {
|
||||||
|
return (host: Tree) => {
|
||||||
|
return chain([
|
||||||
|
updateJsonInTree(getWorkspacePath(host), (json) => {
|
||||||
|
const project = json.projects[options.projectName];
|
||||||
|
|
||||||
|
if (options.linter === Linter.TsLint) {
|
||||||
|
project.architect.lint.options.exclude.push(
|
||||||
|
'!' + join(normalize(options.projectRoot), '**/*')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.projectType === 'application') {
|
||||||
|
project.architect.lint.options.tsConfig = project.architect.lint.options.tsConfig.filter(
|
||||||
|
(path) =>
|
||||||
|
path !==
|
||||||
|
join(normalize(options.projectRoot), 'tsconfig.spec.json') &&
|
||||||
|
path !==
|
||||||
|
join(normalize(options.projectRoot), 'e2e/tsconfig.json')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.projectType === 'library') {
|
||||||
|
project.architect.lint.options.tsConfig = Array.from(
|
||||||
|
new Set(project.architect.lint.options.tsConfig)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.linter === Linter.EsLint) {
|
||||||
|
project.architect.lint = createAngularProjectESLintLintTarget(
|
||||||
|
options.projectRoot
|
||||||
|
);
|
||||||
|
host.delete(`${options.projectRoot}/tslint.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
json.projects[options.projectName] = project;
|
||||||
|
return json;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addLintingGenerator = wrapAngularDevkitSchematic(
|
||||||
|
'@nrwl/angular',
|
||||||
|
'add-linting'
|
||||||
|
);
|
||||||
9
packages/angular/src/schematics/add-linting/schema.d.ts
vendored
Normal file
9
packages/angular/src/schematics/add-linting/schema.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Linter } from '@nrwl/workspace';
|
||||||
|
|
||||||
|
export interface Schema {
|
||||||
|
projectName: string;
|
||||||
|
projectType: 'application' | 'library';
|
||||||
|
projectRoot: string;
|
||||||
|
prefix: string;
|
||||||
|
linter: Linter;
|
||||||
|
}
|
||||||
31
packages/angular/src/schematics/add-linting/schema.json
Normal file
31
packages/angular/src/schematics/add-linting/schema.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"id": "SchematicsAngularAddLinting",
|
||||||
|
"title": "Add linting to an Angular project",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"prefix": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "html-selector",
|
||||||
|
"description": "The prefix to apply to generated selectors."
|
||||||
|
},
|
||||||
|
"projectName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the selected project."
|
||||||
|
},
|
||||||
|
"projectType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["application", "library"]
|
||||||
|
},
|
||||||
|
"projectRoot": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The path to the root of the selected project."
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"description": "The tool to use for running lint checks.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["tslint", "eslint"],
|
||||||
|
"default": "eslint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,9 +24,8 @@ import {
|
|||||||
replaceNodeValue,
|
replaceNodeValue,
|
||||||
updateJsonInTree,
|
updateJsonInTree,
|
||||||
updateWorkspace,
|
updateWorkspace,
|
||||||
addLintFiles,
|
|
||||||
Linter,
|
|
||||||
generateProjectLint,
|
generateProjectLint,
|
||||||
|
Linter,
|
||||||
} from '@nrwl/workspace';
|
} from '@nrwl/workspace';
|
||||||
import { join, normalize } from '@angular-devkit/core';
|
import { join, normalize } from '@angular-devkit/core';
|
||||||
import init from '../init/init';
|
import init from '../init/init';
|
||||||
@ -41,10 +40,6 @@ import {
|
|||||||
updateWorkspaceInTree,
|
updateWorkspaceInTree,
|
||||||
appsDir,
|
appsDir,
|
||||||
} from '@nrwl/workspace/src/utils/ast-utils';
|
} from '@nrwl/workspace/src/utils/ast-utils';
|
||||||
import {
|
|
||||||
createAngularEslintJson,
|
|
||||||
extraEslintDependencies,
|
|
||||||
} from '../../utils/lint';
|
|
||||||
import { names, offsetFromRoot } from '@nrwl/devkit';
|
import { names, offsetFromRoot } from '@nrwl/devkit';
|
||||||
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
||||||
|
|
||||||
@ -417,45 +412,6 @@ function updateComponentSpec(options: NormalizedSchema) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTsLintConfig(options: NormalizedSchema): Rule {
|
|
||||||
return chain([
|
|
||||||
updateJsonInTree('tslint.json', (json) => {
|
|
||||||
if (
|
|
||||||
json.rulesDirectory &&
|
|
||||||
json.rulesDirectory.indexOf('node_modules/codelyzer') === -1
|
|
||||||
) {
|
|
||||||
json.rulesDirectory.push('node_modules/codelyzer');
|
|
||||||
json.rules = {
|
|
||||||
...json.rules,
|
|
||||||
|
|
||||||
'directive-selector': [true, 'attribute', 'app', 'camelCase'],
|
|
||||||
'component-selector': [true, 'element', 'app', 'kebab-case'],
|
|
||||||
'no-conflicting-lifecycle': true,
|
|
||||||
'no-host-metadata-property': true,
|
|
||||||
'no-input-rename': true,
|
|
||||||
'no-inputs-metadata-property': true,
|
|
||||||
'no-output-native': true,
|
|
||||||
'no-output-on-prefix': true,
|
|
||||||
'no-output-rename': true,
|
|
||||||
'no-outputs-metadata-property': true,
|
|
||||||
'template-banana-in-box': true,
|
|
||||||
'template-no-negated-async': true,
|
|
||||||
'use-lifecycle-interface': true,
|
|
||||||
'use-pipe-transform-interface': true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}),
|
|
||||||
updateJsonInTree(`${options.appProjectRoot}/tslint.json`, (json) => {
|
|
||||||
json.extends = `${offsetFromRoot(options.appProjectRoot)}tslint.json`;
|
|
||||||
json.linterOptions = {
|
|
||||||
exclude: ['!**/*'],
|
|
||||||
};
|
|
||||||
return json;
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSchematicFiles(
|
function addSchematicFiles(
|
||||||
appProjectRoot: string,
|
appProjectRoot: string,
|
||||||
options: NormalizedSchema
|
options: NormalizedSchema
|
||||||
@ -488,30 +444,6 @@ function updateProject(options: NormalizedSchema): Rule {
|
|||||||
|
|
||||||
delete fixedProject.architect.test;
|
delete fixedProject.architect.test;
|
||||||
|
|
||||||
if (options.linter === Linter.TsLint) {
|
|
||||||
fixedProject.architect.lint.options.tsConfig = fixedProject.architect.lint.options.tsConfig.filter(
|
|
||||||
(path) =>
|
|
||||||
path !==
|
|
||||||
join(normalize(options.appProjectRoot), 'tsconfig.spec.json') &&
|
|
||||||
path !==
|
|
||||||
join(normalize(options.appProjectRoot), 'e2e/tsconfig.json')
|
|
||||||
);
|
|
||||||
fixedProject.architect.lint.options.exclude.push(
|
|
||||||
'!' + join(normalize(options.appProjectRoot), '**/*')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.linter === Linter.EsLint) {
|
|
||||||
fixedProject.architect.lint.builder = '@nrwl/linter:eslint';
|
|
||||||
fixedProject.architect.lint.options.lintFilePatterns = [
|
|
||||||
`${options.appProjectRoot}/src/**/*.ts`,
|
|
||||||
`${options.appProjectRoot}/src/**/*.html`,
|
|
||||||
];
|
|
||||||
delete fixedProject.architect.lint.options.tsConfig;
|
|
||||||
delete fixedProject.architect.lint.options.exclude;
|
|
||||||
host.delete(`${options.appProjectRoot}/tslint.json`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.unitTestRunner === 'none') {
|
if (options.unitTestRunner === 'none') {
|
||||||
host.delete(
|
host.delete(
|
||||||
`${options.appProjectRoot}/src/app/app.component.spec.ts`
|
`${options.appProjectRoot}/src/app/app.component.spec.ts`
|
||||||
@ -817,18 +749,7 @@ export default function (schema: Schema): Rule {
|
|||||||
updateComponentStyles(options),
|
updateComponentStyles(options),
|
||||||
options.unitTestRunner !== 'none' ? updateComponentSpec(options) : noop(),
|
options.unitTestRunner !== 'none' ? updateComponentSpec(options) : noop(),
|
||||||
options.routing ? addRouterRootConfiguration(options) : noop(),
|
options.routing ? addRouterRootConfiguration(options) : noop(),
|
||||||
addLintFiles(options.appProjectRoot, options.linter, {
|
addLinting(options),
|
||||||
onlyGlobal: options.linter === Linter.TsLint, // local lint files are added differently when tslint
|
|
||||||
localConfig:
|
|
||||||
options.linter === Linter.TsLint
|
|
||||||
? undefined
|
|
||||||
: createAngularEslintJson(options.appProjectRoot, options.prefix),
|
|
||||||
extraPackageDeps:
|
|
||||||
options.linter === Linter.TsLint
|
|
||||||
? undefined
|
|
||||||
: extraEslintDependencies,
|
|
||||||
}),
|
|
||||||
options.linter === 'tslint' ? updateTsLintConfig(options) : noop(),
|
|
||||||
options.unitTestRunner === 'jest'
|
options.unitTestRunner === 'jest'
|
||||||
? externalSchematic('@nrwl/jest', 'jest-project', {
|
? externalSchematic('@nrwl/jest', 'jest-project', {
|
||||||
project: options.name,
|
project: options.name,
|
||||||
@ -860,6 +781,31 @@ export default function (schema: Schema): Rule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addLinting = (options: NormalizedSchema) => () => {
|
||||||
|
return chain([
|
||||||
|
schematic('add-linting', {
|
||||||
|
linter: options.linter,
|
||||||
|
projectType: 'application',
|
||||||
|
projectName: options.name,
|
||||||
|
projectRoot: options.appProjectRoot,
|
||||||
|
prefix: options.prefix,
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* I cannot explain why this extra rule is needed, the add-linting
|
||||||
|
* schematic applies the exact same host.delete() call but the main
|
||||||
|
* chain of this schematic still preserves it...
|
||||||
|
*/
|
||||||
|
(host) => {
|
||||||
|
if (
|
||||||
|
options.linter === Linter.EsLint &&
|
||||||
|
host.exists(`${options.appProjectRoot}/tslint.json`)
|
||||||
|
) {
|
||||||
|
host.delete(`${options.appProjectRoot}/tslint.json`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
|
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
|
||||||
const appDirectory = options.directory
|
const appDirectory = options.directory
|
||||||
? `${names(options.directory).fileName}/${names(options.name).fileName}`
|
? `${names(options.directory).fileName}/${names(options.name).fileName}`
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import {
|
|||||||
} from '@angular-devkit/schematics';
|
} from '@angular-devkit/schematics';
|
||||||
import {
|
import {
|
||||||
getWorkspacePath,
|
getWorkspacePath,
|
||||||
Linter,
|
|
||||||
replaceAppNameWithPath,
|
replaceAppNameWithPath,
|
||||||
updateJsonInTree,
|
updateJsonInTree,
|
||||||
} from '@nrwl/workspace';
|
} from '@nrwl/workspace';
|
||||||
@ -147,28 +146,6 @@ export function updateProject(options: NormalizedSchema): Rule {
|
|||||||
|
|
||||||
delete fixedProject.architect.test;
|
delete fixedProject.architect.test;
|
||||||
|
|
||||||
if (options.linter === Linter.TsLint) {
|
|
||||||
fixedProject.architect.lint.options.tsConfig = fixedProject.architect.lint.options.tsConfig.filter(
|
|
||||||
(path) =>
|
|
||||||
path !==
|
|
||||||
join(normalize(options.projectRoot), 'tsconfig.spec.json')
|
|
||||||
);
|
|
||||||
fixedProject.architect.lint.options.exclude.push(
|
|
||||||
'!' + join(normalize(options.projectRoot), '**/*')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.linter === Linter.EsLint) {
|
|
||||||
fixedProject.architect.lint.builder = '@nrwl/linter:eslint';
|
|
||||||
fixedProject.architect.lint.options.lintFilePatterns = [
|
|
||||||
`${options.projectRoot}/src/**/*.ts`,
|
|
||||||
`${options.projectRoot}/src/**/*.html`,
|
|
||||||
];
|
|
||||||
delete fixedProject.architect.lint.options.tsConfig;
|
|
||||||
delete fixedProject.architect.lint.options.exclude;
|
|
||||||
host.delete(`${options.projectRoot}/tslint.json`);
|
|
||||||
}
|
|
||||||
|
|
||||||
json.projects[options.name] = fixedProject;
|
json.projects[options.name] = fixedProject;
|
||||||
return json;
|
return json;
|
||||||
});
|
});
|
||||||
@ -191,17 +168,6 @@ export function updateProject(options: NormalizedSchema): Rule {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
options.linter === Linter.TsLint
|
|
||||||
? updateJsonInTree(`${options.projectRoot}/tslint.json`, (json) => {
|
|
||||||
return {
|
|
||||||
...json,
|
|
||||||
extends: `${offsetFromRoot(options.projectRoot)}tslint.json`,
|
|
||||||
linterOptions: {
|
|
||||||
exclude: ['!**/*'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: noop(),
|
|
||||||
updateJsonInTree(`/nx.json`, (json) => {
|
updateJsonInTree(`/nx.json`, (json) => {
|
||||||
return {
|
return {
|
||||||
...json,
|
...json,
|
||||||
|
|||||||
@ -1164,6 +1164,7 @@ describe('lib', () => {
|
|||||||
{ name: 'myLib', linter: 'eslint' },
|
{ name: 'myLib', linter: 'eslint' },
|
||||||
appTree
|
appTree
|
||||||
);
|
);
|
||||||
|
expect(tree.exists('libs/my-lib/tslint.json')).toBe(false);
|
||||||
const workspaceJson = readJsonInTree(tree, 'workspace.json');
|
const workspaceJson = readJsonInTree(tree, 'workspace.json');
|
||||||
expect(workspaceJson.projects['my-lib'].architect.lint)
|
expect(workspaceJson.projects['my-lib'].architect.lint)
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
|
|||||||
@ -8,12 +8,7 @@ import {
|
|||||||
SchematicsException,
|
SchematicsException,
|
||||||
Tree,
|
Tree,
|
||||||
} from '@angular-devkit/schematics';
|
} from '@angular-devkit/schematics';
|
||||||
import {
|
import { formatFiles, Linter, updateJsonInTree } from '@nrwl/workspace';
|
||||||
addLintFiles,
|
|
||||||
formatFiles,
|
|
||||||
Linter,
|
|
||||||
updateJsonInTree,
|
|
||||||
} from '@nrwl/workspace';
|
|
||||||
import init, { addUnitTestRunner } from '../init/init';
|
import init, { addUnitTestRunner } from '../init/init';
|
||||||
import { addModule } from './lib/add-module';
|
import { addModule } from './lib/add-module';
|
||||||
import { normalizeOptions } from './lib/normalize-options';
|
import { normalizeOptions } from './lib/normalize-options';
|
||||||
@ -22,11 +17,8 @@ import { updateProject } from './lib/update-project';
|
|||||||
import { updateTsConfig } from './lib/update-tsconfig';
|
import { updateTsConfig } from './lib/update-tsconfig';
|
||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
import { enableStrictTypeChecking } from './lib/enable-strict-type-checking';
|
import { enableStrictTypeChecking } from './lib/enable-strict-type-checking';
|
||||||
import {
|
|
||||||
createAngularEslintJson,
|
|
||||||
extraEslintDependencies,
|
|
||||||
} from '../../utils/lint';
|
|
||||||
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
||||||
|
import { NormalizedSchema } from './lib/normalized-schema';
|
||||||
|
|
||||||
export default function (schema: Schema): Rule {
|
export default function (schema: Schema): Rule {
|
||||||
return (host: Tree): Rule => {
|
return (host: Tree): Rule => {
|
||||||
@ -50,17 +42,6 @@ export default function (schema: Schema): Rule {
|
|||||||
...options,
|
...options,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
}),
|
}),
|
||||||
addLintFiles(options.projectRoot, options.linter, {
|
|
||||||
onlyGlobal: options.linter === Linter.TsLint,
|
|
||||||
localConfig:
|
|
||||||
options.linter === Linter.TsLint
|
|
||||||
? undefined
|
|
||||||
: createAngularEslintJson(options.projectRoot, options.prefix),
|
|
||||||
extraPackageDeps:
|
|
||||||
options.linter === Linter.TsLint
|
|
||||||
? undefined
|
|
||||||
: extraEslintDependencies,
|
|
||||||
}),
|
|
||||||
addUnitTestRunner(options),
|
addUnitTestRunner(options),
|
||||||
// TODO: Remove this after Angular 10.1.0
|
// TODO: Remove this after Angular 10.1.0
|
||||||
updateJsonInTree('tsconfig.json', () => ({
|
updateJsonInTree('tsconfig.json', () => ({
|
||||||
@ -101,10 +82,37 @@ export default function (schema: Schema): Rule {
|
|||||||
: noop(),
|
: noop(),
|
||||||
addModule(options),
|
addModule(options),
|
||||||
options.strict ? enableStrictTypeChecking(options) : noop(),
|
options.strict ? enableStrictTypeChecking(options) : noop(),
|
||||||
|
addLinting(options),
|
||||||
formatFiles(options),
|
formatFiles(options),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addLinting = (options: NormalizedSchema) => () => {
|
||||||
|
return chain([
|
||||||
|
schematic('add-linting', {
|
||||||
|
linter: options.linter,
|
||||||
|
projectType: 'library',
|
||||||
|
projectName: options.name,
|
||||||
|
projectRoot: options.projectRoot,
|
||||||
|
prefix: options.prefix,
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* I cannot explain why this extra rule is needed, the add-linting
|
||||||
|
* schematic applies the exact same host.delete() call but the main
|
||||||
|
* chain of this library schematic still preserves it...
|
||||||
|
*/
|
||||||
|
(host) => {
|
||||||
|
if (
|
||||||
|
options.linter === Linter.EsLint &&
|
||||||
|
host.exists(`${options.projectRoot}/tslint.json`)
|
||||||
|
) {
|
||||||
|
host.delete(`${options.projectRoot}/tslint.json`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
export const libraryGenerator = wrapAngularDevkitSchematic(
|
export const libraryGenerator = wrapAngularDevkitSchematic(
|
||||||
'@nrwl/angular',
|
'@nrwl/angular',
|
||||||
'library'
|
'library'
|
||||||
|
|||||||
@ -10,12 +10,12 @@ Array [
|
|||||||
"/jest.config.js",
|
"/jest.config.js",
|
||||||
"/jest.preset.js",
|
"/jest.preset.js",
|
||||||
"/.eslintrc.json",
|
"/.eslintrc.json",
|
||||||
"/libs/test-ui-lib/.eslintrc.json",
|
|
||||||
"/libs/test-ui-lib/README.md",
|
"/libs/test-ui-lib/README.md",
|
||||||
"/libs/test-ui-lib/tsconfig.lib.json",
|
"/libs/test-ui-lib/tsconfig.lib.json",
|
||||||
"/libs/test-ui-lib/tsconfig.json",
|
"/libs/test-ui-lib/tsconfig.json",
|
||||||
"/libs/test-ui-lib/jest.config.js",
|
"/libs/test-ui-lib/jest.config.js",
|
||||||
"/libs/test-ui-lib/tsconfig.spec.json",
|
"/libs/test-ui-lib/tsconfig.spec.json",
|
||||||
|
"/libs/test-ui-lib/.eslintrc.json",
|
||||||
"/libs/test-ui-lib/src/index.ts",
|
"/libs/test-ui-lib/src/index.ts",
|
||||||
"/libs/test-ui-lib/src/test-setup.ts",
|
"/libs/test-ui-lib/src/test-setup.ts",
|
||||||
"/libs/test-ui-lib/src/lib/test-ui-lib.module.ts",
|
"/libs/test-ui-lib/src/lib/test-ui-lib.module.ts",
|
||||||
|
|||||||
@ -1,5 +1,20 @@
|
|||||||
|
import type { TargetDefinition } from '@angular-devkit/core/src/workspace';
|
||||||
import { angularEslintVersion } from './versions';
|
import { angularEslintVersion } from './versions';
|
||||||
|
|
||||||
|
export function createAngularProjectESLintLintTarget(
|
||||||
|
projectRoot: string
|
||||||
|
): TargetDefinition {
|
||||||
|
return {
|
||||||
|
builder: '@nrwl/linter:eslint',
|
||||||
|
options: {
|
||||||
|
lintFilePatterns: [
|
||||||
|
`${projectRoot}/src/**/*.ts`,
|
||||||
|
`${projectRoot}/src/**/*.html`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const extraEslintDependencies = {
|
export const extraEslintDependencies = {
|
||||||
dependencies: {},
|
dependencies: {},
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { cypressProjectGenerator } from './src/generators/cypress-project/cypress-project';
|
export { cypressProjectGenerator } from './src/generators/cypress-project/cypress-project';
|
||||||
export { cypressInitGenerator } from './src/generators/init/init';
|
export { cypressInitGenerator } from './src/generators/init/init';
|
||||||
|
export { conversionGenerator } from './src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint';
|
||||||
|
|||||||
@ -0,0 +1,282 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Cypress applications 1`] = `
|
||||||
|
Object {
|
||||||
|
"dependencies": Object {},
|
||||||
|
"devDependencies": Object {
|
||||||
|
"@nrwl/eslint-plugin-nx": "*",
|
||||||
|
"@nrwl/linter": "*",
|
||||||
|
"@typescript-eslint/eslint-plugin": "4.3.0",
|
||||||
|
"@typescript-eslint/parser": "4.3.0",
|
||||||
|
"eslint": "7.10.0",
|
||||||
|
"eslint-config-prettier": "8.1.0",
|
||||||
|
"eslint-plugin-cypress": "^2.10.3",
|
||||||
|
"eslint-plugin-import": "latest",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Cypress applications 2`] = `
|
||||||
|
Object {
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/e2e-app-1",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": Object {
|
||||||
|
"lintFilePatterns": Array [
|
||||||
|
"apps/e2e-app-1/**/*.{js,ts}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Cypress applications 3`] = `
|
||||||
|
Object {
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@nrwl/nx/enforce-module-boundaries": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"@nx-example/shared/product/data/testing",
|
||||||
|
],
|
||||||
|
"depConstraints": Array [
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:feature",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:app",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:ui",
|
||||||
|
"type:data",
|
||||||
|
"type:types",
|
||||||
|
"type:state",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:feature",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:types",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:state",
|
||||||
|
"type:types",
|
||||||
|
"type:data",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:state",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:data",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:e2e-utils",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:e2e",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:ui",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:products",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:products",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:cart",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:cart",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/typescript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/javascript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"eslint-plugin-import",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "error",
|
||||||
|
"@typescript-eslint/dot-notation": "off",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": Array [
|
||||||
|
"off",
|
||||||
|
Object {
|
||||||
|
"accessibility": "explicit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "error",
|
||||||
|
"@typescript-eslint/naming-convention": "error",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"ignoreParameters": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "error",
|
||||||
|
"@typescript-eslint/no-shadow": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"hoist": "all",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "error",
|
||||||
|
"@typescript-eslint/prefer-function-type": "error",
|
||||||
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
"arrow-body-style": "error",
|
||||||
|
"constructor-super": "error",
|
||||||
|
"eqeqeq": Array [
|
||||||
|
"error",
|
||||||
|
"smart",
|
||||||
|
],
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"id-blacklist": "off",
|
||||||
|
"id-match": "off",
|
||||||
|
"import/no-deprecated": "warn",
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-console": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"log",
|
||||||
|
"warn",
|
||||||
|
"dir",
|
||||||
|
"timeLog",
|
||||||
|
"assert",
|
||||||
|
"clear",
|
||||||
|
"count",
|
||||||
|
"countReset",
|
||||||
|
"group",
|
||||||
|
"groupEnd",
|
||||||
|
"table",
|
||||||
|
"dirxml",
|
||||||
|
"error",
|
||||||
|
"groupCollapsed",
|
||||||
|
"_buffer",
|
||||||
|
"_counters",
|
||||||
|
"_timers",
|
||||||
|
"_groupDepth",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-empty": "off",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-restricted-imports": Array [
|
||||||
|
"error",
|
||||||
|
"rxjs/Rx",
|
||||||
|
],
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"radix": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"@nrwl/nx",
|
||||||
|
],
|
||||||
|
"root": true,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for Cypress applications 4`] = `
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:cypress/recommended",
|
||||||
|
"../../.eslintrc.json",
|
||||||
|
],
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"!**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": "apps/e2e-app-1/tsconfig.*?.json",
|
||||||
|
},
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"src/plugins/index.js",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
|
"no-undef": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,161 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
joinPathFragments,
|
||||||
|
readJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
writeJson,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||||
|
import { exampleRootTslintJson } from '@nrwl/linter';
|
||||||
|
import { conversionGenerator } from './convert-tslint-to-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't run actual child_process implementation of installPackagesTask()
|
||||||
|
*/
|
||||||
|
jest.mock('child_process');
|
||||||
|
|
||||||
|
const projectName = 'e2e-app-1';
|
||||||
|
const projectRoot = `apps/${projectName}`;
|
||||||
|
const projectTSLintJsonPath = joinPathFragments(projectRoot, 'tslint.json');
|
||||||
|
// Used to configure the test Tree and stub the response from tslint-to-eslint-config util findReportedConfiguration()
|
||||||
|
const projectTslintJsonData = {
|
||||||
|
raw: {
|
||||||
|
extends: '../../tslint.json',
|
||||||
|
rules: {
|
||||||
|
// User custom TS rule
|
||||||
|
'no-empty-interface': true,
|
||||||
|
// User custom rule with no known automated converter
|
||||||
|
'some-super-custom-rule-with-no-converter': true,
|
||||||
|
},
|
||||||
|
linterOptions: {
|
||||||
|
exclude: ['!**/*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tslintPrintConfigResult: {
|
||||||
|
rules: {
|
||||||
|
'no-empty-interface': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'some-super-custom-rule-with-no-converter': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function mockFindReportedConfiguration(_, pathToTslintJson) {
|
||||||
|
switch (pathToTslintJson) {
|
||||||
|
case 'tslint.json':
|
||||||
|
return exampleRootTslintJson.tslintPrintConfigResult;
|
||||||
|
case projectTSLintJsonPath:
|
||||||
|
return projectTslintJsonData.tslintPrintConfigResult;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`mockFindReportedConfiguration - Did not recognize path ${pathToTslintJson}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See ./mock-tslint-to-eslint-config.ts for why this is needed
|
||||||
|
*/
|
||||||
|
jest.mock('tslint-to-eslint-config', () => {
|
||||||
|
return {
|
||||||
|
// Since upgrading to (ts-)jest 26 this usage of this mock has caused issues...
|
||||||
|
// @ts-ignore
|
||||||
|
...jest.requireActual('tslint-to-eslint-config'),
|
||||||
|
findReportedConfiguration: jest.fn(mockFindReportedConfiguration),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock the the mutating fs utilities used within the conversion logic, they are not
|
||||||
|
* needed because of our stubbed response for findReportedConfiguration() above, and
|
||||||
|
* they would cause noise in the git data of the actual Nx repo when the tests run.
|
||||||
|
*/
|
||||||
|
jest.mock('fs', () => {
|
||||||
|
return {
|
||||||
|
// Since upgrading to (ts-)jest 26 this usage of this mock has caused issues...
|
||||||
|
// @ts-ignore
|
||||||
|
...jest.requireActual('fs'),
|
||||||
|
writeFileSync: jest.fn(),
|
||||||
|
mkdirSync: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('convert-tslint-to-eslint', () => {
|
||||||
|
let host: Tree;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
host = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
writeJson(host, 'tslint.json', exampleRootTslintJson.raw);
|
||||||
|
|
||||||
|
addProjectConfiguration(host, projectName, {
|
||||||
|
root: projectRoot,
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - BEFORE CONVERSION
|
||||||
|
*
|
||||||
|
* TSLint executor configured for the project
|
||||||
|
*/
|
||||||
|
lint: {
|
||||||
|
executor: '@angular-devkit/build-angular:tslint',
|
||||||
|
options: {
|
||||||
|
exclude: ['**/node_modules/**', '!apps/e2e-app-1/**/*'],
|
||||||
|
tsConfig: ['apps/e2e-app-1/tsconfig.app.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Existing tslint.json file for the project
|
||||||
|
*/
|
||||||
|
writeJson(host, 'apps/e2e-app-1/tslint.json', projectTslintJsonData.raw);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for Cypress applications', async () => {
|
||||||
|
await conversionGenerator(host, {
|
||||||
|
project: projectName,
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It should ensure the required Nx packages are installed and available
|
||||||
|
*
|
||||||
|
* NOTE: tslint-to-eslint-config should NOT be present
|
||||||
|
*/
|
||||||
|
expect(readJson(host, 'package.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - AFTER CONVERSION
|
||||||
|
*
|
||||||
|
* It should replace the TSLint executor with the ESLint one
|
||||||
|
*/
|
||||||
|
expect(readProjectConfiguration(host, projectName)).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root level .eslintrc.json should now have been generated
|
||||||
|
*/
|
||||||
|
expect(readJson(host, '.eslintrc.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project level .eslintrc.json should now have been generated
|
||||||
|
* and extend from the root, as well as applying any customizations
|
||||||
|
* which are specific to this projectType.
|
||||||
|
*/
|
||||||
|
expect(
|
||||||
|
readJson(host, joinPathFragments(projectRoot, '.eslintrc.json'))
|
||||||
|
).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project's TSLint file should have been deleted
|
||||||
|
*/
|
||||||
|
expect(host.exists(projectTSLintJsonPath)).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
import {
|
||||||
|
convertNxGenerator,
|
||||||
|
formatFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
Tree,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { ConvertTSLintToESLintSchema, ProjectConverter } from '@nrwl/linter';
|
||||||
|
import type { Linter } from 'eslint';
|
||||||
|
import {
|
||||||
|
addLinter,
|
||||||
|
CypressProjectSchema,
|
||||||
|
} from '../cypress-project/cypress-project';
|
||||||
|
|
||||||
|
export async function conversionGenerator(
|
||||||
|
host: Tree,
|
||||||
|
options: ConvertTSLintToESLintSchema
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The ProjectConverter instance encapsulates all the standard operations we need
|
||||||
|
* to perform in order to convert a project from TSLint to ESLint, as well as some
|
||||||
|
* extensibility points for adjusting the behavior on a per package basis.
|
||||||
|
*
|
||||||
|
* E.g. @nrwl/angular projects might need to make different changes to the final
|
||||||
|
* ESLint config when compared with @nrwl/next projects.
|
||||||
|
*
|
||||||
|
* See the ProjectConverter implementation for a full breakdown of what it does.
|
||||||
|
*/
|
||||||
|
const projectConverter = new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName: options.project,
|
||||||
|
eslintInitializer: async ({ projectName, projectConfig }) => {
|
||||||
|
await addLinter(host, {
|
||||||
|
linter: 'eslint',
|
||||||
|
projectName,
|
||||||
|
projectRoot: projectConfig.root,
|
||||||
|
} as CypressProjectSchema);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically install tslint-to-eslint-config to assist with the conversion.
|
||||||
|
*/
|
||||||
|
projectConverter.installTSLintToESLintConfigPackage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the standard (which is applicable to the current package) ESLint setup
|
||||||
|
* for converting the project.
|
||||||
|
*/
|
||||||
|
await projectConverter.initESLint();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the root tslint.json and apply the converted rules to the root .eslintrc.json.
|
||||||
|
*/
|
||||||
|
const rootConfigInstallTask = await projectConverter.convertRootTSLintConfig(
|
||||||
|
(json) => removeCodelyzerRelatedRules(json)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the project's tslint.json to an equivalent ESLint config.
|
||||||
|
*/
|
||||||
|
const projectConfigInstallTask = await projectConverter.convertProjectConfig(
|
||||||
|
(json) => json
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up the original TSLint configuration for the project.
|
||||||
|
*/
|
||||||
|
projectConverter.removeProjectTSLintFile();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store user preference regarding removeTSLintIfNoMoreTSLintTargets for the collection
|
||||||
|
*/
|
||||||
|
projectConverter.setDefaults(
|
||||||
|
'@nrwl/cypress',
|
||||||
|
options.removeTSLintIfNoMoreTSLintTargets
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on user preference and remaining usage, remove TSLint from the workspace entirely.
|
||||||
|
*/
|
||||||
|
let uninstallTSLintTask: GeneratorCallback = () => Promise.resolve(undefined);
|
||||||
|
if (
|
||||||
|
options.removeTSLintIfNoMoreTSLintTargets &&
|
||||||
|
!projectConverter.isTSLintUsedInWorkspace()
|
||||||
|
) {
|
||||||
|
uninstallTSLintTask = projectConverter.removeTSLintFromWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
await formatFiles(host);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
projectConverter.uninstallTSLintToESLintConfigPackage();
|
||||||
|
await rootConfigInstallTask();
|
||||||
|
await projectConfigInstallTask();
|
||||||
|
await uninstallTSLintTask();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const conversionSchematic = convertNxGenerator(conversionGenerator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove any @angular-eslint rules that were applied as a result of converting prior codelyzer
|
||||||
|
* rules, because they are only relevant for Angular projects.
|
||||||
|
*/
|
||||||
|
function removeCodelyzerRelatedRules(json: Linter.Config): Linter.Config {
|
||||||
|
for (const ruleName of Object.keys(json.rules)) {
|
||||||
|
if (ruleName.startsWith('@angular-eslint')) {
|
||||||
|
delete json.rules[ruleName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"id": "cypress-convert-tslint-to-eslint",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Convert a Cypress project from TSLint to ESLint",
|
||||||
|
"description": "NOTE: Does not work in --dry-run mode",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g convert-tslint-to-eslint myapp",
|
||||||
|
"description": "Convert the Cypress project `myapp` from TSLint to ESLint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"description": "The name of the Cypress project to convert.",
|
||||||
|
"type": "string",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "Which Cypress project would you like to convert from TSLint to ESLint?"
|
||||||
|
},
|
||||||
|
"removeTSLintIfNoMoreTSLintTargets": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration",
|
||||||
|
"default": true,
|
||||||
|
"x-prompt": "Would you like to remove TSLint and its related config if there are no TSLint projects remaining after this conversion?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["project"]
|
||||||
|
}
|
||||||
@ -64,7 +64,7 @@ function addProject(host: Tree, options: CypressProjectSchema) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addLinter(host: Tree, options: CypressProjectSchema) {
|
export async function addLinter(host: Tree, options: CypressProjectSchema) {
|
||||||
const installTask = await lintProjectGenerator(host, {
|
const installTask = await lintProjectGenerator(host, {
|
||||||
project: options.projectName,
|
project: options.projectName,
|
||||||
linter: options.linter,
|
linter: options.linter,
|
||||||
|
|||||||
@ -37,7 +37,10 @@ export { parseTargetString } from './src/executors/parse-target-string';
|
|||||||
export { readTargetOptions } from './src/executors/read-target-options';
|
export { readTargetOptions } from './src/executors/read-target-options';
|
||||||
|
|
||||||
export { readJson, writeJson, updateJson } from './src/utils/json';
|
export { readJson, writeJson, updateJson } from './src/utils/json';
|
||||||
export { addDependenciesToPackageJson } from './src/utils/package-json';
|
export {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
removeDependenciesFromPackageJson,
|
||||||
|
} from './src/utils/package-json';
|
||||||
export { installPackagesTask } from './src/tasks/install-packages-task';
|
export { installPackagesTask } from './src/tasks/install-packages-task';
|
||||||
export { names } from './src/utils/names';
|
export { names } from './src/utils/names';
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -47,6 +47,49 @@ export function addDependenciesToPackageJson(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove Dependencies and Dev Dependencies from package.json
|
||||||
|
*
|
||||||
|
* For example, `removeDependenciesFromPackageJson(host, ['react'], ['jest'])`
|
||||||
|
* will remove `react` and `jest` from the dependencies and devDependencies sections of package.json respectively
|
||||||
|
*
|
||||||
|
* @param dependencies Dependencies to be removed from the dependencies section of package.json
|
||||||
|
* @param devDependencies Dependencies to be removed from the devDependencies section of package.json
|
||||||
|
* @returns Callback to uninstall dependencies only if necessary. undefined is returned if changes are not necessary.
|
||||||
|
*/
|
||||||
|
export function removeDependenciesFromPackageJson(
|
||||||
|
host: Tree,
|
||||||
|
dependencies: string[],
|
||||||
|
devDependencies: string[],
|
||||||
|
packageJsonPath: string = 'package.json'
|
||||||
|
): GeneratorCallback {
|
||||||
|
const currentPackageJson = readJson(host, packageJsonPath);
|
||||||
|
|
||||||
|
if (
|
||||||
|
requiresRemovingOfPackages(
|
||||||
|
currentPackageJson,
|
||||||
|
dependencies,
|
||||||
|
devDependencies
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
updateJson(host, packageJsonPath, (json) => {
|
||||||
|
for (const dep of dependencies) {
|
||||||
|
delete json.dependencies[dep];
|
||||||
|
}
|
||||||
|
for (const devDep of devDependencies) {
|
||||||
|
delete json.devDependencies[devDep];
|
||||||
|
}
|
||||||
|
json.dependencies = sortObjectByKeys(json.dependencies);
|
||||||
|
json.devDependencies = sortObjectByKeys(json.devDependencies);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
installPackagesTask(host);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function sortObjectByKeys(obj: unknown) {
|
function sortObjectByKeys(obj: unknown) {
|
||||||
return Object.keys(obj)
|
return Object.keys(obj)
|
||||||
.sort()
|
.sort()
|
||||||
@ -83,3 +126,31 @@ function requiresAddingOfPackages(packageJsonFile, deps, devDeps): boolean {
|
|||||||
|
|
||||||
return needsDepsUpdate || needsDevDepsUpdate;
|
return needsDepsUpdate || needsDevDepsUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies whether the given packageJson dependencies require an update
|
||||||
|
* given the deps & devDeps passed in
|
||||||
|
*/
|
||||||
|
function requiresRemovingOfPackages(
|
||||||
|
packageJsonFile,
|
||||||
|
deps: string[],
|
||||||
|
devDeps: string[]
|
||||||
|
): boolean {
|
||||||
|
let needsDepsUpdate = false;
|
||||||
|
let needsDevDepsUpdate = false;
|
||||||
|
|
||||||
|
packageJsonFile.dependencies = packageJsonFile.dependencies || {};
|
||||||
|
packageJsonFile.devDependencies = packageJsonFile.devDependencies || {};
|
||||||
|
|
||||||
|
if (deps.length > 0) {
|
||||||
|
needsDepsUpdate = deps.some((entry) => packageJsonFile.dependencies[entry]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devDeps.length > 0) {
|
||||||
|
needsDevDepsUpdate = devDeps.some(
|
||||||
|
(entry) => packageJsonFile.devDependencies[entry]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return needsDepsUpdate || needsDevDepsUpdate;
|
||||||
|
}
|
||||||
|
|||||||
@ -10,6 +10,11 @@
|
|||||||
* package.
|
* package.
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es6: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
plugins: ['@angular-eslint'],
|
plugins: ['@angular-eslint'],
|
||||||
extends: ['plugin:@angular-eslint/recommended'],
|
extends: ['plugin:@angular-eslint/recommended'],
|
||||||
rules: {},
|
rules: {},
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export { lintProjectGenerator } from './src/generators/lint-project/lint-project';
|
export { lintProjectGenerator } from './src/generators/lint-project/lint-project';
|
||||||
export { lintInitGenerator } from './src/generators/init/init';
|
export { lintInitGenerator } from './src/generators/init/init';
|
||||||
export { Linter } from './src/generators/utils/linter';
|
export { Linter } from './src/generators/utils/linter';
|
||||||
|
export * from './src/utils/convert-tslint-to-eslint';
|
||||||
|
|||||||
@ -0,0 +1,224 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`convertToESLintConfig() should work for a non-Angular project tslint.json file 1`] = `
|
||||||
|
Object {
|
||||||
|
"convertedESLintConfig": Object {
|
||||||
|
"env": Object {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": "tsconfig.json",
|
||||||
|
"sourceType": "module",
|
||||||
|
},
|
||||||
|
"plugins": Array [
|
||||||
|
"@typescript-eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"ensureESLintPlugins": Array [],
|
||||||
|
"unconvertedTSLintRules": Array [],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convertToESLintConfig() should work for a project tslint.json file 1`] = `
|
||||||
|
Object {
|
||||||
|
"convertedESLintConfig": Object {
|
||||||
|
"env": Object {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": "tsconfig.json",
|
||||||
|
"sourceType": "module",
|
||||||
|
},
|
||||||
|
"plugins": Array [
|
||||||
|
"@angular-eslint/eslint-plugin",
|
||||||
|
"@typescript-eslint",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/component-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "angular-app",
|
||||||
|
"style": "kebab-case",
|
||||||
|
"type": "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/directive-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "angular-app",
|
||||||
|
"style": "camelCase",
|
||||||
|
"type": "attribute",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ensureESLintPlugins": Array [],
|
||||||
|
"unconvertedTSLintRules": Array [],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convertToESLintConfig() should work for a root tslint.json file 1`] = `
|
||||||
|
Object {
|
||||||
|
"convertedESLintConfig": Object {
|
||||||
|
"env": Object {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": "tsconfig.json",
|
||||||
|
"sourceType": "module",
|
||||||
|
},
|
||||||
|
"plugins": Array [
|
||||||
|
"eslint-plugin-import",
|
||||||
|
"@angular-eslint/eslint-plugin",
|
||||||
|
"@angular-eslint/eslint-plugin-template",
|
||||||
|
"@typescript-eslint",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@angular-eslint/component-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "kebab-case",
|
||||||
|
"type": "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/directive-selector": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "camelCase",
|
||||||
|
"type": "attribute",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/no-conflicting-lifecycle": "error",
|
||||||
|
"@angular-eslint/no-host-metadata-property": "error",
|
||||||
|
"@angular-eslint/no-input-rename": "error",
|
||||||
|
"@angular-eslint/no-inputs-metadata-property": "error",
|
||||||
|
"@angular-eslint/no-output-native": "error",
|
||||||
|
"@angular-eslint/no-output-on-prefix": "error",
|
||||||
|
"@angular-eslint/no-output-rename": "error",
|
||||||
|
"@angular-eslint/no-outputs-metadata-property": "error",
|
||||||
|
"@angular-eslint/template/banana-in-box": "error",
|
||||||
|
"@angular-eslint/template/no-negated-async": "error",
|
||||||
|
"@angular-eslint/use-lifecycle-interface": "error",
|
||||||
|
"@angular-eslint/use-pipe-transform-interface": "error",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "error",
|
||||||
|
"@typescript-eslint/dot-notation": "off",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": Array [
|
||||||
|
"off",
|
||||||
|
Object {
|
||||||
|
"accessibility": "explicit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "error",
|
||||||
|
"@typescript-eslint/naming-convention": "error",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"ignoreParameters": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "error",
|
||||||
|
"@typescript-eslint/no-shadow": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"hoist": "all",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "error",
|
||||||
|
"@typescript-eslint/prefer-function-type": "error",
|
||||||
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
"arrow-body-style": "error",
|
||||||
|
"constructor-super": "error",
|
||||||
|
"eqeqeq": Array [
|
||||||
|
"error",
|
||||||
|
"smart",
|
||||||
|
],
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"id-blacklist": "off",
|
||||||
|
"id-match": "off",
|
||||||
|
"import/no-deprecated": "warn",
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-console": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"_buffer",
|
||||||
|
"_counters",
|
||||||
|
"_groupDepth",
|
||||||
|
"_timers",
|
||||||
|
"assert",
|
||||||
|
"clear",
|
||||||
|
"count",
|
||||||
|
"countReset",
|
||||||
|
"dir",
|
||||||
|
"dirxml",
|
||||||
|
"error",
|
||||||
|
"group",
|
||||||
|
"groupCollapsed",
|
||||||
|
"groupEnd",
|
||||||
|
"log",
|
||||||
|
"table",
|
||||||
|
"timeLog",
|
||||||
|
"warn",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-empty": "off",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-restricted-imports": Array [
|
||||||
|
"error",
|
||||||
|
"rxjs/Rx",
|
||||||
|
],
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"radix": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ensureESLintPlugins": Array [
|
||||||
|
"eslint-plugin-import",
|
||||||
|
],
|
||||||
|
"unconvertedTSLintRules": Array [],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convertToESLintConfig() should work for an e2e project tslint.json file 1`] = `
|
||||||
|
Object {
|
||||||
|
"convertedESLintConfig": Object {
|
||||||
|
"env": Object {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": "tsconfig.json",
|
||||||
|
"sourceType": "module",
|
||||||
|
},
|
||||||
|
"plugins": Array [
|
||||||
|
"@typescript-eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"ensureESLintPlugins": Array [],
|
||||||
|
"unconvertedTSLintRules": Array [],
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,336 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`ProjectConverter removeTSLintFromWorkspace() should remove all relevant traces of TSLint from the workspace 1`] = `
|
||||||
|
Object {
|
||||||
|
"dependencies": Object {},
|
||||||
|
"devDependencies": Object {
|
||||||
|
"codelyzer": "latest",
|
||||||
|
"tslint": "latest",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter removeTSLintFromWorkspace() should remove all relevant traces of TSLint from the workspace 2`] = `
|
||||||
|
Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular": Object {
|
||||||
|
"application": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
"library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projects": Object {
|
||||||
|
"foo": Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular:application": Object {
|
||||||
|
"e2eTestRunner": "cypress",
|
||||||
|
"linter": "eslint",
|
||||||
|
"unitTestRunner": "jest",
|
||||||
|
},
|
||||||
|
"@nrwl/angular:library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/foo",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": Object {
|
||||||
|
"exclude": Array [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"!apps/foo/**/*",
|
||||||
|
],
|
||||||
|
"tsConfig": Array [
|
||||||
|
"apps/foo/tsconfig.app.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter removeTSLintFromWorkspace() should remove all relevant traces of TSLint from the workspace 3`] = `
|
||||||
|
Object {
|
||||||
|
"dependencies": Object {},
|
||||||
|
"devDependencies": Object {},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter removeTSLintFromWorkspace() should remove all relevant traces of TSLint from the workspace 4`] = `
|
||||||
|
Object {
|
||||||
|
"projects": Object {
|
||||||
|
"foo": Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular:application": Object {
|
||||||
|
"e2eTestRunner": "cypress",
|
||||||
|
"unitTestRunner": "jest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/foo",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": Object {
|
||||||
|
"exclude": Array [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"!apps/foo/**/*",
|
||||||
|
],
|
||||||
|
"tsConfig": Array [
|
||||||
|
"apps/foo/tsconfig.app.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter removeTSLintFromWorkspace() should remove the entry in generators for convert-tslint-to-eslint because it is no longer needed 1`] = `
|
||||||
|
Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular": Object {
|
||||||
|
"convert-tslint-to-eslint": Object {
|
||||||
|
"removeTSLintIfNoMoreTSLintTargets": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projects": Object {
|
||||||
|
"foo": Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular:application": Object {
|
||||||
|
"e2eTestRunner": "cypress",
|
||||||
|
"linter": "eslint",
|
||||||
|
"unitTestRunner": "jest",
|
||||||
|
},
|
||||||
|
"@nrwl/angular:library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/foo",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": Object {
|
||||||
|
"exclude": Array [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"!apps/foo/**/*",
|
||||||
|
],
|
||||||
|
"tsConfig": Array [
|
||||||
|
"apps/foo/tsconfig.app.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter removeTSLintFromWorkspace() should remove the entry in generators for convert-tslint-to-eslint because it is no longer needed 2`] = `
|
||||||
|
Object {
|
||||||
|
"projects": Object {
|
||||||
|
"foo": Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular:application": Object {
|
||||||
|
"e2eTestRunner": "cypress",
|
||||||
|
"unitTestRunner": "jest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/foo",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": Object {
|
||||||
|
"exclude": Array [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"!apps/foo/**/*",
|
||||||
|
],
|
||||||
|
"tsConfig": Array [
|
||||||
|
"apps/foo/tsconfig.app.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter setDefaults() should set the default configuration for removeTSLintIfNoMoreTSLintTargets in workspace.json 1`] = `
|
||||||
|
Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular": Object {
|
||||||
|
"application": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
"library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projects": Object {
|
||||||
|
"foo": Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular:application": Object {
|
||||||
|
"e2eTestRunner": "cypress",
|
||||||
|
"linter": "eslint",
|
||||||
|
"unitTestRunner": "jest",
|
||||||
|
},
|
||||||
|
"@nrwl/angular:library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/foo",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": Object {
|
||||||
|
"exclude": Array [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"!apps/foo/**/*",
|
||||||
|
],
|
||||||
|
"tsConfig": Array [
|
||||||
|
"apps/foo/tsconfig.app.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter setDefaults() should set the default configuration for removeTSLintIfNoMoreTSLintTargets in workspace.json 2`] = `
|
||||||
|
Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular": Object {
|
||||||
|
"application": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
"convert-tslint-to-eslint": Object {
|
||||||
|
"removeTSLintIfNoMoreTSLintTargets": true,
|
||||||
|
},
|
||||||
|
"library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projects": Object {
|
||||||
|
"foo": Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular:application": Object {
|
||||||
|
"e2eTestRunner": "cypress",
|
||||||
|
"linter": "eslint",
|
||||||
|
"unitTestRunner": "jest",
|
||||||
|
},
|
||||||
|
"@nrwl/angular:library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/foo",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": Object {
|
||||||
|
"exclude": Array [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"!apps/foo/**/*",
|
||||||
|
],
|
||||||
|
"tsConfig": Array [
|
||||||
|
"apps/foo/tsconfig.app.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter setDefaults() should set the default configuration for removeTSLintIfNoMoreTSLintTargets in workspace.json 3`] = `
|
||||||
|
Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular": Object {
|
||||||
|
"application": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
"convert-tslint-to-eslint": Object {
|
||||||
|
"removeTSLintIfNoMoreTSLintTargets": false,
|
||||||
|
},
|
||||||
|
"library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projects": Object {
|
||||||
|
"foo": Object {
|
||||||
|
"generators": Object {
|
||||||
|
"@nrwl/angular:application": Object {
|
||||||
|
"e2eTestRunner": "cypress",
|
||||||
|
"linter": "eslint",
|
||||||
|
"unitTestRunner": "jest",
|
||||||
|
},
|
||||||
|
"@nrwl/angular:library": Object {
|
||||||
|
"linter": "tslint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/foo",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": Object {
|
||||||
|
"exclude": Array [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"!apps/foo/**/*",
|
||||||
|
],
|
||||||
|
"tsConfig": Array [
|
||||||
|
"apps/foo/tsconfig.app.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter should throw if --dry-run is set 1`] = `
|
||||||
|
"NOTE: This generator does not support --dry-run. If you are running this in Nx Console, it should execute fine once you hit the \\"Run\\" button.
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter should throw if --dryRun is set 1`] = `
|
||||||
|
"NOTE: This generator does not support --dry-run. If you are running this in Nx Console, it should execute fine once you hit the \\"Run\\" button.
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter should throw if -d is set 1`] = `
|
||||||
|
"NOTE: This generator does not support --dry-run. If you are running this in Nx Console, it should execute fine once you hit the \\"Run\\" button.
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter should throw if no project tslint.json is found 1`] = `"We could not find a tslint.json for the selected project \\"apps/foo/tslint.json\\", maybe you have already migrated to ESLint?"`;
|
||||||
|
|
||||||
|
exports[`ProjectConverter should throw if no root tslint.json is found 1`] = `"We could not find a tslint.json at the root of your workspace, maybe you have already migrated to ESLint?"`;
|
||||||
@ -0,0 +1,166 @@
|
|||||||
|
import { convertTslintNxRuleToEslintNxRule } from './convert-nx-enforce-module-boundaries-rule';
|
||||||
|
|
||||||
|
describe('convertTslintNxRuleToEslintNxRule()', () => {
|
||||||
|
const configFromNxExamplesRepo = {
|
||||||
|
allow: ['@nx-example/shared/product/data/testing'],
|
||||||
|
depConstraints: [
|
||||||
|
{
|
||||||
|
sourceTag: 'type:app',
|
||||||
|
onlyDependOnLibsWithTags: ['type:feature', 'type:ui'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:feature',
|
||||||
|
onlyDependOnLibsWithTags: [
|
||||||
|
'type:ui',
|
||||||
|
'type:data',
|
||||||
|
'type:types',
|
||||||
|
'type:state',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:types',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:state',
|
||||||
|
onlyDependOnLibsWithTags: ['type:state', 'type:types', 'type:data'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:data',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:e2e',
|
||||||
|
onlyDependOnLibsWithTags: ['type:e2e-utils'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:ui',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types', 'type:ui'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:products',
|
||||||
|
onlyDependOnLibsWithTags: ['scope:products', 'scope:shared'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:cart',
|
||||||
|
onlyDependOnLibsWithTags: ['scope:cart', 'scope:shared'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
tslintJson: {},
|
||||||
|
// Should return null if no existing config found
|
||||||
|
expected: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Real usage in nx-examples repo
|
||||||
|
tslintJson: {
|
||||||
|
rules: {
|
||||||
|
'nx-enforce-module-boundaries': [true, configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
ruleName: '@nrwl/nx/enforce-module-boundaries',
|
||||||
|
ruleConfig: ['error', configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Should respect boolean
|
||||||
|
tslintJson: {
|
||||||
|
defaultSeverity: 'warning',
|
||||||
|
rules: {
|
||||||
|
'nx-enforce-module-boundaries': [false, configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
ruleName: '@nrwl/nx/enforce-module-boundaries',
|
||||||
|
ruleConfig: ['off', configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Should respect boolean + defaultSeverity format
|
||||||
|
tslintJson: {
|
||||||
|
defaultSeverity: 'warning',
|
||||||
|
rules: {
|
||||||
|
'nx-enforce-module-boundaries': [true, configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
ruleName: '@nrwl/nx/enforce-module-boundaries',
|
||||||
|
ruleConfig: ['warn', configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Should respect object format
|
||||||
|
tslintJson: {
|
||||||
|
rules: {
|
||||||
|
'nx-enforce-module-boundaries': {
|
||||||
|
severity: 'error',
|
||||||
|
options: [configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
ruleName: '@nrwl/nx/enforce-module-boundaries',
|
||||||
|
ruleConfig: ['error', configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Should respect object format
|
||||||
|
tslintJson: {
|
||||||
|
rules: {
|
||||||
|
'nx-enforce-module-boundaries': {
|
||||||
|
severity: 'warning',
|
||||||
|
options: [configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
ruleName: '@nrwl/nx/enforce-module-boundaries',
|
||||||
|
ruleConfig: ['warn', configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Should respect object format
|
||||||
|
tslintJson: {
|
||||||
|
rules: {
|
||||||
|
'nx-enforce-module-boundaries': {
|
||||||
|
severity: 'off',
|
||||||
|
options: [configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
ruleName: '@nrwl/nx/enforce-module-boundaries',
|
||||||
|
ruleConfig: ['off', configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Should respect object format + defaultSeverity option
|
||||||
|
tslintJson: {
|
||||||
|
defaultSeverity: 'warning',
|
||||||
|
rules: {
|
||||||
|
'nx-enforce-module-boundaries': {
|
||||||
|
severity: 'default',
|
||||||
|
options: [configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
ruleName: '@nrwl/nx/enforce-module-boundaries',
|
||||||
|
ruleConfig: ['warn', configFromNxExamplesRepo],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach((tc, i) => {
|
||||||
|
it(`should appropriately convert the nx-enforce-module-boundaries rule usage from TSLint, CASE ${i}`, () => {
|
||||||
|
expect(convertTslintNxRuleToEslintNxRule(tc.tslintJson)).toEqual(
|
||||||
|
tc.expected
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
import type { ESLintRuleSeverity } from 'tslint-to-eslint-config';
|
||||||
|
|
||||||
|
type TSLintRuleSeverity = 'default' | 'warning' | 'error' | 'off' | boolean;
|
||||||
|
type TSLintRuleSeverityNonDefaultString = Exclude<
|
||||||
|
TSLintRuleSeverity,
|
||||||
|
boolean | 'default'
|
||||||
|
>;
|
||||||
|
|
||||||
|
function convertTSLintRuleSeverity(
|
||||||
|
tslintConfig: any,
|
||||||
|
tslintSeverity: TSLintRuleSeverity
|
||||||
|
): ESLintRuleSeverity {
|
||||||
|
if (tslintSeverity === true) {
|
||||||
|
tslintSeverity = 'default';
|
||||||
|
}
|
||||||
|
if (tslintSeverity === false) {
|
||||||
|
tslintSeverity = 'off';
|
||||||
|
}
|
||||||
|
if (tslintSeverity === 'default') {
|
||||||
|
tslintSeverity = tslintConfig.defaultSeverity || 'error';
|
||||||
|
}
|
||||||
|
const narrowedTslintSeverity = tslintSeverity as TSLintRuleSeverityNonDefaultString;
|
||||||
|
return narrowedTslintSeverity === 'warning' ? 'warn' : narrowedTslintSeverity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NX_TSLINT_RULE_NAME = 'nx-enforce-module-boundaries';
|
||||||
|
|
||||||
|
export function convertTslintNxRuleToEslintNxRule(
|
||||||
|
tslintJson: Record<string, unknown>
|
||||||
|
): {
|
||||||
|
ruleName: string;
|
||||||
|
ruleConfig: [ESLintRuleSeverity, Record<string, unknown>];
|
||||||
|
} | null {
|
||||||
|
/**
|
||||||
|
* TSLint supports a number of different formats for rule configuration
|
||||||
|
*/
|
||||||
|
const existingRuleDefinition = tslintJson?.rules?.[NX_TSLINT_RULE_NAME];
|
||||||
|
if (!existingRuleDefinition) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let existingRuleSeverity: TSLintRuleSeverity = 'error';
|
||||||
|
let existingRuleConfig = {
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
allow: [],
|
||||||
|
depConstraints: [
|
||||||
|
{
|
||||||
|
sourceTag: '*',
|
||||||
|
onlyDependOnLibsWithTags: ['*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Array.isArray(existingRuleDefinition)) {
|
||||||
|
existingRuleSeverity = existingRuleDefinition[0];
|
||||||
|
existingRuleConfig = existingRuleDefinition[1];
|
||||||
|
} else if (
|
||||||
|
typeof existingRuleDefinition === 'object' &&
|
||||||
|
existingRuleDefinition.severity
|
||||||
|
) {
|
||||||
|
existingRuleSeverity = existingRuleDefinition.severity;
|
||||||
|
if (
|
||||||
|
Array.isArray(existingRuleDefinition.options) &&
|
||||||
|
existingRuleDefinition.options[0]
|
||||||
|
) {
|
||||||
|
existingRuleConfig = existingRuleDefinition.options[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleSeverity: ESLintRuleSeverity = convertTSLintRuleSeverity(
|
||||||
|
tslintJson,
|
||||||
|
existingRuleSeverity
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ruleName: '@nrwl/nx/enforce-module-boundaries',
|
||||||
|
ruleConfig: [ruleSeverity, existingRuleConfig],
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
import {
|
||||||
|
exampleE2eProjectTslintJson,
|
||||||
|
exampleAngularProjectTslintJson,
|
||||||
|
exampleRootTslintJson,
|
||||||
|
exampleNonAngularProjectTslintJson,
|
||||||
|
} from './example-tslint-configs';
|
||||||
|
import { convertToESLintConfig } from './convert-to-eslint-config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual `findReportedConfiguration()` function is used to execute
|
||||||
|
* `tslint --print-config` in a child process and read from the real
|
||||||
|
* file system. This won't work for us in tests where we are dealing
|
||||||
|
* with a Tree, so we mock out the responses from `findReportedConfiguration()`
|
||||||
|
* with previously captured result data from that same command.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function mockFindReportedConfiguration(_, pathToTSLintJson) {
|
||||||
|
if (
|
||||||
|
pathToTSLintJson === 'tslint.json' ||
|
||||||
|
pathToTSLintJson === '/tslint.json'
|
||||||
|
) {
|
||||||
|
return exampleRootTslintJson.tslintPrintConfigResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
pathToTSLintJson === 'apps/app1/tslint.json' ||
|
||||||
|
pathToTSLintJson === 'libs/lib1/tslint.json'
|
||||||
|
) {
|
||||||
|
return exampleAngularProjectTslintJson.tslintPrintConfigResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathToTSLintJson === 'apps/app1-e2e/tslint.json') {
|
||||||
|
return exampleE2eProjectTslintJson.tslintPrintConfigResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathToTSLintJson === 'apps/app2/tslint.json') {
|
||||||
|
return exampleNonAngularProjectTslintJson.tslintPrintConfigResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`${pathToTSLintJson} is not a part of the supported mock data for these tests`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See ./mock-tslint-to-eslint-config.ts for why this is needed
|
||||||
|
*/
|
||||||
|
jest.mock('tslint-to-eslint-config', () => {
|
||||||
|
return {
|
||||||
|
// Since upgrading to (ts-)jest 26 this usage of this mock has caused issues...
|
||||||
|
// @ts-ignore
|
||||||
|
...jest.requireActual('tslint-to-eslint-config'),
|
||||||
|
findReportedConfiguration: jest.fn(mockFindReportedConfiguration),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('convertToESLintConfig()', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for a root tslint.json file', async () => {
|
||||||
|
const converted = await convertToESLintConfig(
|
||||||
|
'tslint.json',
|
||||||
|
exampleRootTslintJson.raw,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
// Ensure no-console snapshot is deterministic
|
||||||
|
converted.convertedESLintConfig.rules['no-console'][1].allow.sort();
|
||||||
|
expect(converted).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for a project tslint.json file', async () => {
|
||||||
|
await expect(
|
||||||
|
convertToESLintConfig(
|
||||||
|
'apps/app1/tslint.json',
|
||||||
|
exampleAngularProjectTslintJson.raw,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
).resolves.toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for an e2e project tslint.json file', async () => {
|
||||||
|
await expect(
|
||||||
|
convertToESLintConfig(
|
||||||
|
'apps/app1-e2e/tslint.json',
|
||||||
|
exampleE2eProjectTslintJson.raw,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
).resolves.toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for a non-Angular project tslint.json file', async () => {
|
||||||
|
await expect(
|
||||||
|
convertToESLintConfig(
|
||||||
|
'apps/app2/tslint.json',
|
||||||
|
exampleNonAngularProjectTslintJson.raw,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
).resolves.toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,151 @@
|
|||||||
|
import type { Linter as ESLintLinter } from 'eslint';
|
||||||
|
import { mkdirSync, writeFileSync } from 'fs';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import type {
|
||||||
|
TSLintRuleOptions,
|
||||||
|
createESLintConfiguration as CreateESLintConfiguration,
|
||||||
|
} from 'tslint-to-eslint-config';
|
||||||
|
|
||||||
|
export async function convertToESLintConfig(
|
||||||
|
pathToTslintJson: string,
|
||||||
|
tslintJson: Record<string, unknown>,
|
||||||
|
ignoreExtendsVals: string[]
|
||||||
|
): Promise<{
|
||||||
|
convertedESLintConfig: ESLintLinter.Config;
|
||||||
|
unconvertedTSLintRules: TSLintRuleOptions[];
|
||||||
|
ensureESLintPlugins: string[];
|
||||||
|
}> {
|
||||||
|
/**
|
||||||
|
* We need to avoid a direct dependency on tslint-to-eslint-config
|
||||||
|
* and ensure we are only resolving the dependency from the user's
|
||||||
|
* node_modules on demand (it will be installed as part of the
|
||||||
|
* conversion generator).
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
createESLintConfiguration,
|
||||||
|
findReportedConfiguration,
|
||||||
|
joinConfigConversionResults,
|
||||||
|
} = require('tslint-to-eslint-config');
|
||||||
|
|
||||||
|
const updatedTSLintJson = tslintJson;
|
||||||
|
/**
|
||||||
|
* If ignoreExtendsVals are provided, strip them from the config
|
||||||
|
* and commit the result to disk per the notes below.
|
||||||
|
*/
|
||||||
|
if (ignoreExtendsVals.length && updatedTSLintJson.extends) {
|
||||||
|
if (
|
||||||
|
typeof updatedTSLintJson.extends === 'string' &&
|
||||||
|
ignoreExtendsVals.includes(updatedTSLintJson.extends)
|
||||||
|
) {
|
||||||
|
delete updatedTSLintJson.extends;
|
||||||
|
}
|
||||||
|
if (Array.isArray(updatedTSLintJson.extends)) {
|
||||||
|
updatedTSLintJson.extends = updatedTSLintJson.extends.filter(
|
||||||
|
(ext) => !ignoreExtendsVals.includes(ext)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The reasons we need to interact with the filesystem here:
|
||||||
|
*
|
||||||
|
* 1) The result of the tslint CLI flag `--print-config` is needed for the
|
||||||
|
* conversion process, and unfortunately no equivalent Node API was ever
|
||||||
|
* added to tslint, so the tslint CLI needs to always read from disk.
|
||||||
|
*
|
||||||
|
* 2) When converting project configs, we need to strip the extends path
|
||||||
|
* which corresponds to the workspace's root config, otherwise all of the
|
||||||
|
* root config's rules will be included in the resultant eslint config for
|
||||||
|
* the project. The interaction with the filesystem is needed because of
|
||||||
|
* point (1) above - we need to strip the relevant extends and commit that
|
||||||
|
* change to disk before the tslint CLI reads the config file.
|
||||||
|
*/
|
||||||
|
mkdirSync(dirname(pathToTslintJson), { recursive: true });
|
||||||
|
writeFileSync(pathToTslintJson, JSON.stringify(updatedTSLintJson));
|
||||||
|
}
|
||||||
|
const reportedConfiguration = await findReportedConfiguration(
|
||||||
|
'npx tslint --print-config',
|
||||||
|
pathToTslintJson
|
||||||
|
);
|
||||||
|
|
||||||
|
if (reportedConfiguration instanceof Error) {
|
||||||
|
if (
|
||||||
|
reportedConfiguration.message.includes('unknown option `--print-config')
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'\nError: TSLint v5.18 required in order to run this schematic. Please update your version and try again.\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Make a print-config issue easier to understand for the end user.
|
||||||
|
* This error could occur if, for example, the user does not have a TSLint plugin installed correctly that they
|
||||||
|
* reference in their config.
|
||||||
|
*/
|
||||||
|
const printConfigFailureMessageStart =
|
||||||
|
'Command failed: npx tslint --print-config "tslint.json"';
|
||||||
|
if (
|
||||||
|
reportedConfiguration.message.startsWith(printConfigFailureMessageStart)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`\nThere was a critical error when trying to inspect your tslint.json: \n${reportedConfiguration.message.replace(
|
||||||
|
printConfigFailureMessageStart,
|
||||||
|
''
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unexpected error: ${reportedConfiguration.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalConfigurations = {
|
||||||
|
tslint: {
|
||||||
|
full: reportedConfiguration,
|
||||||
|
raw: updatedTSLintJson,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const summarizedConfiguration = await (createESLintConfiguration as typeof CreateESLintConfiguration)(
|
||||||
|
originalConfigurations
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We are expecting it to not find a converter for nx-enforce-module-boundaries
|
||||||
|
* and we will explicitly replace it with the ESLint equivalent ourselves.
|
||||||
|
*/
|
||||||
|
if (summarizedConfiguration.missing) {
|
||||||
|
summarizedConfiguration.missing = summarizedConfiguration.missing.filter(
|
||||||
|
(missingRuleData) =>
|
||||||
|
missingRuleData.ruleName !== 'nx-enforce-module-boundaries'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are already covered by our extraEslintDependencies which get installed by the schematic
|
||||||
|
const expectedESLintPlugins = [
|
||||||
|
'@angular-eslint/eslint-plugin',
|
||||||
|
'@angular-eslint/eslint-plugin-template',
|
||||||
|
];
|
||||||
|
|
||||||
|
const convertedESLintConfig = joinConfigConversionResults(
|
||||||
|
summarizedConfiguration,
|
||||||
|
originalConfigurations
|
||||||
|
) as ESLintLinter.Config;
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(convertedESLintConfig.extends) &&
|
||||||
|
convertedESLintConfig.extends.length
|
||||||
|
) {
|
||||||
|
// Ignore any tslint-to-eslint-config default extends that do not apply to Nx
|
||||||
|
convertedESLintConfig.extends = convertedESLintConfig.extends.filter(
|
||||||
|
(ext) => !ext.startsWith('prettier')
|
||||||
|
);
|
||||||
|
if (convertedESLintConfig.extends.length === 0) {
|
||||||
|
delete convertedESLintConfig.extends;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
convertedESLintConfig,
|
||||||
|
unconvertedTSLintRules: summarizedConfiguration.missing,
|
||||||
|
ensureESLintPlugins: Array.from(summarizedConfiguration.plugins).filter(
|
||||||
|
(pluginName) => !expectedESLintPlugins.includes(pluginName)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,324 @@
|
|||||||
|
// Based on latest Angular project root tslint.json + enforce-module-boundaries config from nx-examples
|
||||||
|
export const exampleRootTslintJson = {
|
||||||
|
raw: {
|
||||||
|
rulesDirectory: [
|
||||||
|
'node_modules/@nrwl/workspace/src/tslint',
|
||||||
|
'node_modules/codelyzer',
|
||||||
|
],
|
||||||
|
linterOptions: {
|
||||||
|
exclude: ['**/*'],
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'arrow-return-shorthand': true,
|
||||||
|
'callable-types': true,
|
||||||
|
'class-name': true,
|
||||||
|
deprecation: {
|
||||||
|
severity: 'warn',
|
||||||
|
},
|
||||||
|
forin: true,
|
||||||
|
'import-blacklist': [true, 'rxjs/Rx'],
|
||||||
|
'interface-over-type-literal': true,
|
||||||
|
'member-access': false,
|
||||||
|
'member-ordering': [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
order: [
|
||||||
|
'static-field',
|
||||||
|
'instance-field',
|
||||||
|
'static-method',
|
||||||
|
'instance-method',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-arg': true,
|
||||||
|
'no-bitwise': true,
|
||||||
|
'no-console': [true, 'debug', 'info', 'time', 'timeEnd', 'trace'],
|
||||||
|
'no-construct': true,
|
||||||
|
'no-debugger': true,
|
||||||
|
'no-duplicate-super': true,
|
||||||
|
'no-empty': false,
|
||||||
|
'no-empty-interface': true,
|
||||||
|
'no-eval': true,
|
||||||
|
'no-inferrable-types': [true, 'ignore-params'],
|
||||||
|
'no-misused-new': true,
|
||||||
|
'no-non-null-assertion': true,
|
||||||
|
'no-shadowed-variable': true,
|
||||||
|
'no-string-literal': false,
|
||||||
|
'no-string-throw': true,
|
||||||
|
'no-switch-case-fall-through': true,
|
||||||
|
'no-unnecessary-initializer': true,
|
||||||
|
'no-unused-expression': true,
|
||||||
|
'no-var-keyword': true,
|
||||||
|
'object-literal-sort-keys': false,
|
||||||
|
'prefer-const': true,
|
||||||
|
radix: true,
|
||||||
|
'triple-equals': [true, 'allow-null-check'],
|
||||||
|
'unified-signatures': true,
|
||||||
|
'variable-name': false,
|
||||||
|
'nx-enforce-module-boundaries': [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
allow: ['@nx-example/shared/product/data/testing'],
|
||||||
|
depConstraints: [
|
||||||
|
{
|
||||||
|
sourceTag: 'type:app',
|
||||||
|
onlyDependOnLibsWithTags: ['type:feature', 'type:ui'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:feature',
|
||||||
|
onlyDependOnLibsWithTags: [
|
||||||
|
'type:ui',
|
||||||
|
'type:data',
|
||||||
|
'type:types',
|
||||||
|
'type:state',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:types',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:state',
|
||||||
|
onlyDependOnLibsWithTags: [
|
||||||
|
'type:state',
|
||||||
|
'type:types',
|
||||||
|
'type:data',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:data',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:e2e',
|
||||||
|
onlyDependOnLibsWithTags: ['type:e2e-utils'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:ui',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types', 'type:ui'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:products',
|
||||||
|
onlyDependOnLibsWithTags: ['scope:products', 'scope:shared'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:cart',
|
||||||
|
onlyDependOnLibsWithTags: ['scope:cart', 'scope:shared'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'directive-selector': [true, 'attribute', 'app', 'camelCase'],
|
||||||
|
'component-selector': [true, 'element', 'app', 'kebab-case'],
|
||||||
|
'no-conflicting-lifecycle': true,
|
||||||
|
'no-host-metadata-property': true,
|
||||||
|
'no-input-rename': true,
|
||||||
|
'no-inputs-metadata-property': true,
|
||||||
|
'no-output-native': true,
|
||||||
|
'no-output-on-prefix': true,
|
||||||
|
'no-output-rename': true,
|
||||||
|
'no-outputs-metadata-property': true,
|
||||||
|
'template-banana-in-box': true,
|
||||||
|
'template-no-negated-async': true,
|
||||||
|
'use-lifecycle-interface': true,
|
||||||
|
'use-pipe-transform-interface': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tslintPrintConfigResult: {
|
||||||
|
rules: {
|
||||||
|
'arrow-return-shorthand': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'callable-types': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'class-name': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
deprecation: { ruleSeverity: 'warning' },
|
||||||
|
forin: { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'import-blacklist': { ruleArguments: ['rxjs/Rx'], ruleSeverity: 'error' },
|
||||||
|
'interface-over-type-literal': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'member-access': { ruleArguments: [], ruleSeverity: 'off' },
|
||||||
|
'member-ordering': {
|
||||||
|
ruleArguments: [
|
||||||
|
{
|
||||||
|
order: [
|
||||||
|
'static-field',
|
||||||
|
'instance-field',
|
||||||
|
'static-method',
|
||||||
|
'instance-method',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'no-arg': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-bitwise': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-console': {
|
||||||
|
ruleArguments: ['debug', 'info', 'time', 'timeEnd', 'trace'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'no-construct': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-debugger': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-duplicate-super': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-empty': { ruleArguments: [], ruleSeverity: 'off' },
|
||||||
|
'no-empty-interface': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-eval': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-inferrable-types': {
|
||||||
|
ruleArguments: ['ignore-params'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'no-misused-new': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-non-null-assertion': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-shadowed-variable': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-string-literal': { ruleArguments: [], ruleSeverity: 'off' },
|
||||||
|
'no-string-throw': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-switch-case-fall-through': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'no-unnecessary-initializer': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'no-unused-expression': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-var-keyword': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'object-literal-sort-keys': { ruleArguments: [], ruleSeverity: 'off' },
|
||||||
|
'prefer-const': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
radix: { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'triple-equals': {
|
||||||
|
ruleArguments: ['allow-null-check'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'unified-signatures': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'variable-name': { ruleArguments: [], ruleSeverity: 'off' },
|
||||||
|
'nx-enforce-module-boundaries': {
|
||||||
|
ruleArguments: [
|
||||||
|
{
|
||||||
|
allow: ['@nx-example/shared/product/data/testing'],
|
||||||
|
depConstraints: [
|
||||||
|
{
|
||||||
|
sourceTag: 'type:app',
|
||||||
|
onlyDependOnLibsWithTags: ['type:feature', 'type:ui'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:feature',
|
||||||
|
onlyDependOnLibsWithTags: [
|
||||||
|
'type:ui',
|
||||||
|
'type:data',
|
||||||
|
'type:types',
|
||||||
|
'type:state',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:types',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:state',
|
||||||
|
onlyDependOnLibsWithTags: [
|
||||||
|
'type:state',
|
||||||
|
'type:types',
|
||||||
|
'type:data',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:data',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:e2e',
|
||||||
|
onlyDependOnLibsWithTags: ['type:e2e-utils'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'type:ui',
|
||||||
|
onlyDependOnLibsWithTags: ['type:types', 'type:ui'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:products',
|
||||||
|
onlyDependOnLibsWithTags: ['scope:products', 'scope:shared'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceTag: 'scope:cart',
|
||||||
|
onlyDependOnLibsWithTags: ['scope:cart', 'scope:shared'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'directive-selector': {
|
||||||
|
ruleArguments: ['attribute', 'app', 'camelCase'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'component-selector': {
|
||||||
|
ruleArguments: ['element', 'app', 'kebab-case'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'no-conflicting-lifecycle': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-host-metadata-property': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-input-rename': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-inputs-metadata-property': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'no-output-native': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-output-on-prefix': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-output-rename': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'no-outputs-metadata-property': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'template-banana-in-box': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'template-no-negated-async': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'use-lifecycle-interface': { ruleArguments: [], ruleSeverity: 'error' },
|
||||||
|
'use-pipe-transform-interface': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exampleAngularProjectTslintJson = {
|
||||||
|
raw: {
|
||||||
|
extends: '../../tslint.json',
|
||||||
|
rules: {
|
||||||
|
'directive-selector': [true, 'attribute', 'angular-app', 'camelCase'],
|
||||||
|
'component-selector': [true, 'element', 'angular-app', 'kebab-case'],
|
||||||
|
},
|
||||||
|
linterOptions: {
|
||||||
|
exclude: ['!**/*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tslintPrintConfigResult: {
|
||||||
|
rules: {
|
||||||
|
'directive-selector': {
|
||||||
|
ruleArguments: ['attribute', 'angular-app', 'camelCase'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'component-selector': {
|
||||||
|
ruleArguments: ['element', 'angular-app', 'kebab-case'],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exampleNonAngularProjectTslintJson = {
|
||||||
|
raw: {
|
||||||
|
extends: '../../tslint.json',
|
||||||
|
linterOptions: { exclude: ['!**/*'] },
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
tslintPrintConfigResult: { rules: {} },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exampleE2eProjectTslintJson = {
|
||||||
|
raw: {
|
||||||
|
extends: '../../tslint.json',
|
||||||
|
linterOptions: { exclude: ['!**/*'] },
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
tslintPrintConfigResult: { rules: {} },
|
||||||
|
};
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
export {
|
||||||
|
ConvertTSLintToESLintSchema,
|
||||||
|
ProjectConverter,
|
||||||
|
} from './project-converter';
|
||||||
|
|
||||||
|
// For testing
|
||||||
|
export { exampleRootTslintJson } from './example-tslint-configs';
|
||||||
@ -0,0 +1,267 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
addProjectConfiguration,
|
||||||
|
readJson,
|
||||||
|
readWorkspaceConfiguration,
|
||||||
|
Tree,
|
||||||
|
updateWorkspaceConfiguration,
|
||||||
|
writeJson,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||||
|
import { ProjectConverter } from './project-converter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't run actual child_process implementation of installPackagesTask()
|
||||||
|
*/
|
||||||
|
jest.mock('child_process');
|
||||||
|
|
||||||
|
describe('ProjectConverter', () => {
|
||||||
|
let host: Tree;
|
||||||
|
const projectName = 'foo';
|
||||||
|
const projectRoot = `apps/${projectName}`;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Clean up any previous dry run simulations
|
||||||
|
process.argv = process.argv.filter(
|
||||||
|
(a) => !['--dry-run', '--dryRun', '-d'].includes(a)
|
||||||
|
);
|
||||||
|
host = createTreeWithEmptyWorkspace();
|
||||||
|
addProjectConfiguration(host, projectName, {
|
||||||
|
root: projectRoot,
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - BEFORE CONVERSION
|
||||||
|
*
|
||||||
|
* TSLint executor configured for the project
|
||||||
|
*/
|
||||||
|
lint: {
|
||||||
|
executor: '@angular-devkit/build-angular:tslint',
|
||||||
|
options: {
|
||||||
|
exclude: ['**/node_modules/**', `!${projectRoot}/**/*`],
|
||||||
|
tsConfig: [`${projectRoot}/tsconfig.app.json`],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* PROJECT-LEVEL GENERATOR CONFIG - BEFORE CONVERSION
|
||||||
|
*
|
||||||
|
* Default set to tslint, using shorthand syntax
|
||||||
|
*/
|
||||||
|
generators: {
|
||||||
|
'@nrwl/angular:library': {
|
||||||
|
linter: 'tslint',
|
||||||
|
},
|
||||||
|
'@nrwl/angular:application': {
|
||||||
|
e2eTestRunner: 'cypress',
|
||||||
|
linter: 'eslint',
|
||||||
|
unitTestRunner: 'jest',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if --dry-run is set', () => {
|
||||||
|
writeJson(host, 'tslint.json', {});
|
||||||
|
writeJson(host, `${projectRoot}/tslint.json`, {});
|
||||||
|
|
||||||
|
process.argv.push('--dry-run');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
})
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if --dryRun is set', () => {
|
||||||
|
writeJson(host, 'tslint.json', {});
|
||||||
|
writeJson(host, `${projectRoot}/tslint.json`, {});
|
||||||
|
|
||||||
|
process.argv.push('--dryRun');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
})
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if -d is set', () => {
|
||||||
|
writeJson(host, 'tslint.json', {});
|
||||||
|
writeJson(host, `${projectRoot}/tslint.json`, {});
|
||||||
|
|
||||||
|
process.argv.push('-d');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
})
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if no root tslint.json is found', () => {
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
})
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if no project tslint.json is found', () => {
|
||||||
|
writeJson(host, 'tslint.json', {});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
})
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw when not in dry-run and config files successfully found', () => {
|
||||||
|
writeJson(host, 'tslint.json', {});
|
||||||
|
writeJson(host, `${projectRoot}/tslint.json`, {});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
})
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setDefaults()', () => {
|
||||||
|
it('should set the default configuration for removeTSLintIfNoMoreTSLintTargets in workspace.json', async () => {
|
||||||
|
writeJson(host, 'tslint.json', {});
|
||||||
|
writeJson(host, `${projectRoot}/tslint.json`, {});
|
||||||
|
|
||||||
|
const projectConverter = new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspace = readWorkspaceConfiguration(host);
|
||||||
|
workspace.generators = {
|
||||||
|
'@nrwl/angular': {
|
||||||
|
application: {
|
||||||
|
linter: 'tslint',
|
||||||
|
},
|
||||||
|
library: {
|
||||||
|
linter: 'tslint',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
updateWorkspaceConfiguration(host, workspace);
|
||||||
|
|
||||||
|
// BEFORE - no entry for convert-tslint-to-eslint wthin @nrwl/angular generators
|
||||||
|
expect(readJson(host, 'workspace.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
projectConverter.setDefaults('@nrwl/angular', true);
|
||||||
|
|
||||||
|
// AFTER (1) - convert-tslint-to-eslint wthin @nrwl/angular generators has removeTSLintIfNoMoreTSLintTargets set to true
|
||||||
|
expect(readJson(host, 'workspace.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
projectConverter.setDefaults('@nrwl/angular', false);
|
||||||
|
|
||||||
|
// AFTER (2) - convert-tslint-to-eslint wthin @nrwl/angular generators has removeTSLintIfNoMoreTSLintTargets set to false
|
||||||
|
expect(readJson(host, 'workspace.json')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeTSLintFromWorkspace()', () => {
|
||||||
|
it('should remove all relevant traces of TSLint from the workspace', async () => {
|
||||||
|
writeJson(host, 'tslint.json', {});
|
||||||
|
writeJson(host, `${projectRoot}/tslint.json`, {});
|
||||||
|
|
||||||
|
const projectConverter = new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
await addDependenciesToPackageJson(
|
||||||
|
host,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
codelyzer: 'latest',
|
||||||
|
tslint: 'latest',
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
|
const workspace = readWorkspaceConfiguration(host);
|
||||||
|
// Not using shorthand syntax this time
|
||||||
|
workspace.generators = {
|
||||||
|
'@nrwl/angular': {
|
||||||
|
application: {
|
||||||
|
linter: 'tslint',
|
||||||
|
},
|
||||||
|
library: {
|
||||||
|
linter: 'tslint',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
updateWorkspaceConfiguration(host, workspace);
|
||||||
|
|
||||||
|
// BEFORE - tslint and codelyzer are present
|
||||||
|
expect(readJson(host, 'package.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
// BEFORE - tslint set as both global and project-level default linter for @nrwl/angular generators
|
||||||
|
expect(readJson(host, 'workspace.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
await projectConverter.removeTSLintFromWorkspace()();
|
||||||
|
|
||||||
|
// AFTER - it should remove tslint and codelyzer
|
||||||
|
expect(readJson(host, 'package.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
// AFTER - generators config from global and project-level settings removed (because eslint is always default)
|
||||||
|
expect(readJson(host, 'workspace.json')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the entry in generators for convert-tslint-to-eslint because it is no longer needed', async () => {
|
||||||
|
writeJson(host, 'tslint.json', {});
|
||||||
|
writeJson(host, `${projectRoot}/tslint.json`, {});
|
||||||
|
|
||||||
|
const projectConverter = new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspace = readWorkspaceConfiguration(host);
|
||||||
|
workspace.generators = {
|
||||||
|
'@nrwl/angular': {
|
||||||
|
'convert-tslint-to-eslint': {
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
updateWorkspaceConfiguration(host, workspace);
|
||||||
|
|
||||||
|
// BEFORE - convert-tslint-to-eslint wthin @nrwl/angular generators has a value for removeTSLintIfNoMoreTSLintTargets
|
||||||
|
expect(readJson(host, 'workspace.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
await projectConverter.removeTSLintFromWorkspace()();
|
||||||
|
|
||||||
|
// AFTER - generators config no longer has a reference to convert-tslint-to-eslint
|
||||||
|
expect(readJson(host, 'workspace.json')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,535 @@
|
|||||||
|
import {
|
||||||
|
GeneratorCallback,
|
||||||
|
getPackageManagerCommand,
|
||||||
|
getProjects,
|
||||||
|
joinPathFragments,
|
||||||
|
logger,
|
||||||
|
NxJsonProjectConfiguration,
|
||||||
|
offsetFromRoot,
|
||||||
|
ProjectConfiguration,
|
||||||
|
readJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
readWorkspaceConfiguration,
|
||||||
|
removeDependenciesFromPackageJson,
|
||||||
|
Tree,
|
||||||
|
updateJson,
|
||||||
|
updateProjectConfiguration,
|
||||||
|
updateWorkspaceConfiguration,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import type { Linter } from 'eslint';
|
||||||
|
import { tslintToEslintConfigVersion } from '../versions';
|
||||||
|
import {
|
||||||
|
convertTSLintConfig,
|
||||||
|
ensureESLintPluginsAreInstalled,
|
||||||
|
deduplicateOverrides,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common schema used by all implementations of convert-tslint-to-eslint generators
|
||||||
|
*/
|
||||||
|
export interface ConvertTSLintToESLintSchema {
|
||||||
|
project: string;
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we convert a TSLint setup to an ESLint setup for a particular project, there are a number of
|
||||||
|
* shared/common concerns (implemented as library utilities within @nrwl/linter), and a few things
|
||||||
|
* which are specific to this package and the types of projects it produces.
|
||||||
|
*
|
||||||
|
* The key structure of the converted ESLint support is as follows:
|
||||||
|
*
|
||||||
|
* - We will first generate a workspace root .eslintrc.json which is the same as the one generated
|
||||||
|
* for new workspaces (i.e. it is NOT just a converted version of their root tslint.json). This allows us
|
||||||
|
* to have a consistent base for all users, as well as standardized patterns around "overrides".
|
||||||
|
*
|
||||||
|
* - The user's original root tslint.json will be converted and any applicable settings will be stored
|
||||||
|
* within ADDITIONAL override blocks within the root .eslintrc.json.
|
||||||
|
*
|
||||||
|
* - The user's project-level tslint.json file will be converted into a corresponding .eslintrc.json file
|
||||||
|
* and it will extend from the root workspace .eslintrc.json file as normal.
|
||||||
|
*/
|
||||||
|
export class ProjectConverter {
|
||||||
|
private readonly projectConfig: ProjectConfiguration &
|
||||||
|
NxJsonProjectConfiguration;
|
||||||
|
private readonly rootTSLintJsonPath = 'tslint.json';
|
||||||
|
private readonly rootTSLintJson: Record<string, unknown>;
|
||||||
|
private readonly projectTSLintJsonPath: string;
|
||||||
|
private readonly projectTSLintJson: Record<string, unknown>;
|
||||||
|
private readonly host: Tree;
|
||||||
|
private readonly projectName: string;
|
||||||
|
private readonly eslintInitializer: (projectInfo: {
|
||||||
|
projectName: string;
|
||||||
|
projectConfig: ProjectConfiguration & NxJsonProjectConfiguration;
|
||||||
|
}) => Promise<void>;
|
||||||
|
private readonly pmc: ReturnType<typeof getPackageManagerCommand>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using an object as the argument to the constructor means we sacrifice some
|
||||||
|
* authoring sugar around initializing these properties but it makes the usage
|
||||||
|
* of the class much easier to read and maintain.
|
||||||
|
*/
|
||||||
|
constructor({
|
||||||
|
host,
|
||||||
|
projectName,
|
||||||
|
eslintInitializer,
|
||||||
|
}: {
|
||||||
|
host: Tree;
|
||||||
|
projectName: string;
|
||||||
|
eslintInitializer: (projectInfo: {
|
||||||
|
projectName: string;
|
||||||
|
projectConfig: ProjectConfiguration & NxJsonProjectConfiguration;
|
||||||
|
}) => Promise<void>;
|
||||||
|
}) {
|
||||||
|
this.host = host;
|
||||||
|
this.projectName = projectName;
|
||||||
|
this.eslintInitializer = eslintInitializer;
|
||||||
|
this.projectConfig = readProjectConfiguration(this.host, this.projectName);
|
||||||
|
this.projectTSLintJsonPath = joinPathFragments(
|
||||||
|
this.projectConfig.root,
|
||||||
|
'tslint.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the user is converting a project from TSLint to ESLint, we expect them
|
||||||
|
* to have both a root and a project-specific tslint.json
|
||||||
|
*/
|
||||||
|
if (!host.exists(this.rootTSLintJsonPath)) {
|
||||||
|
throw new Error(
|
||||||
|
'We could not find a tslint.json at the root of your workspace, maybe you have already migrated to ESLint?'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!host.exists(this.projectTSLintJsonPath)) {
|
||||||
|
throw new Error(
|
||||||
|
`We could not find a tslint.json for the selected project "${this.projectTSLintJsonPath}", maybe you have already migrated to ESLint?`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.rootTSLintJson = readJson(host, this.rootTSLintJsonPath);
|
||||||
|
this.projectTSLintJson = readJson(host, this.projectTSLintJsonPath);
|
||||||
|
|
||||||
|
const pm = detectPackageManager();
|
||||||
|
this.pmc = getPackageManagerCommand(pm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We are not able to support --dry-run in this generator, because we need to dynamically install
|
||||||
|
* and use the tslint-to-eslint-config package within the same execution.
|
||||||
|
*
|
||||||
|
* This is a worthwhile trade-off and the dry-run output doesn't offer a ton of value for this use-case anyway.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
process.argv.includes('--dry-run') ||
|
||||||
|
process.argv.includes('--dryRun') ||
|
||||||
|
process.argv.includes('-d')
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'NOTE: This generator does not support --dry-run. If you are running this in Nx Console, it should execute fine once you hit the "Run" button.\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order to avoid all users of Nx needing to have tslint-to-eslint-config (and therefore tslint)
|
||||||
|
* in their node_modules, we dynamically install and uninstall the library as part of the conversion
|
||||||
|
* process.
|
||||||
|
*
|
||||||
|
* NOTE: By taking this approach we have to sacrifice dry-run capabilities for this generator.
|
||||||
|
*/
|
||||||
|
installTSLintToESLintConfigPackage() {
|
||||||
|
execSync(
|
||||||
|
`${this.pmc.addDev} tslint-to-eslint-config@${tslintToEslintConfigVersion}`,
|
||||||
|
{
|
||||||
|
cwd: this.host.root,
|
||||||
|
stdio: [0, 1, 2],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstallTSLintToESLintConfigPackage() {
|
||||||
|
execSync(`${this.pmc.rm} tslint-to-eslint-config`, {
|
||||||
|
cwd: this.host.root,
|
||||||
|
stdio: [0, 1, 2],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async initESLint() {
|
||||||
|
return this.eslintInitializer({
|
||||||
|
projectName: this.projectName,
|
||||||
|
projectConfig: this.projectConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the package-specific shareable config already exists then the workspace must already
|
||||||
|
* be part way through migrating from TSLint to ESLint. In this case we do not want to convert
|
||||||
|
* the root tslint.json again (and this utility will return a noop task), and we instead just
|
||||||
|
* focus on the project-level config conversion.
|
||||||
|
*/
|
||||||
|
async convertRootTSLintConfig(
|
||||||
|
applyPackageSpecificModifications: (json: Linter.Config) => Linter.Config
|
||||||
|
): Promise<Exclude<GeneratorCallback, void>> {
|
||||||
|
const convertedRoot = await convertTSLintConfig(
|
||||||
|
this.rootTSLintJson,
|
||||||
|
this.rootTSLintJsonPath,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const convertedRootESLintConfig = convertedRoot.convertedESLintConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Already set by Nx's shareable configs
|
||||||
|
*/
|
||||||
|
delete convertedRootESLintConfig.env;
|
||||||
|
delete convertedRootESLintConfig.parser;
|
||||||
|
delete convertedRootESLintConfig.parserOptions;
|
||||||
|
convertedRootESLintConfig.plugins = convertedRootESLintConfig.plugins.filter(
|
||||||
|
(p) =>
|
||||||
|
!p.startsWith('@angular-eslint') && !p.startsWith('@typescript-eslint')
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The only piece of the converted root tslint.json that we need to pull out to
|
||||||
|
* apply to the existing overrides within the root .eslintrc.json is the
|
||||||
|
* @nrwl/nx/enforce-module-boundaries rule.
|
||||||
|
*/
|
||||||
|
const nxRuleName = '@nrwl/nx/enforce-module-boundaries';
|
||||||
|
const nxEnforceModuleBoundariesRule =
|
||||||
|
convertedRootESLintConfig.rules[nxRuleName];
|
||||||
|
if (nxEnforceModuleBoundariesRule) {
|
||||||
|
updateJson(this.host, '.eslintrc.json', (json) => {
|
||||||
|
if (!json.overrides) {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
for (const override of json.overrides) {
|
||||||
|
if (!override.rules) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!override.rules[nxRuleName]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
override.rules[nxRuleName] = nxEnforceModuleBoundariesRule;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Remove it once we've used it on the root, so that is isn't applied
|
||||||
|
* to the package-specific shareable config
|
||||||
|
*/
|
||||||
|
delete convertedRootESLintConfig.rules[nxRuleName];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the root workspace .eslintrc.json with additional overrides
|
||||||
|
*/
|
||||||
|
const finalConvertedRootESLintConfig = applyPackageSpecificModifications(
|
||||||
|
convertedRootESLintConfig
|
||||||
|
);
|
||||||
|
updateJson(this.host, '.eslintrc.json', (json) => {
|
||||||
|
json.overrides = json.overrides || [];
|
||||||
|
if (
|
||||||
|
finalConvertedRootESLintConfig.overrides &&
|
||||||
|
finalConvertedRootESLintConfig.overrides.length
|
||||||
|
) {
|
||||||
|
json.overrides = [
|
||||||
|
...json.overrides,
|
||||||
|
...finalConvertedRootESLintConfig.overrides,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
json.overrides.push({
|
||||||
|
files: ['*.ts'],
|
||||||
|
...finalConvertedRootESLintConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
json.overrides = deduplicateOverrides(json.overrides);
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Through converting the config we may encounter TSLint rules whose closest
|
||||||
|
* equivalent in the ESLint ecosystem comes from a separate package/plugin.
|
||||||
|
*
|
||||||
|
* We therefore automatically install those extra packages for the user and
|
||||||
|
* explain that that's what we are doing.
|
||||||
|
*/
|
||||||
|
return ensureESLintPluginsAreInstalled(
|
||||||
|
this.host,
|
||||||
|
convertedRoot.ensureESLintPlugins
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async convertProjectConfig(
|
||||||
|
applyPackageSpecificModifications: (json: Linter.Config) => Linter.Config
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
const convertedProjectConfig = await convertTSLintConfig(
|
||||||
|
this.projectTSLintJson,
|
||||||
|
this.projectTSLintJsonPath,
|
||||||
|
// Strip the extends on workspace tslint.json (see this util's docs for more info)
|
||||||
|
[`${offsetFromRoot(this.projectConfig.root)}tslint.json`]
|
||||||
|
);
|
||||||
|
|
||||||
|
const convertedProjectESLintConfig =
|
||||||
|
convertedProjectConfig.convertedESLintConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Already set by Nx's shareable configs
|
||||||
|
*/
|
||||||
|
delete convertedProjectESLintConfig.env;
|
||||||
|
delete convertedProjectESLintConfig.parser;
|
||||||
|
delete convertedProjectESLintConfig.parserOptions;
|
||||||
|
convertedProjectESLintConfig.plugins = convertedProjectESLintConfig.plugins.filter(
|
||||||
|
(p) =>
|
||||||
|
!p.startsWith('@angular-eslint') && !p.startsWith('@typescript-eslint')
|
||||||
|
);
|
||||||
|
|
||||||
|
const projectESLintConfigPath = joinPathFragments(
|
||||||
|
this.projectConfig.root,
|
||||||
|
'.eslintrc.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply updates to the new .eslintrc.json file for the project
|
||||||
|
*/
|
||||||
|
updateJson(this.host, projectESLintConfigPath, (json) => {
|
||||||
|
if (typeof json.extends === 'string') {
|
||||||
|
json.extends = [json.extends];
|
||||||
|
}
|
||||||
|
// Custom extends from conversion
|
||||||
|
if (
|
||||||
|
Array.isArray(convertedProjectESLintConfig.extends) &&
|
||||||
|
convertedProjectESLintConfig.extends.length
|
||||||
|
) {
|
||||||
|
// Ignore any tslint-to-eslint-config default extends that do not apply to Nx
|
||||||
|
const applicableExtends = convertedProjectESLintConfig.extends.filter(
|
||||||
|
(ext) => !ext.startsWith('prettier')
|
||||||
|
);
|
||||||
|
if (applicableExtends.length) {
|
||||||
|
json.extends = [...json.extends, ...applicableExtends];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Custom plugins from conversion
|
||||||
|
if (
|
||||||
|
Array.isArray(convertedProjectESLintConfig.plugins) &&
|
||||||
|
convertedProjectESLintConfig.plugins.length
|
||||||
|
) {
|
||||||
|
json.plugins = [
|
||||||
|
...json.plugins,
|
||||||
|
...convertedProjectESLintConfig.plugins,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Custom rules
|
||||||
|
*
|
||||||
|
* By default, tslint-to-eslint-config will try and apply any rules without known converters
|
||||||
|
* by using eslint-plugin-tslint. We instead explicitly warn the user about this missing converter,
|
||||||
|
* and therefore at this point we strip out any rules which start with @typescript-eslint/tslint/config
|
||||||
|
*/
|
||||||
|
json.rules = json.rules || {};
|
||||||
|
if (
|
||||||
|
convertedProjectESLintConfig.rules &&
|
||||||
|
Object.keys(convertedProjectESLintConfig.rules).length
|
||||||
|
) {
|
||||||
|
for (const [ruleName, ruleConfig] of Object.entries(
|
||||||
|
convertedProjectESLintConfig.rules
|
||||||
|
)) {
|
||||||
|
if (!ruleName.startsWith('@typescript-eslint/tslint/config')) {
|
||||||
|
// Prioritize the converted rules over any base implementations from the original Nx generator
|
||||||
|
json.rules[ruleName] = ruleConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Apply any package-specific modifications to the converted config before
|
||||||
|
* updating the config file.
|
||||||
|
*/
|
||||||
|
const finalJson = applyPackageSpecificModifications(json);
|
||||||
|
return finalJson;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Through converting the config we may encounter TSLint rules whose closest
|
||||||
|
* equivalent in the ESLint ecosystem comes from a separate package/plugin.
|
||||||
|
*
|
||||||
|
* We therefore automatically install those extra packages for the user and
|
||||||
|
* explain that that's what we are doing.
|
||||||
|
*/
|
||||||
|
return ensureESLintPluginsAreInstalled(
|
||||||
|
this.host,
|
||||||
|
convertedProjectConfig.ensureESLintPlugins
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeProjectTSLintFile() {
|
||||||
|
this.host.delete(joinPathFragments(this.projectConfig.root, 'tslint.json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
isTSLintUsedInWorkspace(): boolean {
|
||||||
|
const projects = getProjects(this.host);
|
||||||
|
for (const [, projectConfig] of projects.entries()) {
|
||||||
|
for (const [, targetConfig] of Object.entries(projectConfig.targets)) {
|
||||||
|
if (targetConfig.executor === '@angular-devkit/build-angular:tslint') {
|
||||||
|
// Workspace is still using TSLint, exit early
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we got this far the user has no remaining TSLint usage
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTSLintFromWorkspace(): GeneratorCallback {
|
||||||
|
logger.info(
|
||||||
|
`No TSLint usage will remain in the workspace, removing TSLint...`
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Delete the root tslint.json
|
||||||
|
*/
|
||||||
|
this.host.delete(this.rootTSLintJsonPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the package.json and the uninstall task
|
||||||
|
*/
|
||||||
|
const uninstallTask = removeDependenciesFromPackageJson(
|
||||||
|
this.host,
|
||||||
|
[],
|
||||||
|
['tslint', 'codelyzer']
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update global linter configuration defaults in workspace.json
|
||||||
|
*/
|
||||||
|
const workspace = readWorkspaceConfiguration(this.host);
|
||||||
|
this.cleanUpGeneratorsConfig(workspace);
|
||||||
|
updateWorkspaceConfiguration(this.host, workspace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update project-level linter configuration defaults in workspace.json
|
||||||
|
*/
|
||||||
|
const projects = getProjects(this.host);
|
||||||
|
for (const [projectName, { generators }] of projects.entries()) {
|
||||||
|
if (!generators || Object.keys(generators).length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const project = readProjectConfiguration(this.host, projectName);
|
||||||
|
this.cleanUpGeneratorsConfig(project);
|
||||||
|
updateProjectConfiguration(this.host, projectName, project);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uninstallTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private cleanUpGeneratorsConfig(parentConfig: { generators?: any }) {
|
||||||
|
if (
|
||||||
|
!parentConfig.generators ||
|
||||||
|
Object.keys(parentConfig.generators).length === 0
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const [collectionName, maybeGeneratorConfig] of Object.entries(
|
||||||
|
parentConfig.generators
|
||||||
|
)) {
|
||||||
|
// Shorthand syntax is possible
|
||||||
|
if (collectionName.includes(':')) {
|
||||||
|
const generatorConfig = maybeGeneratorConfig;
|
||||||
|
for (const optionName of Object.keys(generatorConfig)) {
|
||||||
|
if (optionName === 'linter') {
|
||||||
|
// Default is eslint, so in all cases we can just remove the config altogether
|
||||||
|
delete generatorConfig[optionName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If removing linter leaves no other options in the config, remove the config as well
|
||||||
|
if (Object.keys(generatorConfig).length === 0) {
|
||||||
|
delete parentConfig.generators[collectionName];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not shorthand syntax, so next level down is generator name -> config mapping
|
||||||
|
const collectionConfig = maybeGeneratorConfig;
|
||||||
|
|
||||||
|
for (const [generatorName, generatorConfig] of Object.entries(
|
||||||
|
collectionConfig
|
||||||
|
)) {
|
||||||
|
if (generatorName === 'convert-tslint-to-eslint') {
|
||||||
|
// No longer relevant because of TSLint is being removed the conversion process must be complete
|
||||||
|
delete collectionConfig[generatorName];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const optionName of Object.keys(generatorConfig)) {
|
||||||
|
if (optionName === 'linter') {
|
||||||
|
// Default is eslint, so in all cases we can just remove the config altogether
|
||||||
|
delete generatorConfig[optionName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If removing linter leaves no other options in the config, remove the generator config as well
|
||||||
|
if (Object.keys(generatorConfig).length === 0) {
|
||||||
|
delete collectionConfig[generatorName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If removing the generator leaves no other generators in the config, remove the config as well
|
||||||
|
if (
|
||||||
|
parentConfig.generators[collectionName] &&
|
||||||
|
Object.keys(parentConfig.generators[collectionName]).length === 0
|
||||||
|
) {
|
||||||
|
delete parentConfig.generators[collectionName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If removing the linter defaults leaves absolutely no generators configuration remaining, remove it
|
||||||
|
if (Object.keys(parentConfig.generators).length === 0) {
|
||||||
|
delete parentConfig.generators;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the project which is the subject of the ProjectConverter instance is an application,
|
||||||
|
* figure out its associated e2e project's name.
|
||||||
|
*/
|
||||||
|
getE2EProjectName(): string | null {
|
||||||
|
if (this.projectConfig.projectType !== 'application') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let e2eProjectName = null;
|
||||||
|
|
||||||
|
const projects = getProjects(this.host);
|
||||||
|
for (const [projectName, projectConfig] of projects.entries()) {
|
||||||
|
for (const [, targetConfig] of Object.entries(projectConfig.targets)) {
|
||||||
|
if (targetConfig.executor === '@nrwl/cypress:cypress') {
|
||||||
|
if (
|
||||||
|
targetConfig.options.devServerTarget === `${this.projectName}:serve`
|
||||||
|
) {
|
||||||
|
e2eProjectName = projectName;
|
||||||
|
logger.info(
|
||||||
|
`Found e2e project for "${this.projectName}" called "${e2eProjectName}", converting that project as well...`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e2eProjectName;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaults(
|
||||||
|
collectionName: string,
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: ConvertTSLintToESLintSchema['removeTSLintIfNoMoreTSLintTargets']
|
||||||
|
) {
|
||||||
|
const workspace = readWorkspaceConfiguration(this.host);
|
||||||
|
|
||||||
|
workspace.generators = workspace.generators || {};
|
||||||
|
workspace.generators[collectionName] =
|
||||||
|
workspace.generators[collectionName] || {};
|
||||||
|
const prev = workspace.generators[collectionName];
|
||||||
|
|
||||||
|
workspace.generators = {
|
||||||
|
...workspace.generators,
|
||||||
|
[collectionName]: {
|
||||||
|
...prev,
|
||||||
|
'convert-tslint-to-eslint': {
|
||||||
|
removeTSLintIfNoMoreTSLintTargets,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
updateWorkspaceConfiguration(this.host, workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import type { Linter } from 'eslint';
|
||||||
|
import { deduplicateOverrides } from './utils';
|
||||||
|
|
||||||
|
describe('deduplicateOverrides()', () => {
|
||||||
|
it('should deduplicate overrides with identical values for "files"', () => {
|
||||||
|
const initialOverrides: Linter.Config['overrides'] = [
|
||||||
|
{
|
||||||
|
files: ['*.ts'],
|
||||||
|
env: {
|
||||||
|
foo: true,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
bar: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.html'],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: '*.ts',
|
||||||
|
plugins: ['wat'],
|
||||||
|
parserOptions: {
|
||||||
|
qux: false,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
bar: 'warn',
|
||||||
|
baz: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.ts'],
|
||||||
|
extends: ['something'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(deduplicateOverrides(initialOverrides)).toEqual([
|
||||||
|
{
|
||||||
|
files: ['*.ts'],
|
||||||
|
env: {
|
||||||
|
foo: true,
|
||||||
|
},
|
||||||
|
plugins: ['wat'],
|
||||||
|
extends: ['something'],
|
||||||
|
parserOptions: {
|
||||||
|
qux: false,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
bar: 'warn',
|
||||||
|
baz: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.html'],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
136
packages/linter/src/utils/convert-tslint-to-eslint/utils.ts
Normal file
136
packages/linter/src/utils/convert-tslint-to-eslint/utils.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
GeneratorCallback,
|
||||||
|
logger,
|
||||||
|
Tree,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import type { Linter } from 'eslint';
|
||||||
|
import type { TSLintRuleOptions } from 'tslint-to-eslint-config';
|
||||||
|
import { convertTslintNxRuleToEslintNxRule } from './convert-nx-enforce-module-boundaries-rule';
|
||||||
|
import { convertToESLintConfig } from './convert-to-eslint-config';
|
||||||
|
|
||||||
|
export function ensureESLintPluginsAreInstalled(
|
||||||
|
host: Tree,
|
||||||
|
eslintPluginsToBeInstalled: string[]
|
||||||
|
): GeneratorCallback {
|
||||||
|
if (!eslintPluginsToBeInstalled?.length) {
|
||||||
|
return () => undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalDevDependencies = {};
|
||||||
|
|
||||||
|
for (const pluginName of eslintPluginsToBeInstalled) {
|
||||||
|
additionalDevDependencies[pluginName] = 'latest';
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
'\nINFO: To most closely match your tslint.json, we will ensure the `latest` version of the following eslint plugin(s) are installed:'
|
||||||
|
);
|
||||||
|
logger.info('\n - ' + eslintPluginsToBeInstalled.join('\n - '));
|
||||||
|
logger.info(
|
||||||
|
'\nPlease note, you may later wish to pin these to a specific version number in your package.json, rather than leaving it open to `latest`.\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
return addDependenciesToPackageJson(host, {}, additionalDevDependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We don't want the user to depend on the TSLint fallback plugin, we will instead
|
||||||
|
* explicitly inform them of the rules that could not be converted automatically and
|
||||||
|
* advise them on what to do next.
|
||||||
|
*/
|
||||||
|
function warnInCaseOfUnconvertedRules(
|
||||||
|
tslintConfigPath: string,
|
||||||
|
unconvertedTSLintRules: TSLintRuleOptions[]
|
||||||
|
): void {
|
||||||
|
const unconvertedTSLintRuleNames = unconvertedTSLintRules
|
||||||
|
.filter(
|
||||||
|
// Ignore formatting related rules, they are handled by Nx format/prettier
|
||||||
|
(unconverted) =>
|
||||||
|
!['import-spacing', 'whitespace', 'typedef'].includes(
|
||||||
|
unconverted.ruleName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((unconverted) => unconverted.ruleName);
|
||||||
|
|
||||||
|
if (unconvertedTSLintRuleNames.length > 0) {
|
||||||
|
logger.warn(
|
||||||
|
`\nWARNING: Within "${tslintConfigPath}", the following ${unconvertedTSLintRuleNames.length} rule(s) did not have known converters in https://github.com/typescript-eslint/tslint-to-eslint-config`
|
||||||
|
);
|
||||||
|
logger.warn('\n - ' + unconvertedTSLintRuleNames.join('\n - '));
|
||||||
|
logger.warn(
|
||||||
|
'\nYou will need to decide on how to handle the above manually, but everything else has been handled for you automatically.\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function convertTSLintConfig(
|
||||||
|
rawTSLintJson: any,
|
||||||
|
tslintJsonPath: string,
|
||||||
|
ignoreExtendsVals: string[]
|
||||||
|
) {
|
||||||
|
const convertedProject = await convertToESLintConfig(
|
||||||
|
tslintJsonPath,
|
||||||
|
rawTSLintJson,
|
||||||
|
ignoreExtendsVals
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the custom converter for the nx-module-boundaries rule if applicable
|
||||||
|
*/
|
||||||
|
const convertedNxRule = convertTslintNxRuleToEslintNxRule(rawTSLintJson);
|
||||||
|
if (convertedNxRule) {
|
||||||
|
convertedProject.convertedESLintConfig.rules =
|
||||||
|
convertedProject.convertedESLintConfig.rules || {};
|
||||||
|
convertedProject.convertedESLintConfig.rules[convertedNxRule.ruleName] =
|
||||||
|
convertedNxRule.ruleConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
warnInCaseOfUnconvertedRules(
|
||||||
|
tslintJsonPath,
|
||||||
|
convertedProject.unconvertedTSLintRules
|
||||||
|
);
|
||||||
|
|
||||||
|
return convertedProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deduplicateOverrides(
|
||||||
|
overrides: Linter.Config['overrides'] = []
|
||||||
|
) {
|
||||||
|
const map = new Map();
|
||||||
|
for (const override of overrides) {
|
||||||
|
const mapKey: string =
|
||||||
|
typeof override.files === 'string'
|
||||||
|
? override.files
|
||||||
|
: override.files.join(',');
|
||||||
|
const existing: Set<Linter.ConfigOverride> = map.get(mapKey);
|
||||||
|
if (existing) {
|
||||||
|
existing.add(override);
|
||||||
|
map.set(mapKey, existing);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const set = new Set();
|
||||||
|
set.add(override);
|
||||||
|
map.set(mapKey, set);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dedupedOverrides = [];
|
||||||
|
|
||||||
|
for (const [, overrides] of map.entries()) {
|
||||||
|
const overridesArr = Array.from(overrides);
|
||||||
|
if (overridesArr.length === 1) {
|
||||||
|
dedupedOverrides = [...dedupedOverrides, ...overridesArr];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mergedOverride = {};
|
||||||
|
for (const override of overridesArr) {
|
||||||
|
mergedOverride = {
|
||||||
|
...mergedOverride,
|
||||||
|
...(override as any),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
dedupedOverrides.push(mergedOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dedupedOverrides;
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
export const nxVersion = '*';
|
export const nxVersion = '*';
|
||||||
|
|
||||||
export const tslintVersion = '~6.1.0';
|
export const tslintVersion = '~6.1.0';
|
||||||
|
export const tslintToEslintConfigVersion = '2.2.0';
|
||||||
export const buildAngularVersion = '~0.1102.0';
|
export const buildAngularVersion = '~0.1102.0';
|
||||||
|
|
||||||
export const typescriptESLintVersion = '4.3.0';
|
export const typescriptESLintVersion = '4.3.0';
|
||||||
|
|||||||
@ -91,6 +91,18 @@
|
|||||||
"factory": "./src/schematics/nestjs-schematics/nestjs-schematics#service",
|
"factory": "./src/schematics/nestjs-schematics/nestjs-schematics#service",
|
||||||
"schema": "./src/schematics/nestjs-schematics/schema.json",
|
"schema": "./src/schematics/nestjs-schematics/schema.json",
|
||||||
"description": "Run the 'service' NestJs generator with Nx project support"
|
"description": "Run the 'service' NestJs generator with Nx project support"
|
||||||
|
},
|
||||||
|
"convert-tslint-to-eslint": {
|
||||||
|
"factory": "./src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint#conversionSchematic",
|
||||||
|
"schema": "./src/generators/convert-tslint-to-eslint/schema.json",
|
||||||
|
"description": "Convert a project from TSLint to ESLint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generators": {
|
||||||
|
"convert-tslint-to-eslint": {
|
||||||
|
"factory": "./src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint#conversionGenerator",
|
||||||
|
"schema": "./src/generators/convert-tslint-to-eslint/schema.json",
|
||||||
|
"description": "Convert a project from TSLint to ESLint"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nrwl/devkit": "*",
|
"@nrwl/devkit": "*",
|
||||||
|
"@nrwl/linter": "*",
|
||||||
"@nrwl/node": "*",
|
"@nrwl/node": "*",
|
||||||
"@nrwl/jest": "*",
|
"@nrwl/jest": "*",
|
||||||
"@angular-devkit/core": "~11.2.0",
|
"@angular-devkit/core": "~11.2.0",
|
||||||
|
|||||||
@ -0,0 +1,573 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for NestJS applications 1`] = `
|
||||||
|
Object {
|
||||||
|
"dependencies": Object {},
|
||||||
|
"devDependencies": Object {
|
||||||
|
"@nrwl/eslint-plugin-nx": "*",
|
||||||
|
"@nrwl/linter": "*",
|
||||||
|
"@typescript-eslint/eslint-plugin": "4.3.0",
|
||||||
|
"@typescript-eslint/parser": "4.3.0",
|
||||||
|
"eslint": "7.10.0",
|
||||||
|
"eslint-config-prettier": "8.1.0",
|
||||||
|
"eslint-plugin-import": "latest",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for NestJS applications 2`] = `
|
||||||
|
Object {
|
||||||
|
"projectType": "application",
|
||||||
|
"root": "apps/nest-app-1",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": Object {
|
||||||
|
"lintFilePatterns": Array [
|
||||||
|
"apps/nest-app-1/**/*.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for NestJS applications 3`] = `
|
||||||
|
Object {
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@nrwl/nx/enforce-module-boundaries": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"@nx-example/shared/product/data/testing",
|
||||||
|
],
|
||||||
|
"depConstraints": Array [
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:feature",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:app",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:ui",
|
||||||
|
"type:data",
|
||||||
|
"type:types",
|
||||||
|
"type:state",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:feature",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:types",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:state",
|
||||||
|
"type:types",
|
||||||
|
"type:data",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:state",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:data",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:e2e-utils",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:e2e",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:ui",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:products",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:products",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:cart",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:cart",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/typescript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/javascript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"eslint-plugin-import",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "error",
|
||||||
|
"@typescript-eslint/dot-notation": "off",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": Array [
|
||||||
|
"off",
|
||||||
|
Object {
|
||||||
|
"accessibility": "explicit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "error",
|
||||||
|
"@typescript-eslint/naming-convention": "error",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"ignoreParameters": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "error",
|
||||||
|
"@typescript-eslint/no-shadow": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"hoist": "all",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "error",
|
||||||
|
"@typescript-eslint/prefer-function-type": "error",
|
||||||
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
"arrow-body-style": "error",
|
||||||
|
"constructor-super": "error",
|
||||||
|
"eqeqeq": Array [
|
||||||
|
"error",
|
||||||
|
"smart",
|
||||||
|
],
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"id-blacklist": "off",
|
||||||
|
"id-match": "off",
|
||||||
|
"import/no-deprecated": "warn",
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-console": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"log",
|
||||||
|
"warn",
|
||||||
|
"dir",
|
||||||
|
"timeLog",
|
||||||
|
"assert",
|
||||||
|
"clear",
|
||||||
|
"count",
|
||||||
|
"countReset",
|
||||||
|
"group",
|
||||||
|
"groupEnd",
|
||||||
|
"table",
|
||||||
|
"dirxml",
|
||||||
|
"error",
|
||||||
|
"groupCollapsed",
|
||||||
|
"_buffer",
|
||||||
|
"_counters",
|
||||||
|
"_timers",
|
||||||
|
"_groupDepth",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-empty": "off",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-restricted-imports": Array [
|
||||||
|
"error",
|
||||||
|
"rxjs/Rx",
|
||||||
|
],
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"radix": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"@nrwl/nx",
|
||||||
|
],
|
||||||
|
"root": true,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for NestJS applications 4`] = `
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"../../.eslintrc.json",
|
||||||
|
],
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"!**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": Array [
|
||||||
|
"apps/nest-app-1/tsconfig.*?.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for NestJS libraries 1`] = `
|
||||||
|
Object {
|
||||||
|
"dependencies": Object {},
|
||||||
|
"devDependencies": Object {
|
||||||
|
"@nrwl/eslint-plugin-nx": "*",
|
||||||
|
"@nrwl/linter": "*",
|
||||||
|
"@typescript-eslint/eslint-plugin": "4.3.0",
|
||||||
|
"@typescript-eslint/parser": "4.3.0",
|
||||||
|
"eslint": "7.10.0",
|
||||||
|
"eslint-config-prettier": "8.1.0",
|
||||||
|
"eslint-plugin-import": "latest",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for NestJS libraries 2`] = `
|
||||||
|
Object {
|
||||||
|
"projectType": "library",
|
||||||
|
"root": "libs/nest-lib-1",
|
||||||
|
"targets": Object {
|
||||||
|
"lint": Object {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": Object {
|
||||||
|
"lintFilePatterns": Array [
|
||||||
|
"libs/nest-lib-1/**/*.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for NestJS libraries 3`] = `
|
||||||
|
Object {
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@nrwl/nx/enforce-module-boundaries": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"@nx-example/shared/product/data/testing",
|
||||||
|
],
|
||||||
|
"depConstraints": Array [
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:feature",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:app",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:ui",
|
||||||
|
"type:data",
|
||||||
|
"type:types",
|
||||||
|
"type:state",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:feature",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:types",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:state",
|
||||||
|
"type:types",
|
||||||
|
"type:data",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:state",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:data",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:e2e-utils",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:e2e",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"type:types",
|
||||||
|
"type:ui",
|
||||||
|
],
|
||||||
|
"sourceTag": "type:ui",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:products",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:products",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"onlyDependOnLibsWithTags": Array [
|
||||||
|
"scope:cart",
|
||||||
|
"scope:shared",
|
||||||
|
],
|
||||||
|
"sourceTag": "scope:cart",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/typescript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"plugin:@nrwl/nx/javascript",
|
||||||
|
],
|
||||||
|
"files": Array [
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"eslint-plugin-import",
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "error",
|
||||||
|
"@typescript-eslint/dot-notation": "off",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": Array [
|
||||||
|
"off",
|
||||||
|
Object {
|
||||||
|
"accessibility": "explicit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "error",
|
||||||
|
"@typescript-eslint/naming-convention": "error",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"ignoreParameters": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "error",
|
||||||
|
"@typescript-eslint/no-shadow": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"hoist": "all",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "error",
|
||||||
|
"@typescript-eslint/prefer-function-type": "error",
|
||||||
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
"arrow-body-style": "error",
|
||||||
|
"constructor-super": "error",
|
||||||
|
"eqeqeq": Array [
|
||||||
|
"error",
|
||||||
|
"smart",
|
||||||
|
],
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"id-blacklist": "off",
|
||||||
|
"id-match": "off",
|
||||||
|
"import/no-deprecated": "warn",
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-console": Array [
|
||||||
|
"error",
|
||||||
|
Object {
|
||||||
|
"allow": Array [
|
||||||
|
"log",
|
||||||
|
"warn",
|
||||||
|
"dir",
|
||||||
|
"timeLog",
|
||||||
|
"assert",
|
||||||
|
"clear",
|
||||||
|
"count",
|
||||||
|
"countReset",
|
||||||
|
"group",
|
||||||
|
"groupEnd",
|
||||||
|
"table",
|
||||||
|
"dirxml",
|
||||||
|
"error",
|
||||||
|
"groupCollapsed",
|
||||||
|
"_buffer",
|
||||||
|
"_counters",
|
||||||
|
"_timers",
|
||||||
|
"_groupDepth",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-empty": "off",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-restricted-imports": Array [
|
||||||
|
"error",
|
||||||
|
"rxjs/Rx",
|
||||||
|
],
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"radix": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"plugins": Array [
|
||||||
|
"@nrwl/nx",
|
||||||
|
],
|
||||||
|
"root": true,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`convert-tslint-to-eslint should work for NestJS libraries 4`] = `
|
||||||
|
Object {
|
||||||
|
"extends": Array [
|
||||||
|
"../../.eslintrc.json",
|
||||||
|
],
|
||||||
|
"ignorePatterns": Array [
|
||||||
|
"!**/*",
|
||||||
|
],
|
||||||
|
"overrides": Array [
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"parserOptions": Object {
|
||||||
|
"project": Array [
|
||||||
|
"libs/nest-lib-1/tsconfig.*?.json",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"files": Array [
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": Object {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"rules": Object {
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,235 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
joinPathFragments,
|
||||||
|
readJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
writeJson,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||||
|
import { exampleRootTslintJson } from '@nrwl/linter';
|
||||||
|
import { conversionGenerator } from './convert-tslint-to-eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't run actual child_process implementation of installPackagesTask()
|
||||||
|
*/
|
||||||
|
jest.mock('child_process');
|
||||||
|
|
||||||
|
const appProjectName = 'nest-app-1';
|
||||||
|
const appProjectRoot = `apps/${appProjectName}`;
|
||||||
|
const appProjectTSLintJsonPath = joinPathFragments(
|
||||||
|
appProjectRoot,
|
||||||
|
'tslint.json'
|
||||||
|
);
|
||||||
|
const libProjectName = 'nest-lib-1';
|
||||||
|
const libProjectRoot = `libs/${libProjectName}`;
|
||||||
|
const libProjectTSLintJsonPath = joinPathFragments(
|
||||||
|
libProjectRoot,
|
||||||
|
'tslint.json'
|
||||||
|
);
|
||||||
|
// Used to configure the test Tree and stub the response from tslint-to-eslint-config util findReportedConfiguration()
|
||||||
|
const projectTslintJsonData = {
|
||||||
|
raw: {
|
||||||
|
extends: '../../tslint.json',
|
||||||
|
rules: {
|
||||||
|
// User custom TS rule
|
||||||
|
'no-empty-interface': true,
|
||||||
|
// User custom rule with no known automated converter
|
||||||
|
'some-super-custom-rule-with-no-converter': true,
|
||||||
|
},
|
||||||
|
linterOptions: {
|
||||||
|
exclude: ['!**/*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tslintPrintConfigResult: {
|
||||||
|
rules: {
|
||||||
|
'no-empty-interface': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
'some-super-custom-rule-with-no-converter': {
|
||||||
|
ruleArguments: [],
|
||||||
|
ruleSeverity: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function mockFindReportedConfiguration(_, pathToTslintJson) {
|
||||||
|
switch (pathToTslintJson) {
|
||||||
|
case 'tslint.json':
|
||||||
|
return exampleRootTslintJson.tslintPrintConfigResult;
|
||||||
|
case appProjectTSLintJsonPath:
|
||||||
|
return projectTslintJsonData.tslintPrintConfigResult;
|
||||||
|
case libProjectTSLintJsonPath:
|
||||||
|
return projectTslintJsonData.tslintPrintConfigResult;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`mockFindReportedConfiguration - Did not recognize path ${pathToTslintJson}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See ./mock-tslint-to-eslint-config.ts for why this is needed
|
||||||
|
*/
|
||||||
|
jest.mock('tslint-to-eslint-config', () => {
|
||||||
|
return {
|
||||||
|
// Since upgrading to (ts-)jest 26 this usage of this mock has caused issues...
|
||||||
|
// @ts-ignore
|
||||||
|
...jest.requireActual('tslint-to-eslint-config'),
|
||||||
|
findReportedConfiguration: jest.fn(mockFindReportedConfiguration),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock the the mutating fs utilities used within the conversion logic, they are not
|
||||||
|
* needed because of our stubbed response for findReportedConfiguration() above, and
|
||||||
|
* they would cause noise in the git data of the actual Nx repo when the tests run.
|
||||||
|
*/
|
||||||
|
jest.mock('fs', () => {
|
||||||
|
return {
|
||||||
|
// Since upgrading to (ts-)jest 26 this usage of this mock has caused issues...
|
||||||
|
// @ts-ignore
|
||||||
|
...jest.requireActual('fs'),
|
||||||
|
writeFileSync: jest.fn(),
|
||||||
|
mkdirSync: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('convert-tslint-to-eslint', () => {
|
||||||
|
let host: Tree;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
host = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
writeJson(host, 'tslint.json', exampleRootTslintJson.raw);
|
||||||
|
|
||||||
|
addProjectConfiguration(host, appProjectName, {
|
||||||
|
root: appProjectRoot,
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - BEFORE CONVERSION
|
||||||
|
*
|
||||||
|
* TSLint executor configured for the project
|
||||||
|
*/
|
||||||
|
lint: {
|
||||||
|
executor: '@angular-devkit/build-angular:tslint',
|
||||||
|
options: {
|
||||||
|
exclude: ['**/node_modules/**', '!apps/nest-app-1/**/*'],
|
||||||
|
tsConfig: ['apps/nest-app-1/tsconfig.app.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
addProjectConfiguration(host, libProjectName, {
|
||||||
|
root: libProjectRoot,
|
||||||
|
projectType: 'library',
|
||||||
|
targets: {
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - BEFORE CONVERSION
|
||||||
|
*
|
||||||
|
* TSLint executor configured for the project
|
||||||
|
*/
|
||||||
|
lint: {
|
||||||
|
executor: '@angular-devkit/build-angular:tslint',
|
||||||
|
options: {
|
||||||
|
exclude: ['**/node_modules/**', '!libs/nest-lib-1/**/*'],
|
||||||
|
tsConfig: ['libs/nest-lib-1/tsconfig.app.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Existing tslint.json file for the app project
|
||||||
|
*/
|
||||||
|
writeJson(host, 'apps/nest-app-1/tslint.json', projectTslintJsonData.raw);
|
||||||
|
/**
|
||||||
|
* Existing tslint.json file for the lib project
|
||||||
|
*/
|
||||||
|
writeJson(host, 'libs/nest-lib-1/tslint.json', projectTslintJsonData.raw);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for NestJS applications', async () => {
|
||||||
|
await conversionGenerator(host, {
|
||||||
|
project: appProjectName,
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It should ensure the required Nx packages are installed and available
|
||||||
|
*
|
||||||
|
* NOTE: tslint-to-eslint-config should NOT be present
|
||||||
|
*/
|
||||||
|
expect(readJson(host, 'package.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - AFTER CONVERSION
|
||||||
|
*
|
||||||
|
* It should replace the TSLint executor with the ESLint one
|
||||||
|
*/
|
||||||
|
expect(readProjectConfiguration(host, appProjectName)).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root level .eslintrc.json should now have been generated
|
||||||
|
*/
|
||||||
|
expect(readJson(host, '.eslintrc.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project level .eslintrc.json should now have been generated
|
||||||
|
* and extend from the root, as well as applying any customizations
|
||||||
|
* which are specific to this projectType.
|
||||||
|
*/
|
||||||
|
expect(
|
||||||
|
readJson(host, joinPathFragments(appProjectRoot, '.eslintrc.json'))
|
||||||
|
).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project's TSLint file should have been deleted
|
||||||
|
*/
|
||||||
|
expect(host.exists(appProjectTSLintJsonPath)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for NestJS libraries', async () => {
|
||||||
|
await conversionGenerator(host, {
|
||||||
|
project: libProjectName,
|
||||||
|
removeTSLintIfNoMoreTSLintTargets: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It should ensure the required Nx packages are installed and available
|
||||||
|
*
|
||||||
|
* NOTE: tslint-to-eslint-config should NOT be present
|
||||||
|
*/
|
||||||
|
expect(readJson(host, 'package.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LINT TARGET CONFIG - AFTER CONVERSION
|
||||||
|
*
|
||||||
|
* It should replace the TSLint executor with the ESLint one
|
||||||
|
*/
|
||||||
|
expect(readProjectConfiguration(host, libProjectName)).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root level .eslintrc.json should now have been generated
|
||||||
|
*/
|
||||||
|
expect(readJson(host, '.eslintrc.json')).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project level .eslintrc.json should now have been generated
|
||||||
|
* and extend from the root, as well as applying any customizations
|
||||||
|
* which are specific to this projectType.
|
||||||
|
*/
|
||||||
|
expect(
|
||||||
|
readJson(host, joinPathFragments(libProjectRoot, '.eslintrc.json'))
|
||||||
|
).toMatchSnapshot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The project's TSLint file should have been deleted
|
||||||
|
*/
|
||||||
|
expect(host.exists(libProjectTSLintJsonPath)).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
import {
|
||||||
|
convertNxGenerator,
|
||||||
|
formatFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
Tree,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { ConvertTSLintToESLintSchema, ProjectConverter } from '@nrwl/linter';
|
||||||
|
import {
|
||||||
|
addLintingToApplication,
|
||||||
|
NormalizedSchema as AddLintForApplicationSchema,
|
||||||
|
} from '@nrwl/node/src/generators/application/application';
|
||||||
|
import {
|
||||||
|
addLint as addLintingToLibraryGenerator,
|
||||||
|
NormalizedSchema as AddLintForLibrarySchema,
|
||||||
|
} from '@nrwl/workspace/src/generators/library/library';
|
||||||
|
import type { Linter } from 'eslint';
|
||||||
|
|
||||||
|
export async function conversionGenerator(
|
||||||
|
host: Tree,
|
||||||
|
options: ConvertTSLintToESLintSchema
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The ProjectConverter instance encapsulates all the standard operations we need
|
||||||
|
* to perform in order to convert a project from TSLint to ESLint, as well as some
|
||||||
|
* extensibility points for adjusting the behavior on a per package basis.
|
||||||
|
*
|
||||||
|
* E.g. @nrwl/angular projects might need to make different changes to the final
|
||||||
|
* ESLint config when compared with @nrwl/nest projects.
|
||||||
|
*
|
||||||
|
* See the ProjectConverter implementation for a full breakdown of what it does.
|
||||||
|
*/
|
||||||
|
const projectConverter = new ProjectConverter({
|
||||||
|
host,
|
||||||
|
projectName: options.project,
|
||||||
|
eslintInitializer: async ({ projectName, projectConfig }) => {
|
||||||
|
/**
|
||||||
|
* Using .js is not an option with NestJS, so we always set it to false when
|
||||||
|
* delegating to the external (more generic) generators below.
|
||||||
|
*/
|
||||||
|
const js = false;
|
||||||
|
|
||||||
|
if (projectConfig.projectType === 'application') {
|
||||||
|
await addLintingToApplication(host, {
|
||||||
|
linter: 'eslint' as any,
|
||||||
|
name: projectName,
|
||||||
|
appProjectRoot: projectConfig.root,
|
||||||
|
js,
|
||||||
|
} as AddLintForApplicationSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectConfig.projectType === 'library') {
|
||||||
|
await addLintingToLibraryGenerator(host, {
|
||||||
|
linter: 'eslint',
|
||||||
|
name: projectName,
|
||||||
|
projectRoot: projectConfig.root,
|
||||||
|
js,
|
||||||
|
} as AddLintForLibrarySchema);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically install tslint-to-eslint-config to assist with the conversion.
|
||||||
|
*/
|
||||||
|
projectConverter.installTSLintToESLintConfigPackage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the standard (which is applicable to the current package) ESLint setup
|
||||||
|
* for converting the project.
|
||||||
|
*/
|
||||||
|
await projectConverter.initESLint();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the root tslint.json and apply the converted rules to the root .eslintrc.json.
|
||||||
|
*/
|
||||||
|
const rootConfigInstallTask = await projectConverter.convertRootTSLintConfig(
|
||||||
|
(json) => removeCodelyzerRelatedRules(json)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the project's tslint.json to an equivalent ESLint config.
|
||||||
|
*/
|
||||||
|
const projectConfigInstallTask = await projectConverter.convertProjectConfig(
|
||||||
|
(json) => json
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up the original TSLint configuration for the project.
|
||||||
|
*/
|
||||||
|
projectConverter.removeProjectTSLintFile();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store user preference regarding removeTSLintIfNoMoreTSLintTargets for the collection
|
||||||
|
*/
|
||||||
|
projectConverter.setDefaults(
|
||||||
|
'@nrwl/nest',
|
||||||
|
options.removeTSLintIfNoMoreTSLintTargets
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on user preference and remaining usage, remove TSLint from the workspace entirely.
|
||||||
|
*/
|
||||||
|
let uninstallTSLintTask: GeneratorCallback = () => Promise.resolve(undefined);
|
||||||
|
if (
|
||||||
|
options.removeTSLintIfNoMoreTSLintTargets &&
|
||||||
|
!projectConverter.isTSLintUsedInWorkspace()
|
||||||
|
) {
|
||||||
|
uninstallTSLintTask = projectConverter.removeTSLintFromWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
await formatFiles(host);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
projectConverter.uninstallTSLintToESLintConfigPackage();
|
||||||
|
await rootConfigInstallTask();
|
||||||
|
await projectConfigInstallTask();
|
||||||
|
await uninstallTSLintTask();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const conversionSchematic = convertNxGenerator(conversionGenerator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove any @angular-eslint rules that were applied as a result of converting prior codelyzer
|
||||||
|
* rules, because they are only relevant for Angular projects.
|
||||||
|
*/
|
||||||
|
function removeCodelyzerRelatedRules(json: Linter.Config): Linter.Config {
|
||||||
|
for (const ruleName of Object.keys(json.rules)) {
|
||||||
|
if (ruleName.startsWith('@angular-eslint')) {
|
||||||
|
delete json.rules[ruleName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"id": "nest-convert-tslint-to-eslint",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Convert a NestJS project from TSLint to ESLint",
|
||||||
|
"description": "NOTE: Does not work in --dry-run mode",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "g convert-tslint-to-eslint myapp",
|
||||||
|
"description": "Convert the NestJS project `myapp` from TSLint to ESLint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"description": "The name of the NestJS project to convert.",
|
||||||
|
"type": "string",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "Which NestJS project would you like to convert from TSLint to ESLint?"
|
||||||
|
},
|
||||||
|
"removeTSLintIfNoMoreTSLintTargets": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If this conversion leaves no more TSLint usage in the workspace, it will remove TSLint and related dependencies and configuration",
|
||||||
|
"default": true,
|
||||||
|
"x-prompt": "Would you like to remove TSLint and its related config if there are no TSLint projects remaining after this conversion?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["project"]
|
||||||
|
}
|
||||||
@ -30,7 +30,7 @@ import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-ser
|
|||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
import { initGenerator } from '../init/init';
|
import { initGenerator } from '../init/init';
|
||||||
|
|
||||||
interface NormalizedSchema extends Schema {
|
export interface NormalizedSchema extends Schema {
|
||||||
appProjectRoot: string;
|
appProjectRoot: string;
|
||||||
parsedTags: string[];
|
parsedTags: string[];
|
||||||
}
|
}
|
||||||
@ -157,6 +157,25 @@ function addProxy(tree: Tree, options: NormalizedSchema) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addLintingToApplication(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
const lintTask = await lintProjectGenerator(tree, {
|
||||||
|
linter: options.linter,
|
||||||
|
project: options.name,
|
||||||
|
tsConfigPaths: [
|
||||||
|
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
||||||
|
],
|
||||||
|
eslintFilePatterns: [
|
||||||
|
`${options.appProjectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
|
||||||
|
],
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return lintTask;
|
||||||
|
}
|
||||||
|
|
||||||
export async function applicationGenerator(tree: Tree, schema: Schema) {
|
export async function applicationGenerator(tree: Tree, schema: Schema) {
|
||||||
const options = normalizeOptions(tree, schema);
|
const options = normalizeOptions(tree, schema);
|
||||||
|
|
||||||
@ -171,15 +190,7 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
|
|||||||
addProject(tree, options);
|
addProject(tree, options);
|
||||||
|
|
||||||
if (options.linter !== Linter.None) {
|
if (options.linter !== Linter.None) {
|
||||||
const lintTask = await lintProjectGenerator(tree, {
|
const lintTask = await addLintingToApplication(tree, options);
|
||||||
linter: options.linter,
|
|
||||||
project: options.name,
|
|
||||||
tsConfigPaths: [
|
|
||||||
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
|
||||||
],
|
|
||||||
eslintFilePatterns: [`${options.appProjectRoot}/**/*.ts`],
|
|
||||||
skipFormat: true,
|
|
||||||
});
|
|
||||||
tasks.push(lintTask);
|
tasks.push(lintTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ function addProject(tree: Tree, options: NormalizedSchema) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLint(
|
export function addLint(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: NormalizedSchema
|
options: NormalizedSchema
|
||||||
): Promise<GeneratorCallback> {
|
): Promise<GeneratorCallback> {
|
||||||
|
|||||||
@ -27,7 +27,13 @@ const IGNORE_MATCHES = {
|
|||||||
'identity-obj-proxy',
|
'identity-obj-proxy',
|
||||||
'@angular-devkit/schematics',
|
'@angular-devkit/schematics',
|
||||||
],
|
],
|
||||||
linter: ['eslint', '@angular-devkit/schematics', '@angular-devkit/architect'],
|
linter: [
|
||||||
|
'eslint',
|
||||||
|
'@angular-devkit/schematics',
|
||||||
|
'@angular-devkit/architect',
|
||||||
|
// Installed and uninstalled dynamically when the conversion generator runs
|
||||||
|
'tslint-to-eslint-config',
|
||||||
|
],
|
||||||
next: [
|
next: [
|
||||||
'@angular-devkit/architect',
|
'@angular-devkit/architect',
|
||||||
'@nrwl/devkit',
|
'@nrwl/devkit',
|
||||||
|
|||||||
@ -28,6 +28,8 @@
|
|||||||
"@nrwl/express": ["./packages/express"],
|
"@nrwl/express": ["./packages/express"],
|
||||||
"@nrwl/nest": ["./packages/nest"],
|
"@nrwl/nest": ["./packages/nest"],
|
||||||
"@nrwl/next": ["./packages/next"],
|
"@nrwl/next": ["./packages/next"],
|
||||||
|
"@nrwl/node": ["./packages/node"],
|
||||||
|
"@nrwl/node/*": ["./packages/node/*"],
|
||||||
"@nrwl/linter": ["./packages/linter"],
|
"@nrwl/linter": ["./packages/linter"],
|
||||||
"@nrwl/jest": ["./packages/jest"],
|
"@nrwl/jest": ["./packages/jest"],
|
||||||
"@nrwl/workspace/testing": ["./packages/workspace/testing"],
|
"@nrwl/workspace/testing": ["./packages/workspace/testing"],
|
||||||
|
|||||||
52
yarn.lock
52
yarn.lock
@ -8124,6 +8124,11 @@ codelyzer@~5.0.1:
|
|||||||
source-map "^0.5.7"
|
source-map "^0.5.7"
|
||||||
sprintf-js "^1.1.2"
|
sprintf-js "^1.1.2"
|
||||||
|
|
||||||
|
coffeescript@1.12.7:
|
||||||
|
version "1.12.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27"
|
||||||
|
integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==
|
||||||
|
|
||||||
collect-v8-coverage@^1.0.0:
|
collect-v8-coverage@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59"
|
resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59"
|
||||||
@ -8219,6 +8224,11 @@ commander@3.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
|
||||||
integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
|
integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
|
||||||
|
|
||||||
|
commander@7.1.0:
|
||||||
|
version "7.1.0"
|
||||||
|
resolved "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff"
|
||||||
|
integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==
|
||||||
|
|
||||||
commander@^2.11.0, commander@^2.12.1, commander@^2.19.0, commander@^2.20.0:
|
commander@^2.11.0, commander@^2.12.1, commander@^2.19.0, commander@^2.20.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
@ -8999,6 +9009,13 @@ crypto-random-string@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
||||||
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
|
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
|
||||||
|
|
||||||
|
cson-parser@4.0.8:
|
||||||
|
version "4.0.8"
|
||||||
|
resolved "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.8.tgz#373f3bde1be018267fccbc575d82120c2a7a645e"
|
||||||
|
integrity sha512-Hdv3N2E5JU4vAp88cxcs/Y+0L0y0HJnpoc067E//qbXNF4/cG713rFLryD0QvKZYK6w3QBA67t7UOfo2ymh8Sg==
|
||||||
|
dependencies:
|
||||||
|
coffeescript "1.12.7"
|
||||||
|
|
||||||
css-color-keywords@^1.0.0:
|
css-color-keywords@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
|
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
|
||||||
@ -10563,7 +10580,7 @@ escodegen@^1.11.1, escodegen@^1.14.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
eslint-config-prettier@^8.1.0:
|
eslint-config-prettier@8.1.0, eslint-config-prettier@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz#4ef1eaf97afe5176e6a75ddfb57c335121abc5a6"
|
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz#4ef1eaf97afe5176e6a75ddfb57c335121abc5a6"
|
||||||
integrity sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==
|
integrity sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==
|
||||||
@ -14452,6 +14469,13 @@ json3@^3.3.2, json3@^3.3.3:
|
|||||||
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
|
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
|
||||||
integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
|
integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
|
||||||
|
|
||||||
|
json5@2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
|
||||||
|
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
|
||||||
|
dependencies:
|
||||||
|
minimist "^1.2.5"
|
||||||
|
|
||||||
json5@2.x, json5@^2.1.1, json5@^2.1.2, json5@^2.1.3:
|
json5@2.x, json5@^2.1.1, json5@^2.1.2, json5@^2.1.3:
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
|
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
|
||||||
@ -15209,6 +15233,11 @@ lodash@4.17.20, lodash@^4.17.20:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||||
|
|
||||||
|
lodash@4.17.21:
|
||||||
|
version "4.17.21"
|
||||||
|
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
lodash@^4.0.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.0:
|
lodash@^4.0.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.0:
|
||||||
version "4.17.19"
|
version "4.17.19"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||||
@ -21932,6 +21961,22 @@ tslib@^1.13.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
|
tslint-to-eslint-config@2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.npmjs.org/tslint-to-eslint-config/-/tslint-to-eslint-config-2.2.0.tgz#711a1596b765175139193a3878a8a12ebdf91318"
|
||||||
|
integrity sha512-ta+V1G8y431CPXuJHbzlYYxuAyvKZM8llLZnFN7jy0C98dMsz0jIQCZW7dH5I6wt10gTyMOw7h5W+JEeQumbfQ==
|
||||||
|
dependencies:
|
||||||
|
chalk "4.1.0"
|
||||||
|
commander "7.1.0"
|
||||||
|
cson-parser "4.0.8"
|
||||||
|
eslint-config-prettier "8.1.0"
|
||||||
|
glob "7.1.6"
|
||||||
|
json5 "2.2.0"
|
||||||
|
lodash "4.17.21"
|
||||||
|
minimatch "3.0.4"
|
||||||
|
tslint "6.1.3"
|
||||||
|
typescript "4.2.2"
|
||||||
|
|
||||||
tslint@6.1.3:
|
tslint@6.1.3:
|
||||||
version "6.1.3"
|
version "6.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904"
|
resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904"
|
||||||
@ -22076,6 +22121,11 @@ typescript@4.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
|
||||||
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
|
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
|
||||||
|
|
||||||
|
typescript@4.2.2:
|
||||||
|
version "4.2.2"
|
||||||
|
resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz#1450f020618f872db0ea17317d16d8da8ddb8c4c"
|
||||||
|
integrity sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==
|
||||||
|
|
||||||
uglify-js@3.4.x:
|
uglify-js@3.4.x:
|
||||||
version "3.4.10"
|
version "3.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user