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
│   │   ├── 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:

View File

@ -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

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
{

View File

@ -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

View File

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

View File

@ -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(

View File

@ -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": {

View File

@ -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,

View File

@ -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)

View File

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

View File

@ -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,

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
// 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;

View File

@ -31,7 +31,7 @@ describe('eslint-util', () => {
const lintedFiles = new Set();
await lint(
'/root',
'./.eslintrc',
'./.eslintrc.json',
<any>{ foo: 'bar' },
lintedFiles,
'ts-program'
@ -46,13 +46,13 @@ describe('eslint-util', () => {
const lintedFiles = new Set();
await lint(
'/root',
'./.eslintrc',
'./.eslintrc.json',
<any>{ 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',
<any>{ foo: 'bar' },
lintedFiles
);
@ -91,7 +91,7 @@ describe('eslint-util', () => {
const lintedFiles = new Set();
const lintPromise = lint(
'/root',
'./.eslintrc',
'./.eslintrc.json',
<any>{ tsConfig: 'my-ts-project' },
lintedFiles,
program,
@ -114,7 +114,7 @@ describe('eslint-util', () => {
const lintedFiles = new Set();
const lintPromise = lint(
'/root',
'./.eslintrc',
'./.eslintrc.json',
<any>{ tsConfig: 'my-ts-project' },
lintedFiles,
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', () => {
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(

View File

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

View File

@ -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(

View File

@ -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) => {
return updateJsonInTree(
`${projectConfig.root}/.eslintrc.json`,
(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');
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);
});

View File

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

View File

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

View File

@ -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 () => {

View File

@ -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",