Skip to content

Commit

Permalink
feat(linter): update-root-eslint-config-to-use-overrides migration
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesHenry committed Oct 22, 2020
1 parent cfea9ea commit bea0cfe
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 0 deletions.
5 changes: 5 additions & 0 deletions packages/linter/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"version": "10.4.0-beta.0",
"description": "Update ESLint config files to use preset configs which eslint-plugin-nx exports",
"factory": "./src/migrations/update-10-4-0/update-eslint-configs-to-use-nx-presets"
},
"update-root-eslint-config-to-use-overrides": {
"version": "10.4.0-beta.1",
"description": "Update root ESLint config to use overrides",
"factory": "./src/migrations/update-10-4-0/update-root-eslint-config-to-use-overrides"
}
},
"packageJsonUpdates": {
Expand Down
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);
});
});
});
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()]);
}

0 comments on commit bea0cfe

Please sign in to comment.