@titan-design/style-checker 0.0.0 → 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/README.md +75 -11
- package/dist/index.d.ts +18 -1
- package/dist/index.js +240 -39
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -21,8 +21,13 @@ import { generateRuffConfig, orchestrate } from "@titan-design/style-checker";
|
|
|
21
21
|
generateRuffConfig(profile);
|
|
22
22
|
// { lint: { select: ["N", "D", "C90"], pydocstyle: { convention: "google" }, mccabe: { "max-complexity": 30 } }, "line-length": 100 }
|
|
23
23
|
|
|
24
|
-
const { diagnostics, summary } = await orchestrate({
|
|
24
|
+
const { diagnostics, failures, skippedRules, summary } = await orchestrate({
|
|
25
|
+
profile,
|
|
26
|
+
files: ["src/app.ts", "app.py"],
|
|
27
|
+
});
|
|
25
28
|
// diagnostics: [{ file: "app.py", line: 5, column: 4, severity: "warn", category: "naming", rule: "N806", ... }]
|
|
29
|
+
// failures: [] means every tool that ran checked every file it was given
|
|
30
|
+
// skippedRules: [{ tool: "eslint", rule: "unicorn/filename-case", plugin: "unicorn", reason: "eslint-plugin-unicorn is not installed in /app" }]
|
|
26
31
|
```
|
|
27
32
|
|
|
28
33
|
## API
|
|
@@ -33,20 +38,75 @@ const { diagnostics, summary } = await orchestrate({ profile, files: ["src/app.t
|
|
|
33
38
|
and mccabe max complexity.
|
|
34
39
|
- `generateEslintConfig(profile)` returns an `EslintFlatConfigEntry[]`: one entry for
|
|
35
40
|
`**/*.ts` and `**/*.tsx` holding the rules style-profile's rule builders produce, or an
|
|
36
|
-
empty array when there are none.
|
|
41
|
+
empty array when there are none. It is data: rule names and options, with no plugin
|
|
42
|
+
objects and no parser. Style-profile's `info` tier becomes `warn`, because ESLint accepts
|
|
43
|
+
only `off`, `warn` and `error`.
|
|
37
44
|
- `orchestrate({ profile, files, fix?, language? })` picks the files by extension, runs
|
|
38
|
-
ESLint (through `npx eslint`) on `.ts`, `.tsx`, `.js`, `.jsx` and ruff on `.py`,
|
|
39
|
-
returns `{ diagnostics, summary }`. A tool is skipped when
|
|
40
|
-
`language` restricts it to one tool; otherwise it is
|
|
45
|
+
ESLint (through `npx --no -- eslint`) on `.ts`, `.tsx`, `.js`, `.jsx` and ruff on `.py`,
|
|
46
|
+
and returns `{ diagnostics, failures, skippedRules, summary }`. A tool is skipped when
|
|
47
|
+
its generated config is empty. `language` restricts it to one tool; otherwise it is
|
|
48
|
+
detected from the file list.
|
|
41
49
|
- `parseEslintJsonOutput(json)` and `parseRuffJsonOutput(json)` turn each tool's
|
|
42
50
|
`--format json` output into `CheckDiagnostic[]`. Both throw on output that is not JSON.
|
|
51
|
+
Messages with no rule (ESLint parse errors and ignored files) and ruff syntax errors
|
|
52
|
+
(`"code": "invalid-syntax"` in ruff 0.16.8, a null `code` in 0.9.10) are not
|
|
53
|
+
diagnostics; the runners report them as `file-not-checked` failures.
|
|
43
54
|
- `formatDiagnostic(d)` prints `file:line:column severity message [category.rule]`.
|
|
44
55
|
- `diffAgainstProfile(profile, observations)` compares each observation's value with the
|
|
45
56
|
profile's convention for its `type` and returns `{ deviations, summary }`. Observations
|
|
46
57
|
whose type has no profile rule count toward `total` but neither match nor deviate.
|
|
47
58
|
- Types: `CheckDiagnostic`, `CheckResult`, `OrchestratorOptions`, `OrchestratorResult`,
|
|
48
|
-
`
|
|
49
|
-
(re-exported from
|
|
59
|
+
`ToolFailure`, `ToolFailureKind`, `ToolName`, `SkippedRule`, `RuffConfig`,
|
|
60
|
+
`EslintFlatConfigEntry`, `Deviation`, `DiffResult`, and `Severity` (re-exported from
|
|
61
|
+
style-profile).
|
|
62
|
+
|
|
63
|
+
## Failures
|
|
64
|
+
|
|
65
|
+
A run that could not check the files is never reported as a clean run. Each problem
|
|
66
|
+
becomes a `ToolFailure` (`{ tool, kind, message, file? }`) in `failures`; the message
|
|
67
|
+
carries the tool's stderr. `diagnostics: []` with `failures: []` means the tools ran and
|
|
68
|
+
found nothing.
|
|
69
|
+
|
|
70
|
+
| `kind` | When |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `spawn-failed` | the executable (`npx`, `ruff`) could not be started |
|
|
73
|
+
| `timeout` | the run outlived its timeout (60 s) and was killed |
|
|
74
|
+
| `signal` | the process was killed by a signal |
|
|
75
|
+
| `exit-code` | an exit code other than 0 or 1 |
|
|
76
|
+
| `unparseable-output` | exit 0 or 1 with empty or non-JSON stdout |
|
|
77
|
+
| `file-not-checked` | the tool ran but could not check one file (`file` names it): an ESLint parse error or ignored file, a ruff syntax error, or a path ruff could not read (which it reports only on stderr, with exit 0) |
|
|
78
|
+
| `missing-dependency` | no TypeScript parser in the project, so ESLint was not run |
|
|
79
|
+
|
|
80
|
+
An unreadable ruff path is detected by matching ruff's stderr warning `warning: Failed to
|
|
81
|
+
lint <path>: <reason>`, captured from ruff 0.16.8. ruff has no structured signal for it
|
|
82
|
+
and does not treat the wording as stable; if a later ruff rewords it, that case goes
|
|
83
|
+
silent again.
|
|
84
|
+
|
|
85
|
+
Exit codes 0 and 1 are both successful runs. ESLint documents 0 as no errors, 1 as at
|
|
86
|
+
least one error, and 2 as a configuration problem or internal error
|
|
87
|
+
([CLI reference](https://eslint.org/docs/latest/use/command-line-interface#exit-codes)).
|
|
88
|
+
ruff documents 0 as no violations (or all fixed), 1 as violations found, and 2 as abnormal
|
|
89
|
+
termination from invalid configuration, invalid options or an internal error
|
|
90
|
+
([linter docs](https://docs.astral.sh/ruff/linter/#exit-codes)).
|
|
91
|
+
|
|
92
|
+
## ESLint plugins
|
|
93
|
+
|
|
94
|
+
The checker runs ESLint in the project (the current working directory), so plugins must
|
|
95
|
+
come from the project's own `node_modules`. At run time it resolves each plugin a rule
|
|
96
|
+
needs from that directory and writes a config that imports it by absolute path:
|
|
97
|
+
|
|
98
|
+
| Rule prefix | Package |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `@typescript-eslint/` | `@typescript-eslint/eslint-plugin`, or `typescript-eslint` |
|
|
101
|
+
| `perfectionist/` | `eslint-plugin-perfectionist` |
|
|
102
|
+
| `unicorn/` | `eslint-plugin-unicorn` |
|
|
103
|
+
| `jsdoc/` | `eslint-plugin-jsdoc` |
|
|
104
|
+
|
|
105
|
+
A rule whose plugin is not installed is left out of the run and listed in `skippedRules`
|
|
106
|
+
with the package it needs; the rest of the rules still run. The TypeScript parser
|
|
107
|
+
(`@typescript-eslint/parser`, or `typescript-eslint`) is required: without it ESLint
|
|
108
|
+
cannot parse `.ts`, so the run is not attempted and a `missing-dependency` failure names
|
|
109
|
+
the package. `npx --no` stops npx from downloading an ESLint the project does not have.
|
|
50
110
|
|
|
51
111
|
Which runners fire, with which configs, is product policy. The runner seam is one
|
|
52
112
|
function per tool (`runEslint`, `runRuff`) over a shared `runTool` spawn helper; new
|
|
@@ -54,11 +114,15 @@ runners are added beside them.
|
|
|
54
114
|
|
|
55
115
|
## Gotchas
|
|
56
116
|
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
117
|
+
- The generated ESLint config covers only `**/*.ts` and `**/*.tsx`, though `orchestrate`
|
|
118
|
+
sends `.js` and `.jsx` to ESLint too. A `.jsx` file comes back as a `file-not-checked`
|
|
119
|
+
failure. A `.js` file matches ESLint's built-in defaults, so it is linted with none of
|
|
120
|
+
the profile's rules and comes back clean.
|
|
121
|
+
- ESLint runs from the current working directory. A file outside it comes back as a
|
|
122
|
+
`file-not-checked` failure ("File ignored because outside of base path").
|
|
60
123
|
- ruff's `max-complexity` is set from the profile's `functionMaxLines`, a line count, not a
|
|
61
124
|
cyclomatic complexity.
|
|
62
125
|
- Every ruff diagnostic has severity `warn`. ESLint severity 2 maps to `error`, anything
|
|
63
|
-
else to `warn`. Nothing produces `info`.
|
|
126
|
+
else to `warn`. Nothing produces `info`. ruff 0.16.8 emits a `severity` field, but it is
|
|
127
|
+
`"error"` for every rule finding, so it is ignored.
|
|
64
128
|
- `summary.fixed` is always 0, even with `fix: true`.
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,20 @@ interface CheckResult {
|
|
|
21
21
|
tool: "eslint" | "ruff";
|
|
22
22
|
exitCode: number;
|
|
23
23
|
}
|
|
24
|
+
type ToolName = "eslint" | "ruff";
|
|
25
|
+
type ToolFailureKind = "spawn-failed" | "timeout" | "signal" | "exit-code" | "unparseable-output" | "file-not-checked" | "missing-dependency";
|
|
26
|
+
interface ToolFailure {
|
|
27
|
+
tool: ToolName;
|
|
28
|
+
kind: ToolFailureKind;
|
|
29
|
+
message: string;
|
|
30
|
+
file?: string;
|
|
31
|
+
}
|
|
32
|
+
interface SkippedRule {
|
|
33
|
+
tool: "eslint";
|
|
34
|
+
rule: string;
|
|
35
|
+
plugin: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
}
|
|
24
38
|
interface OrchestratorOptions {
|
|
25
39
|
profile: Profile;
|
|
26
40
|
files: string[];
|
|
@@ -29,6 +43,9 @@ interface OrchestratorOptions {
|
|
|
29
43
|
}
|
|
30
44
|
interface OrchestratorResult {
|
|
31
45
|
diagnostics: CheckDiagnostic[];
|
|
46
|
+
/** Empty only when every tool that ran checked every file it was given. */
|
|
47
|
+
failures: ToolFailure[];
|
|
48
|
+
skippedRules: SkippedRule[];
|
|
32
49
|
summary: {
|
|
33
50
|
total: number;
|
|
34
51
|
errors: number;
|
|
@@ -90,4 +107,4 @@ interface DiffResult {
|
|
|
90
107
|
}
|
|
91
108
|
declare function diffAgainstProfile(profile: Profile, observations: Observation[]): DiffResult;
|
|
92
109
|
|
|
93
|
-
export { type CheckDiagnostic, type CheckResult, type Deviation, type DiffResult, type EslintFlatConfigEntry, type OrchestratorOptions, type OrchestratorResult, type RuffConfig, diffAgainstProfile, formatDiagnostic, generateEslintConfig, generateRuffConfig, orchestrate, parseEslintJsonOutput, parseRuffJsonOutput };
|
|
110
|
+
export { type CheckDiagnostic, type CheckResult, type Deviation, type DiffResult, type EslintFlatConfigEntry, type OrchestratorOptions, type OrchestratorResult, type RuffConfig, type SkippedRule, type ToolFailure, type ToolFailureKind, type ToolName, diffAgainstProfile, formatDiagnostic, generateEslintConfig, generateRuffConfig, orchestrate, parseEslintJsonOutput, parseRuffJsonOutput };
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,11 @@ import {
|
|
|
6
6
|
buildFileNamingRule,
|
|
7
7
|
buildJsdocRules
|
|
8
8
|
} from "@titan-design/style-profile";
|
|
9
|
-
function
|
|
10
|
-
|
|
9
|
+
function withEslintSeverity(value) {
|
|
10
|
+
if (Array.isArray(value) && value[0] === "info") return ["warn", ...value.slice(1)];
|
|
11
|
+
return value === "info" ? "warn" : value;
|
|
12
|
+
}
|
|
13
|
+
function collectRules(profile) {
|
|
11
14
|
const rules = {};
|
|
12
15
|
const namingRule = buildNamingConventionRule(profile);
|
|
13
16
|
if (namingRule) rules[namingRule[0]] = namingRule[1];
|
|
@@ -21,6 +24,12 @@ function generateEslintConfig(profile) {
|
|
|
21
24
|
for (const [name, value] of jsdocRules) {
|
|
22
25
|
rules[name] = value;
|
|
23
26
|
}
|
|
27
|
+
for (const name of Object.keys(rules)) rules[name] = withEslintSeverity(rules[name]);
|
|
28
|
+
return rules;
|
|
29
|
+
}
|
|
30
|
+
function generateEslintConfig(profile) {
|
|
31
|
+
const entries = [];
|
|
32
|
+
const rules = collectRules(profile);
|
|
24
33
|
if (Object.keys(rules).length > 0) {
|
|
25
34
|
entries.push({
|
|
26
35
|
files: ["**/*.ts", "**/*.tsx"],
|
|
@@ -83,7 +92,7 @@ function generateRuffConfig(profile) {
|
|
|
83
92
|
|
|
84
93
|
// src/runners/eslint-runner.ts
|
|
85
94
|
import { writeFileSync, mkdtempSync, rmSync } from "fs";
|
|
86
|
-
import { join } from "path";
|
|
95
|
+
import { join as join2 } from "path";
|
|
87
96
|
import { tmpdir } from "os";
|
|
88
97
|
|
|
89
98
|
// src/runners/tool-runner.ts
|
|
@@ -92,26 +101,176 @@ function runTool(command, args, options) {
|
|
|
92
101
|
return new Promise((resolve, reject) => {
|
|
93
102
|
const child = spawn(command, args, {
|
|
94
103
|
cwd: options?.cwd,
|
|
95
|
-
timeout: options?.timeout ?? 6e4,
|
|
96
104
|
stdio: ["ignore", "pipe", "pipe"]
|
|
97
105
|
});
|
|
98
106
|
const chunks = [];
|
|
99
107
|
const errChunks = [];
|
|
108
|
+
let timedOut = false;
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
timedOut = true;
|
|
111
|
+
child.kill("SIGKILL");
|
|
112
|
+
}, options?.timeout ?? 6e4);
|
|
100
113
|
child.stdout.on("data", (chunk) => chunks.push(chunk));
|
|
101
114
|
child.stderr.on("data", (chunk) => errChunks.push(chunk));
|
|
102
115
|
child.on("error", (err) => {
|
|
116
|
+
clearTimeout(timer);
|
|
103
117
|
reject(new Error(`Failed to spawn ${command}: ${err.message}`));
|
|
104
118
|
});
|
|
105
|
-
child.on("close", (code) => {
|
|
119
|
+
child.on("close", (code, signal) => {
|
|
120
|
+
clearTimeout(timer);
|
|
106
121
|
resolve({
|
|
107
122
|
stdout: Buffer.concat(chunks).toString("utf-8"),
|
|
108
123
|
stderr: Buffer.concat(errChunks).toString("utf-8"),
|
|
109
|
-
exitCode: code
|
|
124
|
+
exitCode: code,
|
|
125
|
+
signal,
|
|
126
|
+
timedOut
|
|
110
127
|
});
|
|
111
128
|
});
|
|
112
129
|
});
|
|
113
130
|
}
|
|
114
131
|
|
|
132
|
+
// src/runners/failures.ts
|
|
133
|
+
var SUCCESS_EXIT_CODES = /* @__PURE__ */ new Set([0, 1]);
|
|
134
|
+
function excerpt(text) {
|
|
135
|
+
const trimmed = text.trim();
|
|
136
|
+
return trimmed.length > 500 ? `${trimmed.slice(0, 500)}\u2026` : trimmed;
|
|
137
|
+
}
|
|
138
|
+
function withStderr(message, stderr) {
|
|
139
|
+
const detail = excerpt(stderr);
|
|
140
|
+
return detail ? `${message}: ${detail}` : message;
|
|
141
|
+
}
|
|
142
|
+
function classifyRun(tool, run) {
|
|
143
|
+
const fail = (kind, message) => ({
|
|
144
|
+
ok: false,
|
|
145
|
+
failure: { tool, kind, message: withStderr(message, run.stderr) },
|
|
146
|
+
exitCode: run.exitCode
|
|
147
|
+
});
|
|
148
|
+
if (run.timedOut) return fail("timeout", `${tool} timed out and was killed`);
|
|
149
|
+
if (run.exitCode === null) {
|
|
150
|
+
return fail("signal", `${tool} was killed by ${run.signal ?? "a signal"}`);
|
|
151
|
+
}
|
|
152
|
+
if (!SUCCESS_EXIT_CODES.has(run.exitCode)) {
|
|
153
|
+
return fail("exit-code", `${tool} exited with code ${run.exitCode}`);
|
|
154
|
+
}
|
|
155
|
+
if (!run.stdout.trim()) {
|
|
156
|
+
return fail("unparseable-output", `${tool} exited ${run.exitCode} with no output`);
|
|
157
|
+
}
|
|
158
|
+
return { ok: true, stdout: run.stdout, stderr: run.stderr, exitCode: run.exitCode };
|
|
159
|
+
}
|
|
160
|
+
async function runAndClassify(tool, command, args, options) {
|
|
161
|
+
try {
|
|
162
|
+
return classifyRun(tool, await runTool(command, args, options));
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
165
|
+
return { ok: false, failure: { tool, kind: "spawn-failed", message }, exitCode: null };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function parseOrFail(tool, parse) {
|
|
169
|
+
try {
|
|
170
|
+
return { ok: true, value: parse() };
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
+
return { ok: false, failure: { tool, kind: "unparseable-output", message } };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/runners/eslint-plugins.ts
|
|
178
|
+
import { createRequire } from "module";
|
|
179
|
+
import { join } from "path";
|
|
180
|
+
import { pathToFileURL } from "url";
|
|
181
|
+
var PLUGIN_SOURCES = {
|
|
182
|
+
"@typescript-eslint": [
|
|
183
|
+
{ specifier: "@typescript-eslint/eslint-plugin" },
|
|
184
|
+
{ specifier: "typescript-eslint", member: "plugin" }
|
|
185
|
+
],
|
|
186
|
+
perfectionist: [{ specifier: "eslint-plugin-perfectionist" }],
|
|
187
|
+
unicorn: [{ specifier: "eslint-plugin-unicorn" }],
|
|
188
|
+
jsdoc: [{ specifier: "eslint-plugin-jsdoc" }]
|
|
189
|
+
};
|
|
190
|
+
var PARSER_SOURCES = [
|
|
191
|
+
{ specifier: "@typescript-eslint/parser" },
|
|
192
|
+
{ specifier: "typescript-eslint", member: "parser" }
|
|
193
|
+
];
|
|
194
|
+
function pluginNamespace(rule) {
|
|
195
|
+
const slash = rule.lastIndexOf("/");
|
|
196
|
+
return slash === -1 ? null : rule.slice(0, slash);
|
|
197
|
+
}
|
|
198
|
+
function describeSources(sources) {
|
|
199
|
+
return sources.map((s) => s.specifier).join(" or ");
|
|
200
|
+
}
|
|
201
|
+
function resolveModule(sources, fromDir) {
|
|
202
|
+
const require2 = createRequire(join(fromDir, "package.json"));
|
|
203
|
+
for (const source of sources) {
|
|
204
|
+
try {
|
|
205
|
+
const url = pathToFileURL(require2.resolve(source.specifier)).href;
|
|
206
|
+
return { url, member: source.member };
|
|
207
|
+
} catch {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/runners/eslint-config-module.ts
|
|
215
|
+
function importExpr(ctx, mod) {
|
|
216
|
+
let id = ctx.ids.get(mod.url);
|
|
217
|
+
if (!id) {
|
|
218
|
+
id = `m${ctx.ids.size}`;
|
|
219
|
+
ctx.ids.set(mod.url, id);
|
|
220
|
+
ctx.imports.push(`import ${id} from ${JSON.stringify(mod.url)};`);
|
|
221
|
+
}
|
|
222
|
+
return mod.member ? `${id}.${mod.member}` : id;
|
|
223
|
+
}
|
|
224
|
+
function resolvePlugin(ctx, namespace) {
|
|
225
|
+
if (!ctx.plugins.has(namespace)) {
|
|
226
|
+
const sources = PLUGIN_SOURCES[namespace];
|
|
227
|
+
ctx.plugins.set(namespace, sources ? resolveModule(sources, ctx.fromDir) : null);
|
|
228
|
+
}
|
|
229
|
+
return ctx.plugins.get(namespace) ?? null;
|
|
230
|
+
}
|
|
231
|
+
function skipReason(ctx, namespace) {
|
|
232
|
+
const sources = PLUGIN_SOURCES[namespace];
|
|
233
|
+
if (!sources) return `no known package provides the ESLint plugin "${namespace}"`;
|
|
234
|
+
return `${describeSources(sources)} is not installed in ${ctx.fromDir}`;
|
|
235
|
+
}
|
|
236
|
+
function renderEntry(ctx, entry, parserExpr, skipped) {
|
|
237
|
+
const rules = {};
|
|
238
|
+
const plugins = [];
|
|
239
|
+
for (const [rule, value] of Object.entries(entry.rules ?? {})) {
|
|
240
|
+
const namespace = pluginNamespace(rule);
|
|
241
|
+
const plugin = namespace === null ? null : resolvePlugin(ctx, namespace);
|
|
242
|
+
if (namespace !== null && !plugin) {
|
|
243
|
+
skipped.push({ tool: "eslint", rule, plugin: namespace, reason: skipReason(ctx, namespace) });
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
rules[rule] = value;
|
|
247
|
+
if (namespace !== null && plugin) plugins.push(`${JSON.stringify(namespace)}: ${importExpr(ctx, plugin)}`);
|
|
248
|
+
}
|
|
249
|
+
const data = JSON.stringify({ files: entry.files, rules });
|
|
250
|
+
const text = `{ ...${data}, languageOptions: { parser: ${parserExpr} }, plugins: { ${[...new Set(plugins)].join(", ")} } }`;
|
|
251
|
+
return { text, ruleCount: Object.keys(rules).length };
|
|
252
|
+
}
|
|
253
|
+
function buildEslintConfigModule(config, fromDir) {
|
|
254
|
+
const parser = resolveModule(PARSER_SOURCES, fromDir);
|
|
255
|
+
if (!parser) {
|
|
256
|
+
const message = `${describeSources(PARSER_SOURCES)} is not installed in ${fromDir}, so ESLint cannot parse TypeScript`;
|
|
257
|
+
return { ok: false, failure: { tool: "eslint", kind: "missing-dependency", message } };
|
|
258
|
+
}
|
|
259
|
+
const ctx = { fromDir, imports: [], ids: /* @__PURE__ */ new Map(), plugins: /* @__PURE__ */ new Map() };
|
|
260
|
+
const parserExpr = importExpr(ctx, parser);
|
|
261
|
+
const skippedRules = [];
|
|
262
|
+
const entries = config.map((entry) => renderEntry(ctx, entry, parserExpr, skippedRules));
|
|
263
|
+
const ruleCount = entries.reduce((sum, e) => sum + e.ruleCount, 0);
|
|
264
|
+
const body = entries.map((e) => e.text).join(",\n ");
|
|
265
|
+
const source = `${ctx.imports.join("\n")}
|
|
266
|
+
|
|
267
|
+
export default [
|
|
268
|
+
${body},
|
|
269
|
+
];
|
|
270
|
+
`;
|
|
271
|
+
return { ok: true, source, ruleCount, skippedRules };
|
|
272
|
+
}
|
|
273
|
+
|
|
115
274
|
// src/formatters/unified.ts
|
|
116
275
|
var RUFF_CODE_CATEGORY = {
|
|
117
276
|
N: "naming",
|
|
@@ -175,6 +334,9 @@ ${jsonStr.slice(0, 500)}`
|
|
|
175
334
|
}
|
|
176
335
|
return diagnostics;
|
|
177
336
|
}
|
|
337
|
+
function isRuffSyntaxError(code) {
|
|
338
|
+
return code === null || code === "invalid-syntax";
|
|
339
|
+
}
|
|
178
340
|
function parseRuffJsonOutput(jsonStr) {
|
|
179
341
|
let entries;
|
|
180
342
|
try {
|
|
@@ -185,7 +347,7 @@ function parseRuffJsonOutput(jsonStr) {
|
|
|
185
347
|
${jsonStr.slice(0, 500)}`
|
|
186
348
|
);
|
|
187
349
|
}
|
|
188
|
-
return entries.
|
|
350
|
+
return entries.flatMap((entry) => isRuffSyntaxError(entry.code) ? [] : [{
|
|
189
351
|
file: entry.filename,
|
|
190
352
|
line: entry.location.row,
|
|
191
353
|
column: entry.location.column,
|
|
@@ -194,30 +356,45 @@ ${jsonStr.slice(0, 500)}`
|
|
|
194
356
|
category: categorizeRuffCode(entry.code),
|
|
195
357
|
rule: entry.code,
|
|
196
358
|
fixable: entry.fix != null
|
|
197
|
-
})
|
|
359
|
+
}]);
|
|
198
360
|
}
|
|
199
361
|
function formatDiagnostic(d) {
|
|
200
362
|
return `${d.file}:${d.line}:${d.column} ${d.severity} ${d.message} [${d.category}.${d.rule}]`;
|
|
201
363
|
}
|
|
202
364
|
|
|
203
365
|
// src/runners/eslint-runner.ts
|
|
366
|
+
function uncheckedFiles(stdout) {
|
|
367
|
+
const entries = JSON.parse(stdout);
|
|
368
|
+
return entries.flatMap(
|
|
369
|
+
(entry) => entry.messages.filter((m) => m.ruleId === null && (m.fatal === true || m.message.startsWith("File ignored"))).map((m) => ({ tool: "eslint", kind: "file-not-checked", file: entry.filePath, message: m.message }))
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
function readEslintOutput(stdout) {
|
|
373
|
+
const parsed = parseOrFail("eslint", () => ({
|
|
374
|
+
diagnostics: parseEslintJsonOutput(stdout),
|
|
375
|
+
failures: uncheckedFiles(stdout)
|
|
376
|
+
}));
|
|
377
|
+
return parsed.ok ? parsed.value : { diagnostics: [], failures: [parsed.failure] };
|
|
378
|
+
}
|
|
379
|
+
function eslintArgs(configPath, files, fix) {
|
|
380
|
+
return ["--no", "--", "eslint", "--config", configPath, "--format", "json", ...fix ? ["--fix"] : [], ...files];
|
|
381
|
+
}
|
|
204
382
|
async function runEslint(config, files, options) {
|
|
205
|
-
const
|
|
206
|
-
const
|
|
383
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
384
|
+
const built = buildEslintConfigModule(config, cwd);
|
|
385
|
+
if (!built.ok) return { diagnostics: [], exitCode: null, failures: [built.failure], skippedRules: [] };
|
|
386
|
+
const { skippedRules } = built;
|
|
387
|
+
if (built.ruleCount === 0) return { diagnostics: [], exitCode: null, failures: [], skippedRules };
|
|
388
|
+
const tempDir = mkdtempSync(join2(tmpdir(), "codewatch-eslint-"));
|
|
389
|
+
const configPath = join2(tempDir, "eslint.config.mjs");
|
|
207
390
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
...options?.fix ? ["--fix"] : [],
|
|
216
|
-
...files
|
|
217
|
-
];
|
|
218
|
-
const result = await runTool("npx", ["eslint", ...args]);
|
|
219
|
-
const diagnostics = result.stdout.trim() ? parseEslintJsonOutput(result.stdout) : [];
|
|
220
|
-
return { diagnostics, exitCode: result.exitCode };
|
|
391
|
+
writeFileSync(configPath, built.source, "utf-8");
|
|
392
|
+
const run = await runAndClassify("eslint", "npx", eslintArgs(configPath, files, options?.fix), {
|
|
393
|
+
cwd,
|
|
394
|
+
timeout: options?.timeout
|
|
395
|
+
});
|
|
396
|
+
if (!run.ok) return { diagnostics: [], exitCode: run.exitCode, failures: [run.failure], skippedRules };
|
|
397
|
+
return { ...readEslintOutput(run.stdout), exitCode: run.exitCode, skippedRules };
|
|
221
398
|
} finally {
|
|
222
399
|
rmSync(tempDir, { recursive: true, force: true });
|
|
223
400
|
}
|
|
@@ -225,7 +402,7 @@ async function runEslint(config, files, options) {
|
|
|
225
402
|
|
|
226
403
|
// src/runners/ruff-runner.ts
|
|
227
404
|
import { writeFileSync as writeFileSync2, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
|
|
228
|
-
import { join as
|
|
405
|
+
import { join as join3 } from "path";
|
|
229
406
|
import { tmpdir as tmpdir2 } from "os";
|
|
230
407
|
function quoteList(values) {
|
|
231
408
|
return `[${values.map((s) => `"${s}"`).join(", ")}]`;
|
|
@@ -268,9 +445,30 @@ function toToml(config) {
|
|
|
268
445
|
}
|
|
269
446
|
return lines.join("\n") + "\n";
|
|
270
447
|
}
|
|
448
|
+
function notChecked(file, message) {
|
|
449
|
+
return { tool: "ruff", kind: "file-not-checked", file, message };
|
|
450
|
+
}
|
|
451
|
+
function unparsedFiles(stdout) {
|
|
452
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
453
|
+
for (const e of JSON.parse(stdout)) {
|
|
454
|
+
if (!isRuffSyntaxError(e.code)) continue;
|
|
455
|
+
byFile.set(e.filename, [...byFile.get(e.filename) ?? [], e.message]);
|
|
456
|
+
}
|
|
457
|
+
return [...byFile].map(([file, messages]) => notChecked(file, messages.join("; ")));
|
|
458
|
+
}
|
|
459
|
+
function unreadFiles(stderr) {
|
|
460
|
+
return [...stderr.matchAll(/^warning: Failed to lint (.+?): (.+)$/gm)].map((m) => notChecked(m[1], m[2]));
|
|
461
|
+
}
|
|
462
|
+
function readRuffOutput(stdout, stderr) {
|
|
463
|
+
const parsed = parseOrFail("ruff", () => ({
|
|
464
|
+
diagnostics: parseRuffJsonOutput(stdout),
|
|
465
|
+
failures: [...unparsedFiles(stdout), ...unreadFiles(stderr)]
|
|
466
|
+
}));
|
|
467
|
+
return parsed.ok ? parsed.value : { diagnostics: [], failures: [parsed.failure] };
|
|
468
|
+
}
|
|
271
469
|
async function runRuff(config, files, options) {
|
|
272
|
-
const tempDir = mkdtempSync2(
|
|
273
|
-
const configPath =
|
|
470
|
+
const tempDir = mkdtempSync2(join3(tmpdir2(), "codewatch-ruff-"));
|
|
471
|
+
const configPath = join3(tempDir, "ruff.toml");
|
|
274
472
|
try {
|
|
275
473
|
writeFileSync2(configPath, toToml(config), "utf-8");
|
|
276
474
|
const args = [
|
|
@@ -282,9 +480,9 @@ async function runRuff(config, files, options) {
|
|
|
282
480
|
...options?.fix ? ["--fix"] : [],
|
|
283
481
|
...files
|
|
284
482
|
];
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
return {
|
|
483
|
+
const run = await runAndClassify("ruff", "ruff", args, { cwd: options?.cwd, timeout: options?.timeout });
|
|
484
|
+
if (!run.ok) return { diagnostics: [], exitCode: run.exitCode, failures: [run.failure], skippedRules: [] };
|
|
485
|
+
return { ...readRuffOutput(run.stdout, run.stderr), exitCode: run.exitCode, skippedRules: [] };
|
|
288
486
|
} finally {
|
|
289
487
|
rmSync2(tempDir, { recursive: true, force: true });
|
|
290
488
|
}
|
|
@@ -307,29 +505,32 @@ function buildSummary(diagnostics) {
|
|
|
307
505
|
fixed: 0
|
|
308
506
|
};
|
|
309
507
|
}
|
|
310
|
-
async function
|
|
508
|
+
async function runTools(options) {
|
|
311
509
|
const { profile, files, fix } = options;
|
|
312
510
|
const language = options.language ?? detectLanguage(files);
|
|
313
|
-
const allDiagnostics = [];
|
|
314
511
|
const tsFiles = files.filter((f) => /\.[tj]sx?$/.test(f));
|
|
315
512
|
const pyFiles = files.filter((f) => /\.py$/.test(f));
|
|
513
|
+
const results = [];
|
|
316
514
|
if ((language === "typescript" || language === "mixed") && tsFiles.length > 0) {
|
|
317
515
|
const eslintConfig = generateEslintConfig(profile);
|
|
318
|
-
if (eslintConfig.length > 0) {
|
|
319
|
-
const result = await runEslint(eslintConfig, tsFiles, { fix });
|
|
320
|
-
allDiagnostics.push(...result.diagnostics);
|
|
321
|
-
}
|
|
516
|
+
if (eslintConfig.length > 0) results.push(await runEslint(eslintConfig, tsFiles, { fix }));
|
|
322
517
|
}
|
|
323
518
|
if ((language === "python" || language === "mixed") && pyFiles.length > 0) {
|
|
324
519
|
const ruffConfig = generateRuffConfig(profile);
|
|
325
520
|
if (ruffConfig.lint?.select && ruffConfig.lint.select.length > 0) {
|
|
326
|
-
|
|
327
|
-
allDiagnostics.push(...result.diagnostics);
|
|
521
|
+
results.push(await runRuff(ruffConfig, pyFiles, { fix }));
|
|
328
522
|
}
|
|
329
523
|
}
|
|
524
|
+
return results;
|
|
525
|
+
}
|
|
526
|
+
async function orchestrate(options) {
|
|
527
|
+
const results = await runTools(options);
|
|
528
|
+
const diagnostics = results.flatMap((r) => r.diagnostics);
|
|
330
529
|
return {
|
|
331
|
-
diagnostics
|
|
332
|
-
|
|
530
|
+
diagnostics,
|
|
531
|
+
failures: results.flatMap((r) => r.failures),
|
|
532
|
+
skippedRules: results.flatMap((r) => r.skippedRules),
|
|
533
|
+
summary: buildSummary(diagnostics)
|
|
333
534
|
};
|
|
334
535
|
}
|
|
335
536
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generators/eslint.ts","../src/generators/ruff.ts","../src/runners/eslint-runner.ts","../src/runners/tool-runner.ts","../src/formatters/unified.ts","../src/runners/ruff-runner.ts","../src/orchestrator/orchestrate.ts","../src/profile-diff/diff-against-profile.ts"],"sourcesContent":["import type { Profile } from \"@titan-design/style-profile\";\nimport {\n buildNamingConventionRule,\n buildImportOrderRule,\n buildFunctionLengthRule,\n buildFileNamingRule,\n buildJsdocRules,\n} from \"@titan-design/style-profile\";\n\nexport interface EslintFlatConfigEntry {\n plugins?: Record<string, unknown>;\n rules?: Record<string, unknown>;\n files?: string[];\n}\n\nexport function generateEslintConfig(profile: Profile): EslintFlatConfigEntry[] {\n const entries: EslintFlatConfigEntry[] = [];\n const rules: Record<string, unknown> = {};\n\n const namingRule = buildNamingConventionRule(profile);\n if (namingRule) rules[namingRule[0]] = namingRule[1];\n\n const importRule = buildImportOrderRule(profile);\n if (importRule) rules[importRule[0]] = importRule[1];\n\n const fnLengthRule = buildFunctionLengthRule(profile);\n if (fnLengthRule) rules[fnLengthRule[0]] = fnLengthRule[1];\n\n const fileNamingRule = buildFileNamingRule(profile);\n if (fileNamingRule) rules[fileNamingRule[0]] = fileNamingRule[1];\n\n const jsdocRules = buildJsdocRules(profile);\n for (const [name, value] of jsdocRules) {\n rules[name] = value;\n }\n\n if (Object.keys(rules).length > 0) {\n entries.push({\n files: [\"**/*.ts\", \"**/*.tsx\"],\n rules,\n });\n }\n\n return entries;\n}\n","import type { Profile } from \"@titan-design/style-profile\";\n\nexport interface RuffConfig {\n lint?: {\n select?: string[];\n ignore?: string[];\n fixable?: string[];\n \"per-file-ignores\"?: Record<string, string[]>;\n mccabe?: { \"max-complexity\"?: number };\n pydocstyle?: { convention?: string };\n isort?: {\n \"known-first-party\"?: string[];\n \"section-order\"?: string[];\n };\n };\n \"line-length\"?: number;\n}\n\ntype RuffLint = NonNullable<RuffConfig[\"lint\"]> & { select: string[] };\n\nfunction addNaming(profile: Profile, lint: RuffLint): void {\n if (profile.naming) {\n const hasNamingRules = Object.values(profile.naming).some(\n (rule) => rule.confidence >= profile.severityThresholds.info,\n );\n if (hasNamingRules) lint.select.push(\"N\");\n }\n}\n\nfunction addImportOrder(profile: Profile, lint: RuffLint): void {\n const importOrder = profile.structure?.importOrder;\n if (importOrder && importOrder.confidence >= profile.severityThresholds.info) {\n lint.select.push(\"I\");\n if (Array.isArray(importOrder.convention)) {\n lint.isort = {\n \"section-order\": importOrder.convention as string[],\n };\n }\n }\n}\n\nfunction addFunctionDocs(profile: Profile, lint: RuffLint): void {\n const functionDocs = profile.documentation?.functionDocs;\n if (functionDocs && functionDocs.confidence >= profile.severityThresholds.info) {\n lint.select.push(\"D\");\n const conv = functionDocs.convention;\n if (\n typeof conv === \"string\" &&\n conv !== \"jsdoc-selective\" &&\n conv !== \"jsdoc-all\"\n ) {\n lint.pydocstyle = { convention: conv };\n }\n }\n}\n\nfunction addComplexity(profile: Profile, lint: RuffLint): void {\n const maxLines = profile.structure?.functionMaxLines;\n if (\n maxLines &&\n maxLines.confidence >= profile.severityThresholds.info &&\n typeof maxLines.convention === \"number\"\n ) {\n lint.select.push(\"C90\");\n lint.mccabe = { \"max-complexity\": maxLines.convention };\n }\n}\n\nexport function generateRuffConfig(profile: Profile): RuffConfig {\n const lint: RuffLint = { select: [] };\n const config: RuffConfig = { lint };\n\n addNaming(profile, lint);\n addImportOrder(profile, lint);\n addFunctionDocs(profile, lint);\n addComplexity(profile, lint);\n\n const lineLength = profile.formatting?.lineLength;\n if (lineLength && typeof lineLength.convention === \"number\") {\n config[\"line-length\"] = lineLength.convention;\n }\n\n return config;\n}\n","import { writeFileSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { runTool } from \"./tool-runner.js\";\nimport { parseEslintJsonOutput } from \"../formatters/unified.js\";\nimport type { CheckDiagnostic } from \"../orchestrator/types.js\";\nimport type { EslintFlatConfigEntry } from \"../generators/eslint.js\";\n\nexport async function runEslint(\n config: EslintFlatConfigEntry[],\n files: string[],\n options?: { fix?: boolean },\n): Promise<{ diagnostics: CheckDiagnostic[]; exitCode: number }> {\n const tempDir = mkdtempSync(join(tmpdir(), \"codewatch-eslint-\"));\n const configPath = join(tempDir, \"eslint.config.js\");\n\n try {\n const configContent = `export default ${JSON.stringify(config, null, 2)};`;\n writeFileSync(configPath, configContent, \"utf-8\");\n\n const args = [\n \"--config\",\n configPath,\n \"--format\",\n \"json\",\n ...(options?.fix ? [\"--fix\"] : []),\n ...files,\n ];\n\n const result = await runTool(\"npx\", [\"eslint\", ...args]);\n const diagnostics =\n result.stdout.trim() ? parseEslintJsonOutput(result.stdout) : [];\n\n return { diagnostics, exitCode: result.exitCode };\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n}\n","import { spawn } from \"node:child_process\";\n\nexport interface ToolRunResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport function runTool(\n command: string,\n args: string[],\n options?: { cwd?: string; timeout?: number },\n): Promise<ToolRunResult> {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n cwd: options?.cwd,\n timeout: options?.timeout ?? 60_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const chunks: Buffer[] = [];\n const errChunks: Buffer[] = [];\n\n child.stdout.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => errChunks.push(chunk));\n\n child.on(\"error\", (err) => {\n reject(new Error(`Failed to spawn ${command}: ${err.message}`));\n });\n\n child.on(\"close\", (code) => {\n resolve({\n stdout: Buffer.concat(chunks).toString(\"utf-8\"),\n stderr: Buffer.concat(errChunks).toString(\"utf-8\"),\n exitCode: code ?? 1,\n });\n });\n });\n}\n","import type { CheckDiagnostic } from \"../orchestrator/types.js\";\n\nconst RUFF_CODE_CATEGORY: Record<string, string> = {\n N: \"naming\",\n I: \"imports\",\n D: \"documentation\",\n C: \"complexity\",\n E: \"formatting\",\n W: \"formatting\",\n F: \"errors\",\n};\n\nconst ESLINT_RULE_CATEGORY: Record<string, string> = {\n \"@typescript-eslint/naming-convention\": \"naming\",\n \"unicorn/filename-case\": \"naming\",\n \"perfectionist/sort-imports\": \"imports\",\n \"import/order\": \"imports\",\n \"max-lines-per-function\": \"structure\",\n \"max-depth\": \"structure\",\n \"jsdoc/require-jsdoc\": \"documentation\",\n};\n\nfunction eslintSeverityToSeverity(eslintSeverity: number): \"error\" | \"warn\" {\n return eslintSeverity === 2 ? \"error\" : \"warn\";\n}\n\nfunction categorizeEslintRule(ruleId: string): string {\n if (ESLINT_RULE_CATEGORY[ruleId]) return ESLINT_RULE_CATEGORY[ruleId];\n if (ruleId.startsWith(\"@typescript-eslint/\")) return \"typescript\";\n if (ruleId.startsWith(\"jsdoc/\")) return \"documentation\";\n if (ruleId.startsWith(\"unicorn/\")) return \"style\";\n if (ruleId.startsWith(\"import/\")) return \"imports\";\n return \"other\";\n}\n\nfunction categorizeRuffCode(code: string): string {\n const prefix = code.replace(/[0-9]/g, \"\");\n return RUFF_CODE_CATEGORY[prefix] ?? \"other\";\n}\n\ninterface EslintJsonEntry {\n filePath: string;\n messages: Array<{\n ruleId: string | null;\n severity: number;\n message: string;\n line: number;\n column: number;\n fix?: { range: [number, number]; text: string } | null;\n }>;\n}\n\ninterface RuffJsonEntry {\n code: string;\n message: string;\n filename: string;\n location: { row: number; column: number };\n end_location: { row: number; column: number };\n fix?: {\n edits: Array<{\n content: string;\n location: { row: number; column: number };\n end_location: { row: number; column: number };\n }>;\n } | null;\n}\n\nexport function parseEslintJsonOutput(jsonStr: string): CheckDiagnostic[] {\n let entries: EslintJsonEntry[];\n try {\n entries = JSON.parse(jsonStr);\n } catch {\n throw new Error(\n `Failed to parse ESLint JSON output. Raw output:\\n${jsonStr.slice(0, 500)}`,\n );\n }\n const diagnostics: CheckDiagnostic[] = [];\n\n for (const entry of entries) {\n for (const msg of entry.messages) {\n if (!msg.ruleId) continue;\n diagnostics.push({\n file: entry.filePath,\n line: msg.line,\n column: msg.column,\n severity: eslintSeverityToSeverity(msg.severity),\n message: msg.message,\n category: categorizeEslintRule(msg.ruleId),\n rule: msg.ruleId,\n fixable: msg.fix != null,\n fix: msg.fix ?? undefined,\n });\n }\n }\n\n return diagnostics;\n}\n\nexport function parseRuffJsonOutput(jsonStr: string): CheckDiagnostic[] {\n let entries: RuffJsonEntry[];\n try {\n entries = JSON.parse(jsonStr);\n } catch {\n throw new Error(\n `Failed to parse Ruff JSON output. Raw output:\\n${jsonStr.slice(0, 500)}`,\n );\n }\n return entries.map((entry) => ({\n file: entry.filename,\n line: entry.location.row,\n column: entry.location.column,\n severity: \"warn\" as const,\n message: entry.message,\n category: categorizeRuffCode(entry.code),\n rule: entry.code,\n fixable: entry.fix != null,\n }));\n}\n\nexport function formatDiagnostic(d: CheckDiagnostic): string {\n return `${d.file}:${d.line}:${d.column} ${d.severity} ${d.message} [${d.category}.${d.rule}]`;\n}\n","import { writeFileSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { runTool } from \"./tool-runner.js\";\nimport { parseRuffJsonOutput } from \"../formatters/unified.js\";\nimport type { CheckDiagnostic } from \"../orchestrator/types.js\";\nimport type { RuffConfig } from \"../generators/ruff.js\";\n\ntype RuffLint = NonNullable<RuffConfig[\"lint\"]>;\n\nfunction quoteList(values: string[]): string {\n return `[${values.map((s) => `\"${s}\"`).join(\", \")}]`;\n}\n\nfunction lintSelectionLines(lint: RuffLint): string[] {\n const lines: string[] = [];\n if (lint.select) lines.push(`select = ${quoteList(lint.select)}`);\n if (lint.ignore) lines.push(`ignore = ${quoteList(lint.ignore)}`);\n return lines;\n}\n\nfunction lintSubsectionLines(lint: RuffLint): string[] {\n const lines: string[] = [];\n if (lint.mccabe) {\n lines.push(\"[lint.mccabe]\");\n if (lint.mccabe[\"max-complexity\"]) {\n lines.push(`max-complexity = ${lint.mccabe[\"max-complexity\"]}`);\n }\n }\n if (lint.pydocstyle) {\n lines.push(\"[lint.pydocstyle]\");\n if (lint.pydocstyle.convention) {\n lines.push(`convention = \"${lint.pydocstyle.convention}\"`);\n }\n }\n if (lint.isort) {\n lines.push(\"[lint.isort]\");\n if (lint.isort[\"section-order\"]) {\n lines.push(`section-order = ${quoteList(lint.isort[\"section-order\"])}`);\n }\n }\n return lines;\n}\n\nfunction toToml(config: RuffConfig): string {\n const lines: string[] = [];\n\n if (config[\"line-length\"]) {\n lines.push(`line-length = ${config[\"line-length\"]}`);\n }\n\n if (config.lint) {\n lines.push(\"[lint]\", ...lintSelectionLines(config.lint), ...lintSubsectionLines(config.lint));\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n\nexport async function runRuff(\n config: RuffConfig,\n files: string[],\n options?: { fix?: boolean },\n): Promise<{ diagnostics: CheckDiagnostic[]; exitCode: number }> {\n const tempDir = mkdtempSync(join(tmpdir(), \"codewatch-ruff-\"));\n const configPath = join(tempDir, \"ruff.toml\");\n\n try {\n writeFileSync(configPath, toToml(config), \"utf-8\");\n\n const args = [\n \"check\",\n \"--config\",\n configPath,\n \"--output-format\",\n \"json\",\n ...(options?.fix ? [\"--fix\"] : []),\n ...files,\n ];\n\n const result = await runTool(\"ruff\", args);\n const diagnostics =\n result.stdout.trim() ? parseRuffJsonOutput(result.stdout) : [];\n\n return { diagnostics, exitCode: result.exitCode };\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n}\n","import { generateEslintConfig } from \"../generators/eslint.js\";\nimport { generateRuffConfig } from \"../generators/ruff.js\";\nimport { runEslint } from \"../runners/eslint-runner.js\";\nimport { runRuff } from \"../runners/ruff-runner.js\";\nimport type {\n OrchestratorOptions,\n OrchestratorResult,\n CheckDiagnostic,\n} from \"./types.js\";\n\nfunction detectLanguage(files: string[]): \"typescript\" | \"python\" | \"mixed\" {\n const tsFiles = files.filter((f) => /\\.[tj]sx?$/.test(f));\n const pyFiles = files.filter((f) => /\\.py$/.test(f));\n if (tsFiles.length > 0 && pyFiles.length === 0) return \"typescript\";\n if (pyFiles.length > 0 && tsFiles.length === 0) return \"python\";\n return \"mixed\";\n}\n\nfunction buildSummary(\n diagnostics: CheckDiagnostic[],\n): OrchestratorResult[\"summary\"] {\n return {\n total: diagnostics.length,\n errors: diagnostics.filter((d) => d.severity === \"error\").length,\n warnings: diagnostics.filter((d) => d.severity === \"warn\").length,\n infos: diagnostics.filter((d) => d.severity === \"info\").length,\n fixed: 0,\n };\n}\n\nexport async function orchestrate(\n options: OrchestratorOptions,\n): Promise<OrchestratorResult> {\n const { profile, files, fix } = options;\n const language = options.language ?? detectLanguage(files);\n const allDiagnostics: CheckDiagnostic[] = [];\n\n const tsFiles = files.filter((f) => /\\.[tj]sx?$/.test(f));\n const pyFiles = files.filter((f) => /\\.py$/.test(f));\n\n if (\n (language === \"typescript\" || language === \"mixed\") &&\n tsFiles.length > 0\n ) {\n const eslintConfig = generateEslintConfig(profile);\n if (eslintConfig.length > 0) {\n const result = await runEslint(eslintConfig, tsFiles, { fix });\n allDiagnostics.push(...result.diagnostics);\n }\n }\n\n if ((language === \"python\" || language === \"mixed\") && pyFiles.length > 0) {\n const ruffConfig = generateRuffConfig(profile);\n if (ruffConfig.lint?.select && ruffConfig.lint.select.length > 0) {\n const result = await runRuff(ruffConfig, pyFiles, { fix });\n allDiagnostics.push(...result.diagnostics);\n }\n }\n\n return {\n diagnostics: allDiagnostics,\n summary: buildSummary(allDiagnostics),\n };\n}\n","import { PROFILE_CATEGORIES } from \"@titan-design/style-profile\";\nimport type { Profile, ProfileCategory } from \"@titan-design/style-profile\";\nimport type { Observation } from \"@titan-design/style-analyzer\";\n\nexport interface Deviation {\n file: string;\n line: number;\n rule: string;\n expected: string;\n found: string;\n severity: \"error\" | \"warn\" | \"info\";\n}\n\nexport interface DiffResult {\n deviations: Deviation[];\n summary: {\n total: number;\n matching: number;\n deviating: number;\n };\n}\n\nfunction getSeverity(\n confidence: number,\n thresholds: Profile[\"severityThresholds\"],\n): \"error\" | \"warn\" | \"info\" {\n if (confidence >= thresholds.error) return \"error\";\n if (confidence >= thresholds.warn) return \"warn\";\n return \"info\";\n}\n\nfunction resolveProfileRule(\n profile: Profile,\n observationType: string,\n): { convention: unknown; confidence: number } | undefined {\n const [category, rule] = observationType.split(\".\");\n if (!PROFILE_CATEGORIES.includes(category as ProfileCategory)) return undefined;\n const section = profile[category as ProfileCategory];\n if (!section || typeof section !== \"object\") return undefined;\n const ruleObj = rule === undefined ? undefined : section[rule];\n if (!ruleObj || typeof ruleObj !== \"object\") return undefined;\n if (ruleObj.convention === undefined || ruleObj.confidence === undefined) {\n return undefined;\n }\n return { convention: ruleObj.convention, confidence: ruleObj.confidence };\n}\n\nexport function diffAgainstProfile(\n profile: Profile,\n observations: Observation[],\n): DiffResult {\n const deviations: Deviation[] = [];\n let matching = 0;\n\n for (const obs of observations) {\n const profileRule = resolveProfileRule(profile, obs.type);\n if (!profileRule) continue;\n\n const expected = String(profileRule.convention);\n const found = String(obs.value);\n\n if (found === expected) {\n matching++;\n } else {\n deviations.push({\n file: obs.file,\n line: obs.line,\n rule: obs.type,\n expected,\n found,\n severity: getSeverity(\n profileRule.confidence,\n profile.severityThresholds,\n ),\n });\n }\n }\n\n return {\n deviations,\n summary: {\n total: observations.length,\n matching,\n deviating: deviations.length,\n },\n };\n}\n"],"mappings":"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","names":["writeFileSync","mkdtempSync","rmSync","join","tmpdir","mkdtempSync","join","tmpdir","writeFileSync","rmSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/generators/eslint.ts","../src/generators/ruff.ts","../src/runners/eslint-runner.ts","../src/runners/tool-runner.ts","../src/runners/failures.ts","../src/runners/eslint-plugins.ts","../src/runners/eslint-config-module.ts","../src/formatters/unified.ts","../src/runners/ruff-runner.ts","../src/orchestrator/orchestrate.ts","../src/profile-diff/diff-against-profile.ts"],"sourcesContent":["import type { Profile } from \"@titan-design/style-profile\";\nimport {\n buildNamingConventionRule,\n buildImportOrderRule,\n buildFunctionLengthRule,\n buildFileNamingRule,\n buildJsdocRules,\n} from \"@titan-design/style-profile\";\n\nexport interface EslintFlatConfigEntry {\n plugins?: Record<string, unknown>;\n rules?: Record<string, unknown>;\n files?: string[];\n}\n\n// ESLint accepts only off, warn and error; style-profile's \"info\" tier becomes warn, as its own exporter does.\nfunction withEslintSeverity(value: unknown): unknown {\n if (Array.isArray(value) && value[0] === \"info\") return [\"warn\", ...value.slice(1)];\n return value === \"info\" ? \"warn\" : value;\n}\n\nfunction collectRules(profile: Profile): Record<string, unknown> {\n const rules: Record<string, unknown> = {};\n\n const namingRule = buildNamingConventionRule(profile);\n if (namingRule) rules[namingRule[0]] = namingRule[1];\n\n const importRule = buildImportOrderRule(profile);\n if (importRule) rules[importRule[0]] = importRule[1];\n\n const fnLengthRule = buildFunctionLengthRule(profile);\n if (fnLengthRule) rules[fnLengthRule[0]] = fnLengthRule[1];\n\n const fileNamingRule = buildFileNamingRule(profile);\n if (fileNamingRule) rules[fileNamingRule[0]] = fileNamingRule[1];\n\n const jsdocRules = buildJsdocRules(profile);\n for (const [name, value] of jsdocRules) {\n rules[name] = value;\n }\n\n for (const name of Object.keys(rules)) rules[name] = withEslintSeverity(rules[name]);\n return rules;\n}\n\nexport function generateEslintConfig(profile: Profile): EslintFlatConfigEntry[] {\n const entries: EslintFlatConfigEntry[] = [];\n const rules = collectRules(profile);\n\n if (Object.keys(rules).length > 0) {\n entries.push({\n files: [\"**/*.ts\", \"**/*.tsx\"],\n rules,\n });\n }\n\n return entries;\n}\n","import type { Profile } from \"@titan-design/style-profile\";\n\nexport interface RuffConfig {\n lint?: {\n select?: string[];\n ignore?: string[];\n fixable?: string[];\n \"per-file-ignores\"?: Record<string, string[]>;\n mccabe?: { \"max-complexity\"?: number };\n pydocstyle?: { convention?: string };\n isort?: {\n \"known-first-party\"?: string[];\n \"section-order\"?: string[];\n };\n };\n \"line-length\"?: number;\n}\n\ntype RuffLint = NonNullable<RuffConfig[\"lint\"]> & { select: string[] };\n\nfunction addNaming(profile: Profile, lint: RuffLint): void {\n if (profile.naming) {\n const hasNamingRules = Object.values(profile.naming).some(\n (rule) => rule.confidence >= profile.severityThresholds.info,\n );\n if (hasNamingRules) lint.select.push(\"N\");\n }\n}\n\nfunction addImportOrder(profile: Profile, lint: RuffLint): void {\n const importOrder = profile.structure?.importOrder;\n if (importOrder && importOrder.confidence >= profile.severityThresholds.info) {\n lint.select.push(\"I\");\n if (Array.isArray(importOrder.convention)) {\n lint.isort = {\n \"section-order\": importOrder.convention as string[],\n };\n }\n }\n}\n\nfunction addFunctionDocs(profile: Profile, lint: RuffLint): void {\n const functionDocs = profile.documentation?.functionDocs;\n if (functionDocs && functionDocs.confidence >= profile.severityThresholds.info) {\n lint.select.push(\"D\");\n const conv = functionDocs.convention;\n if (\n typeof conv === \"string\" &&\n conv !== \"jsdoc-selective\" &&\n conv !== \"jsdoc-all\"\n ) {\n lint.pydocstyle = { convention: conv };\n }\n }\n}\n\nfunction addComplexity(profile: Profile, lint: RuffLint): void {\n const maxLines = profile.structure?.functionMaxLines;\n if (\n maxLines &&\n maxLines.confidence >= profile.severityThresholds.info &&\n typeof maxLines.convention === \"number\"\n ) {\n lint.select.push(\"C90\");\n lint.mccabe = { \"max-complexity\": maxLines.convention };\n }\n}\n\nexport function generateRuffConfig(profile: Profile): RuffConfig {\n const lint: RuffLint = { select: [] };\n const config: RuffConfig = { lint };\n\n addNaming(profile, lint);\n addImportOrder(profile, lint);\n addFunctionDocs(profile, lint);\n addComplexity(profile, lint);\n\n const lineLength = profile.formatting?.lineLength;\n if (lineLength && typeof lineLength.convention === \"number\") {\n config[\"line-length\"] = lineLength.convention;\n }\n\n return config;\n}\n","import { writeFileSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { runAndClassify, parseOrFail } from \"./failures.js\";\nimport { buildEslintConfigModule } from \"./eslint-config-module.js\";\nimport { parseEslintJsonOutput } from \"../formatters/unified.js\";\nimport type { CheckDiagnostic, ToolFailure } from \"../orchestrator/types.js\";\nimport type { EslintFlatConfigEntry } from \"../generators/eslint.js\";\nimport type { RunnerOptions, RunnerResult } from \"./types.js\";\n\ninterface EslintJsonMessage {\n ruleId: string | null;\n fatal?: boolean;\n message: string;\n}\n\n// ESLint reports a file it could not lint as a rule-less message: a fatal parse error, or \"File ignored ...\".\nfunction uncheckedFiles(stdout: string): ToolFailure[] {\n const entries = JSON.parse(stdout) as Array<{ filePath: string; messages: EslintJsonMessage[] }>;\n return entries.flatMap((entry) =>\n entry.messages\n .filter((m) => m.ruleId === null && (m.fatal === true || m.message.startsWith(\"File ignored\")))\n .map((m) => ({ tool: \"eslint\" as const, kind: \"file-not-checked\" as const, file: entry.filePath, message: m.message })),\n );\n}\n\nfunction readEslintOutput(stdout: string): { diagnostics: CheckDiagnostic[]; failures: ToolFailure[] } {\n const parsed = parseOrFail(\"eslint\", () => ({\n diagnostics: parseEslintJsonOutput(stdout),\n failures: uncheckedFiles(stdout),\n }));\n return parsed.ok ? parsed.value : { diagnostics: [], failures: [parsed.failure] };\n}\n\nfunction eslintArgs(configPath: string, files: string[], fix?: boolean): string[] {\n // --no stops npx from downloading an ESLint the project has not installed.\n return [\"--no\", \"--\", \"eslint\", \"--config\", configPath, \"--format\", \"json\", ...(fix ? [\"--fix\"] : []), ...files];\n}\n\nexport async function runEslint(\n config: EslintFlatConfigEntry[],\n files: string[],\n options?: RunnerOptions,\n): Promise<RunnerResult> {\n const cwd = options?.cwd ?? process.cwd();\n const built = buildEslintConfigModule(config, cwd);\n if (!built.ok) return { diagnostics: [], exitCode: null, failures: [built.failure], skippedRules: [] };\n const { skippedRules } = built;\n if (built.ruleCount === 0) return { diagnostics: [], exitCode: null, failures: [], skippedRules };\n\n const tempDir = mkdtempSync(join(tmpdir(), \"codewatch-eslint-\"));\n const configPath = join(tempDir, \"eslint.config.mjs\");\n try {\n writeFileSync(configPath, built.source, \"utf-8\");\n const run = await runAndClassify(\"eslint\", \"npx\", eslintArgs(configPath, files, options?.fix), {\n cwd,\n timeout: options?.timeout,\n });\n if (!run.ok) return { diagnostics: [], exitCode: run.exitCode, failures: [run.failure], skippedRules };\n return { ...readEslintOutput(run.stdout), exitCode: run.exitCode, skippedRules };\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n}\n","import { spawn } from \"node:child_process\";\n\nexport interface ToolRunResult {\n stdout: string;\n stderr: string;\n /** Null when the child was killed by a signal; see `signal`. */\n exitCode: number | null;\n signal: NodeJS.Signals | null;\n timedOut: boolean;\n}\n\nexport function runTool(\n command: string,\n args: string[],\n options?: { cwd?: string; timeout?: number },\n): Promise<ToolRunResult> {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n cwd: options?.cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const chunks: Buffer[] = [];\n const errChunks: Buffer[] = [];\n let timedOut = false;\n // Our own timer, because spawn's `timeout` option kills without telling us it did.\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGKILL\");\n }, options?.timeout ?? 60_000);\n\n child.stdout.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => errChunks.push(chunk));\n\n child.on(\"error\", (err) => {\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${command}: ${err.message}`));\n });\n\n child.on(\"close\", (code, signal) => {\n clearTimeout(timer);\n resolve({\n stdout: Buffer.concat(chunks).toString(\"utf-8\"),\n stderr: Buffer.concat(errChunks).toString(\"utf-8\"),\n exitCode: code,\n signal,\n timedOut,\n });\n });\n });\n}\n","import { runTool } from \"./tool-runner.js\";\nimport type { ToolRunResult } from \"./tool-runner.js\";\nimport type { ToolFailure, ToolName } from \"../orchestrator/types.js\";\n\n// ESLint and ruff both document 0 (clean) and 1 (problems found) as successful runs.\nconst SUCCESS_EXIT_CODES = new Set([0, 1]);\n\nexport type ToolRun =\n | { ok: true; stdout: string; stderr: string; exitCode: number }\n | { ok: false; failure: ToolFailure; exitCode: number | null };\n\nfunction excerpt(text: string): string {\n const trimmed = text.trim();\n return trimmed.length > 500 ? `${trimmed.slice(0, 500)}…` : trimmed;\n}\n\nfunction withStderr(message: string, stderr: string): string {\n const detail = excerpt(stderr);\n return detail ? `${message}: ${detail}` : message;\n}\n\nexport function classifyRun(tool: ToolName, run: ToolRunResult): ToolRun {\n const fail = (kind: ToolFailure[\"kind\"], message: string): ToolRun => ({\n ok: false,\n failure: { tool, kind, message: withStderr(message, run.stderr) },\n exitCode: run.exitCode,\n });\n if (run.timedOut) return fail(\"timeout\", `${tool} timed out and was killed`);\n if (run.exitCode === null) {\n return fail(\"signal\", `${tool} was killed by ${run.signal ?? \"a signal\"}`);\n }\n if (!SUCCESS_EXIT_CODES.has(run.exitCode)) {\n return fail(\"exit-code\", `${tool} exited with code ${run.exitCode}`);\n }\n if (!run.stdout.trim()) {\n return fail(\"unparseable-output\", `${tool} exited ${run.exitCode} with no output`);\n }\n return { ok: true, stdout: run.stdout, stderr: run.stderr, exitCode: run.exitCode };\n}\n\nexport async function runAndClassify(\n tool: ToolName,\n command: string,\n args: string[],\n options?: { cwd?: string; timeout?: number },\n): Promise<ToolRun> {\n try {\n return classifyRun(tool, await runTool(command, args, options));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { ok: false, failure: { tool, kind: \"spawn-failed\", message }, exitCode: null };\n }\n}\n\nexport function parseOrFail<T>(\n tool: ToolName,\n parse: () => T,\n): { ok: true; value: T } | { ok: false; failure: ToolFailure } {\n try {\n return { ok: true, value: parse() };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { ok: false, failure: { tool, kind: \"unparseable-output\", message } };\n }\n}\n","import { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nexport interface ModuleSource {\n specifier: string;\n member?: \"plugin\" | \"parser\";\n}\n\nexport interface ResolvedModule {\n url: string;\n member?: \"plugin\" | \"parser\";\n}\n\n// The typescript-eslint meta-package is the fallback because under pnpm it is often the only one a project can resolve.\nexport const PLUGIN_SOURCES: Record<string, ModuleSource[]> = {\n \"@typescript-eslint\": [\n { specifier: \"@typescript-eslint/eslint-plugin\" },\n { specifier: \"typescript-eslint\", member: \"plugin\" },\n ],\n perfectionist: [{ specifier: \"eslint-plugin-perfectionist\" }],\n unicorn: [{ specifier: \"eslint-plugin-unicorn\" }],\n jsdoc: [{ specifier: \"eslint-plugin-jsdoc\" }],\n};\n\nexport const PARSER_SOURCES: ModuleSource[] = [\n { specifier: \"@typescript-eslint/parser\" },\n { specifier: \"typescript-eslint\", member: \"parser\" },\n];\n\nexport function pluginNamespace(rule: string): string | null {\n const slash = rule.lastIndexOf(\"/\");\n return slash === -1 ? null : rule.slice(0, slash);\n}\n\nexport function describeSources(sources: ModuleSource[]): string {\n return sources.map((s) => s.specifier).join(\" or \");\n}\n\nexport function resolveModule(\n sources: ModuleSource[],\n fromDir: string,\n): ResolvedModule | null {\n // Resolve from the project, not from this package: ESLint runs there and loads plugins from there.\n const require = createRequire(join(fromDir, \"package.json\"));\n for (const source of sources) {\n try {\n const url = pathToFileURL(require.resolve(source.specifier)).href;\n return { url, member: source.member };\n } catch {\n continue;\n }\n }\n return null;\n}\n","import {\n PARSER_SOURCES,\n PLUGIN_SOURCES,\n describeSources,\n pluginNamespace,\n resolveModule,\n} from \"./eslint-plugins.js\";\nimport type { ResolvedModule } from \"./eslint-plugins.js\";\nimport type { EslintFlatConfigEntry } from \"../generators/eslint.js\";\nimport type { SkippedRule, ToolFailure } from \"../orchestrator/types.js\";\n\nexport type EslintConfigModule =\n | { ok: true; source: string; ruleCount: number; skippedRules: SkippedRule[] }\n | { ok: false; failure: ToolFailure };\n\ninterface ModuleContext {\n fromDir: string;\n imports: string[];\n ids: Map<string, string>;\n plugins: Map<string, ResolvedModule | null>;\n}\n\nfunction importExpr(ctx: ModuleContext, mod: ResolvedModule): string {\n let id = ctx.ids.get(mod.url);\n if (!id) {\n id = `m${ctx.ids.size}`;\n ctx.ids.set(mod.url, id);\n ctx.imports.push(`import ${id} from ${JSON.stringify(mod.url)};`);\n }\n return mod.member ? `${id}.${mod.member}` : id;\n}\n\nfunction resolvePlugin(ctx: ModuleContext, namespace: string): ResolvedModule | null {\n if (!ctx.plugins.has(namespace)) {\n const sources = PLUGIN_SOURCES[namespace];\n ctx.plugins.set(namespace, sources ? resolveModule(sources, ctx.fromDir) : null);\n }\n return ctx.plugins.get(namespace) ?? null;\n}\n\nfunction skipReason(ctx: ModuleContext, namespace: string): string {\n const sources = PLUGIN_SOURCES[namespace];\n if (!sources) return `no known package provides the ESLint plugin \"${namespace}\"`;\n return `${describeSources(sources)} is not installed in ${ctx.fromDir}`;\n}\n\nfunction renderEntry(\n ctx: ModuleContext,\n entry: EslintFlatConfigEntry,\n parserExpr: string,\n skipped: SkippedRule[],\n): { text: string; ruleCount: number } {\n const rules: Record<string, unknown> = {};\n const plugins: string[] = [];\n for (const [rule, value] of Object.entries(entry.rules ?? {})) {\n const namespace = pluginNamespace(rule);\n const plugin = namespace === null ? null : resolvePlugin(ctx, namespace);\n if (namespace !== null && !plugin) {\n skipped.push({ tool: \"eslint\", rule, plugin: namespace, reason: skipReason(ctx, namespace) });\n continue;\n }\n rules[rule] = value;\n if (namespace !== null && plugin) plugins.push(`${JSON.stringify(namespace)}: ${importExpr(ctx, plugin)}`);\n }\n const data = JSON.stringify({ files: entry.files, rules });\n const text = `{ ...${data}, languageOptions: { parser: ${parserExpr} }, plugins: { ${[...new Set(plugins)].join(\", \")} } }`;\n return { text, ruleCount: Object.keys(rules).length };\n}\n\nexport function buildEslintConfigModule(\n config: EslintFlatConfigEntry[],\n fromDir: string,\n): EslintConfigModule {\n const parser = resolveModule(PARSER_SOURCES, fromDir);\n if (!parser) {\n const message = `${describeSources(PARSER_SOURCES)} is not installed in ${fromDir}, so ESLint cannot parse TypeScript`;\n return { ok: false, failure: { tool: \"eslint\", kind: \"missing-dependency\", message } };\n }\n const ctx: ModuleContext = { fromDir, imports: [], ids: new Map(), plugins: new Map() };\n const parserExpr = importExpr(ctx, parser);\n const skippedRules: SkippedRule[] = [];\n const entries = config.map((entry) => renderEntry(ctx, entry, parserExpr, skippedRules));\n const ruleCount = entries.reduce((sum, e) => sum + e.ruleCount, 0);\n const body = entries.map((e) => e.text).join(\",\\n \");\n const source = `${ctx.imports.join(\"\\n\")}\\n\\nexport default [\\n ${body},\\n];\\n`;\n return { ok: true, source, ruleCount, skippedRules };\n}\n","import type { CheckDiagnostic } from \"../orchestrator/types.js\";\n\nconst RUFF_CODE_CATEGORY: Record<string, string> = {\n N: \"naming\",\n I: \"imports\",\n D: \"documentation\",\n C: \"complexity\",\n E: \"formatting\",\n W: \"formatting\",\n F: \"errors\",\n};\n\nconst ESLINT_RULE_CATEGORY: Record<string, string> = {\n \"@typescript-eslint/naming-convention\": \"naming\",\n \"unicorn/filename-case\": \"naming\",\n \"perfectionist/sort-imports\": \"imports\",\n \"import/order\": \"imports\",\n \"max-lines-per-function\": \"structure\",\n \"max-depth\": \"structure\",\n \"jsdoc/require-jsdoc\": \"documentation\",\n};\n\nfunction eslintSeverityToSeverity(eslintSeverity: number): \"error\" | \"warn\" {\n return eslintSeverity === 2 ? \"error\" : \"warn\";\n}\n\nfunction categorizeEslintRule(ruleId: string): string {\n if (ESLINT_RULE_CATEGORY[ruleId]) return ESLINT_RULE_CATEGORY[ruleId];\n if (ruleId.startsWith(\"@typescript-eslint/\")) return \"typescript\";\n if (ruleId.startsWith(\"jsdoc/\")) return \"documentation\";\n if (ruleId.startsWith(\"unicorn/\")) return \"style\";\n if (ruleId.startsWith(\"import/\")) return \"imports\";\n return \"other\";\n}\n\nfunction categorizeRuffCode(code: string): string {\n const prefix = code.replace(/[0-9]/g, \"\");\n return RUFF_CODE_CATEGORY[prefix] ?? \"other\";\n}\n\ninterface EslintJsonEntry {\n filePath: string;\n messages: Array<{\n ruleId: string | null;\n severity: number;\n message: string;\n line: number;\n column: number;\n fix?: { range: [number, number]; text: string } | null;\n }>;\n}\n\ninterface RuffJsonEntry {\n code: string | null;\n message: string;\n filename: string;\n location: { row: number; column: number };\n end_location: { row: number; column: number };\n fix?: {\n edits: Array<{\n content: string;\n location: { row: number; column: number };\n end_location: { row: number; column: number };\n }>;\n } | null;\n}\n\nexport function parseEslintJsonOutput(jsonStr: string): CheckDiagnostic[] {\n let entries: EslintJsonEntry[];\n try {\n entries = JSON.parse(jsonStr);\n } catch {\n throw new Error(\n `Failed to parse ESLint JSON output. Raw output:\\n${jsonStr.slice(0, 500)}`,\n );\n }\n const diagnostics: CheckDiagnostic[] = [];\n\n for (const entry of entries) {\n for (const msg of entry.messages) {\n if (!msg.ruleId) continue;\n diagnostics.push({\n file: entry.filePath,\n line: msg.line,\n column: msg.column,\n severity: eslintSeverityToSeverity(msg.severity),\n message: msg.message,\n category: categorizeEslintRule(msg.ruleId),\n rule: msg.ruleId,\n fixable: msg.fix != null,\n fix: msg.fix ?? undefined,\n });\n }\n }\n\n return diagnostics;\n}\n\n// ruff 0.16.8 reports a syntax error as code \"invalid-syntax\"; 0.9.10 used a null code.\nexport function isRuffSyntaxError(code: string | null): code is \"invalid-syntax\" | null {\n return code === null || code === \"invalid-syntax\";\n}\n\nexport function parseRuffJsonOutput(jsonStr: string): CheckDiagnostic[] {\n let entries: RuffJsonEntry[];\n try {\n entries = JSON.parse(jsonStr);\n } catch {\n throw new Error(\n `Failed to parse Ruff JSON output. Raw output:\\n${jsonStr.slice(0, 500)}`,\n );\n }\n return entries.flatMap((entry) => isRuffSyntaxError(entry.code) ? [] : [{\n file: entry.filename,\n line: entry.location.row,\n column: entry.location.column,\n severity: \"warn\" as const,\n message: entry.message,\n category: categorizeRuffCode(entry.code),\n rule: entry.code,\n fixable: entry.fix != null,\n }]);\n}\n\nexport function formatDiagnostic(d: CheckDiagnostic): string {\n return `${d.file}:${d.line}:${d.column} ${d.severity} ${d.message} [${d.category}.${d.rule}]`;\n}\n","import { writeFileSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { runAndClassify, parseOrFail } from \"./failures.js\";\nimport { isRuffSyntaxError, parseRuffJsonOutput } from \"../formatters/unified.js\";\nimport type { CheckDiagnostic, ToolFailure } from \"../orchestrator/types.js\";\nimport type { RunnerOptions, RunnerResult } from \"./types.js\";\nimport type { RuffConfig } from \"../generators/ruff.js\";\n\ntype RuffLint = NonNullable<RuffConfig[\"lint\"]>;\n\nfunction quoteList(values: string[]): string {\n return `[${values.map((s) => `\"${s}\"`).join(\", \")}]`;\n}\n\nfunction lintSelectionLines(lint: RuffLint): string[] {\n const lines: string[] = [];\n if (lint.select) lines.push(`select = ${quoteList(lint.select)}`);\n if (lint.ignore) lines.push(`ignore = ${quoteList(lint.ignore)}`);\n return lines;\n}\n\nfunction lintSubsectionLines(lint: RuffLint): string[] {\n const lines: string[] = [];\n if (lint.mccabe) {\n lines.push(\"[lint.mccabe]\");\n if (lint.mccabe[\"max-complexity\"]) {\n lines.push(`max-complexity = ${lint.mccabe[\"max-complexity\"]}`);\n }\n }\n if (lint.pydocstyle) {\n lines.push(\"[lint.pydocstyle]\");\n if (lint.pydocstyle.convention) {\n lines.push(`convention = \"${lint.pydocstyle.convention}\"`);\n }\n }\n if (lint.isort) {\n lines.push(\"[lint.isort]\");\n if (lint.isort[\"section-order\"]) {\n lines.push(`section-order = ${quoteList(lint.isort[\"section-order\"])}`);\n }\n }\n return lines;\n}\n\nfunction toToml(config: RuffConfig): string {\n const lines: string[] = [];\n\n if (config[\"line-length\"]) {\n lines.push(`line-length = ${config[\"line-length\"]}`);\n }\n\n if (config.lint) {\n lines.push(\"[lint]\", ...lintSelectionLines(config.lint), ...lintSubsectionLines(config.lint));\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n\ninterface RuffJsonMessage {\n code: string | null;\n filename: string;\n message: string;\n}\n\nfunction notChecked(file: string, message: string): ToolFailure {\n return { tool: \"ruff\", kind: \"file-not-checked\", file, message };\n}\n\n// ruff emits one entry per syntax error; a file that fails to parse is reported once.\nfunction unparsedFiles(stdout: string): ToolFailure[] {\n const byFile = new Map<string, string[]>();\n for (const e of JSON.parse(stdout) as RuffJsonMessage[]) {\n if (!isRuffSyntaxError(e.code)) continue;\n byFile.set(e.filename, [...(byFile.get(e.filename) ?? []), e.message]);\n }\n return [...byFile].map(([file, messages]) => notChecked(file, messages.join(\"; \")));\n}\n\n// ruff 0.16.8 flags an unreadable path (exit 0, \"[]\") only in this stderr wording; if ruff rewords it, the case goes silent again.\nfunction unreadFiles(stderr: string): ToolFailure[] {\n return [...stderr.matchAll(/^warning: Failed to lint (.+?): (.+)$/gm)].map((m) => notChecked(m[1]!, m[2]!));\n}\n\nfunction readRuffOutput(stdout: string, stderr: string): { diagnostics: CheckDiagnostic[]; failures: ToolFailure[] } {\n const parsed = parseOrFail(\"ruff\", () => ({\n diagnostics: parseRuffJsonOutput(stdout),\n failures: [...unparsedFiles(stdout), ...unreadFiles(stderr)],\n }));\n return parsed.ok ? parsed.value : { diagnostics: [], failures: [parsed.failure] };\n}\n\nexport async function runRuff(\n config: RuffConfig,\n files: string[],\n options?: RunnerOptions,\n): Promise<RunnerResult> {\n const tempDir = mkdtempSync(join(tmpdir(), \"codewatch-ruff-\"));\n const configPath = join(tempDir, \"ruff.toml\");\n\n try {\n writeFileSync(configPath, toToml(config), \"utf-8\");\n\n const args = [\n \"check\",\n \"--config\",\n configPath,\n \"--output-format\",\n \"json\",\n ...(options?.fix ? [\"--fix\"] : []),\n ...files,\n ];\n\n const run = await runAndClassify(\"ruff\", \"ruff\", args, { cwd: options?.cwd, timeout: options?.timeout });\n if (!run.ok) return { diagnostics: [], exitCode: run.exitCode, failures: [run.failure], skippedRules: [] };\n return { ...readRuffOutput(run.stdout, run.stderr), exitCode: run.exitCode, skippedRules: [] };\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n}\n","import { generateEslintConfig } from \"../generators/eslint.js\";\nimport { generateRuffConfig } from \"../generators/ruff.js\";\nimport { runEslint } from \"../runners/eslint-runner.js\";\nimport { runRuff } from \"../runners/ruff-runner.js\";\nimport type { RunnerResult } from \"../runners/types.js\";\nimport type {\n OrchestratorOptions,\n OrchestratorResult,\n CheckDiagnostic,\n} from \"./types.js\";\n\nfunction detectLanguage(files: string[]): \"typescript\" | \"python\" | \"mixed\" {\n const tsFiles = files.filter((f) => /\\.[tj]sx?$/.test(f));\n const pyFiles = files.filter((f) => /\\.py$/.test(f));\n if (tsFiles.length > 0 && pyFiles.length === 0) return \"typescript\";\n if (pyFiles.length > 0 && tsFiles.length === 0) return \"python\";\n return \"mixed\";\n}\n\nfunction buildSummary(\n diagnostics: CheckDiagnostic[],\n): OrchestratorResult[\"summary\"] {\n return {\n total: diagnostics.length,\n errors: diagnostics.filter((d) => d.severity === \"error\").length,\n warnings: diagnostics.filter((d) => d.severity === \"warn\").length,\n infos: diagnostics.filter((d) => d.severity === \"info\").length,\n fixed: 0,\n };\n}\n\nasync function runTools(options: OrchestratorOptions): Promise<RunnerResult[]> {\n const { profile, files, fix } = options;\n const language = options.language ?? detectLanguage(files);\n const tsFiles = files.filter((f) => /\\.[tj]sx?$/.test(f));\n const pyFiles = files.filter((f) => /\\.py$/.test(f));\n const results: RunnerResult[] = [];\n\n if ((language === \"typescript\" || language === \"mixed\") && tsFiles.length > 0) {\n const eslintConfig = generateEslintConfig(profile);\n if (eslintConfig.length > 0) results.push(await runEslint(eslintConfig, tsFiles, { fix }));\n }\n\n if ((language === \"python\" || language === \"mixed\") && pyFiles.length > 0) {\n const ruffConfig = generateRuffConfig(profile);\n if (ruffConfig.lint?.select && ruffConfig.lint.select.length > 0) {\n results.push(await runRuff(ruffConfig, pyFiles, { fix }));\n }\n }\n return results;\n}\n\nexport async function orchestrate(\n options: OrchestratorOptions,\n): Promise<OrchestratorResult> {\n const results = await runTools(options);\n const diagnostics = results.flatMap((r) => r.diagnostics);\n return {\n diagnostics,\n failures: results.flatMap((r) => r.failures),\n skippedRules: results.flatMap((r) => r.skippedRules),\n summary: buildSummary(diagnostics),\n };\n}\n","import { PROFILE_CATEGORIES } from \"@titan-design/style-profile\";\nimport type { Profile, ProfileCategory } from \"@titan-design/style-profile\";\nimport type { Observation } from \"@titan-design/style-analyzer\";\n\nexport interface Deviation {\n file: string;\n line: number;\n rule: string;\n expected: string;\n found: string;\n severity: \"error\" | \"warn\" | \"info\";\n}\n\nexport interface DiffResult {\n deviations: Deviation[];\n summary: {\n total: number;\n matching: number;\n deviating: number;\n };\n}\n\nfunction getSeverity(\n confidence: number,\n thresholds: Profile[\"severityThresholds\"],\n): \"error\" | \"warn\" | \"info\" {\n if (confidence >= thresholds.error) return \"error\";\n if (confidence >= thresholds.warn) return \"warn\";\n return \"info\";\n}\n\nfunction resolveProfileRule(\n profile: Profile,\n observationType: string,\n): { convention: unknown; confidence: number } | undefined {\n const [category, rule] = observationType.split(\".\");\n if (!PROFILE_CATEGORIES.includes(category as ProfileCategory)) return undefined;\n const section = profile[category as ProfileCategory];\n if (!section || typeof section !== \"object\") return undefined;\n const ruleObj = rule === undefined ? undefined : section[rule];\n if (!ruleObj || typeof ruleObj !== \"object\") return undefined;\n if (ruleObj.convention === undefined || ruleObj.confidence === undefined) {\n return undefined;\n }\n return { convention: ruleObj.convention, confidence: ruleObj.confidence };\n}\n\nexport function diffAgainstProfile(\n profile: Profile,\n observations: Observation[],\n): DiffResult {\n const deviations: Deviation[] = [];\n let matching = 0;\n\n for (const obs of observations) {\n const profileRule = resolveProfileRule(profile, obs.type);\n if (!profileRule) continue;\n\n const expected = String(profileRule.convention);\n const found = String(obs.value);\n\n if (found === expected) {\n matching++;\n } else {\n deviations.push({\n file: obs.file,\n line: obs.line,\n rule: obs.type,\n expected,\n found,\n severity: getSeverity(\n profileRule.confidence,\n profile.severityThresholds,\n ),\n });\n }\n }\n\n return {\n deviations,\n summary: {\n total: observations.length,\n matching,\n deviating: deviations.length,\n },\n };\n}\n"],"mappings":"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","names":["join","require","join","writeFileSync","mkdtempSync","rmSync","join","tmpdir","mkdtempSync","join","tmpdir","writeFileSync","rmSync"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@titan-design/style-checker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Run external lint tools (ruff, ESLint) against configs generated from a style profile, normalize their output into one diagnostic shape, and diff observations against a profile",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,13 +31,16 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@titan-design/style-analyzer": "^0.
|
|
35
|
-
"@titan-design/style-profile": "^0.
|
|
34
|
+
"@titan-design/style-analyzer": "^0.1.0",
|
|
35
|
+
"@titan-design/style-profile": "^0.1.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"zod": "^4.0.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
|
42
|
+
"@typescript-eslint/parser": "^8.46.0",
|
|
43
|
+
"eslint": "^9.39.0",
|
|
41
44
|
"zod": "^4.4.3"
|
|
42
45
|
},
|
|
43
46
|
"scripts": {
|