@geonosis/lint-parity 1.3.0 → 1.4.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/dist/{chunk-DFMYYU5B.js → chunk-D7OGKQ2C.js} +144 -17
- package/dist/index.d.ts +37 -1
- package/dist/index.js +5 -1
- package/dist/parity-cli.js +18 -2
- package/package.json +4 -1
|
@@ -1,16 +1,100 @@
|
|
|
1
|
+
// src/envelope.ts
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
6
|
+
var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
|
|
7
|
+
var UnbalancedEnvelope = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "UnbalancedEnvelope";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var isCount = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
14
|
+
var unbalancedMessage = (envelope, next) => `${envelope.tool}: considered ${envelope.considered} but accounts for ${envelope.read + envelope.refused.length + envelope.excused.length} \u2014 ${envelope.read} read + ${envelope.refused.length} refused + ${envelope.excused.length} excused. A run that has lost count of its own inputs cannot say what it measured, so no verdict was rendered and no envelope was written. Next: ${next}`;
|
|
15
|
+
var writeEnvelope = ({
|
|
16
|
+
envelope,
|
|
17
|
+
next,
|
|
18
|
+
root
|
|
19
|
+
}) => {
|
|
20
|
+
if (envelope.tool.trim() === "") {
|
|
21
|
+
throw new UnbalancedEnvelope(
|
|
22
|
+
`an envelope with no tool name cannot be filed or reported against. Next: ${next}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (envelope.version.trim() === "") {
|
|
26
|
+
throw new UnbalancedEnvelope(
|
|
27
|
+
`${envelope.tool}: an envelope that cannot name the build that wrote it dates nothing, and a stale one reads exactly like a fresh one. Next: ${next}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (!isCount(envelope.considered) || !isCount(envelope.read)) {
|
|
31
|
+
throw new UnbalancedEnvelope(
|
|
32
|
+
`${envelope.tool}: considered ${envelope.considered} and read ${envelope.read} \u2014 a census is a whole number of things, and arithmetic over anything else balances by accident. Next: ${next}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (envelope.considered !== envelope.read + envelope.refused.length + envelope.excused.length) {
|
|
36
|
+
throw new UnbalancedEnvelope(unbalancedMessage(envelope, next));
|
|
37
|
+
}
|
|
38
|
+
const at = envelopePath(root, envelope.tool);
|
|
39
|
+
mkdirSync(dirname(at), { recursive: true });
|
|
40
|
+
writeFileSync(at, `${JSON.stringify(envelope, void 0, 2)}
|
|
41
|
+
`);
|
|
42
|
+
return at;
|
|
43
|
+
};
|
|
44
|
+
var UNKNOWN = "unknown";
|
|
45
|
+
var versionIn = (dir) => {
|
|
46
|
+
try {
|
|
47
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
48
|
+
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var versionOf = (moduleUrl) => {
|
|
54
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
55
|
+
for (; ; ) {
|
|
56
|
+
const found = versionIn(dir);
|
|
57
|
+
if (found !== void 0) return found;
|
|
58
|
+
const up = dirname(dir);
|
|
59
|
+
if (up === dir) return UNKNOWN;
|
|
60
|
+
dir = up;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var PARITY_TOOL = "lint-parity";
|
|
64
|
+
var PARITY_NEXT = "oxlint --debug=files --config <each config> <paths> and diff the two lists \u2014 a parity run only compares the files BOTH configs opened, and the ones only one of them opened are the silence this refuses over";
|
|
65
|
+
var parityEnvelope = (census, findings, durationMs) => ({
|
|
66
|
+
considered: census.both.length + census.onlyInA.length + census.onlyInB.length,
|
|
67
|
+
durationMs,
|
|
68
|
+
excused: [
|
|
69
|
+
...census.onlyInA.map((path) => ({
|
|
70
|
+
path,
|
|
71
|
+
reason: "config B would not lint this file, so the two runs cannot be compared over it"
|
|
72
|
+
})),
|
|
73
|
+
...census.onlyInB.map((path) => ({
|
|
74
|
+
path,
|
|
75
|
+
reason: "config A would not lint this file, so the two runs cannot be compared over it"
|
|
76
|
+
}))
|
|
77
|
+
],
|
|
78
|
+
findings,
|
|
79
|
+
read: census.both.length,
|
|
80
|
+
refused: [],
|
|
81
|
+
tool: PARITY_TOOL,
|
|
82
|
+
version: versionOf(import.meta.url)
|
|
83
|
+
});
|
|
84
|
+
|
|
1
85
|
// src/parity.ts
|
|
2
86
|
import { spawnSync } from "child_process";
|
|
3
87
|
import {
|
|
4
88
|
cpSync,
|
|
5
89
|
existsSync,
|
|
6
|
-
mkdirSync,
|
|
90
|
+
mkdirSync as mkdirSync2,
|
|
7
91
|
mkdtempSync,
|
|
8
|
-
readFileSync,
|
|
92
|
+
readFileSync as readFileSync2,
|
|
9
93
|
rmSync,
|
|
10
|
-
writeFileSync
|
|
94
|
+
writeFileSync as writeFileSync2
|
|
11
95
|
} from "fs";
|
|
12
96
|
import { tmpdir } from "os";
|
|
13
|
-
import { dirname, join, resolve } from "path";
|
|
97
|
+
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
14
98
|
var ANSI = /\[[0-9;]*m/g;
|
|
15
99
|
var SUMMARY_COUNT = /^(\d+) problems?$/m;
|
|
16
100
|
var RULE_IN_LINE = /\[(?:Error|Warning)\/([^\]]+)\]$/;
|
|
@@ -21,7 +105,7 @@ var resolveOxlint = (from) => {
|
|
|
21
105
|
for (; ; ) {
|
|
22
106
|
const bin = resolve(dir, "node_modules/.bin/oxlint");
|
|
23
107
|
if (existsSync(bin)) return bin;
|
|
24
|
-
const parent =
|
|
108
|
+
const parent = dirname2(dir);
|
|
25
109
|
if (parent === dir) return "oxlint";
|
|
26
110
|
dir = parent;
|
|
27
111
|
}
|
|
@@ -156,7 +240,7 @@ var formatSummary = (parity) => {
|
|
|
156
240
|
].join("\n");
|
|
157
241
|
};
|
|
158
242
|
var rulesNamedBy = (config) => {
|
|
159
|
-
const parsed = JSON.parse(
|
|
243
|
+
const parsed = JSON.parse(readFileSync2(config, "utf8"));
|
|
160
244
|
return [
|
|
161
245
|
.../* @__PURE__ */ new Set([
|
|
162
246
|
...Object.keys(parsed.rules ?? {}),
|
|
@@ -197,10 +281,10 @@ var verdictOf = (changed, claimed) => {
|
|
|
197
281
|
return claimed ? "unproven" : "unclaimed";
|
|
198
282
|
};
|
|
199
283
|
var readManifest = (corpus) => {
|
|
200
|
-
const path =
|
|
284
|
+
const path = join2(corpus, MANIFEST_FILE);
|
|
201
285
|
let parsed;
|
|
202
286
|
try {
|
|
203
|
-
parsed = JSON.parse(
|
|
287
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
204
288
|
} catch (error) {
|
|
205
289
|
throw new ParityError(`could not read ${path}: ${error.message}`);
|
|
206
290
|
}
|
|
@@ -219,8 +303,8 @@ var corpusOf = ({
|
|
|
219
303
|
}) => {
|
|
220
304
|
const manifest = readManifest(corpus);
|
|
221
305
|
const exercised = new Set(manifest.rules);
|
|
222
|
-
const root = mkdtempSync(
|
|
223
|
-
const here =
|
|
306
|
+
const root = mkdtempSync(join2(tmpdir(), "geonosis-corpus-"));
|
|
307
|
+
const here = join2(root, "corpus");
|
|
224
308
|
const foundIn = (config, side) => {
|
|
225
309
|
const counts = /* @__PURE__ */ new Map();
|
|
226
310
|
for (const finding of readRun(
|
|
@@ -395,6 +479,44 @@ var lint = ({
|
|
|
395
479
|
throw new ParityError(`could not run ${oxlint}: ${run.error.message}`);
|
|
396
480
|
return { code: run.status ?? -1, output: `${run.stdout ?? ""}${run.stderr ?? ""}` };
|
|
397
481
|
};
|
|
482
|
+
var filesLintedBy = ({
|
|
483
|
+
config,
|
|
484
|
+
cwd,
|
|
485
|
+
oxlint,
|
|
486
|
+
paths
|
|
487
|
+
}) => {
|
|
488
|
+
const run = spawnSync(oxlint, ["--debug=files", "--config", config, ...paths], {
|
|
489
|
+
cwd,
|
|
490
|
+
encoding: "utf8",
|
|
491
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
492
|
+
maxBuffer: 64 * 1024 * 1024
|
|
493
|
+
});
|
|
494
|
+
if (run.error !== void 0) {
|
|
495
|
+
throw new ParityError(`could not run ${oxlint}: ${run.error.message}`);
|
|
496
|
+
}
|
|
497
|
+
if (run.status !== 0) {
|
|
498
|
+
throw new ParityError(
|
|
499
|
+
`oxlint --debug=files exited ${String(run.status)} under ${config}, so which files it would read is not known:
|
|
500
|
+
${`${run.stdout ?? ""}${run.stderr ?? ""}`.trim()}`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
return (run.stdout ?? "").split("\n").map((line) => line.trim().replace(`${cwd}/`, "").replace(/^\.\//, "")).filter((line) => line !== "").toSorted();
|
|
504
|
+
};
|
|
505
|
+
var fileCensus = ({
|
|
506
|
+
configA,
|
|
507
|
+
configB,
|
|
508
|
+
cwd,
|
|
509
|
+
oxlint,
|
|
510
|
+
paths
|
|
511
|
+
}) => {
|
|
512
|
+
const inA = filesLintedBy({ config: configA, cwd, oxlint, paths });
|
|
513
|
+
const inB = new Set(filesLintedBy({ config: configB, cwd, oxlint, paths }));
|
|
514
|
+
return {
|
|
515
|
+
both: inA.filter((file) => inB.has(file)),
|
|
516
|
+
onlyInA: inA.filter((file) => !inB.has(file)),
|
|
517
|
+
onlyInB: [...inB].filter((file) => !inA.includes(file)).toSorted()
|
|
518
|
+
};
|
|
519
|
+
};
|
|
398
520
|
var parityOf = ({
|
|
399
521
|
configA,
|
|
400
522
|
configB,
|
|
@@ -408,18 +530,21 @@ var parityOf = ({
|
|
|
408
530
|
b: readRun(lint({ config: configB, cwd, oxlint, paths }), cwd, "B")
|
|
409
531
|
});
|
|
410
532
|
if (out !== void 0) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
533
|
+
mkdirSync2(out, { recursive: true });
|
|
534
|
+
writeFileSync2(join2(out, "a.txt"), asFile(parity.a));
|
|
535
|
+
writeFileSync2(join2(out, "b.txt"), asFile(parity.b));
|
|
536
|
+
writeFileSync2(join2(out, "only-in-a.txt"), asFile(parity.onlyInA));
|
|
537
|
+
writeFileSync2(join2(out, "only-in-b.txt"), asFile(parity.onlyInB));
|
|
538
|
+
writeFileSync2(join2(out, "changed.txt"), asFile(changedLines(parity.messageChanged)));
|
|
539
|
+
writeFileSync2(join2(out, "SUMMARY.md"), formatSummary(parity));
|
|
418
540
|
}
|
|
419
541
|
return parity;
|
|
420
542
|
};
|
|
421
543
|
|
|
422
544
|
export {
|
|
545
|
+
writeEnvelope,
|
|
546
|
+
PARITY_NEXT,
|
|
547
|
+
parityEnvelope,
|
|
423
548
|
resolveOxlint,
|
|
424
549
|
ParityError,
|
|
425
550
|
normaliseFindings,
|
|
@@ -434,5 +559,7 @@ export {
|
|
|
434
559
|
corpusOf,
|
|
435
560
|
formatCorpus,
|
|
436
561
|
parseParityArgs,
|
|
562
|
+
filesLintedBy,
|
|
563
|
+
fileCensus,
|
|
437
564
|
parityOf
|
|
438
565
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** The files each config would actually read, split three ways. */
|
|
2
|
+
type FileCensus = {
|
|
3
|
+
both: string[];
|
|
4
|
+
onlyInA: string[];
|
|
5
|
+
onlyInB: string[];
|
|
6
|
+
};
|
|
7
|
+
|
|
1
8
|
/**
|
|
2
9
|
* The consumer's own oxlint, walking up from a directory, not one `npx` might fetch: a run under a
|
|
3
10
|
* different binary from the one the repo gates with compares two things nobody ships. Exported
|
|
@@ -174,6 +181,35 @@ type ParityArgs = {
|
|
|
174
181
|
paths: string[];
|
|
175
182
|
};
|
|
176
183
|
declare const parseParityArgs: (argv: string[]) => ParityArgs;
|
|
184
|
+
/**
|
|
185
|
+
* The files a config would actually lint, asked of oxlint itself.
|
|
186
|
+
*
|
|
187
|
+
* `--debug=files` prints one path per line and exits 0 without linting anything: measured here,
|
|
188
|
+
* `--config narrow.json` (whose `ignorePatterns` name `b.ts`) printed one file where
|
|
189
|
+
* `--config wide.json` printed two. Nothing else in a `--format=unix` run carries this — that
|
|
190
|
+
* output is findings and only findings, which is why a parity run could report agreement over a
|
|
191
|
+
* tree the second config never opened.
|
|
192
|
+
*
|
|
193
|
+
* The argv is deliberately the same SHAPE `lint` uses, config and paths passed through exactly as
|
|
194
|
+
* they arrived. It has to be: oxlint's ignore resolution is sensitive to that shape — an absolute
|
|
195
|
+
* `--config` with a relative path argument applies no `ignorePatterns` at all, where the same
|
|
196
|
+
* config named relatively does. A census taken under different arguments would describe a run
|
|
197
|
+
* nobody made, which is the failure this whole envelope is about.
|
|
198
|
+
*/
|
|
199
|
+
declare const filesLintedBy: ({ config, cwd, oxlint, paths, }: {
|
|
200
|
+
config: string;
|
|
201
|
+
cwd: string;
|
|
202
|
+
oxlint: string;
|
|
203
|
+
paths: string[];
|
|
204
|
+
}) => string[];
|
|
205
|
+
/** Which files both configs would read, and which each would read alone. */
|
|
206
|
+
declare const fileCensus: ({ configA, configB, cwd, oxlint, paths, }: {
|
|
207
|
+
configA: string;
|
|
208
|
+
configB: string;
|
|
209
|
+
cwd: string;
|
|
210
|
+
oxlint: string;
|
|
211
|
+
paths: string[];
|
|
212
|
+
}) => FileCensus;
|
|
177
213
|
declare const parityOf: ({ configA, configB, cwd, out, oxlint, paths, }: {
|
|
178
214
|
configA: string;
|
|
179
215
|
configB: string;
|
|
@@ -183,4 +219,4 @@ declare const parityOf: ({ configA, configB, cwd, out, oxlint, paths, }: {
|
|
|
183
219
|
paths: string[];
|
|
184
220
|
}) => Parity;
|
|
185
221
|
|
|
186
|
-
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 };
|
|
222
|
+
export { type CorpusClaim, type CorpusManifest, type CorpusReport, type FileCensus, MANIFEST_FILE, type MessageChange, type Parity, type ParityArgs, ParityError, type RuleReach, type RuleTally, compareFindings, corpusOf, fileCensus, filesLintedBy, formatCorpus, formatSummary, manifestOf, normaliseFindings, parityOf, parseParityArgs, readManifest, readRun, resolveOxlint, ruleIdOf, rulesNamedBy };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
ParityError,
|
|
4
4
|
compareFindings,
|
|
5
5
|
corpusOf,
|
|
6
|
+
fileCensus,
|
|
7
|
+
filesLintedBy,
|
|
6
8
|
formatCorpus,
|
|
7
9
|
formatSummary,
|
|
8
10
|
manifestOf,
|
|
@@ -14,12 +16,14 @@ import {
|
|
|
14
16
|
resolveOxlint,
|
|
15
17
|
ruleIdOf,
|
|
16
18
|
rulesNamedBy
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-D7OGKQ2C.js";
|
|
18
20
|
export {
|
|
19
21
|
MANIFEST_FILE,
|
|
20
22
|
ParityError,
|
|
21
23
|
compareFindings,
|
|
22
24
|
corpusOf,
|
|
25
|
+
fileCensus,
|
|
26
|
+
filesLintedBy,
|
|
23
27
|
formatCorpus,
|
|
24
28
|
formatSummary,
|
|
25
29
|
manifestOf,
|
package/dist/parity-cli.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PARITY_NEXT,
|
|
2
3
|
corpusOf,
|
|
4
|
+
fileCensus,
|
|
3
5
|
formatCorpus,
|
|
4
6
|
formatSummary,
|
|
7
|
+
parityEnvelope,
|
|
5
8
|
parityOf,
|
|
6
9
|
parseParityArgs,
|
|
7
|
-
resolveOxlint
|
|
8
|
-
|
|
10
|
+
resolveOxlint,
|
|
11
|
+
writeEnvelope
|
|
12
|
+
} from "./chunk-D7OGKQ2C.js";
|
|
9
13
|
|
|
10
14
|
// src/parity-cli.ts
|
|
11
15
|
import { mkdirSync, writeFileSync } from "fs";
|
|
@@ -76,6 +80,7 @@ and it reads them as oxlint does rather than interpreting them itself.
|
|
|
76
80
|
}
|
|
77
81
|
return report.neither.length > 0 || !report.claimsHeld ? 1 : 0;
|
|
78
82
|
}
|
|
83
|
+
const startedAt = Date.now();
|
|
79
84
|
const parity = parityOf({
|
|
80
85
|
configA,
|
|
81
86
|
configB,
|
|
@@ -85,6 +90,17 @@ and it reads them as oxlint does rather than interpreting them itself.
|
|
|
85
90
|
paths: args.paths
|
|
86
91
|
});
|
|
87
92
|
process.stdout.write(`${formatSummary(parity)}
|
|
93
|
+
`);
|
|
94
|
+
const at = writeEnvelope({
|
|
95
|
+
envelope: parityEnvelope(
|
|
96
|
+
fileCensus({ configA, configB, cwd, oxlint, paths: args.paths }),
|
|
97
|
+
parity.onlyInA,
|
|
98
|
+
Date.now() - startedAt
|
|
99
|
+
),
|
|
100
|
+
next: PARITY_NEXT,
|
|
101
|
+
root: cwd
|
|
102
|
+
});
|
|
103
|
+
process.stdout.write(`envelope: ${at}
|
|
88
104
|
`);
|
|
89
105
|
return parity.lost ? 1 : 0;
|
|
90
106
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/lint-parity",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"types": "./dist/index.d.ts",
|
|
5
5
|
"description": "Findings parity and rule reach over any two oxlint configs — the diff a fork is deleted on.",
|
|
6
6
|
"keywords": [
|
|
@@ -43,6 +43,9 @@
|
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@geonosis/ratchet": "1.4.0"
|
|
48
|
+
},
|
|
46
49
|
"scripts": {
|
|
47
50
|
"build": "tsup",
|
|
48
51
|
"typecheck": "tsc --noEmit"
|