From 6c1edb0988bdb56148c645fdcf2793f00bd42a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Chalk?= Date: Fri, 1 Dec 2023 14:48:20 +0100 Subject: [PATCH] test(plugin-eslint): integration test for Nx helper with mock monorepo --- .nxignore | 2 + .../mocks/fixtures/nx-monorepo/.gitignore | 1 + .../mocks/fixtures/nx-monorepo/nx.json | 7 ++ .../nx-monorepo/packages/cli/.eslintrc.json | 25 +++++ .../nx-monorepo/packages/cli/project.json | 17 ++++ .../nx-monorepo/packages/cli/src/index.ts | 3 + .../nx-monorepo/packages/cli/tsconfig.json | 3 + .../nx-monorepo/packages/core/.eslintrc.json | 25 +++++ .../nx-monorepo/packages/core/project.json | 17 ++++ .../nx-monorepo/packages/core/src/index.ts | 5 + .../nx-monorepo/packages/core/tsconfig.json | 3 + .../packages/nx-plugin/.eslintrc.json | 32 +++++++ .../packages/nx-plugin/project.json | 18 ++++ .../packages/nx-plugin/src/index.ts | 5 + .../packages/nx-plugin/tsconfig.json | 3 + .../nx-monorepo/packages/utils/project.json | 17 ++++ .../nx-monorepo/packages/utils/src/index.ts | 1 + .../nx-monorepo/packages/utils/tsconfig.json | 3 + .../fixtures/nx-monorepo/tsconfig.base.json | 25 +++++ .../src/lib/nx.integration.test.ts | 92 +++++++++++++++++++ .../src/lib/nx/find-all-projects.ts | 8 +- packages/plugin-eslint/src/lib/nx/utils.ts | 5 +- 22 files changed, 311 insertions(+), 6 deletions(-) create mode 100644 .nxignore create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/.gitignore create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/nx.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/.eslintrc.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/project.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/src/index.ts create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/tsconfig.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/.eslintrc.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/project.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/src/index.ts create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/tsconfig.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/.eslintrc.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/project.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/src/index.ts create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/tsconfig.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/project.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/src/index.ts create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/tsconfig.json create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/tsconfig.base.json create mode 100644 packages/plugin-eslint/src/lib/nx.integration.test.ts diff --git a/.nxignore b/.nxignore new file mode 100644 index 000000000..ec58c2555 --- /dev/null +++ b/.nxignore @@ -0,0 +1,2 @@ +# exclude mock Nx monorepos used for testing +mocks diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/.gitignore b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/.gitignore new file mode 100644 index 000000000..0cba03f65 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/.gitignore @@ -0,0 +1 @@ +.nx/cache \ No newline at end of file diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/nx.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/nx.json new file mode 100644 index 000000000..be05532ae --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/nx.json @@ -0,0 +1,7 @@ +{ + "pluginsConfig": { + "@nrwl/js": { + "analyzeSourceFiles": true + } + } +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/.eslintrc.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/.eslintrc.json new file mode 100644 index 000000000..498a74926 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": ["error"] + } + } + ] +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/project.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/project.json new file mode 100644 index 000000000..eaa517967 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/project.json @@ -0,0 +1,17 @@ +{ + "name": "cli", + "sourceRoot": "packages/cli/src", + "projectType": "application", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/cli/**/*.ts", + "packages/cli/package.json" + ] + } + } + } +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/src/index.ts b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/src/index.ts new file mode 100644 index 000000000..a51f5d7d0 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/src/index.ts @@ -0,0 +1,3 @@ +import { exampleCore } from '@example/core'; + +exampleCore(); diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/tsconfig.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/tsconfig.json new file mode 100644 index 000000000..9536a0f41 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/cli/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.base.json" +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/.eslintrc.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/.eslintrc.json new file mode 100644 index 000000000..498a74926 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": ["error"] + } + } + ] +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/project.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/project.json new file mode 100644 index 000000000..238ad8866 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/project.json @@ -0,0 +1,17 @@ +{ + "name": "core", + "sourceRoot": "packages/core/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/core/**/*.ts", + "packages/core/package.json" + ] + } + } + } +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/src/index.ts b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/src/index.ts new file mode 100644 index 000000000..6a00d8538 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/src/index.ts @@ -0,0 +1,5 @@ +import { exampleSharedUtil } from '@example/utils'; + +export function exampleCore() { + exampleSharedUtil(); +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/tsconfig.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/tsconfig.json new file mode 100644 index 000000000..9536a0f41 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/core/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.base.json" +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/.eslintrc.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/.eslintrc.json new file mode 100644 index 000000000..055ebf9d6 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/.eslintrc.json @@ -0,0 +1,32 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": ["error"] + } + }, + { + "files": ["./package.json", "./generators.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/nx-plugin-checks": "error" + } + } + ] +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/project.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/project.json new file mode 100644 index 000000000..faa609fb0 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/project.json @@ -0,0 +1,18 @@ +{ + "name": "nx-plugin", + "sourceRoot": "packages/nx-plugin/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/nx-plugin/**/*.ts", + "packages/nx-plugin/package.json", + "packages/nx-plugin/generators.json" + ] + } + } + } +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/src/index.ts b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/src/index.ts new file mode 100644 index 000000000..d4b78efbd --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/src/index.ts @@ -0,0 +1,5 @@ +import { exampleSharedUtil } from '@example/utils'; + +export function exampleNxPlugin() { + exampleSharedUtil(); +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/tsconfig.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/tsconfig.json new file mode 100644 index 000000000..9536a0f41 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/nx-plugin/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.base.json" +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/project.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/project.json new file mode 100644 index 000000000..77632393b --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/project.json @@ -0,0 +1,17 @@ +{ + "name": "utils", + "sourceRoot": "packages/utils/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/utils/**/*.ts", + "packages/utils/package.json" + ] + } + } + } +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/src/index.ts b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/src/index.ts new file mode 100644 index 000000000..02e4dccc0 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/src/index.ts @@ -0,0 +1 @@ +export function exampleSharedUtil() {} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/tsconfig.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/tsconfig.json new file mode 100644 index 000000000..9536a0f41 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/packages/utils/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.base.json" +} diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/tsconfig.base.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/tsconfig.base.json new file mode 100644 index 000000000..05d05e672 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/tsconfig.base.json @@ -0,0 +1,25 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "rootDir": ".", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "es2015", + "module": "esnext", + "lib": ["es2020", "dom"], + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "baseUrl": ".", + "paths": { + "@example/cli": ["packages/cli/src/index.ts"], + "@example/core": ["packages/core/src/index.ts"], + "@example/nx-plugin": ["packages/nx-plugin/src/index.ts"], + "@example/utils": ["packages/utils/src/index.ts"] + } + }, + "exclude": ["node_modules", "tmp"] +} diff --git a/packages/plugin-eslint/src/lib/nx.integration.test.ts b/packages/plugin-eslint/src/lib/nx.integration.test.ts new file mode 100644 index 000000000..6bd918f2a --- /dev/null +++ b/packages/plugin-eslint/src/lib/nx.integration.test.ts @@ -0,0 +1,92 @@ +import { setWorkspaceRoot } from 'nx/src/utils/workspace-root'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import type { SpyInstance } from 'vitest'; +import { ESLintPluginConfig } from './config'; +import { eslintConfigFromNxProjects } from './nx'; + +describe('Nx helpers', () => { + let cwdSpy: SpyInstance; + + beforeAll(() => { + const workspaceDir = join( + fileURLToPath(dirname(import.meta.url)), + '..', + '..', + 'mocks', + 'fixtures', + 'nx-monorepo', + ); + cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(workspaceDir); + + process.env['NX_DAEMON'] = 'false'; + + setWorkspaceRoot(workspaceDir); + }); + + afterAll(() => { + cwdSpy.mockRestore(); + }); + + describe('create config from all Nx projects', () => { + it('should include eslintrc and patterns of each project', async () => { + await expect(eslintConfigFromNxProjects()).resolves.toEqual({ + eslintrc: { + root: true, + overrides: [ + { + files: ['packages/cli/**/*.ts', 'packages/cli/package.json'], + extends: './packages/cli/.eslintrc.json', + }, + { + files: ['packages/core/**/*.ts', 'packages/core/package.json'], + extends: './packages/core/.eslintrc.json', + }, + { + files: [ + 'packages/nx-plugin/**/*.ts', + 'packages/nx-plugin/package.json', + 'packages/nx-plugin/generators.json', + ], + extends: './packages/nx-plugin/.eslintrc.json', + }, + { + files: ['packages/utils/**/*.ts', 'packages/utils/package.json'], + extends: './packages/utils/.eslintrc.json', + }, + ], + }, + patterns: [ + 'packages/cli/**/*.ts', + 'packages/cli/package.json', + 'packages/cli/src/*.spec.ts', + 'packages/cli/src/*.cy.ts', + 'packages/cli/src/*.stories.ts', + 'packages/cli/src/.storybook/main.ts', + + 'packages/core/**/*.ts', + 'packages/core/package.json', + 'packages/core/src/*.spec.ts', + 'packages/core/src/*.cy.ts', + 'packages/core/src/*.stories.ts', + 'packages/core/src/.storybook/main.ts', + + 'packages/nx-plugin/**/*.ts', + 'packages/nx-plugin/package.json', + 'packages/nx-plugin/generators.json', + 'packages/nx-plugin/src/*.spec.ts', + 'packages/nx-plugin/src/*.cy.ts', + 'packages/nx-plugin/src/*.stories.ts', + 'packages/nx-plugin/src/.storybook/main.ts', + + 'packages/utils/**/*.ts', + 'packages/utils/package.json', + 'packages/utils/src/*.spec.ts', + 'packages/utils/src/*.cy.ts', + 'packages/utils/src/*.stories.ts', + 'packages/utils/src/.storybook/main.ts', + ], + } satisfies ESLintPluginConfig); + }); + }); +}); diff --git a/packages/plugin-eslint/src/lib/nx/find-all-projects.ts b/packages/plugin-eslint/src/lib/nx/find-all-projects.ts index 4a2b8929a..29b42e296 100644 --- a/packages/plugin-eslint/src/lib/nx/find-all-projects.ts +++ b/packages/plugin-eslint/src/lib/nx/find-all-projects.ts @@ -15,9 +15,9 @@ export async function eslintConfigFromNxProjects(): Promise const projectGraph = await createProjectGraphAsync({ exitOnError: false }); const projectsConfiguration = readProjectsConfigurationFromProjectGraph(projectGraph); - const projects = Object.values(projectsConfiguration.projects).filter( - project => 'lint' in (project.targets ?? {}), - ); + const projects = Object.values(projectsConfiguration.projects) + .filter(project => 'lint' in (project.targets ?? {})) + .sort((a, b) => a.root.localeCompare(b.root)); // create single ESLint config with project-specific overrides const eslintConfig: ESLint.ConfigData = { @@ -37,7 +37,7 @@ export async function eslintConfigFromNxProjects(): Promise // HACK: ESLint.calculateConfigForFile won't find rules included only for subsets of *.ts when globs used // so we explicitly provide additional patterns used by @code-pushup/eslint-config to ensure those rules are included // this workaround won't be necessary once flat configs are stable (much easier to find all rules) - `${project.sourceRoot}/*.test.ts`, // jest/* and vitest/* rules + `${project.sourceRoot}/*.spec.ts`, // jest/* and vitest/* rules `${project.sourceRoot}/*.cy.ts`, // cypress/* rules `${project.sourceRoot}/*.stories.ts`, // storybook/* rules `${project.sourceRoot}/.storybook/main.ts`, // storybook/no-uninstalled-addons rule diff --git a/packages/plugin-eslint/src/lib/nx/utils.ts b/packages/plugin-eslint/src/lib/nx/utils.ts index 3c623fa8a..323949f1c 100644 --- a/packages/plugin-eslint/src/lib/nx/utils.ts +++ b/packages/plugin-eslint/src/lib/nx/utils.ts @@ -1,4 +1,5 @@ import type { ProjectConfiguration } from '@nx/devkit'; +import { join } from 'node:path'; import { fileExists, toArray } from '@code-pushup/utils'; export async function findCodePushupEslintrc( @@ -11,7 +12,7 @@ export async function findCodePushupEslintrc( // eslint-disable-next-line functional/no-loop-statements for (const ext of extensions) { const filename = `./${project.root}/${name}.${ext}`; - if (await fileExists(filename)) { + if (await fileExists(join(process.cwd(), filename))) { return filename; } } @@ -32,5 +33,5 @@ export function getEslintConfig(project: ProjectConfiguration): string { const options = project.targets?.['lint']?.options as | { eslintConfig?: string } | undefined; - return options?.eslintConfig ?? `${project.root}/.eslintrc.json`; + return options?.eslintConfig ?? `./${project.root}/.eslintrc.json`; }