feat(linter): add explicit file extension to config files (#3847)

* feat(linter): add explicit file extension to config files

* feat(linter): update references to .eslintrc for new projects

* fix(linter): fix quotes in global eslint config
This commit is contained in:
James Henry 2020-10-02 02:59:45 +04:00 committed by GitHub
parent b45734b1d2
commit e339ece224
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 303 additions and 110 deletions

View File

@ -44,7 +44,7 @@ happynrwl/
│   │   ├── jest.conf.js │   │   ├── jest.conf.js
│   │   ├── tsconfig.json │   │   ├── tsconfig.json
│   │   ├── tsconfig.spec.json │   │   ├── tsconfig.spec.json
│   │   └── .eslintrc │   │   └── .eslintrc.json
│   └── tuskdesk-e2e/ │   └── tuskdesk-e2e/
│   │   ├── src/ │   │   ├── src/
│   │   │   ├── integrations/ │   │   │   ├── integrations/
@ -54,14 +54,14 @@ happynrwl/
│   │   │   └── support/ │   │   │   └── support/
│   │   ├── cypress.json │   │   ├── cypress.json
│   │   ├── tsconfig.e2e.json │   │   ├── tsconfig.e2e.json
│   │   └── .eslintrc │   │   └── .eslintrc.json
├── libs/ ├── libs/
├── workspace.json ├── workspace.json
├── nx.json ├── nx.json
├── package.json ├── package.json
├── tools/ ├── tools/
├── tsconfig.json ├── tsconfig.json
└── .eslintrc └── .eslintrc.json
``` ```
Run: Run:

View File

@ -167,7 +167,7 @@ Read more about workspace schematics in the Workspace Schematics guide.
### Workspace Lint Checks ### 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 ## Developer Workflow

View File

@ -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 ```json
{ {

View File

@ -38,7 +38,7 @@ myorg/
│   │   ├── jest.conf.js │   │   ├── jest.conf.js
│   │   ├── tsconfig.json │   │   ├── tsconfig.json
│   │   ├── tsconfig.spec.json │   │   ├── tsconfig.spec.json
│   │   └── .eslintrc │   │   └── .eslintrc.json
│   └── myapp-e2e/ │   └── myapp-e2e/
│   │   ├── src/ │   │   ├── src/
│   │   │   ├── integrations/ │   │   │   ├── integrations/
@ -48,14 +48,14 @@ myorg/
│   │   │   └── support/ │   │   │   └── support/
│   │   ├── cypress.json │   │   ├── cypress.json
│   │   ├── tsconfig.e2e.json │   │   ├── tsconfig.e2e.json
│   │   └── .eslintrc │   │   └── .eslintrc.json
├── libs/ ├── libs/
├── workspace.json ├── workspace.json
├── nx.json ├── nx.json
├── package.json ├── package.json
├── tools/ ├── tools/
├── tsconfig.json ├── tsconfig.json
└── .eslintrc └── .eslintrc.json
``` ```
## See Also ## See Also

View File

@ -18,9 +18,9 @@ forEachCli('nx', () => {
runCLI(`generate @nrwl/react:app ${myapp}`); runCLI(`generate @nrwl/react:app ${myapp}`);
const eslintrc = readJson('.eslintrc'); const eslintrc = readJson('.eslintrc.json');
eslintrc.rules['no-console'] = 'error'; 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");`); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`);
@ -34,9 +34,9 @@ forEachCli('nx', () => {
runCLI(`generate @nrwl/react:app ${myapp}`); runCLI(`generate @nrwl/react:app ${myapp}`);
const eslintrc = readJson('.eslintrc'); const eslintrc = readJson('.eslintrc.json');
eslintrc.rules['no-console'] = 'error'; 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");`); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`);
@ -49,9 +49,9 @@ forEachCli('nx', () => {
runCLI(`generate @nrwl/react:app ${myapp}`); runCLI(`generate @nrwl/react:app ${myapp}`);
const eslintrc = readJson('.eslintrc'); const eslintrc = readJson('.eslintrc.json');
eslintrc.rules['no-console'] = undefined; 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");`); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`);
@ -81,9 +81,9 @@ forEachCli('nx', () => {
}; };
updateFile('workspace.json', JSON.stringify(workspaceJson, null, 2)); updateFile('workspace.json', JSON.stringify(workspaceJson, null, 2));
const eslintrc = readJson('.eslintrc'); const eslintrc = readJson('.eslintrc.json');
eslintrc.rules['no-console'] = undefined; 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");`); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`);
@ -129,9 +129,9 @@ forEachCli('nx', () => {
runCLI(`generate @nrwl/react:app ${myapp}`); runCLI(`generate @nrwl/react:app ${myapp}`);
const eslintrc = readJson('.eslintrc'); const eslintrc = readJson('.eslintrc.json');
eslintrc.rules['no-console'] = 'error'; 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");`); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`);
const outputFile = 'a/b/c/lint-output.json'; const outputFile = 'a/b/c/lint-output.json';
@ -167,9 +167,12 @@ forEachCli('nx', () => {
runCLI(`generate @nrwl/react:app ${myapp}`); runCLI(`generate @nrwl/react:app ${myapp}`);
const eslintrc = readJson(`apps/${myapp}/.eslintrc`); const eslintrc = readJson(`apps/${myapp}/.eslintrc.json`);
eslintrc.rules['no-console'] = 'warn'; 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( updateFile(
`apps/${myapp}/src/main.ts`, `apps/${myapp}/src/main.ts`,
`console.log('once'); console.log('twice');` `console.log('once'); console.log('twice');`

View File

@ -246,7 +246,7 @@ describe('schematic:cypress-project', () => {
const packageJson = readJsonInTree(tree, 'package.json'); const packageJson = readJsonInTree(tree, 'package.json');
const eslintrcJson = readJsonInTree( const eslintrcJson = readJsonInTree(
tree, tree,
'apps/my-app-e2e/.eslintrc' 'apps/my-app-e2e/.eslintrc.json'
); );
expect( expect(

View File

@ -9,6 +9,11 @@
"version": "10.3.0-beta.0", "version": "10.3.0-beta.0",
"description": "Migrate to the new ESLint builder and ESLint config style", "description": "Migrate to the new ESLint builder and ESLint config style",
"factory": "./src/migrations/update-10-3-0/update-eslint-builder-and-config" "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": { "packageJsonUpdates": {

View File

@ -39,7 +39,7 @@ function createValidRunBuilderOptions(
): Schema { ): Schema {
return { return {
lintFilePatterns: [], lintFilePatterns: [],
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
fix: true, fix: true,
cache: true, cache: true,
cacheLocation: 'cacheLocation1', cacheLocation: 'cacheLocation1',
@ -118,7 +118,7 @@ describe('Linter Builder', () => {
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
lintFilePatterns: [], lintFilePatterns: [],
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
fix: true, fix: true,
cache: true, cache: true,
cacheLocation: 'cacheLocation1', cacheLocation: 'cacheLocation1',
@ -131,9 +131,9 @@ describe('Linter Builder', () => {
quiet: false, quiet: false,
}) })
); );
expect(lint).toHaveBeenCalledWith('/root/.eslintrc', { expect(lint).toHaveBeenCalledWith('/root/.eslintrc.json', {
lintFilePatterns: [], lintFilePatterns: [],
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
fix: true, fix: true,
cache: true, cache: true,
cacheLocation: 'cacheLocation1', cacheLocation: 'cacheLocation1',
@ -164,7 +164,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
}) })
@ -172,7 +172,7 @@ describe('Linter Builder', () => {
expect(mockLoadFormatter).toHaveBeenCalledWith('json'); expect(mockLoadFormatter).toHaveBeenCalledWith('json');
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'html', format: 'html',
}) })
@ -184,7 +184,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
fix: false, fix: false,
@ -212,7 +212,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
silent: false, silent: false,
@ -255,7 +255,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
silent: false, silent: false,
@ -303,7 +303,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -347,7 +347,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder( const output = await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -375,7 +375,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder( const output = await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -403,7 +403,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder( const output = await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -422,7 +422,7 @@ describe('Linter Builder', () => {
const { createDirectory } = require('@nrwl/workspace'); const { createDirectory } = require('@nrwl/workspace');
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -442,7 +442,7 @@ describe('Linter Builder', () => {
jest.spyOn(fs, 'writeFileSync').mockImplementation(); jest.spyOn(fs, 'writeFileSync').mockImplementation();
await runBuilder( await runBuilder(
createValidRunBuilderOptions({ createValidRunBuilderOptions({
eslintConfig: './.eslintrc', eslintConfig: './.eslintrc.json',
lintFilePatterns: ['includedFile1'], lintFilePatterns: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,

View File

@ -40,7 +40,7 @@ async function run(
/** /**
* We want users to have the option of not specifying the config path, and let * 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 const eslintConfigPath = options.eslintConfig
? path.resolve(systemRoot, options.eslintConfig) ? path.resolve(systemRoot, options.eslintConfig)

View File

@ -18,14 +18,14 @@ describe('eslint-utils', () => {
}); });
it('should create the ESLint instance with the proper parameters', async () => { it('should create the ESLint instance with the proper parameters', async () => {
await lint('./.eslintrc', <any>{ await lint('./.eslintrc.json', <any>{
fix: true, fix: true,
cache: true, cache: true,
cacheLocation: '/root/cache', cacheLocation: '/root/cache',
}).catch(() => {}); }).catch(() => {});
expect(ESLint).toHaveBeenCalledWith({ expect(ESLint).toHaveBeenCalledWith({
overrideConfigFile: './.eslintrc', overrideConfigFile: './.eslintrc.json',
fix: true, fix: true,
cache: true, cache: true,
cacheLocation: '/root/cache', cacheLocation: '/root/cache',

View File

@ -75,7 +75,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const result = runBuilder({ const result = runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: [], files: [],
}); });
await expect(result).rejects.toThrow( await expect(result).rejects.toThrow(
@ -88,7 +88,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const result = runBuilder({ const result = runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: [], files: [],
}); });
await expect(result).resolves.not.toThrow(); await expect(result).resolves.not.toThrow();
@ -97,7 +97,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const result = runBuilder({ const result = runBuilder({
linter: 'tslint', linter: 'tslint',
config: './.eslintrc', config: './.eslintrc.json',
files: [], files: [],
}); });
await expect(result).rejects.toThrow( await expect(result).rejects.toThrow(
@ -112,7 +112,7 @@ describe('Linter Builder', () => {
const { createProgram } = require('./utility/ts-utils'); const { createProgram } = require('./utility/ts-utils');
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
tsConfig: './tsconfig.json', tsConfig: './tsconfig.json',
}); });
expect(createProgram).toHaveBeenCalledTimes(1); expect(createProgram).toHaveBeenCalledTimes(1);
@ -120,7 +120,7 @@ describe('Linter Builder', () => {
expect(lint).toHaveBeenCalledTimes(1); expect(lint).toHaveBeenCalledTimes(1);
expect(lint).toHaveBeenCalledWith( expect(lint).toHaveBeenCalledWith(
'/root', '/root',
'/root/.eslintrc', '/root/.eslintrc.json',
expect.anything(), expect.anything(),
expect.any(Set), expect.any(Set),
'/root/tsconfig.json-program', '/root/tsconfig.json-program',
@ -133,7 +133,7 @@ describe('Linter Builder', () => {
const { createProgram } = require('./utility/ts-utils'); const { createProgram } = require('./utility/ts-utils');
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
tsConfig: ['./tsconfig.json', './tsconfig2.json'], tsConfig: ['./tsconfig.json', './tsconfig2.json'],
}); });
expect(createProgram).toHaveBeenCalledTimes(2); expect(createProgram).toHaveBeenCalledTimes(2);
@ -143,7 +143,7 @@ describe('Linter Builder', () => {
expect(lint).toHaveBeenNthCalledWith( expect(lint).toHaveBeenNthCalledWith(
1, 1,
'/root', '/root',
'/root/.eslintrc', '/root/.eslintrc.json',
expect.anything(), expect.anything(),
expect.any(Set), expect.any(Set),
'/root/tsconfig.json-program', '/root/tsconfig.json-program',
@ -152,7 +152,7 @@ describe('Linter Builder', () => {
expect(lint).toHaveBeenNthCalledWith( expect(lint).toHaveBeenNthCalledWith(
2, 2,
'/root', '/root',
'/root/.eslintrc', '/root/.eslintrc.json',
expect.anything(), expect.anything(),
expect.any(Set), expect.any(Set),
'/root/tsconfig2.json-program', '/root/tsconfig2.json-program',
@ -165,14 +165,14 @@ describe('Linter Builder', () => {
const { createProgram } = require('./utility/ts-utils'); const { createProgram } = require('./utility/ts-utils');
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: [], files: [],
}); });
expect(createProgram).not.toHaveBeenCalled(); expect(createProgram).not.toHaveBeenCalled();
expect(lint).toHaveBeenCalledTimes(1); expect(lint).toHaveBeenCalledTimes(1);
expect(lint).toHaveBeenCalledWith( expect(lint).toHaveBeenCalledWith(
'/root', '/root',
'/root/.eslintrc', '/root/.eslintrc.json',
expect.anything(), expect.anything(),
expect.any(Set) expect.any(Set)
); );
@ -184,7 +184,7 @@ describe('Linter Builder', () => {
const { lint } = require('./utility/eslint-utils'); const { lint } = require('./utility/eslint-utils');
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
exclude: ['excludedFile1'], exclude: ['excludedFile1'],
fix: true, fix: true,
@ -195,7 +195,7 @@ describe('Linter Builder', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
{ {
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
exclude: ['excludedFile1'], exclude: ['excludedFile1'],
fix: true, fix: true,
@ -219,7 +219,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const result = runBuilder({ const result = runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
}); });
await expect(result).rejects.toThrow( await expect(result).rejects.toThrow(
@ -230,14 +230,14 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
}); });
expect(mockGetFormatter).toHaveBeenCalledWith('json'); expect(mockGetFormatter).toHaveBeenCalledWith('json');
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'html', format: 'html',
}); });
@ -247,7 +247,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
fix: false, fix: false,
@ -274,7 +274,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
silent: false, silent: false,
@ -315,7 +315,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder({ const output = await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
silent: false, silent: false,
@ -353,7 +353,7 @@ describe('Linter Builder', () => {
const { createDirectory } = require('@nrwl/workspace'); const { createDirectory } = require('@nrwl/workspace');
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
outputFile: 'a/b/c/outputFile1', outputFile: 'a/b/c/outputFile1',
}); });
@ -368,7 +368,7 @@ describe('Linter Builder', () => {
jest.spyOn(fs, 'writeFileSync').mockImplementation(); jest.spyOn(fs, 'writeFileSync').mockImplementation();
await runBuilder({ await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
}); });
expect(fs.writeFileSync).not.toHaveBeenCalled(); expect(fs.writeFileSync).not.toHaveBeenCalled();
@ -391,7 +391,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder({ const output = await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -434,7 +434,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder({ const output = await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -459,7 +459,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder({ const output = await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -485,7 +485,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder({ const output = await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,
@ -511,7 +511,7 @@ describe('Linter Builder', () => {
setupMocks(); setupMocks();
const output = await runBuilder({ const output = await runBuilder({
linter: 'eslint', linter: 'eslint',
config: './.eslintrc', config: './.eslintrc.json',
files: ['includedFile1'], files: ['includedFile1'],
format: 'json', format: 'json',
silent: true, silent: true,

View File

@ -42,7 +42,7 @@ async function run(options: Schema, context: BuilderContext): Promise<any> {
} }
// We want users to have the option of not specifying the config path, and let // 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 const eslintConfigPath = options.config
? path.resolve(systemRoot, options.config) ? path.resolve(systemRoot, options.config)
: undefined; : undefined;

View File

@ -31,7 +31,7 @@ describe('eslint-util', () => {
const lintedFiles = new Set(); const lintedFiles = new Set();
await lint( await lint(
'/root', '/root',
'./.eslintrc', './.eslintrc.json',
<any>{ foo: 'bar' }, <any>{ foo: 'bar' },
lintedFiles, lintedFiles,
'ts-program' 'ts-program'
@ -46,13 +46,13 @@ describe('eslint-util', () => {
const lintedFiles = new Set(); const lintedFiles = new Set();
await lint( await lint(
'/root', '/root',
'./.eslintrc', './.eslintrc.json',
<any>{ fix: true, cache: true, cacheLocation: '/root/cache' }, <any>{ fix: true, cache: true, cacheLocation: '/root/cache' },
lintedFiles, lintedFiles,
'ts-program' 'ts-program'
).catch(() => {}); ).catch(() => {});
expect(CLIEngine).toHaveBeenCalledWith({ expect(CLIEngine).toHaveBeenCalledWith({
configFile: './.eslintrc', configFile: './.eslintrc.json',
fix: true, fix: true,
cache: true, cache: true,
cacheLocation: '/root/cache', cacheLocation: '/root/cache',
@ -72,7 +72,7 @@ describe('eslint-util', () => {
lintedFiles.add('file4'); lintedFiles.add('file4');
const reports = await lint( const reports = await lint(
'/root', '/root',
'./.eslintrc', './.eslintrc.json',
<any>{ foo: 'bar' }, <any>{ foo: 'bar' },
lintedFiles lintedFiles
); );
@ -91,7 +91,7 @@ describe('eslint-util', () => {
const lintedFiles = new Set(); const lintedFiles = new Set();
const lintPromise = lint( const lintPromise = lint(
'/root', '/root',
'./.eslintrc', './.eslintrc.json',
<any>{ tsConfig: 'my-ts-project' }, <any>{ tsConfig: 'my-ts-project' },
lintedFiles, lintedFiles,
program, program,
@ -114,7 +114,7 @@ describe('eslint-util', () => {
const lintedFiles = new Set(); const lintedFiles = new Set();
const lintPromise = lint( const lintPromise = lint(
'/root', '/root',
'./.eslintrc', './.eslintrc.json',
<any>{ tsConfig: 'my-ts-project' }, <any>{ tsConfig: 'my-ts-project' },
lintedFiles, lintedFiles,
program, program,

View File

@ -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');
});
});

View File

@ -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(),
]);
}

View File

@ -197,14 +197,14 @@ describe('app', () => {
}); });
describe('--linter=eslint', () => { describe('--linter=eslint', () => {
it('should add .eslintrc and dependencies', async () => { it('should add .eslintrc.json and dependencies', async () => {
const tree = await runSchematic( const tree = await runSchematic(
'app', 'app',
{ name: 'myApp', linter: 'eslint' }, { name: 'myApp', linter: 'eslint' },
appTree appTree
); );
const eslintJson = readJsonInTree(tree, '/apps/my-app/.eslintrc'); const eslintJson = readJsonInTree(tree, '/apps/my-app/.eslintrc.json');
const packageJson = readJsonInTree(tree, '/package.json'); const packageJson = readJsonInTree(tree, '/package.json');
expect(eslintJson.plugins).toEqual( expect(eslintJson.plugins).toEqual(

View File

@ -112,9 +112,11 @@ describe('app', () => {
expect(tsconfigApp.extends).toEqual('./tsconfig.json'); expect(tsconfigApp.extends).toEqual('./tsconfig.json');
const eslintrc = JSON.parse( 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'], expectedValue: ['node'],
}, },
{ {
path: 'apps/my-dir/my-node-app/.eslintrc', path: 'apps/my-dir/my-node-app/.eslintrc.json',
lookupFn: (json) => json.extends, lookupFn: (json) => json.extends,
expectedValue: '../../../.eslintrc', expectedValue: '../../../.eslintrc.json',
}, },
].forEach(hasJsonValue); ].forEach(hasJsonValue);
}); });

View File

@ -71,9 +71,9 @@ describe('app', () => {
expect(tsconfigApp.extends).toEqual('./tsconfig.json'); expect(tsconfigApp.extends).toEqual('./tsconfig.json');
const eslintJson = JSON.parse( 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(); expect(tree.exists('apps/my-app-e2e/cypress.json')).toBeTruthy();
const tsconfigE2E = JSON.parse( const tsconfigE2E = JSON.parse(
@ -155,9 +155,9 @@ describe('app', () => {
expectedValue: '../../../dist/out-tsc', expectedValue: '../../../dist/out-tsc',
}, },
{ {
path: 'apps/my-dir/my-app/.eslintrc', path: 'apps/my-dir/my-app/.eslintrc.json',
lookupFn: (json) => json.extends, lookupFn: (json) => json.extends,
expectedValue: ['../../../.eslintrc'], expectedValue: ['../../../.eslintrc.json'],
}, },
].forEach(hasJsonValue); ].forEach(hasJsonValue);
}); });
@ -358,14 +358,14 @@ describe('app', () => {
expect(appContent).not.toMatch(/extends Component/); 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( const tree = await runSchematic(
'app', 'app',
{ name: 'myApp', linter: 'eslint' }, { name: 'myApp', linter: 'eslint' },
appTree appTree
); );
const eslintJson = readJsonInTree(tree, '/apps/my-app/.eslintrc'); const eslintJson = readJsonInTree(tree, '/apps/my-app/.eslintrc.json');
const packageJson = readJsonInTree(tree, '/package.json'); const packageJson = readJsonInTree(tree, '/package.json');
expect(eslintJson.plugins).toEqual( expect(eslintJson.plugins).toEqual(

View File

@ -201,7 +201,7 @@ function configureTsSolutionConfig(schema: StorybookConfigureSchema): Rule {
* which includes *.stories files. * which includes *.stories files.
* *
* For TSLint this is done via the builder config, for ESLint this is * 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 { function updateLintConfig(schema: StorybookConfigureSchema): Rule {
const { name: projectName } = schema; const { name: projectName } = schema;
@ -233,13 +233,16 @@ function updateLintConfig(schema: StorybookConfigureSchema): Rule {
return; return;
} }
return updateJsonInTree(`${projectConfig.root}/.eslintrc`, (json) => { return updateJsonInTree(
if (Array.isArray(json.parserOptions?.project)) { `${projectConfig.root}/.eslintrc.json`,
json.parserOptions.project.push( (json) => {
`${projectConfig.root}/.storybook/tsconfig.json` if (Array.isArray(json.parserOptions?.project)) {
); json.parserOptions.project.push(
`${projectConfig.root}/.storybook/tsconfig.json`
);
}
} }
}); );
}, },
]); ]);
} }

View File

@ -70,9 +70,9 @@ describe('app', () => {
expect(tsconfigApp.extends).toEqual('./tsconfig.json'); expect(tsconfigApp.extends).toEqual('./tsconfig.json');
const linter = JSON.parse( 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(); expect(tree.exists('apps/my-app-e2e/cypress.json')).toBeTruthy();
const tsconfigE2E = JSON.parse( const tsconfigE2E = JSON.parse(
@ -154,9 +154,9 @@ describe('app', () => {
expectedValue: '../../../dist/out-tsc', expectedValue: '../../../dist/out-tsc',
}, },
{ {
path: 'apps/my-dir/my-app/.eslintrc', path: 'apps/my-dir/my-app/.eslintrc.json',
lookupFn: (json) => json.extends, lookupFn: (json) => json.extends,
expectedValue: '../../../.eslintrc', expectedValue: '../../../.eslintrc.json',
}, },
].forEach(hasJsonValue); ].forEach(hasJsonValue);
}); });

View File

@ -71,6 +71,7 @@ export { formatFiles } from './src/utils/rules/format-files';
export { deleteFile } from './src/utils/rules/deleteFile'; export { deleteFile } from './src/utils/rules/deleteFile';
export * from './src/utils/rules/ng-add'; export * from './src/utils/rules/ng-add';
export { updateKarmaConf } from './src/utils/rules/update-karma-conf'; 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'; export { setDefaultCollection } from './src/utils/rules/workspace';
import * as strings from './src/utils/strings'; import * as strings from './src/utils/strings';
export { checkAndCleanWithSemver } from './src/utils/version-utils'; export { checkAndCleanWithSemver } from './src/utils/version-utils';

View File

@ -184,7 +184,7 @@ describe('lib', () => {
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.ts') tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.ts')
).toBeTruthy(); ).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.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 () => { 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( const tree = await runSchematic(
'lib', 'lib',
{ name: 'myLib', directory: 'myDir' }, { name: 'myLib', directory: 'myDir' },
appTree appTree
); );
const lint = readJsonInTree(tree, 'libs/my-dir/my-lib/.eslintrc'); const lint = readJsonInTree(tree, 'libs/my-dir/my-lib/.eslintrc.json');
expect(lint.extends).toEqual('../../../.eslintrc'); expect(lint.extends).toEqual('../../../.eslintrc.json');
}); });
}); });

View File

@ -98,9 +98,9 @@ describe('preset', () => {
expect( expect(
tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts') tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
).toBe(true); ).toBe(true);
expect(tree.exists('/apps/proj/.eslintrc')).toBe(true); expect(tree.exists('/apps/proj/.eslintrc.json')).toBe(true);
expect(tree.exists('/apps/api/.eslintrc')).toBe(true); expect(tree.exists('/apps/api/.eslintrc.json')).toBe(true);
expect(tree.exists('/libs/api-interfaces/.eslintrc')).toBe(true); expect(tree.exists('/libs/api-interfaces/.eslintrc.json')).toBe(true);
}); });
it('should work with unnormalized names', async () => { it('should work with unnormalized names', async () => {

View File

@ -92,9 +92,9 @@ export function addLintFiles(
} }
if (linter === 'eslint') { if (linter === 'eslint') {
if (!host.exists('/.eslintrc')) { if (!host.exists('/.eslintrc.json')) {
chainedCommands.push((host: Tree) => { chainedCommands.push((host: Tree) => {
host.create('/.eslintrc', globalESLint); host.create('/.eslintrc.json', globalESLint);
return addDepsToPackageJson( return addDepsToPackageJson(
{ {
@ -119,7 +119,7 @@ export function addLintFiles(
if (!options.onlyGlobal) { if (!options.onlyGlobal) {
chainedCommands.push((host: Tree) => { chainedCommands.push((host: Tree) => {
let configJson; 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). // Include all project files to be linted (since they are turned off in the root eslintrc file).
const ignorePatterns = ['!**/*']; const ignorePatterns = ['!**/*'];
@ -155,7 +155,7 @@ export function addLintFiles(
} }
host.create( host.create(
join(projectRoot as any, `.eslintrc`), join(projectRoot as any, `.eslintrc.json`),
JSON.stringify(configJson) JSON.stringify(configJson)
); );
}); });
@ -246,11 +246,11 @@ const globalESLint = `
"ignorePatterns": ["**/*"], "ignorePatterns": ["**/*"],
"plugins": ["@typescript-eslint", "@nrwl/nx"], "plugins": ["@typescript-eslint", "@nrwl/nx"],
"extends": [ "extends": [
'eslint:recommended', "eslint:recommended",
'plugin:@typescript-eslint/eslint-recommended', "plugin:@typescript-eslint/eslint-recommended",
'plugin:@typescript-eslint/recommended', "plugin:@typescript-eslint/recommended",
'prettier', "prettier",
'prettier/@typescript-eslint' "prettier/@typescript-eslint"
], ],
"rules": { "rules": {
"@typescript-eslint/explicit-member-accessibility": "off", "@typescript-eslint/explicit-member-accessibility": "off",