-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(plugin-eslint): provide Nx helper to combine eslint configs from…
… project with deps
- Loading branch information
1 parent
6c1edb0
commit 29cd887
Showing
6 changed files
with
206 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,29 @@ | ||
import { | ||
createProjectGraphAsync, | ||
readProjectsConfigurationFromProjectGraph, | ||
} from '@nx/devkit'; | ||
import type { ESLint } from 'eslint'; | ||
import { createProjectGraphAsync } from '@nx/devkit'; | ||
import type { ESLintPluginConfig } from '../config'; | ||
import { | ||
findCodePushupEslintrc, | ||
getEslintConfig, | ||
getLintFilePatterns, | ||
} from './utils'; | ||
import { nxProjectsToConfig } from './projects-to-config'; | ||
|
||
/** | ||
* Finds all Nx projects in workspace and converts their lint configurations to Code PushUp ESLint plugin parameters. | ||
* | ||
* Use when you wish to automatically include every Nx project in a single Code PushUp project. | ||
* If you prefer to only include a subset of your Nx monorepo, refer to {@link eslintConfigFromNxProject} instead. | ||
* | ||
* @example | ||
* import eslintPlugin, { | ||
* eslintConfigFromNxProjects, | ||
* } from '@code-pushup/eslint-plugin'; | ||
* | ||
* export default { | ||
* plugins: [ | ||
* await eslintPlugin( | ||
* await eslintConfigFromNxProjects() | ||
* ) | ||
* ] | ||
* } | ||
* | ||
* @returns ESLint config and patterns, intended to be passed to {@link eslintPlugin} | ||
*/ | ||
export async function eslintConfigFromNxProjects(): Promise<ESLintPluginConfig> { | ||
// find Nx projects with lint target | ||
const projectGraph = await createProjectGraphAsync({ exitOnError: false }); | ||
const projectsConfiguration = | ||
readProjectsConfigurationFromProjectGraph(projectGraph); | ||
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 = { | ||
root: true, | ||
overrides: await Promise.all( | ||
projects.map(async project => ({ | ||
files: getLintFilePatterns(project), | ||
extends: | ||
(await findCodePushupEslintrc(project)) ?? getEslintConfig(project), | ||
})), | ||
), | ||
}; | ||
|
||
// include patterns from each project | ||
const patterns = projects.flatMap(project => [ | ||
...getLintFilePatterns(project), | ||
// 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}/*.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 | ||
]); | ||
|
||
return { | ||
eslintrc: eslintConfig, | ||
patterns, | ||
}; | ||
return nxProjectsToConfig(projectGraph); | ||
} |
68 changes: 68 additions & 0 deletions
68
packages/plugin-eslint/src/lib/nx/find-project-with-deps.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { ProjectGraph, createProjectGraphAsync } from '@nx/devkit'; | ||
import type { ESLintPluginConfig } from '../config'; | ||
import { nxProjectsToConfig } from './projects-to-config'; | ||
|
||
/** | ||
* Accepts a target Nx projects, finds projects it depends on, and converts lint configurations to Code PushUp ESLint plugin parameters. | ||
* | ||
* Use when you wish to include a targetted subset of your Nx monorepo in your Code PushUp project. | ||
* If you prefer to include all Nx projects, refer to {@link eslintConfigFromNxProjects} instead. | ||
* | ||
* @example | ||
* import eslintPlugin, { | ||
* eslintConfigFromNxProject, | ||
* } from '@code-pushup/eslint-plugin'; | ||
* | ||
* const projectName = 'backoffice'; // <-- name from project.json | ||
* | ||
* export default { | ||
* plugins: [ | ||
* await eslintPlugin( | ||
* await eslintConfigFromNxProject(projectName) | ||
* ) | ||
* ] | ||
* } | ||
* | ||
* @param projectName Nx project serving as main entry point | ||
* @returns ESLint config and patterns, intended to be passed to {@link eslintPlugin} | ||
*/ | ||
export async function eslintConfigFromNxProject( | ||
projectName: string, | ||
): Promise<ESLintPluginConfig> { | ||
const projectGraph = await createProjectGraphAsync({ exitOnError: false }); | ||
|
||
const dependencies = findAllDependencies(projectName, projectGraph); | ||
|
||
return nxProjectsToConfig( | ||
projectGraph, | ||
project => | ||
!!project.name && | ||
(project.name === projectName || dependencies.has(project.name)), | ||
); | ||
} | ||
|
||
function findAllDependencies( | ||
name: string, | ||
projectGraph: ProjectGraph, | ||
): ReadonlySet<string> { | ||
const results = new Set<string>(); | ||
const queue = [name]; | ||
|
||
// eslint-disable-next-line functional/no-loop-statements | ||
while (queue.length > 0) { | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const source = queue.shift()!; | ||
const dependencies = projectGraph.dependencies[source]; | ||
|
||
// eslint-disable-next-line functional/no-loop-statements | ||
for (const { target } of dependencies ?? []) { | ||
// skip duplicates (cycle in graph) | ||
if (!results.has(target)) { | ||
results.add(target); | ||
queue.push(target); | ||
} | ||
} | ||
} | ||
|
||
return results; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export { eslintConfigFromNxProjects } from './find-all-projects'; | ||
export { eslintConfigFromNxProject } from './find-project-with-deps'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { | ||
ProjectConfiguration, | ||
ProjectGraph, | ||
readProjectsConfigurationFromProjectGraph, | ||
} from '@nx/devkit'; | ||
import type { ESLint } from 'eslint'; | ||
import type { ESLintPluginConfig } from '../config'; | ||
import { | ||
findCodePushupEslintrc, | ||
getEslintConfig, | ||
getLintFilePatterns, | ||
} from './utils'; | ||
|
||
export async function nxProjectsToConfig( | ||
projectGraph: ProjectGraph, | ||
predicate: (project: ProjectConfiguration) => boolean = () => true, | ||
): Promise<ESLintPluginConfig> { | ||
// find Nx projects with lint target | ||
const projectsConfiguration = | ||
readProjectsConfigurationFromProjectGraph(projectGraph); | ||
const projects = Object.values(projectsConfiguration.projects) | ||
.filter(project => 'lint' in (project.targets ?? {})) | ||
.filter(predicate) // apply predicate | ||
.sort((a, b) => a.root.localeCompare(b.root)); | ||
|
||
// create single ESLint config with project-specific overrides | ||
const eslintConfig: ESLint.ConfigData = { | ||
root: true, | ||
overrides: await Promise.all( | ||
projects.map(async project => ({ | ||
files: getLintFilePatterns(project), | ||
extends: | ||
(await findCodePushupEslintrc(project)) ?? getEslintConfig(project), | ||
})), | ||
), | ||
}; | ||
|
||
// include patterns from each project | ||
const patterns = projects.flatMap(project => [ | ||
...getLintFilePatterns(project), | ||
// 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}/*.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 | ||
]); | ||
|
||
return { | ||
eslintrc: eslintConfig, | ||
patterns, | ||
}; | ||
} |