diff --git a/e2e/linter/src/linter.test.ts b/e2e/linter/src/linter.test.ts index 4cde759ca3add..533adbfabca49 100644 --- a/e2e/linter/src/linter.test.ts +++ b/e2e/linter/src/linter.test.ts @@ -19,7 +19,11 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); const eslintrc = readJson('.eslintrc.json'); - eslintrc.rules['no-console'] = 'error'; + eslintrc.overrides.forEach((override) => { + if (override.files.includes('*.ts')) { + override.rules['no-console'] = 'error'; + } + }); updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); @@ -35,7 +39,11 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); const eslintrc = readJson('.eslintrc.json'); - eslintrc.rules['no-console'] = 'error'; + eslintrc.overrides.forEach((override) => { + if (override.files.includes('*.ts')) { + override.rules['no-console'] = 'error'; + } + }); updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); @@ -50,7 +58,11 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); const eslintrc = readJson('.eslintrc.json'); - eslintrc.rules['no-console'] = undefined; + eslintrc.overrides.forEach((override) => { + if (override.files.includes('*.ts')) { + override.rules['no-console'] = undefined; + } + }); updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); @@ -82,7 +94,11 @@ forEachCli('nx', () => { updateFile('workspace.json', JSON.stringify(workspaceJson, null, 2)); const eslintrc = readJson('.eslintrc.json'); - eslintrc.rules['no-console'] = undefined; + eslintrc.overrides.forEach((override) => { + if (override.files.includes('*.ts')) { + override.rules['no-console'] = undefined; + } + }); updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); @@ -130,7 +146,11 @@ forEachCli('nx', () => { runCLI(`generate @nrwl/react:app ${myapp}`); const eslintrc = readJson('.eslintrc.json'); - eslintrc.rules['no-console'] = 'error'; + eslintrc.overrides.forEach((override) => { + if (override.files.includes('*.ts')) { + override.rules['no-console'] = 'error'; + } + }); updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); updateFile(`apps/${myapp}/src/main.ts`, `console.log("should fail");`); diff --git a/packages/eslint-plugin-nx/src/configs/javascript.ts b/packages/eslint-plugin-nx/src/configs/javascript.ts new file mode 100644 index 0000000000000..cf945ad054a89 --- /dev/null +++ b/packages/eslint-plugin-nx/src/configs/javascript.ts @@ -0,0 +1,37 @@ +/** + * This configuration is intended to be applied to ALL .js and .jsx files + * within an Nx workspace. + * + * It should therefore NOT contain any rules or plugins which are specific + * to one ecosystem, such as React, Angular, Node etc. + * + * We use @typescript-eslint/parser rather than the built in JS parser + * because that is what Nx ESLint configs have always done and we don't + * want to change too much all at once. + * + * TODO: Evaluate switching to the built-in JS parser (espree) in Nx v11, + * it should yield a performance improvement but could introduce subtle + * breaking changes - we should also look to replace all the @typescript-eslint + * related plugins and rules below. + */ +export default { + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, + plugins: ['@typescript-eslint'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + 'prettier/@typescript-eslint', + ], + rules: { + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + }, +}; diff --git a/packages/eslint-plugin-nx/src/index.ts b/packages/eslint-plugin-nx/src/index.ts index 9dbaccee87e6e..e130b4b394138 100644 --- a/packages/eslint-plugin-nx/src/index.ts +++ b/packages/eslint-plugin-nx/src/index.ts @@ -1,4 +1,5 @@ import typescript from './configs/typescript'; +import javascript from './configs/javascript'; import reactTmp from './configs/react-tmp'; import reactBase from './configs/react-base'; import reactJsx from './configs/react-jsx'; @@ -11,6 +12,7 @@ import enforceModuleBoundaries, { module.exports = { configs: { typescript, + javascript, react: reactTmp, 'react-base': reactBase, 'react-typescript': reactTypescript, diff --git a/packages/storybook/src/schematics/configuration/configuration.ts b/packages/storybook/src/schematics/configuration/configuration.ts index d7c61b509f045..a903cb1c3dadd 100644 --- a/packages/storybook/src/schematics/configuration/configuration.ts +++ b/packages/storybook/src/schematics/configuration/configuration.ts @@ -236,11 +236,28 @@ function updateLintConfig(schema: StorybookConfigureSchema): Rule { return updateJsonInTree( `${projectConfig.root}/.eslintrc.json`, (json) => { + if (typeof json.parserOptions?.project === 'string') { + json.parserOptions.project = [json.parserOptions.project]; + } + if (Array.isArray(json.parserOptions?.project)) { json.parserOptions.project.push( `${projectConfig.root}/.storybook/tsconfig.json` ); } + + const overrides = json.overrides || []; + for (const override of overrides) { + if (typeof override.parserOptions?.project === 'string') { + override.parserOptions.project = [override.parserOptions.project]; + } + if (Array.isArray(override.parserOptions?.project)) { + override.parserOptions.project.push( + `${projectConfig.root}/.storybook/tsconfig.json` + ); + } + } + return json; } ); diff --git a/packages/workspace/src/utils/lint.ts b/packages/workspace/src/utils/lint.ts index 992e8c2760ebd..9361b27464e6d 100644 --- a/packages/workspace/src/utils/lint.ts +++ b/packages/workspace/src/utils/lint.ts @@ -234,24 +234,69 @@ const globalTsLint = ` } `; -const globalESLint = ` -{ - "root": true, - "ignorePatterns": ["**/*"], - "plugins": ["@nrwl/nx"], - "extends": ["plugin:@nrwl/nx/typescript"], - "parserOptions": { "project": "./tsconfig.*?.json" }, - "rules": { - "@nrwl/nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allow": [], - "depConstraints": [ - { "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] } - ] - } - ] - } -} -`; +const globalESLint = JSON.stringify({ + root: true, + ignorePatterns: ['**/*'], + plugins: ['@nrwl/nx'], + /** + * We leverage ESLint's "overrides" capability so that we can set up a root config which will support + * all permutations of Nx workspaces across all frameworks, libraries and tools. + * + * The key point is that we need entirely different ESLint config to apply to different types of files, + * but we still want to share common config where possible. + */ + overrides: [ + /** + * This configuration is intended to apply to all "source code" (but not + * markup like HTML, or other custom file types like GraphQL) + */ + { + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + rules: { + '@nrwl/nx/enforce-module-boundaries': [ + 'error', + { + enforceBuildableLibDependency: true, + allow: [], + depConstraints: [ + { sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }, + ], + }, + ], + }, + }, + + /** + * This configuration is intended to apply to all TypeScript source files. + * See the eslint-plugin-nx package for what is in the referenced shareable config. + */ + { + files: ['*.ts', '*.tsx'], + extends: ['plugin:@nrwl/nx/typescript'], + /** + * TODO: Remove this usage of project at the root in a follow up PR (and migration), + * it should be set in each project's config + */ + parserOptions: { project: './tsconfig.*?.json' }, + /** + * Having an empty rules object present makes it more obvious to the user where they would + * extend things from if they needed to + */ + rules: {}, + }, + + /** + * This configuration is intended to apply to all JavaScript source files. + * See the eslint-plugin-nx package for what is in the referenced shareable config. + */ + { + files: ['*.js', '*.jsx'], + extends: ['plugin:@nrwl/nx/javascript'], + /** + * Having an empty rules object present makes it more obvious to the user where they would + * extend things from if they needed to + */ + rules: {}, + }, + ], +});