Skip to content

Commit

Permalink
fix(linter): move should migrate all eslint configs (#20709)
Browse files Browse the repository at this point in the history
  • Loading branch information
meeroslav authored Dec 13, 2023
1 parent e8dde99 commit 4314133
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 40 deletions.
71 changes: 49 additions & 22 deletions packages/eslint/src/generators/init/init-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,36 +29,62 @@ export function migrateConfigToMonorepoStyle(
tree: Tree,
unitTestRunner: string
): void {
if (useFlatConfig(tree)) {
// we need this for the compat
addDependenciesToPackageJson(
tree,
{},
{
'@eslint/js': eslintVersion,
}
);
tree.write(
'eslint.base.config.js',
getGlobalFlatEslintConfiguration(unitTestRunner)
);
const rootEslintConfig = findEslintFile(tree);
let skipCleanup = false;
if (
rootEslintConfig?.match(/\.base\./) &&
!projects.some((p) => p.root === '.')
) {
// if the migration has been run already, we need to rename the base config
// and only update the extends paths
tree.rename(rootEslintConfig, rootEslintConfig.replace('.base.', '.'));
skipCleanup = true;
} else {
writeJson(
tree,
'.eslintrc.base.json',
getGlobalEsLintConfiguration(unitTestRunner)
);
if (useFlatConfig(tree)) {
// we need this for the compat
addDependenciesToPackageJson(
tree,
{},
{
'@eslint/js': eslintVersion,
}
);
tree.write(
tree.exists('eslint.config.js')
? 'eslint.base.config.js'
: 'eslint.config.js',
getGlobalFlatEslintConfiguration(unitTestRunner)
);
} else {
const eslintFile = findEslintFile(tree, '.');
writeJson(
tree,
eslintFile ? '.eslintrc.base.json' : '.eslintrc.json',
getGlobalEsLintConfiguration(unitTestRunner)
);
}
}

// update extens in all projects' eslint configs
// update extends in all projects' eslint configs
projects.forEach((project) => {
const lintTarget = findLintTarget(project);
if (lintTarget) {
const eslintFile =
lintTarget.options?.eslintConfig || findEslintFile(tree, project.root);
if (eslintFile) {
const projectEslintPath = joinPathFragments(project.root, eslintFile);
migrateEslintFile(projectEslintPath, tree);
if (skipCleanup) {
const content = tree.read(projectEslintPath, 'utf-8');
tree.write(
projectEslintPath,
content.replace(
rootEslintConfig,
rootEslintConfig.replace('.base.', '.')
)
);
} else {
migrateEslintFile(projectEslintPath, tree);
}
}
}
});
Expand All @@ -76,6 +102,7 @@ export function findLintTarget(
}

function migrateEslintFile(projectEslintPath: string, tree: Tree) {
const baseFile = findEslintFile(tree);
if (isEslintConfigSupported(tree)) {
if (useFlatConfig(tree)) {
let config = tree.read(projectEslintPath, 'utf-8');
Expand All @@ -85,7 +112,7 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) {
config = addImportToFlatConfig(
config,
'baseConfig',
`${offsetFromRoot(dirname(projectEslintPath))}eslint.base.config.js`
`${offsetFromRoot(dirname(projectEslintPath))}${baseFile}`
);
config = addBlockToFlatConfigExport(
config,
Expand Down Expand Up @@ -117,7 +144,7 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) {
json.extends = json.extends || [];
const pathToRootConfig = `${offsetFromRoot(
dirname(projectEslintPath)
)}.eslintrc.base.json`;
)}${baseFile}`;
if (json.extends.indexOf(pathToRootConfig) === -1) {
json.extends.push(pathToRootConfig);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint/src/generators/utils/eslint-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
names,
offsetFromRoot,
readJson,
Tree,
updateJson,
} from '@nx/devkit';
import { Linter } from 'eslint';
import type { Tree } from '@nx/devkit';
import type { Linter } from 'eslint';
import { useFlatConfig } from '../../utils/flat-config';
import {
addBlockToFlatConfigExport,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ describe('monorepo generator', () => {

// Extracted base config files
expect(tree.exists('tsconfig.base.json')).toBeTruthy();
expect(tree.exists('.eslintrc.base.json')).toBeTruthy();
});

it('should respect nested libraries', async () => {
Expand Down Expand Up @@ -140,7 +139,6 @@ describe('monorepo generator', () => {

// Extracted base config files
expect(tree.exists('tsconfig.base.json')).toBeTruthy();
expect(tree.exists('.eslintrc.base.json')).toBeTruthy();
expect(tree.exists('jest.config.ts')).toBeTruthy();
});

Expand Down Expand Up @@ -169,7 +167,6 @@ describe('monorepo generator', () => {

// Extracted base config files
expect(tree.exists('tsconfig.base.json')).toBeTruthy();
expect(tree.exists('.eslintrc.base.json')).toBeTruthy();
expect(tree.exists('jest.config.ts')).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ export async function monorepoGenerator(tree: Tree, options: {}) {

// Need to determine libs vs packages directory base on the type of root project.
for (const [, project] of projects) {
if (project.root === '.') rootProject = project;
projectsToMove.unshift(project); // move the root project 1st
if (project.root === '.') {
rootProject = project;
} else {
projectsToMove.push(project);
}
}
projectsToMove.unshift(rootProject); // move the root project 1st

// Currently, Nx only handles apps+libs or packages. You cannot mix and match them.
// If the standalone project is an app (React, Angular, etc), then use apps+libs.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { joinPathFragments, ProjectConfiguration, Tree } from '@nx/devkit';
import {
getProjects,
joinPathFragments,
ProjectConfiguration,
Tree,
} from '@nx/devkit';

export function maybeExtractTsConfigBase(tree: Tree): void {
let extractTsConfigBase: any;
Expand All @@ -22,23 +27,19 @@ export async function maybeExtractJestConfigBase(tree: Tree): Promise<void> {
await jestInitGenerator(tree, {});
}

export function maybeExtractEslintConfigIfRootProject(
export function maybeMigrateEslintConfigIfRootProject(
tree: Tree,
rootProject: ProjectConfiguration
): void {
if (rootProject.root !== '.') return;
if (tree.exists('.eslintrc.base.json')) return;
let migrateConfigToMonorepoStyle: any;
try {
migrateConfigToMonorepoStyle = require('@nx/' +
'eslint/src/generators/init/init-migration').migrateConfigToMonorepoStyle;
} catch {
// eslint not installed
}
// Only need to handle migrating the root rootProject.
// If other libs/apps exist, then this migration is already done by `@nx/eslint:lint-rootProject` generator.
migrateConfigToMonorepoStyle?.(
[rootProject],
Array.from(getProjects(tree).values()),
tree,
tree.exists(joinPathFragments(rootProject.root, 'jest.config.ts')) ||
tree.exists(joinPathFragments(rootProject.root, 'jest.config.js'))
Expand Down
82 changes: 80 additions & 2 deletions packages/workspace/src/generators/move/move.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readJson, Tree, updateJson } from '@nx/devkit';
import { readJson, Tree, updateJson, writeJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { moveGenerator } from './move';
// nx-ignore-next-line
Expand Down Expand Up @@ -173,7 +173,18 @@ describe('move', () => {
// Test that root configs are extracted
expect(tree.exists('tsconfig.base.json')).toBeTruthy();
expect(tree.exists('jest.config.ts')).toBeTruthy();
expect(tree.exists('.eslintrc.base.json')).toBeTruthy();
expect(tree.exists('.eslintrc.base.json')).not.toBeTruthy();
expect(tree.exists('.eslintrc.json')).toBeTruthy();

// Test that eslint migration was done
expect(readJson(tree, 'my-lib/.eslintrc.json').extends)
.toMatchInlineSnapshot(`
[
"../.eslintrc.json",
]
`);
expect(readJson(tree, 'my-lib/.eslintrc.json').plugins).not.toBeDefined();
expect(readJson(tree, '.eslintrc.json').plugins).toEqual(['@nx']);
});

it('should support moving standalone repos', async () => {
Expand All @@ -190,6 +201,8 @@ describe('move', () => {
style: 'css',
projectNameAndRootFormat: 'as-provided',
});
expect(readJson(tree, '.eslintrc.json').plugins).toEqual(['@nx']);
expect(readJson(tree, 'e2e/.eslintrc.json').plugins).toEqual(['@nx']);

// Test that this does not get moved
tree.write('other-lib/index.ts', '');
Expand All @@ -200,6 +213,14 @@ describe('move', () => {
destination: 'apps/react-app',
projectNameAndRootFormat: 'as-provided',
});

// expect both eslint configs to have been changed
expect(tree.exists('.eslintrc.json')).toBeDefined();
expect(
readJson(tree, 'apps/react-app/.eslintrc.json').plugins
).toBeUndefined();
expect(readJson(tree, 'e2e/.eslintrc.json').plugins).toBeUndefined();

await moveGenerator(tree, {
projectName: 'e2e',
updateImportPath: false,
Expand All @@ -223,6 +244,63 @@ describe('move', () => {
`);
});

it('should correctly move standalone repos that have migrated eslint config', async () => {
// Test that these are not moved
tree.write('.gitignore', '');
tree.write('README.md', '');

await applicationGenerator(tree, {
name: 'react-app',
rootProject: true,
unitTestRunner: 'jest',
e2eTestRunner: 'cypress',
linter: 'eslint',
style: 'css',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, {
name: 'my-lib',
bundler: 'tsc',
buildable: true,
unitTestRunner: 'jest',
linter: 'eslint',
directory: 'my-lib',
projectNameAndRootFormat: 'as-provided',
});
// assess the correct starting position
expect(tree.exists('.eslintrc.base.json')).toBeTruthy();
expect(readJson(tree, '.eslintrc.json').plugins).not.toBeDefined();
expect(readJson(tree, '.eslintrc.json').extends).toEqual([
'plugin:@nx/react',
'./.eslintrc.base.json',
]);
expect(readJson(tree, 'e2e/.eslintrc.json').plugins).not.toBeDefined();
expect(readJson(tree, 'e2e/.eslintrc.json').extends).toEqual([
'plugin:cypress/recommended',
'../.eslintrc.base.json',
]);

await moveGenerator(tree, {
projectName: 'react-app',
updateImportPath: false,
destination: 'apps/react-app',
projectNameAndRootFormat: 'as-provided',
});

// expect both eslint configs to have been changed
expect(tree.exists('.eslintrc.json')).toBeTruthy();
expect(tree.exists('.eslintrc.base.json')).toBeFalsy();

expect(readJson(tree, 'apps/react-app/.eslintrc.json').extends).toEqual([
'plugin:@nx/react',
'../../.eslintrc.json',
]);
expect(readJson(tree, 'e2e/.eslintrc.json').extends).toEqual([
'plugin:cypress/recommended',
'../.eslintrc.json',
]);
});

it('should move project correctly when --project-name-and-root-format=derived', async () => {
await libraryGenerator(tree, {
name: 'my-lib',
Expand Down
8 changes: 6 additions & 2 deletions packages/workspace/src/generators/move/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { updateProjectRootFiles } from './lib/update-project-root-files';
import { updateReadme } from './lib/update-readme';
import { updateStorybookConfig } from './lib/update-storybook-config';
import {
maybeExtractEslintConfigIfRootProject,
maybeMigrateEslintConfigIfRootProject,
maybeExtractJestConfigBase,
maybeExtractTsConfigBase,
} from './lib/extract-base-configs';
Expand All @@ -42,7 +42,6 @@ export async function moveGeneratorInternal(tree: Tree, rawSchema: Schema) {
if (projectConfig.root === '.') {
maybeExtractTsConfigBase(tree);
await maybeExtractJestConfigBase(tree);
maybeExtractEslintConfigIfRootProject(tree, projectConfig);
// Reload config since it has been updated after extracting base configs
projectConfig = readProjectConfiguration(tree, rawSchema.projectName);
}
Expand All @@ -62,6 +61,11 @@ export async function moveGeneratorInternal(tree: Tree, rawSchema: Schema) {
updateDefaultProject(tree, schema);
updateImplicitDependencies(tree, schema);

if (projectConfig.root === '.') {
// we want to migrate eslint config once the root project files are moved
maybeMigrateEslintConfigIfRootProject(tree, projectConfig);
}

await runAngularPlugin(tree, schema);

if (!schema.skipFormat) {
Expand Down

0 comments on commit 4314133

Please sign in to comment.