@vhite/vhite 0.1.0 → 0.6.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.
Files changed (51) hide show
  1. package/README.md +117 -22
  2. package/bin/cli.js +43 -200
  3. package/bin/commands/baseline.js +27 -0
  4. package/bin/commands/hook.js +58 -0
  5. package/bin/commands/info.js +39 -0
  6. package/bin/commands/scan.js +206 -0
  7. package/bin/commands/shared.js +67 -0
  8. package/bin/commands/watch.js +44 -0
  9. package/bin/output/text.js +40 -0
  10. package/package.json +32 -5
  11. package/rules/a11y/a11y-click-div.js +39 -0
  12. package/rules/a11y/a11y-img-alt.js +36 -0
  13. package/rules/a11y/a11y-input-label.js +40 -0
  14. package/rules/{console-log.js → core/console-log.js} +2 -1
  15. package/rules/{debugger-statement.js → core/debugger-statement.js} +2 -1
  16. package/rules/{empty-block.js → core/empty-block.js} +2 -1
  17. package/rules/{empty-catch.js → core/empty-catch.js} +2 -1
  18. package/rules/{hardcoded-secret.js → core/hardcoded-secret.js} +9 -4
  19. package/rules/{leftover-todo.js → core/leftover-todo.js} +6 -2
  20. package/rules/{unused-import.js → core/unused-import.js} +3 -1
  21. package/rules/index.js +18 -8
  22. package/rules/react/react-dangerous-html.js +30 -0
  23. package/rules/react/react-missing-key.js +56 -0
  24. package/rules/shared/jsx-helpers.js +30 -0
  25. package/src/core/args.js +134 -0
  26. package/src/core/config.js +92 -0
  27. package/src/core/engine.js +144 -0
  28. package/src/core/exit-codes.js +15 -0
  29. package/src/{setup.js → core/setup.js} +1 -1
  30. package/src/core/version.js +6 -0
  31. package/src/{safe-fix.js → fix/safe-fix.js} +1 -1
  32. package/src/output/explanations-frontend.js +22 -0
  33. package/src/{explanations.js → output/explanations.js} +2 -0
  34. package/src/output/json-output.js +47 -0
  35. package/src/{reporter.js → output/reporter.js} +20 -1
  36. package/src/{run-report.js → output/run-report.js} +1 -1
  37. package/src/output/sarif.js +60 -0
  38. package/src/scan/baseline.js +60 -0
  39. package/src/scan/git.js +28 -0
  40. package/src/scan/ignore.js +68 -0
  41. package/src/scan/suppress.js +53 -0
  42. package/src/config.js +0 -42
  43. package/src/engine.js +0 -80
  44. /package/src/{files.js → core/files.js} +0 -0
  45. /package/src/{approve.js → fix/approve.js} +0 -0
  46. /package/src/{backup.js → fix/backup.js} +0 -0
  47. /package/src/{fixers → fix/fixers}/debugger-statement.js +0 -0
  48. /package/src/{fixers → fix/fixers}/index.js +0 -0
  49. /package/src/{fixers → fix/fixers}/leftover-todo.js +0 -0
  50. /package/src/{fixers → fix/fixers}/unused-import.js +0 -0
  51. /package/src/{gitignore.js → fix/gitignore.js} +0 -0
package/README.md CHANGED
@@ -1,32 +1,101 @@
1
1
  # @vhite/vhite
2
2
 
3
- vhite scans front-end code for common problems left behind by AI code generators, explains each one in plain English, and can safely fix some of them.
3
+ vhite scans front-end code for problems commonly left behind by AI code generators. It explains each finding in plain English, and it can safely fix some of them.
4
4
 
5
5
  ## Install and run
6
6
 
7
- Run it on any project folder without installing it:
7
+ Run it on any project without installing it:
8
8
 
9
9
  npx @vhite/vhite path/to/your/project
10
10
 
11
- Or run it on the current folder:
11
+ Or on the current folder:
12
12
 
13
- npx @vhite/vhite .
13
+ npx @vhite/vhite
14
+
15
+ To install it once and use a short command:
16
+
17
+ npm install -g @vhite/vhite
18
+ vhite path/to/your/project
19
+
20
+ ## Commands
21
+
22
+ vhite [path] Scan a folder (the default command)
23
+ vhite scan [path] Scan and report
24
+ vhite fix [path] Scan, then choose which fixes to apply
25
+ vhite baseline [path] Save current problems so only new ones are reported
26
+ vhite rules List all rules
27
+ vhite explain <rule> Explain one rule
28
+ vhite version Show the version
29
+
30
+ ## Options
31
+
32
+ --format text|json Output format (default: text)
33
+ --quiet, -q Print only errors; the exit code still applies
34
+ --fix Apply all safe fixes without asking, then rescan
35
+ --changed Scan only files changed since the last commit (Git)
36
+ --staged Scan only files staged for commit (Git)
37
+ --no-baseline Ignore vhite.baseline.json for this run
38
+ --rule <name> Run only this rule (repeatable)
39
+ --disable <name> Turn off a rule (repeatable)
40
+ --include <path> Scan only files under this path (repeatable)
41
+ --exclude <path> Skip files under this path (repeatable)
42
+ --help, -h Show help
43
+
44
+ ## Exit codes
45
+
46
+ 0 no problems
47
+ 1 problems found (errors or warnings; info notes do not count)
48
+ 2 configuration or runtime error
49
+ 3 fixes applied and no problems remain
50
+ 4 fixes were rolled back
51
+
52
+ Use these in CI. For example, a build fails when the exit code is 1 or higher.
14
53
 
15
54
  ## What it checks
16
55
 
17
- - **Debugger statements:** `debugger` lines that pause the app when developer tools are open.
18
- - **console.log calls:** logging left over from debugging.
19
- - **Empty catch blocks:** errors that are caught and silently ignored.
20
- - **Unused imports:** imports the file never uses.
21
- - **Empty blocks:** `if`, `else`, and loop bodies with no code.
22
- - **Hardcoded secrets:** passwords, API keys, and tokens written directly in the code.
23
- - **Comments:** every comment is flagged. TODO and FIXME notes get their own message.
56
+ JavaScript and JSX files (`.js`, `.jsx`, `.mjs`, `.cjs`):
57
+
58
+ - **debugger-statement:** `debugger` lines that pause the app when developer tools are open.
59
+ - **console-log:** `console.log` calls left over from debugging.
60
+ - **empty-catch:** errors that are caught and silently ignored.
61
+ - **unused-import:** imports the file never uses.
62
+ - **empty-block:** `if`, `else`, and loop bodies with no code.
63
+ - **hardcoded-secret:** passwords, API keys, and tokens written directly in the code.
64
+ - **leftover-todo:** comments. TODO and FIXME notes are reported separately from other comments.
65
+ - **react-missing-key:** list items created with `.map()` that have no `key`.
66
+ - **react-dangerous-html:** uses of `dangerouslySetInnerHTML`.
67
+ - **a11y-img-alt:** images with no `alt` attribute.
68
+ - **a11y-input-label:** form inputs with no label or accessible name.
69
+ - **a11y-click-div:** clickable `div` or `span` elements with no `role`.
24
70
 
25
- Supported file types: `.js`, `.jsx`, `.mjs`, `.cjs`, `.html`, and `.css`. Some checks only apply to JavaScript files.
71
+ HTML and CSS files (`.html`, `.css`) are checked for hardcoded secrets only.
72
+
73
+ Run `vhite rules` for the full list, with each rule's default severity and whether it has an automatic fix.
74
+
75
+ ## Severity
76
+
77
+ Each rule has a severity: `error`, `warning`, or `info`. Errors and warnings count toward the exit code. Info notes are shown but don't fail a run.
78
+
79
+ Change a rule's severity in `vhite.config.json`:
80
+
81
+ {
82
+ "rules": {
83
+ "console-log": "error",
84
+ "leftover-todo": "off"
85
+ }
86
+ }
87
+
88
+ Use `"off"` to turn a rule off. You can also turn rules off with a list:
89
+
90
+ {
91
+ "disabledRules": ["console-log"]
92
+ }
93
+
94
+ Unknown rule names are reported as warnings, and the rest of the config still applies.
26
95
 
27
96
  ## Safe fixes
28
97
 
29
- Some problems can be fixed automatically: debugger statements, unused imports, and comments. vhite never changes a file without asking first.
98
+ Some problems can be fixed automatically: debugger statements, unused imports, and comments. vhite never changes a file without your approval.
30
99
 
31
100
  1. It scans the project and lists the problems it found.
32
101
  2. It lists the fixes it can make and asks which ones to apply. Enter numbers such as `1,3`, type `all`, or press Enter to skip.
@@ -34,23 +103,49 @@ Some problems can be fixed automatically: debugger statements, unused imports, a
34
103
  4. After each change, it scans the file again. If the change created a new problem or didn't fix the issue, the original is restored automatically.
35
104
  5. At the end, it can ask whether to delete the backups from this run and whether to create a Markdown report in `.vhite-fix/reports/`.
36
105
 
37
- The first time a fix is applied, vhite adds `.vhite-fix/` to your `.gitignore`, so backups and reports stay out of your repository.
106
+ The first time a fix is applied, vhite adds `.vhite-fix/` to your `.gitignore`.
38
107
 
39
- Fixes only run in an interactive terminal. In scripts or CI, vhite only reports problems.
108
+ For scripts and CI, use `--fix` to apply all safe fixes without asking. `vhite fix` requires an interactive terminal unless `--fix` is given.
40
109
 
41
- ## Settings
110
+ ## Baselines
42
111
 
43
- Create a `vhite.config.json` file in your project to turn off specific checks:
112
+ Projects that already have problems can start with a baseline:
44
113
 
45
- {
46
- "disabledRules": ["console-log", "leftover-todo"]
47
- }
114
+ vhite baseline
115
+
116
+ This saves the current findings to `vhite.baseline.json`. Later scans hide those known findings and report only new ones. A finding stays hidden even if its line number moves. Commit this file so your whole team shares the same baseline, and use `--no-baseline` to see everything.
117
+
118
+ ## Ignoring a finding
119
+
120
+ Add a comment to skip a finding you've reviewed:
121
+
122
+ // vhite-disable-next-line console-log
123
+ console.log("kept on purpose");
124
+
125
+ console.log("kept on purpose"); // vhite-disable-line console-log
126
+
127
+ Leave out the rule name to skip every rule on that line.
128
+
129
+ ## Scanning only changed files
130
+
131
+ vhite --changed # files changed since the last commit
132
+ vhite --staged # files staged for commit
133
+
134
+ Both need the project to be a Git repository.
135
+
136
+ ## Settings file
137
+
138
+ Ignore patterns come from your `.gitignore`. vhite also skips `node_modules`, `dist`, `build`, and `.git` by default.
139
+
140
+ ## Reports
141
+
142
+ --format json Prints results as JSON for scripts and other tools
48
143
 
49
- Use the rule names shown in the report, in brackets at the end of each line.
144
+ The JSON output includes each finding's rule, severity, file, line, column, and message, plus a summary and any fixes that were applied.
50
145
 
51
146
  ## Limits
52
147
 
53
- vhite finds common patterns. It does not understand your whole app, so review each fix before you commit it. Removing an unused import also stops that module from loading, which is usually what you want, but check modules that only run for their side effects.
148
+ vhite finds common patterns. It can't understand your whole app, so review each fix before you commit it. Removing an unused import also stops that module from loading, which is usually what you want, but check modules that only run for their side effects. The accessibility and React rules are heuristics and may report some false positives.
54
149
 
55
150
  ## License
56
151
 
package/bin/cli.js CHANGED
@@ -1,216 +1,59 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { resolve, join, relative } from "node:path";
4
- import { existsSync } from "node:fs";
5
-
6
- import { findFiles } from "../src/files.js";
7
- import { runRules } from "../src/engine.js";
8
- import { formatReport } from "../src/reporter.js";
9
- import { loadConfig } from "../src/config.js";
10
-
11
- import {
12
- CONFIG_NAME,
13
- canAskQuestions,
14
- runFirstTimeSetup,
15
- } from "../src/setup.js";
16
-
17
- import {
18
- proposeFixes,
19
- applyApprovedFixes,
20
- } from "../src/safe-fix.js";
21
-
22
- import { chooseProposals } from "../src/approve.js";
23
- import { deleteRunBackups } from "../src/backup.js";
24
- import { FIX_DIR } from "../src/gitignore.js";
25
-
26
- import {
27
- askYesNo,
28
- writeRunReport,
29
- } from "../src/run-report.js";
30
-
31
- // Capture terminal messages so the report can include them.
32
- const logs = [];
33
-
34
- const originalLog = console.log;
35
- const originalWarn = console.warn;
36
- const originalError = console.error;
37
-
38
- function capture(method, label = "") {
39
- return (...args) => {
40
- const message = args
41
- .map((arg) => {
42
- if (typeof arg === "string") {
43
- return arg;
44
- }
45
-
46
- try {
47
- return JSON.stringify(arg) ?? String(arg);
48
- } catch {
49
- return String(arg);
50
- }
51
- })
52
- .join(" ");
53
-
54
- logs.push(label ? `${label}: ${message}` : message);
55
- method(...args);
56
- };
57
- }
58
-
59
- console.log = capture(originalLog);
60
- console.warn = capture(originalWarn, "WARN");
61
- console.error = capture(originalError, "ERROR");
62
-
63
- const startedAt = new Date();
64
- const target = resolve(process.argv[2] ?? ".");
65
-
66
- let files = [];
67
- let issues = [];
68
- let proposals = [];
69
- let fixResults = [];
70
- let backupCleanup = null;
71
- let config = null;
72
-
73
- console.log(
74
- `vhite is installed and running
75
- loading..
76
- ....
77
- ......
78
- ........
79
- Done..`
80
- );
81
-
82
- try {
83
- if (
84
- !existsSync(join(target, CONFIG_NAME)) &&
85
- canAskQuestions()
86
- ) {
87
- await runFirstTimeSetup(target);
3
+ import { parseArgs, UsageError } from "../src/core/args.js";
4
+ import { EXIT } from "../src/core/exit-codes.js";
5
+ import { readVersion } from "../src/core/version.js";
6
+ import { USAGE } from "./output/text.js";
7
+ import { listRules, explainRule } from "./commands/info.js";
8
+ import { runBaseline } from "./commands/baseline.js";
9
+ import { runScan } from "./commands/scan.js";
10
+ import { runHook } from "./commands/hook.js";
11
+ import { runWatch } from "./commands/watch.js";
12
+
13
+ async function main() {
14
+ let options;
15
+ try {
16
+ options = parseArgs(process.argv.slice(2));
17
+ } catch (error) {
18
+ if (error instanceof UsageError) {
19
+ console.error(`Error: ${error.message}\n`);
20
+ console.error(USAGE);
21
+ return EXIT.ERROR;
22
+ }
23
+ throw error;
88
24
  }
89
25
 
90
- config = await loadConfig(target);
91
-
92
- for (const warning of config.warnings) {
93
- console.log(`Warning: ${warning}`);
26
+ if (options.command === "help") {
27
+ console.log(USAGE);
28
+ return EXIT.CLEAN;
94
29
  }
95
30
 
96
- if (config.disabledRules.size > 0) {
97
- console.log(
98
- `Rules turned off by ${CONFIG_NAME}: ${[...config.disabledRules].join(", ")}`
99
- );
31
+ if (options.command === "version") {
32
+ console.log(await readVersion());
33
+ return EXIT.CLEAN;
100
34
  }
101
35
 
102
- files = await findFiles(target);
103
-
104
- console.log(
105
- `vhite found ${files.length} front-end files in ${relative(process.cwd(), target) || "."}\n`
106
- );
107
-
108
- issues = await runRules(files, {
109
- disabledRules: config.disabledRules,
110
- });
111
-
112
- console.log(formatReport(issues, target));
113
- proposals = await proposeFixes(issues);
114
-
115
- if (proposals.length > 0 && canAskQuestions()) {
116
- const chosen = await chooseProposals(target, proposals);
117
-
118
- if (chosen.length > 0) {
119
- const result = await applyApprovedFixes(
120
- target,
121
- chosen,
122
- { disabledRules: config.disabledRules }
123
- );
124
-
125
- fixResults = result.results;
126
-
127
- if (result.gitignoreUpdated) {
128
- console.log(`Added ${FIX_DIR}/ to .gitignore`);
129
- }
130
-
131
- for (const item of fixResults) {
132
- if (item.status === "applied") {
133
- console.log(
134
- `Applied: ${relative(target, item.proposal.file)} line ${item.proposal.line}`
135
- );
136
- } else {
137
- const label = item.status === "skipped" ? "Skipped" : "Rolled back";
138
- console.log(
139
- `${label}: ${relative(target, item.proposal.file)} line ${item.proposal.line}. ${item.reason}`
140
- );
141
- }
142
- }
143
- }
36
+ if (options.command === "rules") {
37
+ return listRules();
144
38
  }
145
39
 
146
- // Feature 1: Ask whether to remove current-run backups.
147
- const hasRunBackups = fixResults.some(
148
- (item) => typeof item.backupPath === "string"
149
- );
150
-
151
- if (canAskQuestions() && hasRunBackups) {
152
- const shouldDelete = await askYesNo(
153
- "Do you want to delete the backup files created during this run?"
154
- );
155
-
156
- if (shouldDelete) {
157
- const result = await deleteRunBackups(fixResults);
158
- backupCleanup = {
159
- requested: true,
160
- deleted: result.deleted,
161
- };
162
-
163
- console.log(
164
- `Deleted ${result.deleted} backup file(s) created during this run.`
165
- );
166
- } else {
167
- backupCleanup = {
168
- requested: false,
169
- deleted: 0,
170
- };
171
-
172
- console.log("Kept the backup files created during this run.");
173
- }
40
+ if (options.command === "explain") {
41
+ return explainRule(options.ruleName);
174
42
  }
175
43
 
176
- console.log("\nVhite run completed.");
177
-
178
- // Feature 2: Offer a detailed Markdown report.
179
- if (canAskQuestions()) {
180
- const shouldCreateReport = await askYesNo(
181
- "Would you like to create a detailed Markdown report of this run?"
182
- );
183
-
184
- if (shouldCreateReport) {
185
- // Include the log messages generated before report creation.
186
- const reportLogs = [...logs];
187
- const finishedAt = new Date();
188
-
189
- const result = await writeRunReport({
190
- root: target,
191
- target,
192
- startedAt,
193
- finishedAt,
194
- filesScanned: files.length,
195
- issues,
196
- proposals,
197
- fixResults,
198
- logs: reportLogs,
199
- backupCleanup,
200
- });
44
+ if (options.command === "baseline") {
45
+ return runBaseline(options);
46
+ }
201
47
 
202
- if (result.gitignoreUpdated) {
203
- console.log(`Added ${FIX_DIR}/ to .gitignore`);
204
- }
48
+ if (options.command === "hook") {
49
+ return runHook(options);
50
+ }
205
51
 
206
- console.log(
207
- `Report created successfully: ${relative(target, result.reportPath)}`
208
- );
209
- } else {
210
- console.log("Report generation skipped.");
211
- }
52
+ if (options.watch) {
53
+ return runWatch(options);
212
54
  }
213
- } catch (error) {
214
- console.error(`Error: ${error.message}`);
215
- process.exitCode = 1;
55
+
56
+ return runScan(options);
216
57
  }
58
+
59
+ process.exitCode = await main();
@@ -0,0 +1,27 @@
1
+ import { resolve, relative } from "node:path";
2
+ import { loadConfig } from "../../src/core/config.js";
3
+ import { EXIT } from "../../src/core/exit-codes.js";
4
+ import { BASELINE_NAME, buildBaseline, saveBaseline } from "../../src/scan/baseline.js";
5
+ import { selectFiles, collectIssues, disabledSetFor } from "./shared.js";
6
+
7
+ export async function runBaseline(options) {
8
+ const target = resolve(options.path ?? ".");
9
+
10
+ try {
11
+ const config = await loadConfig(target);
12
+ const disabled = disabledSetFor(config, options);
13
+ const files = await selectFiles(target, options);
14
+ const { issues } = await collectIssues(files, target, disabled, config, null);
15
+
16
+ const baseline = buildBaseline(issues, target);
17
+ const path = await saveBaseline(target, baseline);
18
+ const count = issues.length;
19
+
20
+ console.log(`Saved ${count} current issue(s) to ${relative(process.cwd(), path) || BASELINE_NAME}`);
21
+ console.log("Only new issues will be reported from now on.");
22
+ return EXIT.CLEAN;
23
+ } catch (error) {
24
+ console.error(`Error: ${error.message}`);
25
+ return EXIT.ERROR;
26
+ }
27
+ }
@@ -0,0 +1,58 @@
1
+ import { readFile, writeFile, rm, mkdir, chmod, stat } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { EXIT } from "../../src/core/exit-codes.js";
4
+
5
+ const MARKER = "# vhite-pre-commit-hook";
6
+ const HOOK_SCRIPT = `#!/bin/sh
7
+ ${MARKER}
8
+ # Scans staged files before each commit. Remove with: vhite hook remove
9
+ npx --yes @vhite/vhite --staged --no-prompt
10
+ `;
11
+
12
+ async function gitDirFor(root) {
13
+ try {
14
+ const info = await stat(join(root, ".git"));
15
+ return info.isDirectory() ? join(root, ".git") : null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ export async function runHook(options) {
22
+ const root = resolve(options.path ?? ".");
23
+ const gitDir = await gitDirFor(root);
24
+
25
+ if (!gitDir) {
26
+ console.error("Error: this folder is not a Git repository (no .git folder found)");
27
+ return EXIT.ERROR;
28
+ }
29
+
30
+ const hookPath = join(gitDir, "hooks", "pre-commit");
31
+ const existing = await readFile(hookPath, "utf8").catch(() => null);
32
+ const isOurs = existing !== null && existing.includes(MARKER);
33
+
34
+ if (options.hookAction === "install") {
35
+ if (existing !== null && !isOurs && !options.force) {
36
+ console.error("Error: a pre-commit hook already exists. Use --force to replace it.");
37
+ return EXIT.ERROR;
38
+ }
39
+ await mkdir(join(gitDir, "hooks"), { recursive: true });
40
+ await writeFile(hookPath, HOOK_SCRIPT, "utf8");
41
+ await chmod(hookPath, 0o755).catch(() => {});
42
+ console.log(`Installed pre-commit hook at ${hookPath}`);
43
+ return EXIT.CLEAN;
44
+ }
45
+
46
+ if (existing === null) {
47
+ console.log("No pre-commit hook to remove.");
48
+ return EXIT.CLEAN;
49
+ }
50
+ if (!isOurs) {
51
+ console.error("Error: the existing pre-commit hook was not installed by vhite, so it was left alone.");
52
+ return EXIT.ERROR;
53
+ }
54
+
55
+ await rm(hookPath);
56
+ console.log("Removed the vhite pre-commit hook.");
57
+ return EXIT.CLEAN;
58
+ }
@@ -0,0 +1,39 @@
1
+ import { severityOf } from "../../src/core/config.js";
2
+ import { EXIT } from "../../src/core/exit-codes.js";
3
+ import { fixers } from "../../src/fix/fixers/index.js";
4
+ import { explanations } from "../../src/output/explanations.js";
5
+ import { rules } from "../../rules/index.js";
6
+ import { KNOWN_RULES } from "./shared.js";
7
+
8
+ export function listRules() {
9
+ console.log("Rule Severity Fix Description");
10
+ for (const rule of rules) {
11
+ const severity = severityOf(rule.name, { severities: new Map() });
12
+ const fixable = fixers[rule.name] ? "yes" : "no";
13
+ console.log(
14
+ `${rule.name.padEnd(22)}${severity.padEnd(10)}${fixable.padEnd(6)}${rule.description}`
15
+ );
16
+ }
17
+ return EXIT.CLEAN;
18
+ }
19
+
20
+ export function explainRule(name) {
21
+ if (!KNOWN_RULES.has(name)) {
22
+ console.error(`Error: unknown rule "${name}"`);
23
+ console.error(`Known rules: ${[...KNOWN_RULES].join(", ")}`);
24
+ return EXIT.ERROR;
25
+ }
26
+
27
+ const rule = rules.find((item) => item.name === name);
28
+ const info = explanations[name];
29
+
30
+ console.log(name);
31
+ console.log(` ${rule.description}`);
32
+ if (info) {
33
+ console.log(` What happened: ${info.whatHappened}`);
34
+ console.log(` Proposed change: ${info.proposedChange}`);
35
+ }
36
+ console.log(` Default severity: ${severityOf(name, { severities: new Map() })}`);
37
+ console.log(` Automatic fix: ${fixers[name] ? "yes" : "no"}`);
38
+ return EXIT.CLEAN;
39
+ }