diff --git a/docs/react/guides/nextjs.md b/docs/react/guides/nextjs.md index 0d589affeb..9ee11a36be 100644 --- a/docs/react/guides/nextjs.md +++ b/docs/react/guides/nextjs.md @@ -44,7 +44,7 @@ happynrwl/ │   │   ├── jest.conf.js │   │   ├── tsconfig.json │   │   ├── tsconfig.spec.json -│   │   └── .eslintrc +│   │   └── .eslintrc.json │   └── tuskdesk-e2e/ │   │   ├── src/ │   │   │   ├── integrations/ @@ -54,14 +54,14 @@ happynrwl/ │   │   │   └── support/ │   │   ├── cypress.json │   │   ├── tsconfig.e2e.json -│   │   └── .eslintrc +│   │   └── .eslintrc.json ├── libs/ ├── workspace.json ├── nx.json ├── package.json ├── tools/ ├── tsconfig.json -└── .eslintrc +└── .eslintrc.json ``` Run: diff --git a/docs/shared/monorepo-nx-enterprise.md b/docs/shared/monorepo-nx-enterprise.md index 96de8891ab..483214ac36 100644 --- a/docs/shared/monorepo-nx-enterprise.md +++ b/docs/shared/monorepo-nx-enterprise.md @@ -167,7 +167,7 @@ Read more about workspace schematics in the Workspace Schematics guide. ### Workspace Lint Checks -Custom lint checks is another great way to enforce best practices. We can create custom lint checks in the `tools/lint` directory and then register them in `tslint.json`or `.eslintrc`. +Custom lint checks is another great way to enforce best practices. We can create custom lint checks in the `tools/lint` directory and then register them in `tslint.json`or `.eslintrc.json`. ## Developer Workflow diff --git a/docs/shared/monorepo-tags.md b/docs/shared/monorepo-tags.md index 0917384a44..526f0b53c2 100644 --- a/docs/shared/monorepo-tags.md +++ b/docs/shared/monorepo-tags.md @@ -51,7 +51,7 @@ First, use `nx.json` to annotate your projects with tags. In this example, we wi } ``` -Next open the top-level `.eslintrc` or `tslint.json` to add the constraints. +Next open the top-level `.eslintrc.json` or `tslint.json` to add the constraints. ```json { diff --git a/docs/shared/next-plugin.md b/docs/shared/next-plugin.md index 9f16b4e0f7..e4fd1c38fc 100644 --- a/docs/shared/next-plugin.md +++ b/docs/shared/next-plugin.md @@ -38,7 +38,7 @@ myorg/ │   │   ├── jest.conf.js │   │   ├── tsconfig.json │   │   ├── tsconfig.spec.json -│   │   └── .eslintrc +│   │   └── .eslintrc.json │   └── myapp-e2e/ │   │   ├── src/ │   │   │   ├── integrations/ @@ -48,14 +48,14 @@ myorg/ │   │   │   └── support/ │   │   ├── cypress.json │   │   ├── tsconfig.e2e.json -│   │   └── .eslintrc +│   │   └── .eslintrc.json ├── libs/ ├── workspace.json ├── nx.json ├── package.json ├── tools/ ├── tsconfig.json -└── .eslintrc +└── .eslintrc.json ``` ## See Also diff --git a/e2e/linter/src/linter.test.ts b/e2e/linter/src/linter.test.ts index b9bb803cd4..4cde759ca3 100644 --- a/e2e/linter/src/linter.test.ts +++ b/e2e/linter/src/linter.test.ts @@ -18,9 +18,9 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); - const eslintrc = readJson('.eslintrc'); + const eslintrc = readJson('.eslintrc.json'); eslintrc.rules['no-console'] = 'error'; - updateFile('.eslintrc', JSON.stringify(eslintrc, null, 2)); + updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); @@ -34,9 +34,9 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); - const eslintrc = readJson('.eslintrc'); + const eslintrc = readJson('.eslintrc.json'); eslintrc.rules['no-console'] = 'error'; - updateFile('.eslintrc', JSON.stringify(eslintrc, null, 2)); + updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); @@ -49,9 +49,9 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); - const eslintrc = readJson('.eslintrc'); + const eslintrc = readJson('.eslintrc.json'); eslintrc.rules['no-console'] = undefined; - updateFile('.eslintrc', JSON.stringify(eslintrc, null, 2)); + updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); @@ -81,9 +81,9 @@ forEachCli('nx', () => { }; updateFile('workspace.json', JSON.stringify(workspaceJson, null, 2)); - const eslintrc = readJson('.eslintrc'); + const eslintrc = readJson('.eslintrc.json'); eslintrc.rules['no-console'] = undefined; - updateFile('.eslintrc', JSON.stringify(eslintrc, null, 2)); + updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); @@ -129,9 +129,9 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); - const eslintrc = readJson('.eslintrc'); + const eslintrc = readJson('.eslintrc.json'); eslintrc.rules['no-console'] = 'error'; - updateFile('.eslintrc', JSON.stringify(eslintrc, null, 2)); + updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); const outputFile = 'a/b/c/lint-output.json'; @@ -167,9 +167,12 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); - const eslintrc = readJson(`apps/${myapp}/.eslintrc`); + const eslintrc = readJson(`apps/${myapp}/.eslintrc.json`); eslintrc.rules['no-console'] = 'warn'; - updateFile(`apps/${myapp}/.eslintrc`, JSON.stringify(eslintrc, null, 2)); + updateFile( + `apps/${myapp}/.eslintrc.json`, + JSON.stringify(eslintrc, null, 2) + ); updateFile( `apps/${myapp}/src/main.ts`, `console.log('once'); console.log('twice');` diff --git a/packages/cypress/src/schematics/cypress-project/cypress-project.spec.ts b/packages/cypress/src/schematics/cypress-project/cypress-project.spec.ts index 9effad414a..e0a1493000 100644 --- a/packages/cypress/src/schematics/cypress-project/cypress-project.spec.ts +++ b/packages/cypress/src/schematics/cypress-project/cypress-project.spec.ts @@ -246,7 +246,7 @@ describe('schematic:cypress-project', () => { const packageJson = readJsonInTree(tree, 'package.json'); const eslintrcJson = readJsonInTree( tree, - 'apps/my-app-e2e/.eslintrc' + 'apps/my-app-e2e/.eslintrc.json' ); expect( diff --git a/packages/linter/migrations.json b/packages/linter/migrations.json index 9e562700f9..6bbf5dea70 100644 --- a/packages/linter/migrations.json +++ b/packages/linter/migrations.json @@ -9,6 +9,11 @@ "version": "10.3.0-beta.0", "description": "Migrate to the new ESLint builder and ESLint config style", "factory": "./src/migrations/update-10-3-0/update-eslint-builder-and-config" + }, + "add-json-ext-to-eslintrc": { + "version": "10.3.0-beta.2", + "description": "Add explicit .json file extension to .eslintrc files, not using an extension is deprecated", + "factory": "./src/migrations/update-10-3-0/add-json-ext-to-eslintrc" } }, "packageJsonUpdates": { diff --git a/packages/linter/src/builders/eslint/lint.impl.spec.ts b/packages/linter/src/builders/eslint/lint.impl.spec.ts index 72e429b6b5..d3bd7a2ae4 100644 --- a/packages/linter/src/builders/eslint/lint.impl.spec.ts +++ b/packages/linter/src/builders/eslint/lint.impl.spec.ts @@ -39,7 +39,7 @@ function createValidRunBuilderOptions( ): Schema { return { lintFilePatterns: [], - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', fix: true, cache: true, cacheLocation: 'cacheLocation1', @@ -118,7 +118,7 @@ describe('Linter Builder', () => { await runBuilder( createValidRunBuilderOptions({ lintFilePatterns: [], - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', fix: true, cache: true, cacheLocation: 'cacheLocation1', @@ -131,9 +131,9 @@ describe('Linter Builder', () => { quiet: false, }) ); - expect(lint).toHaveBeenCalledWith('/root/.eslintrc', { + expect(lint).toHaveBeenCalledWith('/root/.eslintrc.json', { lintFilePatterns: [], - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', fix: true, cache: true, cacheLocation: 'cacheLocation1', @@ -164,7 +164,7 @@ describe('Linter Builder', () => { setupMocks(); await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', }) @@ -172,7 +172,7 @@ describe('Linter Builder', () => { expect(mockLoadFormatter).toHaveBeenCalledWith('json'); await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'html', }) @@ -184,7 +184,7 @@ describe('Linter Builder', () => { setupMocks(); await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', fix: false, @@ -212,7 +212,7 @@ describe('Linter Builder', () => { setupMocks(); await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', silent: false, @@ -255,7 +255,7 @@ describe('Linter Builder', () => { setupMocks(); await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', silent: false, @@ -303,7 +303,7 @@ describe('Linter Builder', () => { setupMocks(); await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', silent: true, @@ -347,7 +347,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', silent: true, @@ -375,7 +375,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', silent: true, @@ -403,7 +403,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', silent: true, @@ -422,7 +422,7 @@ describe('Linter Builder', () => { const { createDirectory } = require('@nrwl/workspace'); await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', silent: true, @@ -442,7 +442,7 @@ describe('Linter Builder', () => { jest.spyOn(fs, 'writeFileSync').mockImplementation(); await runBuilder( createValidRunBuilderOptions({ - eslintConfig: './.eslintrc', + eslintConfig: './.eslintrc.json', lintFilePatterns: ['includedFile1'], format: 'json', silent: true, diff --git a/packages/linter/src/builders/eslint/lint.impl.ts b/packages/linter/src/builders/eslint/lint.impl.ts index 76277a2458..6a87f12b9e 100644 --- a/packages/linter/src/builders/eslint/lint.impl.ts +++ b/packages/linter/src/builders/eslint/lint.impl.ts @@ -40,7 +40,7 @@ async function run( /** * We want users to have the option of not specifying the config path, and let - * eslint automatically resolve the `.eslintrc` files in each folder. + * eslint automatically resolve the `.eslintrc.json` files in each folder. */ const eslintConfigPath = options.eslintConfig ? path.resolve(systemRoot, options.eslintConfig) diff --git a/packages/linter/src/builders/eslint/utility/eslint-utils.spec.ts b/packages/linter/src/builders/eslint/utility/eslint-utils.spec.ts index abdd90d78c..8384b664e7 100644 --- a/packages/linter/src/builders/eslint/utility/eslint-utils.spec.ts +++ b/packages/linter/src/builders/eslint/utility/eslint-utils.spec.ts @@ -18,14 +18,14 @@ describe('eslint-utils', () => { }); it('should create the ESLint instance with the proper parameters', async () => { - await lint('./.eslintrc', { + await lint('./.eslintrc.json', { fix: true, cache: true, cacheLocation: '/root/cache', }).catch(() => {}); expect(ESLint).toHaveBeenCalledWith({ - overrideConfigFile: './.eslintrc', + overrideConfigFile: './.eslintrc.json', fix: true, cache: true, cacheLocation: '/root/cache', diff --git a/packages/linter/src/builders/lint/lint.impl.spec.ts b/packages/linter/src/builders/lint/lint.impl.spec.ts index d83babfce7..5df9c67a86 100644 --- a/packages/linter/src/builders/lint/lint.impl.spec.ts +++ b/packages/linter/src/builders/lint/lint.impl.spec.ts @@ -75,7 +75,7 @@ describe('Linter Builder', () => { setupMocks(); const result = runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: [], }); await expect(result).rejects.toThrow( @@ -88,7 +88,7 @@ describe('Linter Builder', () => { setupMocks(); const result = runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: [], }); await expect(result).resolves.not.toThrow(); @@ -97,7 +97,7 @@ describe('Linter Builder', () => { setupMocks(); const result = runBuilder({ linter: 'tslint', - config: './.eslintrc', + config: './.eslintrc.json', files: [], }); await expect(result).rejects.toThrow( @@ -112,7 +112,7 @@ describe('Linter Builder', () => { const { createProgram } = require('./utility/ts-utils'); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', tsConfig: './tsconfig.json', }); expect(createProgram).toHaveBeenCalledTimes(1); @@ -120,7 +120,7 @@ describe('Linter Builder', () => { expect(lint).toHaveBeenCalledTimes(1); expect(lint).toHaveBeenCalledWith( '/root', - '/root/.eslintrc', + '/root/.eslintrc.json', expect.anything(), expect.any(Set), '/root/tsconfig.json-program', @@ -133,7 +133,7 @@ describe('Linter Builder', () => { const { createProgram } = require('./utility/ts-utils'); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', tsConfig: ['./tsconfig.json', './tsconfig2.json'], }); expect(createProgram).toHaveBeenCalledTimes(2); @@ -143,7 +143,7 @@ describe('Linter Builder', () => { expect(lint).toHaveBeenNthCalledWith( 1, '/root', - '/root/.eslintrc', + '/root/.eslintrc.json', expect.anything(), expect.any(Set), '/root/tsconfig.json-program', @@ -152,7 +152,7 @@ describe('Linter Builder', () => { expect(lint).toHaveBeenNthCalledWith( 2, '/root', - '/root/.eslintrc', + '/root/.eslintrc.json', expect.anything(), expect.any(Set), '/root/tsconfig2.json-program', @@ -165,14 +165,14 @@ describe('Linter Builder', () => { const { createProgram } = require('./utility/ts-utils'); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: [], }); expect(createProgram).not.toHaveBeenCalled(); expect(lint).toHaveBeenCalledTimes(1); expect(lint).toHaveBeenCalledWith( '/root', - '/root/.eslintrc', + '/root/.eslintrc.json', expect.anything(), expect.any(Set) ); @@ -184,7 +184,7 @@ describe('Linter Builder', () => { const { lint } = require('./utility/eslint-utils'); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], exclude: ['excludedFile1'], fix: true, @@ -195,7 +195,7 @@ describe('Linter Builder', () => { expect.anything(), expect.anything(), { - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], exclude: ['excludedFile1'], fix: true, @@ -219,7 +219,7 @@ describe('Linter Builder', () => { setupMocks(); const result = runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], }); await expect(result).rejects.toThrow( @@ -230,14 +230,14 @@ describe('Linter Builder', () => { setupMocks(); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', }); expect(mockGetFormatter).toHaveBeenCalledWith('json'); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'html', }); @@ -247,7 +247,7 @@ describe('Linter Builder', () => { setupMocks(); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', fix: false, @@ -274,7 +274,7 @@ describe('Linter Builder', () => { setupMocks(); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', silent: false, @@ -315,7 +315,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', silent: false, @@ -353,7 +353,7 @@ describe('Linter Builder', () => { const { createDirectory } = require('@nrwl/workspace'); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], outputFile: 'a/b/c/outputFile1', }); @@ -368,7 +368,7 @@ describe('Linter Builder', () => { jest.spyOn(fs, 'writeFileSync').mockImplementation(); await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], }); expect(fs.writeFileSync).not.toHaveBeenCalled(); @@ -391,7 +391,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', silent: true, @@ -434,7 +434,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', silent: true, @@ -459,7 +459,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', silent: true, @@ -485,7 +485,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', silent: true, @@ -511,7 +511,7 @@ describe('Linter Builder', () => { setupMocks(); const output = await runBuilder({ linter: 'eslint', - config: './.eslintrc', + config: './.eslintrc.json', files: ['includedFile1'], format: 'json', silent: true, diff --git a/packages/linter/src/builders/lint/lint.impl.ts b/packages/linter/src/builders/lint/lint.impl.ts index b46f8fd671..f180826764 100644 --- a/packages/linter/src/builders/lint/lint.impl.ts +++ b/packages/linter/src/builders/lint/lint.impl.ts @@ -42,7 +42,7 @@ async function run(options: Schema, context: BuilderContext): Promise { } // We want users to have the option of not specifying the config path, and let - // eslint automatically resolve the `.eslintrc` files in each folder. + // eslint automatically resolve the `.eslintrc.json` files in each folder. const eslintConfigPath = options.config ? path.resolve(systemRoot, options.config) : undefined; diff --git a/packages/linter/src/builders/lint/utility/eslint-utils.spec.ts b/packages/linter/src/builders/lint/utility/eslint-utils.spec.ts index d36998d232..8fc6231814 100644 --- a/packages/linter/src/builders/lint/utility/eslint-utils.spec.ts +++ b/packages/linter/src/builders/lint/utility/eslint-utils.spec.ts @@ -31,7 +31,7 @@ describe('eslint-util', () => { const lintedFiles = new Set(); await lint( '/root', - './.eslintrc', + './.eslintrc.json', { foo: 'bar' }, lintedFiles, 'ts-program' @@ -46,13 +46,13 @@ describe('eslint-util', () => { const lintedFiles = new Set(); await lint( '/root', - './.eslintrc', + './.eslintrc.json', { fix: true, cache: true, cacheLocation: '/root/cache' }, lintedFiles, 'ts-program' ).catch(() => {}); expect(CLIEngine).toHaveBeenCalledWith({ - configFile: './.eslintrc', + configFile: './.eslintrc.json', fix: true, cache: true, cacheLocation: '/root/cache', @@ -72,7 +72,7 @@ describe('eslint-util', () => { lintedFiles.add('file4'); const reports = await lint( '/root', - './.eslintrc', + './.eslintrc.json', { foo: 'bar' }, lintedFiles ); @@ -91,7 +91,7 @@ describe('eslint-util', () => { const lintedFiles = new Set(); const lintPromise = lint( '/root', - './.eslintrc', + './.eslintrc.json', { tsConfig: 'my-ts-project' }, lintedFiles, program, @@ -114,7 +114,7 @@ describe('eslint-util', () => { const lintedFiles = new Set(); const lintPromise = lint( '/root', - './.eslintrc', + './.eslintrc.json', { tsConfig: 'my-ts-project' }, lintedFiles, program, diff --git a/packages/linter/src/migrations/update-10-3-0/add-json-ext-to-eslintrc.spec.ts b/packages/linter/src/migrations/update-10-3-0/add-json-ext-to-eslintrc.spec.ts new file mode 100644 index 0000000000..3676ac29c3 --- /dev/null +++ b/packages/linter/src/migrations/update-10-3-0/add-json-ext-to-eslintrc.spec.ts @@ -0,0 +1,115 @@ +import { Tree } from '@angular-devkit/schematics'; +import { + readJsonInTree, + readWorkspace, + updateJsonInTree, + updateWorkspace, +} from '@nrwl/workspace'; +import { callRule, createEmptyWorkspace } from '@nrwl/workspace/testing'; +import { runMigration } from '../../utils/testing'; + +describe('Add explicit .json file extension to .eslintrc files', () => { + let tree: Tree; + + beforeEach(async () => { + tree = Tree.empty(); + tree = createEmptyWorkspace(tree); + tree = await callRule( + updateJsonInTree('.eslintrc', () => ({})), + tree + ); + tree = await callRule( + updateWorkspace((workspace) => { + // Old linter builder with ESLint, with explicit config file reference + // that needs to be updated + workspace.projects.add({ + name: 'testProject', + root: 'apps/testProject', + sourceRoot: 'apps/testProject/src', + projectType: 'application', + targets: { + lint: { + builder: '@nrwl/linter:lint', + options: { + linter: 'eslint', + config: '.eslintrc', + tsConfig: [ + 'apps/testProject/tsconfig.app.json', + 'apps/testProject/tsconfig.spec.json', + ], + exclude: ['**/node_modules/**', '!apps/testProject/**/*'], + }, + }, + }, + }); + + // New eslint builder, with explicit config file reference + // that needs to be updated + workspace.projects.add({ + name: 'testProject2', + root: 'apps/testProject2', + sourceRoot: 'apps/testProject2/src', + projectType: 'application', + targets: { + lint: { + builder: '@nrwl/linter:eslint', + options: { + eslintConfig: '.eslintrc', + lintFilePatterns: ['apps/testProject2/**/*.ts'], + }, + }, + }, + }); + }), + tree + ); + tree = await callRule( + updateJsonInTree('apps/testProject/.eslintrc', () => ({})), + tree + ); + tree = await callRule( + updateJsonInTree('apps/testProject2/.eslintrc', () => ({})), + tree + ); + }); + + it('should rename .eslintrc files to .eslintrc.json and update any workspace.json references', async () => { + const result = await runMigration('add-json-ext-to-eslintrc', {}, tree); + + const workspace = readWorkspace(tree); + + // ---------------------------------------- Root + expect(() => + readJsonInTree(result, '.eslintrc') + ).toThrowErrorMatchingInlineSnapshot(`"Cannot find .eslintrc"`); + expect(readJsonInTree(result, '.eslintrc.json')).toMatchInlineSnapshot( + `Object {}` + ); + + // ---------------------------------------- testProject + expect(() => + readJsonInTree(result, 'apps/testProject/.eslintrc') + ).toThrowErrorMatchingInlineSnapshot( + `"Cannot find apps/testProject/.eslintrc"` + ); + expect( + readJsonInTree(result, 'apps/testProject/.eslintrc.json') + ).toMatchInlineSnapshot(`Object {}`); + expect( + workspace.projects['testProject'].architect.lint.options.config + ).toEqual('.eslintrc.json'); + + // ---------------------------------------- testProject2 + expect(() => + readJsonInTree(result, 'apps/testProject2/.eslintrc') + ).toThrowErrorMatchingInlineSnapshot( + `"Cannot find apps/testProject2/.eslintrc"` + ); + expect( + readJsonInTree(result, 'apps/testProject2/.eslintrc.json') + ).toMatchInlineSnapshot(`Object {}`); + expect( + workspace.projects['testProject2'].architect.lint.options.eslintConfig + ).toEqual('.eslintrc.json'); + }); +}); diff --git a/packages/linter/src/migrations/update-10-3-0/add-json-ext-to-eslintrc.ts b/packages/linter/src/migrations/update-10-3-0/add-json-ext-to-eslintrc.ts new file mode 100644 index 0000000000..2b632e7feb --- /dev/null +++ b/packages/linter/src/migrations/update-10-3-0/add-json-ext-to-eslintrc.ts @@ -0,0 +1,64 @@ +import { basename } from '@angular-devkit/core'; +import { chain, Tree } from '@angular-devkit/schematics'; +import { + formatFiles, + readJsonInTree, + serializeJson, + updateWorkspace, + visitNotIgnoredFiles, +} from '@nrwl/workspace'; + +function updateESLintConfigReferencesInWorkspace() { + return updateWorkspace((workspace) => { + workspace.projects.forEach((project) => { + const lintTarget = project.targets.get('lint'); + if ( + lintTarget?.builder !== '@nrwl/linter:eslint' && + (lintTarget?.builder !== '@nrwl/linter:lint' || + lintTarget?.options?.linter === 'tslint') + ) { + return; + } + + if (lintTarget.builder === '@nrwl/linter:eslint') { + if (!lintTarget.options.eslintConfig) { + return; + } + lintTarget.options.eslintConfig = `${lintTarget.options.eslintConfig}.json`; + return; + } + + if (lintTarget.builder === '@nrwl/linter:lint') { + if (!lintTarget.options.config) { + return; + } + lintTarget.options.config = `${lintTarget.options.config}.json`; + return; + } + }); + }); +} + +function renameESLintConfigFiles() { + return visitNotIgnoredFiles((file, host, context) => { + if (basename(file) !== '.eslintrc') { + return; + } + // Using .eslintrc without an explicit file extension is deprecated + const newFilePath = `${file}.json`; + context.logger.info(`Renaming ${file} to ${newFilePath}`); + try { + return host.rename(file, newFilePath); + } catch (e) { + context.logger.error(e); + } + }); +} + +export default function () { + return chain([ + renameESLintConfigFiles, + updateESLintConfigReferencesInWorkspace, + formatFiles(), + ]); +} diff --git a/packages/next/src/schematics/application/application.spec.ts b/packages/next/src/schematics/application/application.spec.ts index c696507c64..9da5197a0d 100644 --- a/packages/next/src/schematics/application/application.spec.ts +++ b/packages/next/src/schematics/application/application.spec.ts @@ -197,14 +197,14 @@ describe('app', () => { }); describe('--linter=eslint', () => { - it('should add .eslintrc and dependencies', async () => { + it('should add .eslintrc.json and dependencies', async () => { const tree = await runSchematic( 'app', { name: 'myApp', linter: 'eslint' }, appTree ); - const eslintJson = readJsonInTree(tree, '/apps/my-app/.eslintrc'); + const eslintJson = readJsonInTree(tree, '/apps/my-app/.eslintrc.json'); const packageJson = readJsonInTree(tree, '/package.json'); expect(eslintJson.plugins).toEqual( diff --git a/packages/node/src/schematics/application/application.spec.ts b/packages/node/src/schematics/application/application.spec.ts index 1eb5cababd..acff7b4711 100644 --- a/packages/node/src/schematics/application/application.spec.ts +++ b/packages/node/src/schematics/application/application.spec.ts @@ -112,9 +112,11 @@ describe('app', () => { expect(tsconfigApp.extends).toEqual('./tsconfig.json'); const eslintrc = JSON.parse( - stripJsonComments(getFileContent(tree, 'apps/my-node-app/.eslintrc')) + stripJsonComments( + getFileContent(tree, 'apps/my-node-app/.eslintrc.json') + ) ); - expect(eslintrc.extends).toEqual('../../.eslintrc'); + expect(eslintrc.extends).toEqual('../../.eslintrc.json'); }); }); @@ -192,9 +194,9 @@ describe('app', () => { expectedValue: ['node'], }, { - path: 'apps/my-dir/my-node-app/.eslintrc', + path: 'apps/my-dir/my-node-app/.eslintrc.json', lookupFn: (json) => json.extends, - expectedValue: '../../../.eslintrc', + expectedValue: '../../../.eslintrc.json', }, ].forEach(hasJsonValue); }); diff --git a/packages/react/src/schematics/application/application.spec.ts b/packages/react/src/schematics/application/application.spec.ts index 7804b2286b..eafef125ca 100644 --- a/packages/react/src/schematics/application/application.spec.ts +++ b/packages/react/src/schematics/application/application.spec.ts @@ -71,9 +71,9 @@ describe('app', () => { expect(tsconfigApp.extends).toEqual('./tsconfig.json'); const eslintJson = JSON.parse( - stripJsonComments(tree.readContent('apps/my-app/.eslintrc')) + stripJsonComments(tree.readContent('apps/my-app/.eslintrc.json')) ); - expect(eslintJson.extends).toEqual(['../../.eslintrc']); + expect(eslintJson.extends).toEqual(['../../.eslintrc.json']); expect(tree.exists('apps/my-app-e2e/cypress.json')).toBeTruthy(); const tsconfigE2E = JSON.parse( @@ -155,9 +155,9 @@ describe('app', () => { expectedValue: '../../../dist/out-tsc', }, { - path: 'apps/my-dir/my-app/.eslintrc', + path: 'apps/my-dir/my-app/.eslintrc.json', lookupFn: (json) => json.extends, - expectedValue: ['../../../.eslintrc'], + expectedValue: ['../../../.eslintrc.json'], }, ].forEach(hasJsonValue); }); @@ -358,14 +358,14 @@ describe('app', () => { expect(appContent).not.toMatch(/extends Component/); }); - it('should add .eslintrc and dependencies', async () => { + it('should add .eslintrc.json and dependencies', async () => { const tree = await runSchematic( 'app', { name: 'myApp', linter: 'eslint' }, appTree ); - const eslintJson = readJsonInTree(tree, '/apps/my-app/.eslintrc'); + const eslintJson = readJsonInTree(tree, '/apps/my-app/.eslintrc.json'); const packageJson = readJsonInTree(tree, '/package.json'); expect(eslintJson.plugins).toEqual( diff --git a/packages/storybook/src/schematics/configuration/configuration.ts b/packages/storybook/src/schematics/configuration/configuration.ts index c61e95d28d..1b6aebae2a 100644 --- a/packages/storybook/src/schematics/configuration/configuration.ts +++ b/packages/storybook/src/schematics/configuration/configuration.ts @@ -201,7 +201,7 @@ function configureTsSolutionConfig(schema: StorybookConfigureSchema): Rule { * which includes *.stories files. * * For TSLint this is done via the builder config, for ESLint this is - * done within the .eslintrc file. + * done within the .eslintrc.json file. */ function updateLintConfig(schema: StorybookConfigureSchema): Rule { const { name: projectName } = schema; @@ -233,13 +233,16 @@ function updateLintConfig(schema: StorybookConfigureSchema): Rule { return; } - return updateJsonInTree(`${projectConfig.root}/.eslintrc`, (json) => { - if (Array.isArray(json.parserOptions?.project)) { - json.parserOptions.project.push( - `${projectConfig.root}/.storybook/tsconfig.json` - ); + return updateJsonInTree( + `${projectConfig.root}/.eslintrc.json`, + (json) => { + if (Array.isArray(json.parserOptions?.project)) { + json.parserOptions.project.push( + `${projectConfig.root}/.storybook/tsconfig.json` + ); + } } - }); + ); }, ]); } diff --git a/packages/web/src/schematics/application/application.spec.ts b/packages/web/src/schematics/application/application.spec.ts index 535f6e1444..a61490e5b9 100644 --- a/packages/web/src/schematics/application/application.spec.ts +++ b/packages/web/src/schematics/application/application.spec.ts @@ -70,9 +70,9 @@ describe('app', () => { expect(tsconfigApp.extends).toEqual('./tsconfig.json'); const linter = JSON.parse( - stripJsonComments(tree.readContent('apps/my-app/.eslintrc')) + stripJsonComments(tree.readContent('apps/my-app/.eslintrc.json')) ); - expect(linter.extends).toEqual('../../.eslintrc'); + expect(linter.extends).toEqual('../../.eslintrc.json'); expect(tree.exists('apps/my-app-e2e/cypress.json')).toBeTruthy(); const tsconfigE2E = JSON.parse( @@ -154,9 +154,9 @@ describe('app', () => { expectedValue: '../../../dist/out-tsc', }, { - path: 'apps/my-dir/my-app/.eslintrc', + path: 'apps/my-dir/my-app/.eslintrc.json', lookupFn: (json) => json.extends, - expectedValue: '../../../.eslintrc', + expectedValue: '../../../.eslintrc.json', }, ].forEach(hasJsonValue); }); diff --git a/packages/workspace/index.ts b/packages/workspace/index.ts index a959c6f540..889dbab7d5 100644 --- a/packages/workspace/index.ts +++ b/packages/workspace/index.ts @@ -71,6 +71,7 @@ export { formatFiles } from './src/utils/rules/format-files'; export { deleteFile } from './src/utils/rules/deleteFile'; export * from './src/utils/rules/ng-add'; export { updateKarmaConf } from './src/utils/rules/update-karma-conf'; +export { visitNotIgnoredFiles } from './src/utils/rules/visit-not-ignored-files'; export { setDefaultCollection } from './src/utils/rules/workspace'; import * as strings from './src/utils/strings'; export { checkAndCleanWithSemver } from './src/utils/version-utils'; diff --git a/packages/workspace/src/schematics/library/library.spec.ts b/packages/workspace/src/schematics/library/library.spec.ts index c201f6ee98..78aebeda77 100644 --- a/packages/workspace/src/schematics/library/library.spec.ts +++ b/packages/workspace/src/schematics/library/library.spec.ts @@ -184,7 +184,7 @@ describe('lib', () => { tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.ts') ).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy(); - expect(tree.exists(`libs/my-dir/my-lib/.eslintrc`)).toBeTruthy(); + expect(tree.exists(`libs/my-dir/my-lib/.eslintrc.json`)).toBeTruthy(); }); it('should update workspace.json', async () => { @@ -242,15 +242,15 @@ describe('lib', () => { ]); }); - it('should create a local .eslintrc', async () => { + it('should create a local .eslintrc.json', async () => { const tree = await runSchematic( 'lib', { name: 'myLib', directory: 'myDir' }, appTree ); - const lint = readJsonInTree(tree, 'libs/my-dir/my-lib/.eslintrc'); - expect(lint.extends).toEqual('../../../.eslintrc'); + const lint = readJsonInTree(tree, 'libs/my-dir/my-lib/.eslintrc.json'); + expect(lint.extends).toEqual('../../../.eslintrc.json'); }); }); diff --git a/packages/workspace/src/schematics/preset/preset.spec.ts b/packages/workspace/src/schematics/preset/preset.spec.ts index 26a43ca97d..2e01998d21 100644 --- a/packages/workspace/src/schematics/preset/preset.spec.ts +++ b/packages/workspace/src/schematics/preset/preset.spec.ts @@ -98,9 +98,9 @@ describe('preset', () => { expect( tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts') ).toBe(true); - expect(tree.exists('/apps/proj/.eslintrc')).toBe(true); - expect(tree.exists('/apps/api/.eslintrc')).toBe(true); - expect(tree.exists('/libs/api-interfaces/.eslintrc')).toBe(true); + expect(tree.exists('/apps/proj/.eslintrc.json')).toBe(true); + expect(tree.exists('/apps/api/.eslintrc.json')).toBe(true); + expect(tree.exists('/libs/api-interfaces/.eslintrc.json')).toBe(true); }); it('should work with unnormalized names', async () => { diff --git a/packages/workspace/src/utils/lint.ts b/packages/workspace/src/utils/lint.ts index 3b1b7638bc..6fc3d880ec 100644 --- a/packages/workspace/src/utils/lint.ts +++ b/packages/workspace/src/utils/lint.ts @@ -92,9 +92,9 @@ export function addLintFiles( } if (linter === 'eslint') { - if (!host.exists('/.eslintrc')) { + if (!host.exists('/.eslintrc.json')) { chainedCommands.push((host: Tree) => { - host.create('/.eslintrc', globalESLint); + host.create('/.eslintrc.json', globalESLint); return addDepsToPackageJson( { @@ -119,7 +119,7 @@ export function addLintFiles( if (!options.onlyGlobal) { chainedCommands.push((host: Tree) => { let configJson; - const rootConfig = `${offsetFromRoot(projectRoot)}.eslintrc`; + const rootConfig = `${offsetFromRoot(projectRoot)}.eslintrc.json`; // Include all project files to be linted (since they are turned off in the root eslintrc file). const ignorePatterns = ['!**/*']; @@ -155,7 +155,7 @@ export function addLintFiles( } host.create( - join(projectRoot as any, `.eslintrc`), + join(projectRoot as any, `.eslintrc.json`), JSON.stringify(configJson) ); }); @@ -246,11 +246,11 @@ const globalESLint = ` "ignorePatterns": ["**/*"], "plugins": ["@typescript-eslint", "@nrwl/nx"], "extends": [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier', - 'prettier/@typescript-eslint' + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + "prettier/@typescript-eslint" ], "rules": { "@typescript-eslint/explicit-member-accessibility": "off",