Skip to content

Commit eb8a7ef

Browse files
committed
feat: add custom formatter
1 parent 8de7b2b commit eb8a7ef

File tree

4 files changed

+246
-3
lines changed

4 files changed

+246
-3
lines changed

nx.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,16 @@
5050
"cache": true,
5151
"options": {
5252
"errorOnUnmatchedPattern": false,
53-
"format": "json",
53+
"format": "./tools/eslint-programmatic-formatter.cjs",
5454
"outputFile": "{projectRoot}/.code-pushup/eslint/eslint-report.json",
5555
"maxWarnings": 0,
5656
"lintFilePatterns": [
5757
"{projectRoot}/**/*.ts",
5858
"{projectRoot}/package.json"
5959
]
60+
},
61+
"env": {
62+
"ESLINT_EXTRA_FORMATS": "{\"formatters\":[{\"name\":\"stylish\",\"output\":\"console\"},{\"name\":\"json\",\"output\":\"file\",\"path\":\"eslint-report.json\",\"options\":{\"pretty\":false}}],\"globalOptions\":{\"verbose\":false,\"timestamp\":false}}"
6063
}
6164
},
6265
"nxv-pkg-install": {

packages/plugin-eslint/src/lib/runner/lint.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ async function executeLint({
4646
...(eslintrc ? [`--config=${filePathToCliArg(eslintrc)}`] : []),
4747
...(typeof eslintrc === 'object' ? ['--no-eslintrc'] : []),
4848
'--no-error-on-unmatched-pattern',
49-
'--format=json',
50-
`--output-file=${reportOutputPath}`,
49+
'--format=../../../tools/eslint-programmatic-formatter.cjs',
5150
...toArray(patterns).map(pattern =>
5251
// globs need to be escaped on Unix
5352
platform() === 'win32' ? pattern : `'${pattern}'`,

tools/eslint-config-examples.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# ESLint Formatter Configuration Examples
2+
3+
## JSON Configuration Format
4+
5+
The `ESLINT_EXTRA_FORMATS` environment variable now supports JSON configuration for advanced formatting options.
6+
7+
### Basic JSON Configuration
8+
9+
```json
10+
{
11+
"formatters": [
12+
{
13+
"name": "stylish",
14+
"output": "console"
15+
},
16+
{
17+
"name": "json",
18+
"output": "file",
19+
"path": "custom-eslint-report.json",
20+
"options": {
21+
"pretty": true
22+
}
23+
}
24+
],
25+
"globalOptions": {
26+
"verbose": true,
27+
"timestamp": true,
28+
"showProgress": false
29+
}
30+
}
31+
```
32+
33+
### Advanced Configuration
34+
35+
```json
36+
{
37+
"formatters": [
38+
{
39+
"name": "stylish",
40+
"output": "console"
41+
},
42+
{
43+
"name": "json",
44+
"output": "file",
45+
"path": "reports/eslint-detailed.json",
46+
"options": {
47+
"pretty": true
48+
}
49+
},
50+
{
51+
"name": "json",
52+
"output": "file",
53+
"path": "reports/eslint-compact.json",
54+
"options": {
55+
"pretty": false
56+
}
57+
}
58+
],
59+
"globalOptions": {
60+
"verbose": true,
61+
"timestamp": true
62+
}
63+
}
64+
```
65+
66+
## Usage Examples
67+
68+
### Using JSON Configuration
69+
70+
```bash
71+
# One-liner (escape quotes properly)
72+
ESLINT_EXTRA_FORMATS='{"formatters":[{"name":"stylish","output":"console"},{"name":"json","output":"file","path":"custom-report.json","options":{"pretty":true}}],"globalOptions":{"verbose":true,"timestamp":true}}' npx nx run utils:lint
73+
74+
# From file
75+
ESLINT_EXTRA_FORMATS="$(cat eslint-config.json)" npx nx run utils:lint
76+
```
77+
78+
### Backwards Compatibility (Comma-separated)
79+
80+
```bash
81+
# Still works for simple cases
82+
ESLINT_EXTRA_FORMATS="stylish" npx nx run utils:lint
83+
```
84+
85+
## Configuration Schema
86+
87+
### Formatter Object
88+
89+
- `name` (string): ESLint formatter name (e.g., "stylish", "json", "checkstyle")
90+
- `output` (string): "console" or "file"
91+
- `path` (string, optional): File path for "file" output
92+
- `options` (object, optional): Formatter-specific options
93+
- `pretty` (boolean): Pretty-print JSON output
94+
95+
### Global Options
96+
97+
- `verbose` (boolean): Show detailed logging
98+
- `timestamp` (boolean): Show execution timestamp
99+
- `showProgress` (boolean): Show progress information
100+
101+
## Default Behavior
102+
103+
Without `ESLINT_EXTRA_FORMATS`, the formatter will:
104+
105+
1. Output stylish format to console
106+
2. Generate JSON file for Nx at `eslint-report.json`
107+
3. Return JSON data to Nx for the configured `outputFile`
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Minimal ESLint multiple formatter for Nx
2+
const { writeFileSync } = require('fs');
3+
const { dirname, resolve } = require('path');
4+
const { sync: mkdirp } = require('mkdirp');
5+
const { ESLint } = require('eslint');
6+
7+
const eslint = new ESLint();
8+
9+
module.exports = async function (results, context) {
10+
// Default configuration
11+
let config = {
12+
formatters: [
13+
{
14+
name: 'stylish',
15+
output: 'console',
16+
},
17+
],
18+
globalOptions: {
19+
verbose: false,
20+
timestamp: false,
21+
showProgress: false,
22+
},
23+
};
24+
25+
// Parse ESLINT_EXTRA_FORMATS - support both JSON and comma-separated formats
26+
if (process.env.ESLINT_EXTRA_FORMATS) {
27+
const extraFormatsValue = process.env.ESLINT_EXTRA_FORMATS.trim();
28+
29+
try {
30+
// Try to parse as JSON first
31+
const jsonConfig = JSON.parse(extraFormatsValue);
32+
33+
// Merge with default config
34+
if (jsonConfig.formatters) {
35+
// Replace default formatters with JSON config formatters
36+
config.formatters = [...jsonConfig.formatters];
37+
}
38+
39+
if (jsonConfig.globalOptions) {
40+
config.globalOptions = {
41+
...config.globalOptions,
42+
...jsonConfig.globalOptions,
43+
};
44+
}
45+
46+
// Handle additional JSON properties
47+
if (jsonConfig.verbose !== undefined)
48+
config.globalOptions.verbose = jsonConfig.verbose;
49+
if (jsonConfig.timestamp !== undefined)
50+
config.globalOptions.timestamp = jsonConfig.timestamp;
51+
if (jsonConfig.showProgress !== undefined)
52+
config.globalOptions.showProgress = jsonConfig.showProgress;
53+
} catch (error) {
54+
// Fallback to comma-separated format for backwards compatibility
55+
const extraFormats = extraFormatsValue.split(',');
56+
for (const format of extraFormats) {
57+
const trimmedFormat = format.trim();
58+
if (
59+
trimmedFormat &&
60+
!config.formatters.some(f => f.name === trimmedFormat)
61+
) {
62+
config.formatters.push({
63+
name: trimmedFormat,
64+
output: 'console',
65+
});
66+
}
67+
}
68+
}
69+
}
70+
71+
// Always ensure JSON formatter for Nx (unless already configured)
72+
if (!config.formatters.some(f => f.name === 'json')) {
73+
config.formatters.push({
74+
name: 'json',
75+
output: 'file',
76+
path: 'eslint-report.json',
77+
});
78+
}
79+
80+
// Apply global options
81+
if (config.globalOptions.timestamp) {
82+
console.log(`ESLint run at: ${new Date().toISOString()}`);
83+
}
84+
85+
let jsonResult = '';
86+
87+
for (const formatterConfig of config.formatters) {
88+
if (config.globalOptions.verbose) {
89+
console.log(`Using formatter: ${formatterConfig.name}`);
90+
}
91+
92+
const formatter = await eslint.loadFormatter(formatterConfig.name);
93+
let formatterResult = formatter.format(results);
94+
95+
// Apply formatter-specific options
96+
if (
97+
formatterConfig.options &&
98+
formatterConfig.name === 'json' &&
99+
formatterConfig.options.pretty
100+
) {
101+
try {
102+
const parsed = JSON.parse(formatterResult);
103+
formatterResult = JSON.stringify(parsed, null, 2);
104+
} catch (e) {
105+
// Keep original if parsing fails
106+
}
107+
}
108+
109+
if (formatterConfig.output === 'console') {
110+
console.log(formatterResult);
111+
} else if (formatterConfig.output === 'file') {
112+
const filePath = resolve(process.cwd(), formatterConfig.path);
113+
try {
114+
mkdirp(dirname(filePath));
115+
writeFileSync(filePath, formatterResult);
116+
117+
if (config.globalOptions.verbose) {
118+
console.log(`Written ${formatterConfig.name} output to: ${filePath}`);
119+
}
120+
} catch (ex) {
121+
console.error('Error writing output file:', ex.message);
122+
return false;
123+
}
124+
}
125+
126+
// Store JSON result for Nx output file handling
127+
if (formatterConfig.name === 'json') {
128+
jsonResult = formatterResult;
129+
}
130+
}
131+
132+
// Return JSON result for Nx to write to outputFile
133+
return jsonResult || JSON.stringify(results);
134+
};

0 commit comments

Comments
 (0)