diff --git a/packages/workspace/migrations.json b/packages/workspace/migrations.json index 48f158b195..15dc52b17d 100644 --- a/packages/workspace/migrations.json +++ b/packages/workspace/migrations.json @@ -99,6 +99,11 @@ "version": "10.0.0-beta.0", "description": "Migrate tsconfigs to solution style tsconfigs", "factory": "./src/migrations/update-10-0-0/solution-tsconfigs" + }, + "migrate-eslintrc-tsconfig": { + "version": "10.0.1-beta.0", + "description": "Migrate .eslintrc files to reference new tsconfig", + "factory": "./src/migrations/update-10-0-1/migrate-eslintrc" } }, "packageJsonUpdates": { diff --git a/packages/workspace/src/migrations/update-10-0-0/solution-tsconfigs.ts b/packages/workspace/src/migrations/update-10-0-0/solution-tsconfigs.ts index b9805f7cc5..306a4e1be5 100644 --- a/packages/workspace/src/migrations/update-10-0-0/solution-tsconfigs.ts +++ b/packages/workspace/src/migrations/update-10-0-0/solution-tsconfigs.ts @@ -1,19 +1,13 @@ import { basename, dirname, join, normalize, Path } from '@angular-devkit/core'; -import { - callRule, - chain, - Rule, - SchematicContext, - Tree, -} from '@angular-devkit/schematics'; +import { chain, Rule, Tree } from '@angular-devkit/schematics'; import { formatFiles, NxJson, readJsonInTree, updateJsonInTree, } from '@nrwl/workspace'; -import ignore from 'ignore'; import { relative } from 'path'; +import { visitNotIgnoredFiles } from '../../utils/rules/visit-not-ignored-files'; function renameRootTsconfig(host: Tree) { if (!host.exists('tsconfig.json')) { @@ -23,40 +17,6 @@ function renameRootTsconfig(host: Tree) { host.rename('tsconfig.json', 'tsconfig.base.json'); } -function visitNotIgnoredFiles( - visitor: (file: Path, host: Tree, context: SchematicContext) => void | Rule, - dir: Path = normalize('') -): Rule { - return (host, context) => { - let ig; - if (host.exists('.gitignore')) { - ig = ignore(); - ig.add(host.read('.gitignore').toString()); - } - function visit(_dir: Path) { - if (_dir && ig?.ignores(_dir)) { - return; - } - const dirEntry = host.getDir(_dir); - dirEntry.subfiles.forEach((file) => { - if (ig?.ignores(join(_dir, file))) { - return; - } - const maybeRule = visitor(join(_dir, file), host, context); - if (maybeRule) { - callRule(maybeRule, host, context).subscribe(); - } - }); - - dirEntry.subdirs.forEach((subdir) => { - visit(join(_dir, subdir)); - }); - } - - visit(dir); - }; -} - function moveIncludesToProjectTsconfig( file: Path, extendedTsconfigPath: Path, diff --git a/packages/workspace/src/migrations/update-10-0-1/migrate-eslintrc.spec.ts b/packages/workspace/src/migrations/update-10-0-1/migrate-eslintrc.spec.ts new file mode 100644 index 0000000000..e3783a5b66 --- /dev/null +++ b/packages/workspace/src/migrations/update-10-0-1/migrate-eslintrc.spec.ts @@ -0,0 +1,53 @@ +import { Tree } from '@angular-devkit/schematics'; +import { callRule, runMigration } from '../../utils/testing'; +import { readJsonInTree, updateJsonInTree } from '@nrwl/workspace'; + +describe('Eslintrc Migration', () => { + let tree: Tree; + + beforeEach(async () => { + tree = Tree.empty(); + tree = await callRule( + updateJsonInTree('.eslintrc', () => ({ + parserOptions: { + project: './tsconfig.json', + }, + })), + tree + ); + tree = await callRule( + updateJsonInTree('project1/.eslintrc', () => ({ + parserOptions: { + project: '../tsconfig.json', + }, + })), + tree + ); + tree = await callRule( + updateJsonInTree('project2/.eslintrc', () => ({ + parserOptions: { + project: './tsconfig.json', + }, + })), + tree + ); + }); + + it('should reference tsconfig.base.json', async () => { + const result = await runMigration('migrate-eslintrc-tsconfig', {}, tree); + const eslintrc = readJsonInTree(result, '.eslintrc'); + expect(eslintrc.parserOptions.project).toEqual('./tsconfig.base.json'); + }); + + it('should reference tsconfig.base.json from .eslintrc files not in the root', async () => { + const result = await runMigration('migrate-eslintrc-tsconfig', {}, tree); + const eslintrc = readJsonInTree(result, 'project1/.eslintrc'); + expect(eslintrc.parserOptions.project).toEqual('../tsconfig.base.json'); + }); + + it("should reference tsconfig.base.json in .eslintrc that don't reference the root tsconfig.json", async () => { + const result = await runMigration('migrate-eslintrc-tsconfig', {}, tree); + const eslintrc = readJsonInTree(result, 'project2/.eslintrc'); + expect(eslintrc.parserOptions.project).toEqual('./tsconfig.json'); + }); +}); diff --git a/packages/workspace/src/migrations/update-10-0-1/migrate-eslintrc.ts b/packages/workspace/src/migrations/update-10-0-1/migrate-eslintrc.ts new file mode 100644 index 0000000000..9137c8600b --- /dev/null +++ b/packages/workspace/src/migrations/update-10-0-1/migrate-eslintrc.ts @@ -0,0 +1,31 @@ +import { basename, dirname, join } from '@angular-devkit/core'; +import { chain, Rule } from '@angular-devkit/schematics'; +import { formatFiles, updateJsonInTree } from '@nrwl/workspace'; +import { visitNotIgnoredFiles } from '../../utils/rules/visit-not-ignored-files'; + +export default function (schema: any): Rule { + return chain([ + visitNotIgnoredFiles((file, host, context) => { + if (basename(file) !== '.eslintrc') { + return; + } + + return updateJsonInTree(file, (json) => { + const tsconfig = json?.parserOptions?.project; + if (tsconfig) { + const tsconfigPath = join(dirname(file), tsconfig); + if (tsconfigPath === 'tsconfig.json') { + json.parserOptions.project = json.parserOptions.project.replace( + /tsconfig.json$/, + 'tsconfig.base.json' + ); + } + return json; + } else { + return json; + } + }); + }), + formatFiles(), + ]); +} diff --git a/packages/workspace/src/utils/rules/visit-not-ignored-files.ts b/packages/workspace/src/utils/rules/visit-not-ignored-files.ts new file mode 100644 index 0000000000..60ce78e111 --- /dev/null +++ b/packages/workspace/src/utils/rules/visit-not-ignored-files.ts @@ -0,0 +1,42 @@ +import { join, normalize, Path } from '@angular-devkit/core'; +import { + callRule, + Rule, + SchematicContext, + Tree, +} from '@angular-devkit/schematics'; +import ignore from 'ignore'; + +export function visitNotIgnoredFiles( + visitor: (file: Path, host: Tree, context: SchematicContext) => void | Rule, + dir: Path = normalize('') +): Rule { + return (host, context) => { + let ig; + if (host.exists('.gitignore')) { + ig = ignore(); + ig.add(host.read('.gitignore').toString()); + } + function visit(_dir: Path) { + if (_dir && ig?.ignores(_dir)) { + return; + } + const dirEntry = host.getDir(_dir); + dirEntry.subfiles.forEach((file) => { + if (ig?.ignores(join(_dir, file))) { + return; + } + const maybeRule = visitor(join(_dir, file), host, context); + if (maybeRule) { + callRule(maybeRule, host, context).subscribe(); + } + }); + + dirEntry.subdirs.forEach((subdir) => { + visit(join(_dir, subdir)); + }); + } + + visit(dir); + }; +}