@geonosis/lint-parity 0.4.0 → 1.0.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 +28 -0
- package/dist/{chunk-OLCJQ6JC.js → chunk-3PCNMLVE.js} +107 -13
- package/dist/index.d.ts +42 -2
- package/dist/index.js +3 -1
- package/dist/parity-cli.js +20 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ pnpm add -D @geonosis/lint-parity oxlint
|
|
|
18
18
|
```bash
|
|
19
19
|
geonosis-lint-parity --a <config-a.json> --b <config-b.json> [--oxlint <path>] [--out <dir>] -- <paths…>
|
|
20
20
|
geonosis-lint-parity --corpus <dir> --a <config-a.json> --b <config-b.json> [--out <dir>]
|
|
21
|
+
[--expect-changed <rule,rule,…>]
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
**Write both configs inside the workspace being linted** — `<ws>/.oxlintrc.parity-a.json`, deleted
|
|
@@ -63,6 +64,33 @@ import { manifestOf, MANIFEST_FILE } from '@geonosis/lint-parity'
|
|
|
63
64
|
writeFileSync(join(corpus, MANIFEST_FILE), `${JSON.stringify(manifestOf(plugin), null, 2)}\n`)
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
## And a release's claims, checked
|
|
68
|
+
|
|
69
|
+
A changelog line that names a rule is a claim. `--expect-changed` makes it data the corpus judges:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
geonosis-lint-parity --corpus node_modules/<plugin>/corpus \
|
|
73
|
+
--a oxlintrc.old.json --b oxlintrc.new.json \
|
|
74
|
+
--expect-changed some-rule,another-rule
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
It exits 1 unless the corpus shows **exactly** those rules behaving differently under the two
|
|
78
|
+
configs — different reach, or a different number of findings:
|
|
79
|
+
|
|
80
|
+
| verdict | means |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `HELD` | claimed, and the corpus shows it changing |
|
|
83
|
+
| `UNPROVEN` | claimed, and the corpus shows nothing — including a rule the corpus does not exercise |
|
|
84
|
+
| `UNCLAIMED` | the corpus shows it changing and the release never said so |
|
|
85
|
+
|
|
86
|
+
The report prints the claims table beside the reach table. Without the flag no claim is made and none
|
|
87
|
+
is judged, which is what a corpus run has always done.
|
|
88
|
+
|
|
89
|
+
Why it exists: a release once announced a behaviour widening for **ten** rules while its corpus
|
|
90
|
+
carried evidence for **two**, and the parity run offered as proof went over a consumer tree that had
|
|
91
|
+
no such violation in it — so every number matched. The claim is the thing to check, and the corpus is
|
|
92
|
+
what checks it.
|
|
93
|
+
|
|
66
94
|
## Then parity
|
|
67
95
|
|
|
68
96
|
It runs oxlint twice with `--format=unix` over the same paths, strips the working-directory prefix,
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
// src/parity.ts
|
|
2
2
|
import { spawnSync } from "child_process";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
cpSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
writeFileSync
|
|
11
|
+
} from "fs";
|
|
4
12
|
import { tmpdir } from "os";
|
|
5
|
-
import { join } from "path";
|
|
13
|
+
import { dirname, join, resolve } from "path";
|
|
6
14
|
var ANSI = /\[[0-9;]*m/g;
|
|
7
15
|
var SUMMARY_COUNT = /^(\d+) problems?$/m;
|
|
8
16
|
var RULE_IN_LINE = /\[(?:Error|Warning)\/([^\]]+)\]$/;
|
|
9
17
|
var UNIX_FINDING = /^\S[^\n]*:\d+:\d+: .*\[(?:Error|Warning)\/[^\]\n]+\]$/;
|
|
10
18
|
var FINDING_PLACE = /^(\S[^\n]*?:\d+:\d+): .*\[(?:Error|Warning)\/([^\]\n]+)\]$/;
|
|
19
|
+
var resolveOxlint = (from) => {
|
|
20
|
+
let dir = from;
|
|
21
|
+
for (; ; ) {
|
|
22
|
+
const bin = resolve(dir, "node_modules/.bin/oxlint");
|
|
23
|
+
if (existsSync(bin)) return bin;
|
|
24
|
+
const parent = dirname(dir);
|
|
25
|
+
if (parent === dir) return "oxlint";
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
11
29
|
var ParityError = class extends Error {
|
|
12
30
|
constructor(message) {
|
|
13
31
|
super(message);
|
|
@@ -152,6 +170,27 @@ var manifestOf = (plugin) => ({
|
|
|
152
170
|
plugin: plugin.meta.name,
|
|
153
171
|
rules: Object.keys(plugin.rules).map((rule) => `${plugin.meta.name}/${rule}`).toSorted()
|
|
154
172
|
});
|
|
173
|
+
var namesRule = (claim, rule) => {
|
|
174
|
+
if (claim === rule) return true;
|
|
175
|
+
const at = rule.indexOf("/");
|
|
176
|
+
return at !== -1 && rule.slice(at + 1) === claim;
|
|
177
|
+
};
|
|
178
|
+
var claimsOver = (reach, expectChanged) => {
|
|
179
|
+
if (expectChanged === void 0) return [];
|
|
180
|
+
const claimed = (rule) => expectChanged.some((one) => namesRule(one, rule));
|
|
181
|
+
const judged = reach.filter((one) => one.changed || claimed(one.rule)).map((one) => ({
|
|
182
|
+
changed: one.changed,
|
|
183
|
+
claimed: claimed(one.rule),
|
|
184
|
+
rule: one.rule,
|
|
185
|
+
verdict: verdictOf(one.changed, claimed(one.rule))
|
|
186
|
+
}));
|
|
187
|
+
const outsideReach = expectChanged.filter((claim) => !reach.some((one) => namesRule(claim, one.rule))).map((rule) => ({ changed: false, claimed: true, rule, verdict: "unproven" }));
|
|
188
|
+
return [...judged, ...outsideReach];
|
|
189
|
+
};
|
|
190
|
+
var verdictOf = (changed, claimed) => {
|
|
191
|
+
if (changed && claimed) return "held";
|
|
192
|
+
return claimed ? "unproven" : "unclaimed";
|
|
193
|
+
};
|
|
155
194
|
var readManifest = (corpus) => {
|
|
156
195
|
const path = join(corpus, MANIFEST_FILE);
|
|
157
196
|
let parsed;
|
|
@@ -170,14 +209,16 @@ var corpusOf = ({
|
|
|
170
209
|
configA,
|
|
171
210
|
configB,
|
|
172
211
|
corpus,
|
|
212
|
+
expectChanged,
|
|
173
213
|
oxlint
|
|
174
214
|
}) => {
|
|
175
215
|
const manifest = readManifest(corpus);
|
|
176
216
|
const exercised = new Set(manifest.rules);
|
|
177
217
|
const root = mkdtempSync(join(tmpdir(), "geonosis-corpus-"));
|
|
178
218
|
const here = join(root, "corpus");
|
|
179
|
-
const
|
|
180
|
-
|
|
219
|
+
const foundIn = (config, side) => {
|
|
220
|
+
const counts = /* @__PURE__ */ new Map();
|
|
221
|
+
for (const finding of readRun(
|
|
181
222
|
lint({
|
|
182
223
|
config,
|
|
183
224
|
cwd: root,
|
|
@@ -187,29 +228,67 @@ var corpusOf = ({
|
|
|
187
228
|
}),
|
|
188
229
|
root,
|
|
189
230
|
side
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
231
|
+
)) {
|
|
232
|
+
const rule = ruleIdOf(finding);
|
|
233
|
+
counts.set(rule, (counts.get(rule) ?? 0) + 1);
|
|
234
|
+
}
|
|
235
|
+
return counts;
|
|
236
|
+
};
|
|
237
|
+
let inA = /* @__PURE__ */ new Map();
|
|
238
|
+
let inB = /* @__PURE__ */ new Map();
|
|
194
239
|
try {
|
|
195
240
|
cpSync(corpus, here, { recursive: true });
|
|
196
|
-
inA =
|
|
197
|
-
inB =
|
|
241
|
+
inA = foundIn(configA, "A");
|
|
242
|
+
inB = foundIn(configB, "B");
|
|
198
243
|
} finally {
|
|
199
244
|
rmSync(root, { force: true, recursive: true });
|
|
200
245
|
}
|
|
201
246
|
const configured = [.../* @__PURE__ */ new Set([...rulesNamedBy(configA), ...rulesNamedBy(configB)])].toSorted();
|
|
202
|
-
const reach = configured.filter((rule) => exercised.has(rule)).map((rule) =>
|
|
247
|
+
const reach = configured.filter((rule) => exercised.has(rule)).map((rule) => {
|
|
248
|
+
const foundInA = inA.get(rule) ?? 0;
|
|
249
|
+
const foundInB = inB.get(rule) ?? 0;
|
|
250
|
+
return {
|
|
251
|
+
changed: foundInA !== foundInB,
|
|
252
|
+
firedInA: foundInA > 0,
|
|
253
|
+
firedInB: foundInB > 0,
|
|
254
|
+
foundInA,
|
|
255
|
+
foundInB,
|
|
256
|
+
rule
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
const claims = claimsOver(reach, expectChanged);
|
|
203
260
|
return {
|
|
261
|
+
claims,
|
|
262
|
+
claimsHeld: claims.every((one) => one.verdict === "held"),
|
|
204
263
|
neither: reach.filter((one) => !one.firedInA && !one.firedInB).map((one) => one.rule),
|
|
205
264
|
outside: configured.filter((rule) => !exercised.has(rule)),
|
|
206
265
|
plugin: manifest.plugin,
|
|
207
266
|
reach
|
|
208
267
|
};
|
|
209
268
|
};
|
|
269
|
+
var VERDICTS = {
|
|
270
|
+
held: "HELD",
|
|
271
|
+
unclaimed: "UNCLAIMED \u2014 the corpus shows this changing and the release did not say so",
|
|
272
|
+
unproven: "UNPROVEN \u2014 claimed, and the corpus shows no change"
|
|
273
|
+
};
|
|
274
|
+
var claimsSection = (report) => {
|
|
275
|
+
if (report.claims.length === 0) return [];
|
|
276
|
+
return [
|
|
277
|
+
"## claims",
|
|
278
|
+
"",
|
|
279
|
+
report.claimsHeld ? "**HELD** \u2014 every rule the release claims is a rule this corpus shows changing, and no other." : "**BROKEN** \u2014 a claim this corpus does not show, or a change the release did not claim.",
|
|
280
|
+
"",
|
|
281
|
+
"| rule | claimed | observed | verdict |",
|
|
282
|
+
"|---|---|---|---|",
|
|
283
|
+
...report.claims.map(
|
|
284
|
+
(one) => `| \`${one.rule}\` | ${one.claimed ? "changed" : "\u2014"} | ${one.changed ? "changed" : "unchanged"} | ${VERDICTS[one.verdict]} |`
|
|
285
|
+
),
|
|
286
|
+
""
|
|
287
|
+
];
|
|
288
|
+
};
|
|
210
289
|
var formatCorpus = (report) => {
|
|
211
290
|
const rows = report.reach.map(
|
|
212
|
-
(one) => `| \`${one.rule}\` | ${one.firedInA ? "yes" : "NO"} | ${one.firedInB ? "yes" : "NO"} |`
|
|
291
|
+
(one) => `| \`${one.rule}\` | ${one.firedInA ? "yes" : "NO"} (${one.foundInA}) | ${one.firedInB ? "yes" : "NO"} (${one.foundInB}) |`
|
|
213
292
|
);
|
|
214
293
|
const outside = report.outside.length === 0 ? "None \u2014 this corpus speaks for every rule the configs enable.\n" : `${report.outside.map((rule) => `- \`${rule}\``).join("\n")}
|
|
215
294
|
`;
|
|
@@ -223,9 +302,10 @@ var formatCorpus = (report) => {
|
|
|
223
302
|
report.neither.length === 0 ? "**PASS** \u2014 every in-scope rule has at least one corpus file that fires it." : `**FAIL** \u2014 these rules fire nowhere in the corpus, so nothing shows they reach anything:
|
|
224
303
|
${report.neither.map((rule) => `- \`${rule}\``).join("\n")}`,
|
|
225
304
|
"",
|
|
305
|
+
...claimsSection(report),
|
|
226
306
|
"## per rule",
|
|
227
307
|
"",
|
|
228
|
-
"| rule | fired under A | fired under B |",
|
|
308
|
+
"| rule | fired under A (findings) | fired under B (findings) |",
|
|
229
309
|
"|---|---|---|",
|
|
230
310
|
...rows,
|
|
231
311
|
"",
|
|
@@ -239,12 +319,14 @@ ${report.neither.map((rule) => `- \`${rule}\``).join("\n")}`,
|
|
|
239
319
|
};
|
|
240
320
|
var FLAGS = { "--a": "configA", "--b": "configB", "--out": "out", "--oxlint": "oxlint" };
|
|
241
321
|
var CORPUS_FLAG = "--corpus";
|
|
322
|
+
var CLAIM_FLAG = "--expect-changed";
|
|
242
323
|
var parseParityArgs = (argv) => {
|
|
243
324
|
const at = argv.indexOf("--");
|
|
244
325
|
const flags = at === -1 ? argv : argv.slice(0, at);
|
|
245
326
|
const paths = at === -1 ? [] : argv.slice(at + 1);
|
|
246
327
|
const read = {};
|
|
247
328
|
let corpus;
|
|
329
|
+
let expectChanged;
|
|
248
330
|
for (let index = 0; index < flags.length; index += 1) {
|
|
249
331
|
const flag = flags[index];
|
|
250
332
|
const value = flags[index + 1];
|
|
@@ -258,6 +340,16 @@ var parseParityArgs = (argv) => {
|
|
|
258
340
|
index += 1;
|
|
259
341
|
continue;
|
|
260
342
|
}
|
|
343
|
+
if (flag === CLAIM_FLAG) {
|
|
344
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
`geonosis-lint-parity: ${CLAIM_FLAG} needs the rules the release claims it changes, comma separated \u2014 a claim about no rules is not a claim`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
expectChanged = value.split(",").map((one) => one.trim()).filter((one) => one !== "");
|
|
350
|
+
index += 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
261
353
|
const name = FLAGS[flag];
|
|
262
354
|
if (name === void 0) throw new Error(`geonosis-lint-parity: unknown argument "${flag}"`);
|
|
263
355
|
if (value === void 0 || value.startsWith("--")) {
|
|
@@ -275,6 +367,7 @@ var parseParityArgs = (argv) => {
|
|
|
275
367
|
configA: read.configA,
|
|
276
368
|
configB: read.configB,
|
|
277
369
|
...corpus === void 0 ? {} : { corpus },
|
|
370
|
+
...expectChanged === void 0 ? {} : { expectChanged },
|
|
278
371
|
...read.out === void 0 ? {} : { out: read.out },
|
|
279
372
|
...read.oxlint === void 0 ? {} : { oxlint: read.oxlint },
|
|
280
373
|
paths: paths.length === 0 ? ["."] : paths
|
|
@@ -322,6 +415,7 @@ var parityOf = ({
|
|
|
322
415
|
};
|
|
323
416
|
|
|
324
417
|
export {
|
|
418
|
+
resolveOxlint,
|
|
325
419
|
ParityError,
|
|
326
420
|
normaliseFindings,
|
|
327
421
|
readRun,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consumer's own oxlint, walking up from a directory, not one `npx` might fetch: a run under a
|
|
3
|
+
* different binary from the one the repo gates with compares two things nobody ships. Exported
|
|
4
|
+
* because every tool that spawns oxlint has to answer this, and a second copy of the answer is a
|
|
5
|
+
* second answer.
|
|
6
|
+
*/
|
|
7
|
+
declare const resolveOxlint: (from: string) => string;
|
|
1
8
|
/** A run that could not be read. Never a number — 0 findings against 0 findings is a PASS. */
|
|
2
9
|
declare class ParityError extends Error {
|
|
3
10
|
constructor(message: string);
|
|
@@ -50,11 +57,34 @@ declare const compareFindings: ({ a, b }: {
|
|
|
50
57
|
}) => Parity;
|
|
51
58
|
declare const formatSummary: (parity: Parity) => string;
|
|
52
59
|
type RuleReach = {
|
|
60
|
+
/**
|
|
61
|
+
* The corpus saw this rule behave differently under the two configs — it started firing, stopped
|
|
62
|
+
* firing, or fired a different number of times. What a release CLAIMS about a rule is checked
|
|
63
|
+
* against this, and nothing else.
|
|
64
|
+
*/
|
|
65
|
+
changed: boolean;
|
|
53
66
|
firedInA: boolean;
|
|
54
67
|
firedInB: boolean;
|
|
68
|
+
/** How many findings each side made, so a rule that gained a case is a change and not a shrug. */
|
|
69
|
+
foundInA: number;
|
|
70
|
+
foundInB: number;
|
|
71
|
+
rule: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* One line of a release's claim, judged. `held` — claimed and shown; `unproven` — claimed and the
|
|
75
|
+
* corpus shows nothing; `unclaimed` — the corpus shows a change nobody named.
|
|
76
|
+
*/
|
|
77
|
+
type CorpusClaim = {
|
|
78
|
+
changed: boolean;
|
|
79
|
+
claimed: boolean;
|
|
55
80
|
rule: string;
|
|
81
|
+
verdict: 'held' | 'unclaimed' | 'unproven';
|
|
56
82
|
};
|
|
57
83
|
type CorpusReport = {
|
|
84
|
+
/** Empty when the run claimed nothing — a corpus run without a claim judges no claim. */
|
|
85
|
+
claims: CorpusClaim[];
|
|
86
|
+
/** False as soon as one claim is unproven or one change went unclaimed. */
|
|
87
|
+
claimsHeld: boolean;
|
|
58
88
|
/** Rules no file in the corpus fires under either config — rules shipping with no evidence. */
|
|
59
89
|
neither: string[];
|
|
60
90
|
/**
|
|
@@ -105,10 +135,15 @@ declare const readManifest: (corpus: string) => CorpusManifest;
|
|
|
105
135
|
* consumer inherits every one of those as a claim it cannot check. The corpus is the evidence, and
|
|
106
136
|
* `neither` is the list that has to stay empty.
|
|
107
137
|
*/
|
|
108
|
-
declare const corpusOf: ({ configA, configB, corpus, oxlint, }: {
|
|
138
|
+
declare const corpusOf: ({ configA, configB, corpus, expectChanged, oxlint, }: {
|
|
109
139
|
configA: string;
|
|
110
140
|
configB: string;
|
|
111
141
|
corpus: string;
|
|
142
|
+
/**
|
|
143
|
+
* The rules a release SAYS it changes. Absent means no claim, and no claim is judged; present
|
|
144
|
+
* means the corpus must show exactly these rules changing and no others.
|
|
145
|
+
*/
|
|
146
|
+
expectChanged?: string[];
|
|
112
147
|
oxlint: string;
|
|
113
148
|
}) => CorpusReport;
|
|
114
149
|
declare const formatCorpus: (report: CorpusReport) => string;
|
|
@@ -121,6 +156,11 @@ type ParityArgs = {
|
|
|
121
156
|
* default would weld this tool to whichever plugin happened to be beside it.
|
|
122
157
|
*/
|
|
123
158
|
corpus?: string;
|
|
159
|
+
/**
|
|
160
|
+
* The rules a release claims it changes. Absent means the run claims nothing and judges nothing;
|
|
161
|
+
* an empty list is not expressible, because a claim about no rules is not a claim.
|
|
162
|
+
*/
|
|
163
|
+
expectChanged?: string[];
|
|
124
164
|
out?: string;
|
|
125
165
|
oxlint?: string;
|
|
126
166
|
paths: string[];
|
|
@@ -135,4 +175,4 @@ declare const parityOf: ({ configA, configB, cwd, out, oxlint, paths, }: {
|
|
|
135
175
|
paths: string[];
|
|
136
176
|
}) => Parity;
|
|
137
177
|
|
|
138
|
-
export { type CorpusManifest, type CorpusReport, MANIFEST_FILE, type MessageChange, type Parity, type ParityArgs, ParityError, type RuleReach, type RuleTally, compareFindings, corpusOf, formatCorpus, formatSummary, manifestOf, normaliseFindings, parityOf, parseParityArgs, readManifest, readRun, ruleIdOf, rulesNamedBy };
|
|
178
|
+
export { type CorpusClaim, type CorpusManifest, type CorpusReport, MANIFEST_FILE, type MessageChange, type Parity, type ParityArgs, ParityError, type RuleReach, type RuleTally, compareFindings, corpusOf, formatCorpus, formatSummary, manifestOf, normaliseFindings, parityOf, parseParityArgs, readManifest, readRun, resolveOxlint, ruleIdOf, rulesNamedBy };
|
package/dist/index.js
CHANGED
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
parseParityArgs,
|
|
12
12
|
readManifest,
|
|
13
13
|
readRun,
|
|
14
|
+
resolveOxlint,
|
|
14
15
|
ruleIdOf,
|
|
15
16
|
rulesNamedBy
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-3PCNMLVE.js";
|
|
17
18
|
export {
|
|
18
19
|
MANIFEST_FILE,
|
|
19
20
|
ParityError,
|
|
@@ -27,6 +28,7 @@ export {
|
|
|
27
28
|
parseParityArgs,
|
|
28
29
|
readManifest,
|
|
29
30
|
readRun,
|
|
31
|
+
resolveOxlint,
|
|
30
32
|
ruleIdOf,
|
|
31
33
|
rulesNamedBy
|
|
32
34
|
};
|
package/dist/parity-cli.js
CHANGED
|
@@ -3,14 +3,16 @@ import {
|
|
|
3
3
|
formatCorpus,
|
|
4
4
|
formatSummary,
|
|
5
5
|
parityOf,
|
|
6
|
-
parseParityArgs
|
|
7
|
-
|
|
6
|
+
parseParityArgs,
|
|
7
|
+
resolveOxlint
|
|
8
|
+
} from "./chunk-3PCNMLVE.js";
|
|
8
9
|
|
|
9
10
|
// src/parity-cli.ts
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
12
|
+
import { join, resolve } from "path";
|
|
12
13
|
var USAGE = `geonosis-lint-parity --a <config-a.json> --b <config-b.json> [--oxlint <path>] [--out <dir>] -- <paths\u2026>
|
|
13
14
|
geonosis-lint-parity --corpus <dir> --a <config-a.json> --b <config-b.json> [--out <dir>]
|
|
15
|
+
[--expect-changed <rule,rule,\u2026>]
|
|
14
16
|
|
|
15
17
|
Runs oxlint twice over the same paths under two configs and diffs the findings. Exits 1 when a
|
|
16
18
|
finding config A reported is missing under config B \u2014 a rule that fired before and fires nowhere
|
|
@@ -23,20 +25,16 @@ is required: this tool ships no corpus, because a corpus is one plugin's evidenc
|
|
|
23
25
|
one a plugin ships, e.g. node_modules/<plugin>/corpus. A corpus names the rules it speaks for in
|
|
24
26
|
its own manifest.json, and rules outside that list are reported unjudged.
|
|
25
27
|
|
|
28
|
+
--expect-changed names the rules a release CLAIMS it changes, and exits 1 unless the corpus shows
|
|
29
|
+
exactly those rules behaving differently under the two configs \u2014 no claim the corpus is silent
|
|
30
|
+
about, no change the release never mentioned. Run it for every changelog line that names a rule: a
|
|
31
|
+
release once claimed a widening for ten rules with corpus evidence for two, and the parity run that
|
|
32
|
+
"proved" it went over a tree with no such violation in it.
|
|
33
|
+
|
|
26
34
|
oxlint resolves a jsPlugins specifier relative to the CONFIG FILE's directory, not the working
|
|
27
35
|
directory. Write both configs INSIDE the workspace being linted, or name the plugin by absolute
|
|
28
36
|
path \u2014 a config in a temp dir cannot find a plugin installed in the workspace, and the run is
|
|
29
37
|
refused rather than counted as zero findings.`;
|
|
30
|
-
var resolveOxlint = (from) => {
|
|
31
|
-
let dir = from;
|
|
32
|
-
for (; ; ) {
|
|
33
|
-
const bin = resolve(dir, "node_modules/.bin/oxlint");
|
|
34
|
-
if (existsSync(bin)) return bin;
|
|
35
|
-
const parent = dirname(dir);
|
|
36
|
-
if (parent === dir) return "oxlint";
|
|
37
|
-
dir = parent;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
38
|
var main = () => {
|
|
41
39
|
const argv = process.argv.slice(2);
|
|
42
40
|
if (argv.includes("--help") || argv.includes("-h") || argv.length === 0) {
|
|
@@ -51,7 +49,13 @@ var main = () => {
|
|
|
51
49
|
const oxlint = args.oxlint ?? resolveOxlint(cwd);
|
|
52
50
|
if (args.corpus !== void 0) {
|
|
53
51
|
const corpus = resolve(cwd, args.corpus);
|
|
54
|
-
const report = corpusOf({
|
|
52
|
+
const report = corpusOf({
|
|
53
|
+
configA,
|
|
54
|
+
configB,
|
|
55
|
+
corpus,
|
|
56
|
+
...args.expectChanged === void 0 ? {} : { expectChanged: args.expectChanged },
|
|
57
|
+
oxlint
|
|
58
|
+
});
|
|
55
59
|
const text = formatCorpus(report);
|
|
56
60
|
process.stdout.write(`${text}
|
|
57
61
|
`);
|
|
@@ -60,7 +64,7 @@ var main = () => {
|
|
|
60
64
|
mkdirSync(out, { recursive: true });
|
|
61
65
|
writeFileSync(join(out, "CORPUS.md"), text);
|
|
62
66
|
}
|
|
63
|
-
return report.neither.length > 0 ? 1 : 0;
|
|
67
|
+
return report.neither.length > 0 || !report.claimsHeld ? 1 : 0;
|
|
64
68
|
}
|
|
65
69
|
const parity = parityOf({
|
|
66
70
|
configA,
|