eslint-try-rules 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +47 -0
- package/dist/lint.mjs +112 -0
- package/dist/report-console.mjs +66 -0
- package/dist/report-html.mjs +73 -0
- package/dist/rules.mjs +30 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025- Dieter Oberkofler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# eslint-try-rules
|
|
2
|
+
|
|
3
|
+
`eslint-try-rules` is a CLI tool designed to help developers incrementally adopt stricter ESLint rules. It allows you to test a set of rules against your codebase and generates a report showing which files would fail, without actually modifying your existing ESLint configuration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Test Rules in Isolation:** Run specific rules against your project to see their impact.
|
|
8
|
+
- **Support for JSON and JSONC:** Load rules from standard JSON or JSON files with comments.
|
|
9
|
+
- **Progress Tracking:** Real-time progress bar with ETA during linting.
|
|
10
|
+
- **Detailed Reports:** Generates a colored CLI summary and a comprehensive HTML report.
|
|
11
|
+
- **Flexible Sorting:** Sort results by rule ID or by severity (total errors + warnings).
|
|
12
|
+
- **ESLint 9+ Support:** Built for the new ESLint Flat Config system.
|
|
13
|
+
- **Safe Adoption:** No changes are made to your existing `eslint.config.js`.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
You can run it directly using `npx`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx eslint-try-rules --rules try-rules.json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or install it globally:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g eslint-try-rules
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or as a development dependency in your project:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -D eslint-try-rules
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
Create a `try-rules.json` (or `.jsonc`) file with the rules you want to test:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
42
|
+
"curly": "error"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Run the tool:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
eslint-try-rules --rules try-rules.json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Try the Example
|
|
53
|
+
|
|
54
|
+
You can run an example against this project's own source code:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm run example
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This uses the rules defined in `example/try-rules.jsonc`.
|
|
61
|
+
|
|
62
|
+
### Options
|
|
63
|
+
|
|
64
|
+
- `--rules <path>`: (Required) Path to the JSON/JSONC file containing the rules to test.
|
|
65
|
+
- `--config <path>`: (Optional) Path to your project's ESLint configuration file (e.g., `eslint.config.js`).
|
|
66
|
+
- `--sort <rule|severity>`: (Optional) Sort results by rule ID (default) or by severity (total errors + warnings).
|
|
67
|
+
|
|
68
|
+
## Output
|
|
69
|
+
|
|
70
|
+
The tool provides:
|
|
71
|
+
1. **CLI Report:** A summary of errors and warnings per rule, including file locations.
|
|
72
|
+
2. **HTML Report:** A detailed, interactive HTML report saved as `eslint-incremental-report.html`.
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
### Build
|
|
77
|
+
```bash
|
|
78
|
+
npm run build
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Lint
|
|
82
|
+
```bash
|
|
83
|
+
npm run lint
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Test
|
|
87
|
+
```bash
|
|
88
|
+
npm run test
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runLint } from "./lint.mjs";
|
|
3
|
+
import { generateHtml } from "./report-html.mjs";
|
|
4
|
+
import { printConsoleReport } from "./report-console.mjs";
|
|
5
|
+
import { parseRulesFile } from "./rules.mjs";
|
|
6
|
+
import { writeFileSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { performance } from "node:perf_hooks";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import ansis from "ansis";
|
|
11
|
+
|
|
12
|
+
//#region src/index.ts
|
|
13
|
+
/**
|
|
14
|
+
* Main execution.
|
|
15
|
+
* @returns {Promise<void>}
|
|
16
|
+
*/
|
|
17
|
+
const main = async () => {
|
|
18
|
+
const start = performance.now();
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program.name("eslint-try-rules").description("Try stricter ESLint rules on your codebase.").argument("[patterns...]", "Files/directories/globs to lint.", ["."]).requiredOption("--rules <path>", "Path to a JSON/JSONC file containing the ESLint rules to test.").option("--config <path>", "Path to your project's ESLint configuration file.").option("--sort <type>", "Sort results by \"rule\" (default) or \"severity\" (errors + warnings).", "rule");
|
|
21
|
+
program.parse();
|
|
22
|
+
const options = program.opts();
|
|
23
|
+
const patterns = program.args;
|
|
24
|
+
const cwd = process.cwd();
|
|
25
|
+
if (options.sort !== "rule" && options.sort !== "severity") throw new Error("Invalid sort option. Use \"rule\" or \"severity\".");
|
|
26
|
+
const rules = parseRulesFile(path.resolve(cwd, options.rules));
|
|
27
|
+
console.log(`${ansis.bold.blue("ℹ")} Starting eslint-try-rules for ${ansis.bold(String(Object.keys(rules).length))} rules...\n`);
|
|
28
|
+
const finalResults = await runLint(cwd, rules, patterns, options.config ? path.resolve(cwd, options.config) : void 0);
|
|
29
|
+
printConsoleReport(finalResults, options.sort);
|
|
30
|
+
const html = generateHtml(finalResults);
|
|
31
|
+
const outPath = path.resolve(cwd, "eslint-incremental-report.html");
|
|
32
|
+
writeFileSync(outPath, html, "utf8");
|
|
33
|
+
const durationMs = (performance.now() - start) / 1e3;
|
|
34
|
+
console.log(`${ansis.bold.green("✔")} Report generated in ${ansis.bold(durationMs.toFixed(2))}s: ${ansis.underline(`file://${outPath}`)}`);
|
|
35
|
+
};
|
|
36
|
+
/* v8 ignore start */
|
|
37
|
+
if (process.argv[1] === import.meta.filename || process.argv[1]?.endsWith("index.mjs")) try {
|
|
38
|
+
await main();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof Error) console.error(`\n${ansis.red.bold("✖")} ${error.message}`);
|
|
41
|
+
else console.error(`\n${ansis.red.bold("✖")} Unknown error`, error);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
/* v8 ignore stop */
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { main };
|
package/dist/lint.mjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ESLint } from "eslint";
|
|
3
|
+
import cliProgress from "cli-progress";
|
|
4
|
+
import { glob } from "tinyglobby";
|
|
5
|
+
|
|
6
|
+
//#region src/lint.ts
|
|
7
|
+
/**
|
|
8
|
+
* Creates a rule filter for ESLint.
|
|
9
|
+
* @param {Record<string, unknown>} rules - The rules to filter.
|
|
10
|
+
* @returns {(rule: {ruleId: string}) => boolean} The filter function.
|
|
11
|
+
*/
|
|
12
|
+
const createRuleFilter = (rules) => ({ ruleId }) => Object.prototype.hasOwnProperty.call(rules, ruleId);
|
|
13
|
+
/**
|
|
14
|
+
* Runs ESLint on the codebase with the provided rules.
|
|
15
|
+
* @param {string} cwd - The current working directory.
|
|
16
|
+
* @param {Record<string, unknown>} rules - The rules to test.
|
|
17
|
+
* @param {string[]} patterns - The file patterns to lint.
|
|
18
|
+
* @param {string} [configFile] - Optional path to the ESLint config file.
|
|
19
|
+
* @returns {Promise<RuleResult[]>} The linting results.
|
|
20
|
+
*/
|
|
21
|
+
const runLint = async (cwd, rules, patterns, configFile) => {
|
|
22
|
+
const eslint = new ESLint({
|
|
23
|
+
cwd,
|
|
24
|
+
cache: false,
|
|
25
|
+
overrideConfigFile: configFile,
|
|
26
|
+
overrideConfig: [{ rules }],
|
|
27
|
+
ruleFilter: createRuleFilter(rules)
|
|
28
|
+
});
|
|
29
|
+
console.log("Searching for files...");
|
|
30
|
+
const allFiles = await glob(patterns.map((p) => {
|
|
31
|
+
if (p === ".") return "**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}";
|
|
32
|
+
if (!p.includes("*") && !p.includes("?") && !p.includes("[") && !p.includes("{")) return path.join(p, "**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}");
|
|
33
|
+
return p;
|
|
34
|
+
}), {
|
|
35
|
+
cwd,
|
|
36
|
+
ignore: [
|
|
37
|
+
"**/node_modules/**",
|
|
38
|
+
"**/dist/**",
|
|
39
|
+
"**/coverage/**"
|
|
40
|
+
],
|
|
41
|
+
absolute: true
|
|
42
|
+
});
|
|
43
|
+
const filesToLint = [];
|
|
44
|
+
for (const file of allFiles) if (!await eslint.isPathIgnored(file)) filesToLint.push(file);
|
|
45
|
+
if (filesToLint.length === 0) {
|
|
46
|
+
console.log("No files found to lint.");
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const progressBar = new cliProgress.SingleBar({
|
|
50
|
+
format: "Linting | {bar} | {percentage}% | {value}/{total} Files | ETA: {eta}s",
|
|
51
|
+
barCompleteChar: "█",
|
|
52
|
+
barIncompleteChar: "░",
|
|
53
|
+
hideCursor: true
|
|
54
|
+
});
|
|
55
|
+
progressBar.start(filesToLint.length, 0);
|
|
56
|
+
const allResults = [];
|
|
57
|
+
const chunkSize = 10;
|
|
58
|
+
for (let i = 0; i < filesToLint.length; i += chunkSize) {
|
|
59
|
+
const chunk = filesToLint.slice(i, i + chunkSize);
|
|
60
|
+
const chunkResults = await eslint.lintFiles(chunk);
|
|
61
|
+
allResults.push(...chunkResults);
|
|
62
|
+
progressBar.update(Math.min(i + chunkSize, filesToLint.length));
|
|
63
|
+
}
|
|
64
|
+
progressBar.stop();
|
|
65
|
+
return processResults(cwd, rules, allResults);
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Processes ESLint results into RuleResult format.
|
|
69
|
+
* @param {string} cwd - Current working directory.
|
|
70
|
+
* @param {Record<string, unknown>} rules - Rules tested.
|
|
71
|
+
* @param {ESLint.LintResult[]} results - Results from ESLint.
|
|
72
|
+
* @returns {RuleResult[]} Processed results.
|
|
73
|
+
*/
|
|
74
|
+
const processResults = (cwd, rules, results) => {
|
|
75
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const ruleId of Object.keys(rules)) ruleMap.set(ruleId, {
|
|
77
|
+
ruleId,
|
|
78
|
+
config: rules[ruleId],
|
|
79
|
+
errors: 0,
|
|
80
|
+
warnings: 0,
|
|
81
|
+
fixable: 0,
|
|
82
|
+
details: []
|
|
83
|
+
});
|
|
84
|
+
for (const res of results) for (const msg of res.messages) {
|
|
85
|
+
const rId = msg.ruleId ?? "unknown";
|
|
86
|
+
let rr = ruleMap.get(rId);
|
|
87
|
+
if (!rr) {
|
|
88
|
+
rr = {
|
|
89
|
+
ruleId: rId,
|
|
90
|
+
config: "unknown",
|
|
91
|
+
errors: 0,
|
|
92
|
+
warnings: 0,
|
|
93
|
+
fixable: 0,
|
|
94
|
+
details: []
|
|
95
|
+
};
|
|
96
|
+
ruleMap.set(rId, rr);
|
|
97
|
+
}
|
|
98
|
+
if (msg.severity === 2) rr.errors++;
|
|
99
|
+
else rr.warnings++;
|
|
100
|
+
if (msg.fix !== void 0) rr.fixable++;
|
|
101
|
+
rr.details.push({
|
|
102
|
+
filePath: path.relative(cwd, res.filePath),
|
|
103
|
+
line: msg.line,
|
|
104
|
+
column: msg.column,
|
|
105
|
+
message: msg.message
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return [...ruleMap.values()].filter((r) => r.errors > 0 || r.warnings > 0);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { runLint };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import ansis from "ansis";
|
|
2
|
+
|
|
3
|
+
//#region src/report-console.ts
|
|
4
|
+
/**
|
|
5
|
+
* Sorts rule results by ruleId.
|
|
6
|
+
* @param {RuleResult} a - First result.
|
|
7
|
+
* @param {RuleResult} b - Second result.
|
|
8
|
+
* @returns {number} Comparison result.
|
|
9
|
+
*/
|
|
10
|
+
const sortByRuleId = (a, b) => a.ruleId.localeCompare(b.ruleId);
|
|
11
|
+
/**
|
|
12
|
+
* Sorts rule results by severity (errors + warnings).
|
|
13
|
+
* @param {RuleResult} a - First result.
|
|
14
|
+
* @param {RuleResult} b - Second result.
|
|
15
|
+
* @returns {number} Comparison result.
|
|
16
|
+
*/
|
|
17
|
+
const sortBySeverity = (a, b) => {
|
|
18
|
+
const totalA = a.errors + a.warnings;
|
|
19
|
+
const totalB = b.errors + b.warnings;
|
|
20
|
+
if (totalA !== totalB) return totalB - totalA;
|
|
21
|
+
return sortByRuleId(a, b);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Sorts message details by file path.
|
|
25
|
+
* @param {MessageDetail} a - First detail.
|
|
26
|
+
* @param {MessageDetail} b - Second detail.
|
|
27
|
+
* @returns {number} Comparison result.
|
|
28
|
+
*/
|
|
29
|
+
const sortByFilePath = (a, b) => a.filePath.localeCompare(b.filePath);
|
|
30
|
+
/**
|
|
31
|
+
* Outputs the results to the console.
|
|
32
|
+
* @param {RuleResult[]} results - The results to output.
|
|
33
|
+
* @param {SortOption} sortOption - The sorting criteria.
|
|
34
|
+
*/
|
|
35
|
+
const printConsoleReport = (results, sortOption) => {
|
|
36
|
+
const finalResults = [...results].toSorted((a, b) => {
|
|
37
|
+
if (sortOption === "severity") return sortBySeverity(a, b);
|
|
38
|
+
return sortByRuleId(a, b);
|
|
39
|
+
});
|
|
40
|
+
let totalErrors = 0;
|
|
41
|
+
let totalWarnings = 0;
|
|
42
|
+
let totalFixable = 0;
|
|
43
|
+
console.log("\n" + ansis.bold.cyan("--- CLI Report ---"));
|
|
44
|
+
for (const r of finalResults) {
|
|
45
|
+
totalErrors += r.errors;
|
|
46
|
+
totalWarnings += r.warnings;
|
|
47
|
+
totalFixable += r.fixable;
|
|
48
|
+
const sortedDetails = r.details.toSorted(sortByFilePath);
|
|
49
|
+
const stats = [];
|
|
50
|
+
if (r.errors > 0) stats.push(ansis.red(`${r.errors} errors`));
|
|
51
|
+
if (r.warnings > 0) stats.push(ansis.yellow(`${r.warnings} warnings`));
|
|
52
|
+
if (r.fixable > 0) stats.push(ansis.green(`${r.fixable} fixable`));
|
|
53
|
+
console.log(`\n${ansis.bold(r.ruleId)} (${stats.join(" | ")})`);
|
|
54
|
+
for (const d of sortedDetails) console.log(` ${ansis.dim("->")} ${ansis.blue(d.filePath)}:${ansis.magenta(String(d.line))}:${ansis.magenta(String(d.column))} ${ansis.dim("-")} ${d.message}`);
|
|
55
|
+
}
|
|
56
|
+
console.log("\n" + ansis.cyan("------------------"));
|
|
57
|
+
const totals = [
|
|
58
|
+
ansis.red.bold(`${totalErrors} errors`),
|
|
59
|
+
ansis.yellow.bold(`${totalWarnings} warnings`),
|
|
60
|
+
ansis.green.bold(`${totalFixable} fixable`)
|
|
61
|
+
].join(" | ");
|
|
62
|
+
console.log(`${ansis.bold("Totals")} | ${totals}\n`);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { printConsoleReport };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/report-html.ts
|
|
2
|
+
const TITLE = "ESLint try rules";
|
|
3
|
+
/**
|
|
4
|
+
* Escapes HTML characters.
|
|
5
|
+
* @param {string} str - The string to escape.
|
|
6
|
+
* @returns {string} The escaped string.
|
|
7
|
+
*/
|
|
8
|
+
const escapeHtml = (str) => str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
9
|
+
/**
|
|
10
|
+
* Generates HTML report.
|
|
11
|
+
* @param {RuleResult[]} results - The results to include in the report.
|
|
12
|
+
* @returns {string} The generated HTML.
|
|
13
|
+
*/
|
|
14
|
+
const generateHtml = (results) => {
|
|
15
|
+
let totalErr = 0;
|
|
16
|
+
let totalWarn = 0;
|
|
17
|
+
let totalFix = 0;
|
|
18
|
+
return `<!DOCTYPE html>
|
|
19
|
+
<html lang="en">
|
|
20
|
+
<head>
|
|
21
|
+
<meta charset="UTF-8">
|
|
22
|
+
<title>${TITLE}</title>
|
|
23
|
+
<style>
|
|
24
|
+
body { font-family: sans-serif; margin: 2rem; }
|
|
25
|
+
table { border-collapse: collapse; width: 100%; }
|
|
26
|
+
th, td { border: 1px solid #ddd; padding: 8px; text-align: right; vertical-align: top; }
|
|
27
|
+
th:first-child, td:first-child, th:nth-child(2), td:nth-child(2) { text-align: left; }
|
|
28
|
+
ul { margin: 0; padding-left: 20px; font-size: 0.9em; }
|
|
29
|
+
tfoot { font-weight: bold; background: #eee; }
|
|
30
|
+
</style>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<h1>${TITLE}</h1>
|
|
34
|
+
<table>
|
|
35
|
+
<thead>
|
|
36
|
+
<tr><th>Rule</th><th>Config</th><th>Errors</th><th>Warnings</th><th>Fixable</th></tr>
|
|
37
|
+
</thead>
|
|
38
|
+
<tbody>
|
|
39
|
+
${results.map((r) => {
|
|
40
|
+
totalErr += r.errors;
|
|
41
|
+
totalWarn += r.warnings;
|
|
42
|
+
totalFix += r.fixable;
|
|
43
|
+
const confStr = escapeHtml(JSON.stringify(r.config ?? "N/A"));
|
|
44
|
+
let detailsRow = "";
|
|
45
|
+
let toggleIcon = "<span style=\"color:#ccc;\">▶</span>";
|
|
46
|
+
if (r.details.length > 0) {
|
|
47
|
+
detailsRow = `<tr style="display:none;background:#f9f9f9;"><td colspan="5"><ul>${r.details.map((d) => `<li><code>${escapeHtml(d.filePath)}:${d.line}:${d.column}</code> - ${escapeHtml(d.message)}</li>`).join("")}</ul></td></tr>`;
|
|
48
|
+
toggleIcon = `<span style="cursor:pointer;" onclick="const r=this.closest('tr').nextElementSibling;r.style.display=r.style.display==='none'?'table-row':'none';this.innerHTML=r.style.display==='none'?'▶':'▼'">▶</span>`;
|
|
49
|
+
}
|
|
50
|
+
return `<tr>
|
|
51
|
+
<td>${toggleIcon} ${escapeHtml(r.ruleId)}</td>
|
|
52
|
+
<td><code>${confStr}</code></td>
|
|
53
|
+
<td>${r.errors}</td>
|
|
54
|
+
<td>${r.warnings}</td>
|
|
55
|
+
<td>${r.fixable}</td>
|
|
56
|
+
</tr>\n\t\t\t\t${detailsRow}`;
|
|
57
|
+
}).join("\n ")}
|
|
58
|
+
</tbody>
|
|
59
|
+
<tfoot>
|
|
60
|
+
<tr>
|
|
61
|
+
<td colspan="2">Totals</td>
|
|
62
|
+
<td>${totalErr}</td>
|
|
63
|
+
<td>${totalWarn}</td>
|
|
64
|
+
<td>${totalFix}</td>
|
|
65
|
+
</tr>
|
|
66
|
+
</tfoot>
|
|
67
|
+
</table>
|
|
68
|
+
</body>
|
|
69
|
+
</html>`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { generateHtml };
|
package/dist/rules.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import stripJsonComments from "strip-json-comments";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/rules.ts
|
|
7
|
+
/**
|
|
8
|
+
* The Zod schema for the rules file.
|
|
9
|
+
*/
|
|
10
|
+
const RulesSchema = z.record(z.string(), z.unknown());
|
|
11
|
+
/**
|
|
12
|
+
* Parses the rules file.
|
|
13
|
+
* @param {string} filePath - Path to the rules file (JSON or JSONC).
|
|
14
|
+
* @returns {Record<string, unknown>} The parsed rules.
|
|
15
|
+
* @throws {Error} If the file cannot be read or parsed.
|
|
16
|
+
*/
|
|
17
|
+
const parseRulesFile = (filePath) => {
|
|
18
|
+
try {
|
|
19
|
+
const content = readFileSync(path.resolve(filePath), "utf8");
|
|
20
|
+
const json = JSON.parse(stripJsonComments(content));
|
|
21
|
+
return RulesSchema.parse(json);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error instanceof z.ZodError) throw new Error(`Invalid rules format: ${error.message}`);
|
|
24
|
+
if (error instanceof SyntaxError) throw new Error(`Failed to parse rules JSON: ${error.message}`);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { parseRulesFile };
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-try-rules",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Dieter Oberkofler",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"keywords": [
|
|
11
|
+
"eslint",
|
|
12
|
+
"rules"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"eslint-try-rules": "./dist/index.mjs"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "tsdown --watch",
|
|
19
|
+
"build": "tsdown",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"lint": "eslint --no-cache . && tsc && prettier --check .",
|
|
22
|
+
"test": "vitest run --coverage",
|
|
23
|
+
"example": "tsx src/index.ts src --rules example/try-rules.jsonc",
|
|
24
|
+
"ci": "npm run lint && npm run build && npm run test",
|
|
25
|
+
"create-changelog": "conventional-changelog --release-count=0",
|
|
26
|
+
"pack-local": "rm -f *.tgz && npm run build && npm pack"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.mjs",
|
|
29
|
+
"module": "./dist/index.mjs",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./dist/index.mjs",
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.12.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git://github.com/doberkofler/eslint-try-rules.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/doberkofler/eslint-try-rules#readme",
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@eslint/js": "^9.31.0",
|
|
45
|
+
"@types/node": "^25.3.0",
|
|
46
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
47
|
+
"@vitest/eslint-plugin": "^1.6.9",
|
|
48
|
+
"conventional-changelog": "^7.1.1",
|
|
49
|
+
"eslint": "^9.31.0",
|
|
50
|
+
"eslint-plugin-jsdoc": "^62.6.1",
|
|
51
|
+
"eslint-plugin-regexp": "^3.0.0",
|
|
52
|
+
"eslint-plugin-unicorn": "^63.0.0",
|
|
53
|
+
"globals": "^17.3.0",
|
|
54
|
+
"prettier": "^3.8.1",
|
|
55
|
+
"tsdown": "^0.20.3",
|
|
56
|
+
"tsx": "^4.21.0",
|
|
57
|
+
"typescript": "^5.9.3",
|
|
58
|
+
"typescript-eslint": "^8.56.0",
|
|
59
|
+
"vitest": "^4.0.18"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@types/cli-progress": "^3.11.6",
|
|
63
|
+
"ansis": "^4.2.0",
|
|
64
|
+
"cli-progress": "^3.12.0",
|
|
65
|
+
"commander": "^14.0.3",
|
|
66
|
+
"strip-json-comments": "^5.0.3",
|
|
67
|
+
"tinyglobby": "^0.2.15",
|
|
68
|
+
"zod": "^4.3.6"
|
|
69
|
+
}
|
|
70
|
+
}
|