fix(linter): add jest to root .eslintrc if selected as unit test runner (#11555)

This commit is contained in:
Miroslav Jonaš 2022-08-18 14:51:09 +02:00 committed by GitHub
parent 99a3563c8f
commit 187f5200c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 141 additions and 110 deletions

View File

@ -12,6 +12,7 @@ export async function addLintingGenerator(
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
const installTask = lintInitGenerator(tree, { const installTask = lintInitGenerator(tree, {
linter: Linter.EsLint, linter: Linter.EsLint,
unitTestRunner: options.unitTestRunner,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
}); });

View File

@ -5,4 +5,5 @@ export interface AddLintingGeneratorSchema {
setParserOptionsProject?: boolean; setParserOptionsProject?: boolean;
skipFormat?: boolean; skipFormat?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
unitTestRunner?: string;
} }

View File

@ -48,6 +48,7 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
eslintFilePatterns: [ eslintFilePatterns: [
joinPathFragments(options.e2eProjectRoot, '**/*.ts'), joinPathFragments(options.e2eProjectRoot, '**/*.ts'),
], ],
unitTestRunner: options.unitTestRunner,
skipFormat: true, skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,

View File

@ -14,5 +14,6 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
prefix: options.prefix, prefix: options.prefix,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
unitTestRunner: options.unitTestRunner,
}); });
} }

View File

@ -168,6 +168,7 @@ async function addLinting(host: Tree, options: NormalizedSchema) {
projectName: options.name, projectName: options.name,
projectRoot: options.projectRoot, projectRoot: options.projectRoot,
prefix: options.prefix, prefix: options.prefix,
unitTestRunner: options.unitTestRunner,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
skipFormat: true, skipFormat: true,
}); });

View File

@ -96,7 +96,7 @@ export async function migrateFromAngularCli(
createRootKarmaConfig(tree); createRootKarmaConfig(tree);
} }
if (workspaceCapabilities.eslint) { if (workspaceCapabilities.eslint) {
updateRootEsLintConfig(tree, eslintConfig); updateRootEsLintConfig(tree, eslintConfig, options.unitTestRunner);
cleanupEsLintPackages(tree); cleanupEsLintPackages(tree);
} }

View File

@ -310,6 +310,7 @@ export class E2eMigrator extends ProjectMigrator<SupportedTargets> {
project: this.project.name, project: this.project.name,
linter: Linter.EsLint, linter: Linter.EsLint,
eslintFilePatterns: [`${this.project.newRoot}/**/*.{js,ts}`], eslintFilePatterns: [`${this.project.newRoot}/**/*.{js,ts}`],
unitTestRunner: this.options.unitTestRunner,
tsConfigPaths: [ tsConfigPaths: [
joinPathFragments(this.project.newRoot, 'tsconfig.json'), joinPathFragments(this.project.newRoot, 'tsconfig.json'),
], ],

View File

@ -163,7 +163,8 @@ export function updatePackageJson(tree: Tree): void {
export function updateRootEsLintConfig( export function updateRootEsLintConfig(
tree: Tree, tree: Tree,
existingEsLintConfig: any | undefined existingEsLintConfig: any | undefined,
unitTestRunner?: string
): void { ): void {
if (tree.exists('.eslintrc.json')) { if (tree.exists('.eslintrc.json')) {
/** /**
@ -175,7 +176,7 @@ export function updateRootEsLintConfig(
tree.delete('.eslintrc.json'); tree.delete('.eslintrc.json');
} }
lintInitGenerator(tree, { linter: Linter.EsLint }); lintInitGenerator(tree, { linter: Linter.EsLint, unitTestRunner });
if (!existingEsLintConfig) { if (!existingEsLintConfig) {
// There was no eslint config in the root, so we keep the generated one as-is. // There was no eslint config in the root, so we keep the generated one as-is.

View File

@ -110,7 +110,7 @@ function addProject(tree: Tree, options: CypressProjectSchema) {
if (!project.targets) { if (!project.targets) {
logger.warn(stripIndents` logger.warn(stripIndents`
NOTE: Project, "${options.project}", does not have any targets defined and a baseUrl was not provided. Nx will use NOTE: Project, "${options.project}", does not have any targets defined and a baseUrl was not provided. Nx will use
"${options.project}:serve" as the devServerTarget. But you may need to define this target within the project, "${options.project}". "${options.project}:serve" as the devServerTarget. But you may need to define this target within the project, "${options.project}".
`); `);
} }

View File

@ -155,6 +155,7 @@ export function addLint(
tsConfigPaths: [ tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'), joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
], ],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [ eslintFilePatterns: [
`${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`, `${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
], ],

View File

@ -16,9 +16,11 @@ import {
import { Linter } from '../utils/linter'; import { Linter } from '../utils/linter';
import { containsEslint } from '../utils/eslint-file'; import { containsEslint } from '../utils/eslint-file';
import { ESLint } from 'eslint';
export interface LinterInitOptions { export interface LinterInitOptions {
linter?: Linter; linter?: Linter;
unitTestRunner?: string;
skipPackageJson?: boolean; skipPackageJson?: boolean;
} }
@ -86,66 +88,78 @@ const globalTsLintConfiguration = {
}, },
}; };
const globalEsLintConfiguration = { const getGlobalEsLintConfiguration = (unitTestRunner?: string) => {
root: true, const config: ESLint.ConfigData = {
ignorePatterns: ['**/*'], root: true,
plugins: ['@nrwl/nx'], 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 * We leverage ESLint's "overrides" capability so that we can set up a root config which will support
* markup like HTML, or other custom file types like GraphQL) * 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: [
files: ['*.ts', '*.tsx', '*.js', '*.jsx'], /**
rules: { * This configuration is intended to apply to all "source code" (but not
'@nrwl/nx/enforce-module-boundaries': [ * markup like HTML, or other custom file types like GraphQL)
'error', */
{ {
enforceBuildableLibDependency: true, files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
allow: [], rules: {
depConstraints: [ '@nrwl/nx/enforce-module-boundaries': [
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }, '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'],
/** /**
* Having an empty rules object present makes it more obvious to the user where they would * This configuration is intended to apply to all TypeScript source files.
* extend things from if they needed to * See the eslint-plugin-nx package for what is in the referenced shareable config.
*/ */
rules: {}, {
}, files: ['*.ts', '*.tsx'],
extends: ['plugin:@nrwl/nx/typescript'],
/**
* 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 * This configuration is intended to apply to all JavaScript source files.
* extend things from if they needed to * 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: {},
},
],
};
if (unitTestRunner === 'jest') {
config.overrides.push({
files: ['*.spec.ts', '*.spec.tsx', '*.spec.js', '*.spec.jsx'],
env: {
jest: true,
},
rules: {}, rules: {},
}, });
], }
return config;
}; };
function initTsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback { function initTsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback {
@ -175,7 +189,11 @@ function initEsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback {
removeDependenciesFromPackageJson(tree, ['@nrwl/linter'], []); removeDependenciesFromPackageJson(tree, ['@nrwl/linter'], []);
} }
writeJson(tree, '.eslintrc.json', globalEsLintConfiguration); writeJson(
tree,
'.eslintrc.json',
getGlobalEsLintConfiguration(options.unitTestRunner)
);
if (tree.exists('.vscode/extensions.json')) { if (tree.exists('.vscode/extensions.json')) {
updateJson(tree, '.vscode/extensions.json', (json) => { updateJson(tree, '.vscode/extensions.json', (json) => {

View File

@ -20,6 +20,7 @@ interface LintProjectOptions {
skipFormat: boolean; skipFormat: boolean;
setParserOptionsProject?: boolean; setParserOptionsProject?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
unitTestRunner?: string;
} }
function createTsLintConfiguration( function createTsLintConfiguration(
@ -90,6 +91,7 @@ export async function lintProjectGenerator(
) { ) {
const installTask = lintInitGenerator(tree, { const installTask = lintInitGenerator(tree, {
linter: options.linter, linter: options.linter,
unitTestRunner: options.unitTestRunner,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
}); });
const projectConfig = readProjectConfiguration(tree, options.project); const projectConfig = readProjectConfiguration(tree, options.project);

View File

@ -20,6 +20,7 @@ export async function addLinting(
tsConfigPaths: [ tsConfigPaths: [
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
], ],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [`${options.appProjectRoot}/**/*.{ts,tsx,js,jsx}`], eslintFilePatterns: [`${options.appProjectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true, skipFormat: true,
}); });

View File

@ -187,6 +187,7 @@ export async function addLintingToApplication(
eslintFilePatterns: [ eslintFilePatterns: [
`${options.appProjectRoot}/**/*.${options.js ? 'js' : 'ts'}`, `${options.appProjectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
], ],
unitTestRunner: options.unitTestRunner,
skipFormat: true, skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
}); });

View File

@ -9,8 +9,8 @@ import {
convertNxGenerator, convertNxGenerator,
Tree, Tree,
formatFiles, formatFiles,
joinPathFragments,
GeneratorCallback, GeneratorCallback,
joinPathFragments,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { normalizeOptions } from './lib/normalize-options'; import { normalizeOptions } from './lib/normalize-options';
import initGenerator from '../init/init'; import initGenerator from '../init/init';
@ -29,14 +29,14 @@ export async function reactNativeApplicationGenerator(
addProject(host, options); addProject(host, options);
const initTask = await initGenerator(host, { ...options, skipFormat: true }); const initTask = await initGenerator(host, { ...options, skipFormat: true });
const lintTask = await addLinting( const lintTask = await addLinting(host, {
host, ...options,
options.projectName, projectRoot: options.appProjectRoot,
options.appProjectRoot, tsConfigPaths: [
[joinPathFragments(options.appProjectRoot, 'tsconfig.app.json')], joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
options.linter, ],
options.setParserOptionsProject });
);
const jestTask = await addJest( const jestTask = await addJest(
host, host,
options.unitTestRunner, options.unitTestRunner,

View File

@ -44,14 +44,13 @@ export async function reactNativeLibraryGenerator(
e2eTestRunner: 'none', e2eTestRunner: 'none',
}); });
const lintTask = await addLinting( const lintTask = await addLinting(host, {
host, ...options,
options.name, projectName: options.name,
options.projectRoot, tsConfigPaths: [
[joinPathFragments(options.projectRoot, 'tsconfig.lib.json')], joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
options.linter, ],
options.setParserOptionsProject });
);
if (!options.skipTsConfig) { if (!options.skipTsConfig) {
updateBaseTsConfig(host, options); updateBaseTsConfig(host, options);

View File

@ -16,13 +16,12 @@ describe('Add Linting', () => {
}); });
it('should add update `workspace.json` file properly when eslint is passed', () => { it('should add update `workspace.json` file properly when eslint is passed', () => {
addLinting( addLinting(tree, {
tree, projectName: 'my-lib',
'my-lib', linter: Linter.EsLint,
'libs/my-lib', tsConfigPaths: ['libs/my-lib/tsconfig.lib.json'],
['libs/my-lib/tsconfig.lib.json'], projectRoot: 'libs/my-lib',
Linter.EsLint });
);
const project = readProjectConfiguration(tree, 'my-lib'); const project = readProjectConfiguration(tree, 'my-lib');
expect(project.targets.lint).toBeDefined(); expect(project.targets.lint).toBeDefined();
@ -30,13 +29,12 @@ describe('Add Linting', () => {
}); });
it('should add update `workspace.json` file properly when tslint is passed', () => { it('should add update `workspace.json` file properly when tslint is passed', () => {
addLinting( addLinting(tree, {
tree, projectName: 'my-lib',
'my-lib', linter: Linter.TsLint,
'libs/my-lib', tsConfigPaths: ['libs/my-lib/tsconfig.lib.json'],
['libs/my-lib/tsconfig.lib.json'], projectRoot: 'libs/my-lib',
Linter.TsLint });
);
const project = readProjectConfiguration(tree, 'my-lib'); const project = readProjectConfiguration(tree, 'my-lib');
expect(project.targets.lint).toBeDefined(); expect(project.targets.lint).toBeDefined();
@ -46,13 +44,12 @@ describe('Add Linting', () => {
}); });
it('should not add lint target when "none" is passed', async () => { it('should not add lint target when "none" is passed', async () => {
addLinting( addLinting(tree, {
tree, projectName: 'my-lib',
'my-lib', linter: Linter.None,
'libs/my-lib', tsConfigPaths: ['libs/my-lib/tsconfig.lib.json'],
['libs/my-lib/tsconfig.lib.json'], projectRoot: 'libs/my-lib',
Linter.None });
);
const project = readProjectConfiguration(tree, 'my-lib'); const project = readProjectConfiguration(tree, 'my-lib');
expect(project.targets.lint).toBeUndefined(); expect(project.targets.lint).toBeUndefined();

View File

@ -9,38 +9,39 @@ import {
import { extraEslintDependencies, createReactEslintJson } from '@nrwl/react'; import { extraEslintDependencies, createReactEslintJson } from '@nrwl/react';
import type { Linter as ESLintLinter } from 'eslint'; import type { Linter as ESLintLinter } from 'eslint';
export async function addLinting( interface NormalizedSchema {
host: Tree, linter?: Linter;
projectName: string, projectName: string;
appProjectRoot: string, projectRoot: string;
tsConfigPaths: string[], setParserOptionsProject?: boolean;
linter: Linter, tsConfigPaths: string[];
setParserOptionsProject?: boolean }
) {
if (linter === Linter.None) { export async function addLinting(host: Tree, options: NormalizedSchema) {
if (options.linter === Linter.None) {
return () => {}; return () => {};
} }
const lintTask = await lintProjectGenerator(host, { const lintTask = await lintProjectGenerator(host, {
linter, linter: options.linter,
project: projectName, project: options.projectName,
tsConfigPaths, tsConfigPaths: options.tsConfigPaths,
eslintFilePatterns: [`${appProjectRoot}/**/*.{ts,tsx,js,jsx}`], eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true, skipFormat: true,
}); });
if (linter === Linter.TsLint) { if (options.linter === Linter.TsLint) {
return () => {}; return () => {};
} }
const reactEslintJson = createReactEslintJson( const reactEslintJson = createReactEslintJson(
appProjectRoot, options.projectRoot,
setParserOptionsProject options.setParserOptionsProject
); );
updateJson( updateJson(
host, host,
joinPathFragments(appProjectRoot, '.eslintrc.json'), joinPathFragments(options.projectRoot, '.eslintrc.json'),
(json: ESLintLinter.Config) => { (json: ESLintLinter.Config) => {
json = reactEslintJson; json = reactEslintJson;
json.ignorePatterns = ['!**/*', 'public', '.cache', 'node_modules']; json.ignorePatterns = ['!**/*', 'public', '.cache', 'node_modules'];

View File

@ -35,6 +35,7 @@ async function addLinting(host: Tree, options: NormalizedSchema) {
tsConfigPaths: [ tsConfigPaths: [
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
], ],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [`${options.appProjectRoot}/**/*.{ts,tsx,js,jsx}`], eslintFilePatterns: [`${options.appProjectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true, skipFormat: true,
}); });

View File

@ -147,6 +147,7 @@ async function addLinting(host: Tree, options: NormalizedSchema) {
tsConfigPaths: [ tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'), joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
], ],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx}`], eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx}`],
skipFormat: true, skipFormat: true,
}); });

View File

@ -206,6 +206,7 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
tsConfigPaths: [ tsConfigPaths: [
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
], ],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [`${options.appProjectRoot}/**/*.ts`], eslintFilePatterns: [`${options.appProjectRoot}/**/*.ts`],
skipFormat: true, skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,

View File

@ -81,6 +81,7 @@ export function addLint(
tsConfigPaths: [ tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.lib.json'), joinPathFragments(options.projectRoot, 'tsconfig.lib.json'),
], ],
unitTestRunner: options.unitTestRunner,
eslintFilePatterns: [ eslintFilePatterns: [
`${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`, `${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
], ],