Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import eslintPlugin from '@code-pushup/eslint-plugin';

export default {
plugins: [
await eslintPlugin(
{ patterns: ['src/*.js'] },
{
artifacts: {
generateArtifactsCommand:
"npx eslint 'src/*.js' --fix --format json --output-file eslint-report.json",
artifactsPaths: ['./code-pushup/eslint-report.json'],
Comment thread
matejchalk marked this conversation as resolved.
Outdated
},
},
),
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** @type {import('eslint').Linter.Config[]} */
module.exports = [
{
ignores: ['code-pushup.config.ts'],
},
{
rules: {
eqeqeq: 'error',
'max-lines': ['warn', 100],
'no-unused-vars': 'warn',
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function unusedFn() {
return '42';
}

module.exports = function orwell() {
if (2 + 2 == 5) {
console.log(1984);
}
};
88 changes: 88 additions & 0 deletions e2e/plugin-eslint-e2e/tests/__snapshots__/collect.e2e.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,91 @@ exports[`PLUGIN collect report with eslint-plugin NPM package > should run ESLin
],
}
`;

exports[`PLUGIN collect report with eslint-plugin NPM package > should run ESLint plugin with artifacts options 1`] = `
{
"packageName": "@code-pushup/core",
"plugins": [
{
"audits": [
{
"description": "ESLint rule **eqeqeq**.",
"details": {
"issues": [],
},
"displayValue": "passed",
"docsUrl": "https://eslint.org/docs/latest/rules/eqeqeq",
"score": 1,
"slug": "eqeqeq",
"title": "Require the use of \`===\` and \`!==\`",
"value": 0,
},
{
"description": "ESLint rule **max-lines**.

Custom options:

\`\`\`json
100
\`\`\`",
"details": {
"issues": [],
},
"displayValue": "passed",
"docsUrl": "https://eslint.org/docs/latest/rules/max-lines",
"score": 1,
"slug": "max-lines-71b54366cb01f77b",
"title": "Enforce a maximum number of lines per file",
"value": 0,
},
{
"description": "ESLint rule **no-unused-vars**.",
"details": {
"issues": [],
},
"displayValue": "passed",
"docsUrl": "https://eslint.org/docs/latest/rules/no-unused-vars",
"score": 1,
"slug": "no-unused-vars",
"title": "Disallow unused variables",
"value": 0,
},
Comment thread
BioPhoton marked this conversation as resolved.
],
"description": "Official Code PushUp ESLint plugin",
"docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin",
"groups": [
{
"description": "Code that either will cause an error or may cause confusing behavior. Developers should consider this a high priority to resolve.",
"refs": [
{
"slug": "no-unused-vars",
"weight": 1,
},
],
"slug": "problems",
"title": "Problems",
},
{
"description": "Something that could be done in a better way but no errors will occur if the code isn't changed.",
"refs": [
{
"slug": "eqeqeq",
"weight": 1,
},
{
"slug": "max-lines-71b54366cb01f77b",
"weight": 1,
},
],
"slug": "suggestions",
"title": "Suggestions",
},
],
"icon": "eslint",
"packageName": "@code-pushup/eslint-plugin",
"slug": "eslint",
"title": "ESLint",
},
],
}
`;
28 changes: 28 additions & 0 deletions e2e/plugin-eslint-e2e/tests/collect.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('PLUGIN collect report with eslint-plugin NPM package', () => {
);
const fixturesFlatConfigDir = path.join(fixturesDir, 'flat-config');
const fixturesLegacyConfigDir = path.join(fixturesDir, 'legacy-config');
const fixturesArtifactsConfigDir = path.join(fixturesDir, 'artifacts-config');

const envRoot = path.join(
E2E_ENVIRONMENTS_DIR,
Expand All @@ -28,22 +29,32 @@ describe('PLUGIN collect report with eslint-plugin NPM package', () => {
);
const flatConfigDir = path.join(envRoot, 'flat-config');
const legacyConfigDir = path.join(envRoot, 'legacy-config');
const artifactsConfigDir = path.join(envRoot, 'artifacts-config');
const flatConfigOutputDir = path.join(flatConfigDir, '.code-pushup');
const legacyConfigOutputDir = path.join(legacyConfigDir, '.code-pushup');
const artifactsConfigOutputDir = path.join(
artifactsConfigDir,
'.code-pushup',
);

beforeAll(async () => {
await cp(fixturesFlatConfigDir, flatConfigDir, { recursive: true });
await cp(fixturesLegacyConfigDir, legacyConfigDir, { recursive: true });
await cp(fixturesArtifactsConfigDir, artifactsConfigDir, {
recursive: true,
});
});

afterAll(async () => {
await teardownTestFolder(flatConfigDir);
await teardownTestFolder(legacyConfigDir);
await teardownTestFolder(artifactsConfigDir);
});

afterEach(async () => {
await teardownTestFolder(flatConfigOutputDir);
await teardownTestFolder(legacyConfigOutputDir);
await teardownTestFolder(artifactsConfigOutputDir);
});

it('should run ESLint plugin for flat config and create report.json', async () => {
Expand Down Expand Up @@ -80,4 +91,21 @@ describe('PLUGIN collect report with eslint-plugin NPM package', () => {
expect(() => reportSchema.parse(report)).not.toThrow();
expect(omitVariableReportData(report as Report)).toMatchSnapshot();
});

it('should run ESLint plugin with artifacts options', async () => {
const { code } = await executeProcess({
command: 'npx',
args: ['@code-pushup/cli', 'collect', '--no-progress'],
cwd: artifactsConfigDir,
});

expect(code).toBe(0);

const report = await readJsonFile(
path.join(artifactsConfigOutputDir, 'report.json'),
);

expect(() => reportSchema.parse(report)).not.toThrow();
expect(omitVariableReportData(report as Report)).toMatchSnapshot();
});
});
2 changes: 1 addition & 1 deletion nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@
},
"code-pushup-eslint": {
"cache": true,
"inputs": ["code-pushup-inputs", "lint-eslint-inputs"],
Comment thread
BioPhoton marked this conversation as resolved.
Outdated
"inputs": ["code-pushup-inputs"],
"outputs": ["{projectRoot}/.code-pushup/eslint/runner-output.json"],
"executor": "nx:run-commands",
"options": {
Expand Down
145 changes: 144 additions & 1 deletion packages/plugin-eslint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,86 @@ Detected ESLint rules are mapped to Code PushUp audits. Audit reports are calcul

5. Run the CLI with `npx code-pushup collect` and view or upload report (refer to [CLI docs](../cli/README.md)).

## Artifacts generation and loading
Comment thread
matejchalk marked this conversation as resolved.
Outdated

In addition to running ESLint from the plugin implementation, you can configure the plugin to consume pre-generated ESLint reports (artifacts). This is particularly useful for:

- **CI/CD pipelines**: Use cached lint results from your build system
- **Monorepo setups**: Aggregate results from multiple projects or targets
- **Performance optimization**: Skip ESLint execution when reports are already available
- **Custom workflows**: Integrate with existing linting infrastructure

The artifacts feature supports loading ESLint JSON reports that follow the standard `ESLint.LintResult[]` format.

### Basic artifact configuration

Specify the path(s) to your ESLint JSON report files:

```js
import eslintPlugin from '@code-pushup/eslint-plugin';

export default {
plugins: [
await eslintPlugin({
artifacts: {
artifactsPaths: './eslint-report.json',
},
}),
],
};
```

### Multiple artifact files

Use glob patterns to aggregate results from multiple files:

```js
export default {
plugins: [
await eslintPlugin({
artifacts: {
artifactsPaths: ['packages/**/eslint-report.json', 'apps/**/.eslint/*.json'],
},
}),
],
};
```

### Generate artifacts with custom command

If you need to generate the artifacts before loading them, use the `generateArtifactsCommand` option:

```js
export default {
plugins: [
await eslintPlugin({
artifacts: {
generateArtifactsCommand: 'npm run lint:report',
artifactsPaths: './eslint-report.json',
},
}),
],
};
```

You can also specify the command with arguments:

```js
export default {
plugins: [
await eslintPlugin({
artifacts: {
generateArtifactsCommand: {
command: 'eslint',
args: ['src/**/*.{js,ts}', '--format=json', '--output-file=eslint-report.json'],
},
artifactsPaths: './eslint-report.json',
},
}),
],
};
```

### Custom groups

You can extend the plugin configuration with custom groups to categorize ESLint rules according to your project's specific needs. Custom groups allow you to assign weights to individual rules, influencing their impact on the report. Rules can be defined as an object with explicit weights or as an array where each rule defaults to a weight of 1. Additionally, you can use wildcard patterns (`*`) to include multiple rules with similar prefixes.
Expand Down Expand Up @@ -228,4 +308,67 @@ export default {

## Nx Monorepo Setup

Find all details in our [Nx setup guide](https://github.com/code-pushup/cli/wiki/Code-PushUp-integration-guide-for-Nx-monorepos#eslint-config).
### Caching artifact generation

To leverage Nx's caching capabilities, you need to generate a JSON artifact for caching while still being able to see the eslint violations in the terminal, to fix them.
Comment thread
BioPhoton marked this conversation as resolved.
Outdated
This can be done by leveraging eslint formatter.

_lint target from nx.json_

```json
{
"lint": {
"inputs": ["lint-eslint-inputs"],
"outputs": ["{projectRoot}/.eslint/**/*"],
"cache": true,
"executor": "nx:run-commands",
"options": {
"command": "eslint",
"args": ["{projectRoot}/**/*.ts", "{projectRoot}/package.json", "--config={projectRoot}/eslint.config.js", "--max-warnings=0", "--no-warn-ignored", "--error-on-unmatched-pattern=false", "--format=@my-org/eslint-formatter-special/src/index.js"],
"env": {
"ESLINT_FORMATTER_CONFIG": "{\"outputDir\":\"{projectRoot}/.eslint\"}"
}
}
}
}
```
Comment thread
matejchalk marked this conversation as resolved.

As you can now generate the `eslint-report.json` from cache your plugin configuration can directly consume them.

_code-pushup.config.ts target from nx.json_

```jsonc
{
"code-pushup": {
"dependsOn": ["lint"],
// also multiple targets can be merged into one report
// "dependsOn": ["lint", "lint-next"],
"executor": "nx:run-commands",
"options": {
"command": "npx code-pushup",
},
},
}
```

and the project configuration leverages `dependsOn` to ensure the artefacts are generated when running code-pushup.

Your `code-pushup.config.ts` can then be configured to consume the cached artifacts:

```js
import eslintPlugin from '@code-pushup/eslint-plugin';

export default {
plugins: [
await eslintPlugin({
artifacts: {
artifactsPaths: 'packages/**/.eslint/eslint-report-*.json',
},
}),
],
};
```

---

Find more details in our [Nx setup guide](https://github.com/code-pushup/cli/wiki/Code-PushUp-integration-guide-for-Nx-monorepos#eslint-config).