-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): update-root-eslint-config-to-use-overrides migration
- Loading branch information
1 parent
cfea9ea
commit bea0cfe
Showing
3 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
209 changes: 209 additions & 0 deletions
209
...es/linter/src/migrations/update-10-4-0/update-root-eslint-config-to-use-overrides.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
import { Tree } from '@angular-devkit/schematics'; | ||
import { readJsonInTree } from '@nrwl/workspace'; | ||
import { createEmptyWorkspace } from '@nrwl/workspace/testing'; | ||
import { runMigration } from '../../utils/testing'; | ||
|
||
describe('Update root ESLint config to use overrides', () => { | ||
let tree: Tree; | ||
beforeEach(async () => { | ||
tree = Tree.empty(); | ||
tree = createEmptyWorkspace(tree); | ||
}); | ||
|
||
const testCases = [ | ||
{ | ||
// Most recent root ESLint config (before this change) with no modifications | ||
input: { | ||
root: true, | ||
ignorePatterns: ['**/*'], | ||
plugins: ['@nrwl/nx'], | ||
extends: ['plugin:@nrwl/nx/typescript'], | ||
rules: { | ||
'@nrwl/nx/enforce-module-boundaries': [ | ||
'error', | ||
{ | ||
enforceBuildableLibDependency: true, | ||
allow: [], | ||
depConstraints: [ | ||
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }, | ||
], | ||
}, | ||
], | ||
}, | ||
}, | ||
expected: { | ||
root: true, | ||
ignorePatterns: ['**/*'], | ||
plugins: ['@nrwl/nx'], | ||
overrides: [ | ||
{ | ||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'], | ||
rules: { | ||
'@nrwl/nx/enforce-module-boundaries': [ | ||
'error', | ||
{ | ||
enforceBuildableLibDependency: true, | ||
allow: [], | ||
depConstraints: [ | ||
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }, | ||
], | ||
}, | ||
], | ||
}, | ||
}, | ||
{ | ||
files: ['*.ts', '*.tsx'], | ||
extends: ['plugin:@nrwl/nx/typescript'], | ||
parserOptions: { project: './tsconfig.*?.json' }, | ||
rules: {}, | ||
}, | ||
{ | ||
files: ['*.js', '*.jsx'], | ||
extends: ['plugin:@nrwl/nx/javascript'], | ||
rules: {}, | ||
}, | ||
], | ||
}, | ||
}, | ||
|
||
{ | ||
// Example using custom overrides already (should be a noop) | ||
input: { | ||
root: true, | ||
ignorePatterns: ['**/*'], | ||
plugins: ['@nrwl/nx'], | ||
extends: ['plugin:@nrwl/nx/typescript'], | ||
overrides: [ | ||
{ | ||
files: ['*.ts'], | ||
rules: { | ||
foo: 'error', | ||
}, | ||
}, | ||
], | ||
}, | ||
expected: { | ||
root: true, | ||
ignorePatterns: ['**/*'], | ||
plugins: ['@nrwl/nx'], | ||
extends: ['plugin:@nrwl/nx/typescript'], | ||
overrides: [ | ||
{ | ||
files: ['*.ts'], | ||
rules: { | ||
foo: 'error', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
|
||
{ | ||
// Example using custom rules and plugins at the top-level | ||
input: { | ||
root: true, | ||
ignorePatterns: ['**/*'], | ||
plugins: ['@nrwl/nx', 'plugin-a', 'plugin-b'], | ||
extends: ['plugin:@nrwl/nx/typescript'], | ||
rules: { | ||
bar: 'warn', | ||
'plugin-a/qux': ['error', { someConfig: true }], | ||
'plugin-b/baz': 'off', | ||
}, | ||
}, | ||
expected: { | ||
root: true, | ||
ignorePatterns: ['**/*'], | ||
plugins: ['@nrwl/nx'], | ||
overrides: [ | ||
{ | ||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'], | ||
plugins: ['plugin-a', 'plugin-b'], | ||
rules: { | ||
bar: 'warn', | ||
'plugin-a/qux': ['error', { someConfig: true }], | ||
'plugin-b/baz': 'off', | ||
}, | ||
}, | ||
{ | ||
files: ['*.ts', '*.tsx'], | ||
extends: ['plugin:@nrwl/nx/typescript'], | ||
parserOptions: { project: './tsconfig.*?.json' }, | ||
rules: {}, | ||
}, | ||
{ | ||
files: ['*.js', '*.jsx'], | ||
extends: ['plugin:@nrwl/nx/javascript'], | ||
rules: {}, | ||
}, | ||
], | ||
}, | ||
}, | ||
|
||
{ | ||
// Example using other custom config at the top-level | ||
input: { | ||
root: true, | ||
ignorePatterns: ['**/*'], | ||
plugins: ['@nrwl/nx'], | ||
settings: { | ||
foo: 'bar', | ||
}, | ||
env: { | ||
browser: true, | ||
}, | ||
parser: 'some-custom-parser-value', | ||
parserOptions: { | ||
custom: 'option', | ||
}, | ||
extends: ['plugin:@nrwl/nx/typescript', 'custom-extends-config'], | ||
}, | ||
expected: { | ||
root: true, | ||
ignorePatterns: ['**/*'], | ||
plugins: ['@nrwl/nx'], | ||
overrides: [ | ||
{ | ||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'], | ||
extends: ['custom-extends-config'], | ||
env: { | ||
browser: true, | ||
}, | ||
settings: { | ||
foo: 'bar', | ||
}, | ||
parser: 'some-custom-parser-value', | ||
parserOptions: { | ||
custom: 'option', | ||
}, | ||
rules: {}, | ||
}, | ||
{ | ||
files: ['*.ts', '*.tsx'], | ||
extends: ['plugin:@nrwl/nx/typescript'], | ||
parserOptions: { project: './tsconfig.*?.json' }, | ||
rules: {}, | ||
}, | ||
{ | ||
files: ['*.js', '*.jsx'], | ||
extends: ['plugin:@nrwl/nx/javascript'], | ||
rules: {}, | ||
}, | ||
], | ||
}, | ||
}, | ||
]; | ||
|
||
testCases.forEach((tc, i) => { | ||
it(`should update the existing root .eslintrc.json file to use overrides, CASE ${i}`, async () => { | ||
tree.create('.eslintrc.json', JSON.stringify(tc.input)); | ||
|
||
const result = await runMigration( | ||
'update-root-eslint-config-to-use-overrides', | ||
{}, | ||
tree | ||
); | ||
expect(readJsonInTree(result, '.eslintrc.json')).toEqual(tc.expected); | ||
}); | ||
}); | ||
}); |
117 changes: 117 additions & 0 deletions
117
packages/linter/src/migrations/update-10-4-0/update-root-eslint-config-to-use-overrides.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { chain, noop, Tree } from '@angular-devkit/schematics'; | ||
import { formatFiles, updateJsonInTree } from '@nrwl/workspace'; | ||
import type { Linter } from 'eslint'; | ||
|
||
/** | ||
* We want to update the JSON in such a way that we: | ||
* - translate the config to use overrides | ||
* - don't break existing setups | ||
* | ||
* In order to achieve the second point we need to assume | ||
* that any existing top level rules/plugins etc are intended | ||
* to be run on all source files. | ||
*/ | ||
function updateRootESLintConfig(host: Tree) { | ||
return host.exists('.eslintrc.json') | ||
? updateJsonInTree('.eslintrc.json', (json: Linter.Config) => { | ||
/** | ||
* If the user already has overrides specified it is likely they have "forged their own path" | ||
* when it comes to their ESLint setup, so we do nothing. | ||
*/ | ||
if (Array.isArray(json.overrides)) { | ||
return json; | ||
} | ||
|
||
let normalizedExtends: string[] | undefined = undefined; | ||
if (json.extends) { | ||
if (typeof json.extends === 'string') { | ||
normalizedExtends = [json.extends]; | ||
} else if (Array.isArray(json.extends)) { | ||
normalizedExtends = json.extends; | ||
} | ||
} | ||
|
||
json.overrides = [ | ||
/** | ||
* This configuration is intended to apply to all "source code" (but not | ||
* markup like HTML, or other custom file types like GraphQL). | ||
* | ||
* This is where we will apply any top-level config that the user currently | ||
* has to ensure that it behaves the same before and after the migration. | ||
*/ | ||
{ | ||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'], | ||
extends: undefinedIfEmptyArr( | ||
normalizedExtends | ||
? normalizedExtends.filter( | ||
(e) => e !== 'plugin:@nrwl/nx/typescript' | ||
) | ||
: normalizedExtends | ||
), | ||
env: json.env, | ||
settings: json.settings, | ||
parser: json.parser, | ||
parserOptions: json.parserOptions, | ||
plugins: undefinedIfEmptyArr( | ||
json.plugins.filter((p) => p !== '@nrwl/nx') // remains at top-level, used everywhere | ||
), | ||
rules: json.rules || {}, | ||
}, | ||
|
||
/** | ||
* This configuration is intended to apply to all TypeScript source files. | ||
* See the eslint-plugin-nx package for what is in the referenced shareable config. | ||
*/ | ||
{ | ||
files: ['*.ts', '*.tsx'], | ||
extends: ['plugin:@nrwl/nx/typescript'], | ||
parserOptions: { project: './tsconfig.*?.json' }, | ||
/** | ||
* Having an empty rules object present makes it more obvious to the user where they would | ||
* extend things from if they needed to | ||
*/ | ||
rules: {}, | ||
}, | ||
|
||
/** | ||
* This configuration is intended to apply to all JavaScript source files. | ||
* See the eslint-plugin-nx package for what is in the referenced shareable config. | ||
*/ | ||
{ | ||
files: ['*.js', '*.jsx'], | ||
extends: ['plugin:@nrwl/nx/javascript'], | ||
/** | ||
* Having an empty rules object present makes it more obvious to the user where they would | ||
* extend things from if they needed to | ||
*/ | ||
rules: {}, | ||
}, | ||
]; | ||
|
||
/** | ||
* Clean up after copying config to main override | ||
*/ | ||
json.plugins = ['@nrwl/nx']; | ||
delete json.rules; | ||
delete json.extends; | ||
delete json.env; | ||
delete json.settings; | ||
delete json.globals; | ||
delete json.parser; | ||
delete json.parserOptions; | ||
|
||
return json; | ||
}) | ||
: noop(); | ||
} | ||
|
||
function undefinedIfEmptyArr<T>(possibleArr: T): T | undefined { | ||
if (Array.isArray(possibleArr) && possibleArr.length === 0) { | ||
return undefined; | ||
} | ||
return possibleArr; | ||
} | ||
|
||
export default function () { | ||
return chain([updateRootESLintConfig, formatFiles()]); | ||
} |