Skip to content

Commit

Permalink
feat(linter): add flat config support to generators (#18534)
Browse files Browse the repository at this point in the history
  • Loading branch information
meeroslav authored Aug 22, 2023
1 parent 1c058de commit e34219a
Show file tree
Hide file tree
Showing 42 changed files with 3,537 additions and 1,157 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,9 @@
"@swc/core": "^1.3.51",
"@swc/jest": "^0.2.20",
"@testing-library/react": "13.4.0",
"@types/css-minimizer-webpack-plugin": "^3.2.1",
"@types/cytoscape": "^3.18.2",
"@types/detect-port": "^1.3.2",
"@types/eslint": "~8.4.1",
"@types/eslint": "~8.44.2",
"@types/express": "4.17.14",
"@types/flat": "^5.0.1",
"@types/fs-extra": "^11.0.0",
Expand Down
64 changes: 57 additions & 7 deletions packages/angular/src/generators/add-linting/add-linting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import {
joinPathFragments,
runTasksInSerial,
Tree,
updateJson,
} from '@nx/devkit';
import { Linter, lintProjectGenerator } from '@nx/linter';
import { mapLintPattern } from '@nx/linter/src/generators/lint-project/lint-project';
import { addAngularEsLintDependencies } from './lib/add-angular-eslint-dependencies';
import { extendAngularEslintJson } from './lib/create-eslint-configuration';
import type { AddLintingGeneratorSchema } from './schema';
import {
findEslintFile,
isEslintConfigSupported,
replaceOverridesInLintConfig,
} from '@nx/linter/src/generators/utils/eslint-file';
import { camelize, dasherize } from '@nx/devkit/src/utils/string-utils';

export async function addLintingGenerator(
tree: Tree,
Expand All @@ -35,11 +39,57 @@ export async function addLintingGenerator(
});
tasks.push(lintTask);

updateJson(
tree,
joinPathFragments(options.projectRoot, '.eslintrc.json'),
(json) => extendAngularEslintJson(json, options)
);
if (isEslintConfigSupported(tree)) {
const eslintFile = findEslintFile(tree, options.projectRoot);
// keep parser options if they exist
const hasParserOptions = tree
.read(joinPathFragments(options.projectRoot, eslintFile), 'utf8')
.includes(`${options.projectRoot}/tsconfig.*?.json`);

replaceOverridesInLintConfig(tree, options.projectRoot, [
{
files: ['*.ts'],
...(hasParserOptions
? {
parserOptions: {
project: [`${options.projectRoot}/tsconfig.*?.json`],
},
}
: {}),
extends: [
'plugin:@nx/angular',
'plugin:@angular-eslint/template/process-inline-templates',
],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: camelize(options.prefix),
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: dasherize(options.prefix),
style: 'kebab-case',
},
],
},
},
{
files: ['*.html'],
extends: ['plugin:@nx/angular-template'],
/**
* Having an empty rules object present makes it more obvious to the user where they would
* extend things from if they needed to
*/
rules: {},
},
]);
}

if (!options.skipPackageJson) {
const installTask = addAngularEsLintDependencies(tree);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ type EslintExtensionSchema = {
prefix: string;
};

/**
* @deprecated Use tools from `@nx/linter/src/generators/utils/eslint-file` instead
*/
export const extendAngularEslintJson = (
json: Linter.Config,
options: EslintExtensionSchema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('Cypress e2e configuration', () => {

beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write('.eslintrc.json', '{}'); // we are explicitly checking for existance of config type
});

afterAll(() => {
Expand Down
108 changes: 61 additions & 47 deletions packages/cypress/src/utils/add-linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import {
readProjectConfiguration,
runTasksInSerial,
Tree,
updateJson,
} from '@nx/devkit';
import { Linter, lintProjectGenerator } from '@nx/linter';
import { globalJavaScriptOverrides } from '@nx/linter/src/generators/init/global-eslint-config';
import { installedCypressVersion } from './cypress-version';
import { eslintPluginCypressVersion } from './versions';
import {
addExtendsToLintConfig,
addOverrideToLintConfig,
addPluginsToLintConfig,
findEslintFile,
isEslintConfigSupported,
replaceOverridesInLintConfig,
} from '@nx/linter/src/generators/utils/eslint-file';
import { javaScriptOverride } from '@nx/linter/src/generators/init/global-eslint-config';

export interface CyLinterOptions {
project: string;
Expand Down Expand Up @@ -42,7 +49,8 @@ export async function addLinterToCyProject(
const tasks: GeneratorCallback[] = [];
const projectConfig = readProjectConfiguration(tree, options.project);

if (!tree.exists(joinPathFragments(projectConfig.root, '.eslintrc.json'))) {
const eslintFile = findEslintFile(tree, projectConfig.root);
if (!eslintFile) {
tasks.push(
await lintProjectGenerator(tree, {
project: options.project,
Expand Down Expand Up @@ -73,60 +81,66 @@ export async function addLinterToCyProject(
: () => {}
);

updateJson(
tree,
joinPathFragments(projectConfig.root, '.eslintrc.json'),
(json) => {
if (options.rootProject) {
json.plugins = ['@nx'];
json.extends = ['plugin:cypress/recommended'];
} else {
json.extends = ['plugin:cypress/recommended', ...json.extends];
}
json.overrides ??= [];
const globals = options.rootProject ? [globalJavaScriptOverrides] : [];
const override = {
if (isEslintConfigSupported(tree)) {
const overrides = [];
if (options.rootProject) {
addPluginsToLintConfig(tree, projectConfig.root, '@nx');
overrides.push(javaScriptOverride);
}
addExtendsToLintConfig(
tree,
projectConfig.root,
'plugin:cypress/recommended'
);
const cyVersion = installedCypressVersion();
/**
* We need this override because we enabled allowJS in the tsconfig to allow for JS based Cypress tests.
* That however leads to issues with the CommonJS Cypress plugin file.
*/
const cy6Override = {
files: [`${options.cypressDir}/plugins/index.js`],
rules: {
'@typescript-eslint/no-var-requires': 'off',
'no-undef': 'off',
},
};
const addCy6Override = cyVersion && cyVersion < 7;

if (options.overwriteExisting) {
overrides.push({
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
parserOptions: !options.setParserOptionsProject
? undefined
: {
project: `${projectConfig.root}/tsconfig.*?.json`,
},
rules: {},
};
const cyFiles = [
{
...override,
files: [
'*.cy.{ts,js,tsx,jsx}',
`${options.cypressDir}/**/*.{ts,js,tsx,jsx}`,
],
},
];
if (options.overwriteExisting) {
json.overrides = [...globals, override];
} else {
json.overrides.push(...globals);
json.overrides.push(...cyFiles);
});
if (addCy6Override) {
overrides.push(cy6Override);
}

const cyVersion = installedCypressVersion();
if (cyVersion && cyVersion < 7) {
/**
* We need this override because we enabled allowJS in the tsconfig to allow for JS based Cypress tests.
* That however leads to issues with the CommonJS Cypress plugin file.
*/
json.overrides.push({
files: [`${options.cypressDir}/plugins/index.js`],
rules: {
'@typescript-eslint/no-var-requires': 'off',
'no-undef': 'off',
},
});
replaceOverridesInLintConfig(tree, projectConfig.root, overrides);
} else {
overrides.unshift({
files: [
'*.cy.{ts,js,tsx,jsx}',
`${options.cypressDir}/**/*.{ts,js,tsx,jsx}`,
],
parserOptions: !options.setParserOptionsProject
? undefined
: {
project: `${projectConfig.root}/tsconfig.*?.json`,
},
rules: {},
});
if (addCy6Override) {
overrides.push(cy6Override);
}
return json;
overrides.forEach((override) =>
addOverrideToLintConfig(tree, projectConfig.root, override)
);
}
);
}

return runTasksInSerial(...tasks);
}
15 changes: 8 additions & 7 deletions packages/detox/src/generators/application/lib/add-linting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import {
joinPathFragments,
runTasksInSerial,
Tree,
updateJson,
} from '@nx/devkit';
import { extendReactEslintJson, extraEslintDependencies } from '@nx/react';
import { extraEslintDependencies } from '@nx/react';
import { NormalizedSchema } from './normalize-options';
import {
addExtendsToLintConfig,
isEslintConfigSupported,
} from '@nx/linter/src/generators/utils/eslint-file';

export async function addLinting(host: Tree, options: NormalizedSchema) {
if (options.linter === Linter.None) {
Expand All @@ -24,11 +27,9 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
skipFormat: true,
});

updateJson(
host,
joinPathFragments(options.e2eProjectRoot, '.eslintrc.json'),
extendReactEslintJson
);
if (isEslintConfigSupported(host)) {
addExtendsToLintConfig(host, options.e2eProjectRoot, 'plugin:@nx/react');
}

const installTask = addDependenciesToPackageJson(
host,
Expand Down
38 changes: 14 additions & 24 deletions packages/expo/src/utils/add-linting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ import { Linter, lintProjectGenerator } from '@nx/linter';
import {
addDependenciesToPackageJson,
GeneratorCallback,
joinPathFragments,
runTasksInSerial,
Tree,
updateJson,
} from '@nx/devkit';
import { extraEslintDependencies } from '@nx/react/src/utils/lint';
import {
extendReactEslintJson,
extraEslintDependencies,
} from '@nx/react/src/utils/lint';
import type { Linter as ESLintLinter } from 'eslint';
addExtendsToLintConfig,
addIgnoresToLintConfig,
isEslintConfigSupported,
} from '@nx/linter/src/generators/utils/eslint-file';

interface NormalizedSchema {
linter?: Linter;
Expand Down Expand Up @@ -39,24 +38,15 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {

tasks.push(lintTask);

updateJson(
host,
joinPathFragments(options.projectRoot, '.eslintrc.json'),
(json: ESLintLinter.Config) => {
json = extendReactEslintJson(json);

json.ignorePatterns = [
...json.ignorePatterns,
'.expo',
'node_modules',
'web-build',
'cache',
'dist',
];

return json;
}
);
if (isEslintConfigSupported(host)) {
addExtendsToLintConfig(host, options.projectRoot, 'plugin:@nx/react');
addIgnoresToLintConfig(host, options.projectRoot, [
'.expo',
'web-build',
'cache',
'dist',
]);
}

if (!options.skipPackageJson) {
const installTask = await addDependenciesToPackageJson(
Expand Down
16 changes: 10 additions & 6 deletions packages/js/src/generators/library/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,20 +256,24 @@ export async function addLint(
setParserOptionsProject: options.setParserOptionsProject,
rootProject: options.rootProject,
});
// Also update the root .eslintrc.json lintProjectGenerator will not generate it for root projects.
// Also update the root ESLint config. The lintProjectGenerator will not generate it for root projects.
// But we need to set the package.json checks.
if (options.rootProject) {
updateJson(tree, '.eslintrc.json', (json) => {
json.overrides ??= [];
json.overrides.push({
const {
addOverrideToLintConfig,
isEslintConfigSupported,
// nx-ignore-next-line
} = require('@nx/linter/src/generators/utils/eslint-file');

if (isEslintConfigSupported(tree)) {
addOverrideToLintConfig(tree, '', {
files: ['*.json'],
parser: 'jsonc-eslint-parser',
rules: {
'@nx/dependency-checks': 'error',
},
});
return json;
});
}
}
return task;
}
Expand Down
Loading

1 comment on commit e34219a

@vercel
Copy link

@vercel vercel bot commented on e34219a Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-git-master-nrwl.vercel.app
nx.dev
nx-dev-nrwl.vercel.app
nx-five.vercel.app

Please sign in to comment.