@jterrazz/typescript 10.2.0 → 10.2.2
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/bin/commands/check.sh +40 -4
- package/lib/check-baseline.js +62 -5
- package/lib/check-suppressions.js +31 -12
- package/package.json +2 -2
package/bin/commands/check.sh
CHANGED
|
@@ -625,6 +625,7 @@ run_checks() {
|
|
|
625
625
|
local checker_status=0
|
|
626
626
|
local checker_dormant=0
|
|
627
627
|
local checker_ratcheted=false
|
|
628
|
+
local checker_ratchet_broken=false
|
|
628
629
|
if [ "$FIX_MODE" = false ]; then
|
|
629
630
|
local checker_index=0
|
|
630
631
|
while IFS= read -r specs_root; do
|
|
@@ -708,12 +709,32 @@ run_checks() {
|
|
|
708
709
|
if member_checker .; then
|
|
709
710
|
"${CHECKER_COMMAND[@]}" --format json > "$tmp_dir/checker.json" 2>/dev/null || true
|
|
710
711
|
baseline_checker=(--checker "$tmp_dir/checker.json")
|
|
711
|
-
|
|
712
|
+
# Zeroing the pass below on the checker's say-so is only honest once
|
|
713
|
+
# the file actually HOLDS its findings — a baseline with none yet is
|
|
714
|
+
# not enrolled, and its debt is not the ratchet's to forgive.
|
|
715
|
+
if [ -f "$BASELINE_FILE" ] && grep -q '"jterrazz-check/' "$BASELINE_FILE"; then
|
|
716
|
+
checker_ratcheted=true
|
|
717
|
+
fi
|
|
712
718
|
fi
|
|
713
719
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
720
|
+
# In fix mode there is no Test Conventions pass to carry a checker
|
|
721
|
+
# breach (the checker itself never runs there), so both reporters
|
|
722
|
+
# still speak through the one verdict this pass already owns. In
|
|
723
|
+
# check mode each reporter's breach is attributed to the pass that
|
|
724
|
+
# judges it: the script prints oxlint's refusals on stdout and the
|
|
725
|
+
# checker's — the enrolment notice among them — on stderr.
|
|
726
|
+
if [ "$FIX_MODE" = true ]; then
|
|
727
|
+
node "$PACKAGE_ROOT/lib/check-baseline.js" "$tmp_dir/lint.json" . \
|
|
728
|
+
"${baseline_checker[@]}" >> "$tmp_dir/lint.log" 2>&1
|
|
729
|
+
lint_status=$?
|
|
730
|
+
else
|
|
731
|
+
node "$PACKAGE_ROOT/lib/check-baseline.js" "$tmp_dir/lint.json" . \
|
|
732
|
+
"${baseline_checker[@]}" > "$tmp_dir/ratchet-oxlint.log" \
|
|
733
|
+
2> "$tmp_dir/ratchet-checker.log"
|
|
734
|
+
[ -s "$tmp_dir/ratchet-oxlint.log" ] && lint_status=1 || lint_status=0
|
|
735
|
+
cat "$tmp_dir/ratchet-oxlint.log" >> "$tmp_dir/lint.log"
|
|
736
|
+
[ -s "$tmp_dir/ratchet-checker.log" ] && checker_ratchet_broken=true
|
|
737
|
+
fi
|
|
717
738
|
fi
|
|
718
739
|
|
|
719
740
|
[ -n "$format_pid" ] && { wait $format_pid; format_status=$?; }
|
|
@@ -748,6 +769,13 @@ run_checks() {
|
|
|
748
769
|
checker_status=0
|
|
749
770
|
fi
|
|
750
771
|
|
|
772
|
+
# But the ratchet still has the last word: a checker id that grew, that
|
|
773
|
+
# nobody recorded, or that only now enrols, is this pass's debt to name —
|
|
774
|
+
# never the oxlint pass's, whichever way the loop above just left it.
|
|
775
|
+
if [ "$checker_ratchet_broken" = true ]; then
|
|
776
|
+
checker_status=1
|
|
777
|
+
fi
|
|
778
|
+
|
|
751
779
|
local publish_failed_logs=()
|
|
752
780
|
index=0
|
|
753
781
|
for pid in "${publish_pids[@]}"; do
|
|
@@ -777,6 +805,14 @@ run_checks() {
|
|
|
777
805
|
join_logs "$tmp_dir/docs.log" "${docs_failed_logs[@]}"
|
|
778
806
|
join_logs "$tmp_dir/publish.log" "${publish_failed_logs[@]}"
|
|
779
807
|
|
|
808
|
+
# The checker's own ratchet breach — a grown id, one nobody recorded, or
|
|
809
|
+
# the enrolment notice — is this pass's log to carry, never the oxlint
|
|
810
|
+
# pass's; `join_logs` above rebuilds the file from the raw runs alone, so
|
|
811
|
+
# it is appended after, not folded into the block that just ran.
|
|
812
|
+
if [ "$checker_ratchet_broken" = true ] && [ -s "$tmp_dir/ratchet-checker.log" ]; then
|
|
813
|
+
cat "$tmp_dir/ratchet-checker.log" >> "$tmp_dir/checker.log"
|
|
814
|
+
fi
|
|
815
|
+
|
|
780
816
|
# Two things this pass says even when it is green, so it asks for its log
|
|
781
817
|
# the way a writer does: which findings the ratchet is holding, and which
|
|
782
818
|
# member resolves a release too old to answer `--member` — a reader left
|
package/lib/check-baseline.js
CHANGED
|
@@ -33,6 +33,18 @@
|
|
|
33
33
|
* rather than read as debts that reached zero: nothing measured them, and the
|
|
34
34
|
* third refusal below would otherwise delete a file the next install needs.
|
|
35
35
|
*
|
|
36
|
+
* A checker report given against a file that holds NO `jterrazz-check/*` entry
|
|
37
|
+
* yet is a second reporter joining an oxlint-only file — the upgrade, not new
|
|
38
|
+
* debt. The ratchet still fails it (a green gate that lies is worse), but
|
|
39
|
+
* names the gesture in ONE line rather than refusing every finding by rule:
|
|
40
|
+
* the per-rule refusals return once `typescript baseline` has recorded at
|
|
41
|
+
* least one `jterrazz-check/*` entry. The oxlint side is judged exactly as
|
|
42
|
+
* before either way.
|
|
43
|
+
*
|
|
44
|
+
* The two reporters' breaches print on two streams, so each is attributed to
|
|
45
|
+
* the pass that judges it: oxlint's on stdout, the checker's — the per-rule
|
|
46
|
+
* refusals and the one-line upgrade notice alike — on stderr.
|
|
47
|
+
*
|
|
36
48
|
* Usage: node check-baseline.js <oxlint-json> [root] [--checker <json>] [--write]
|
|
37
49
|
*
|
|
38
50
|
* `--write` rewrites the file from the current counts — that is `typescript
|
|
@@ -42,7 +54,7 @@
|
|
|
42
54
|
|
|
43
55
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
44
56
|
import { join, resolve } from 'node:path';
|
|
45
|
-
import { argv, exit, stdout } from 'node:process';
|
|
57
|
+
import { argv, exit, stderr, stdout } from 'node:process';
|
|
46
58
|
|
|
47
59
|
/** The file, at the project root, tracked beside the config it ratchets. */
|
|
48
60
|
export const BASELINE = 'oxlint.baseline.json';
|
|
@@ -50,6 +62,11 @@ export const BASELINE = 'oxlint.baseline.json';
|
|
|
50
62
|
/** The namespace the conventions checker's ids take in the file. */
|
|
51
63
|
const CHECKER_NAMESPACE = 'jterrazz-check/';
|
|
52
64
|
|
|
65
|
+
/** A judge() line names its rule first, so the namespace prefix is enough. */
|
|
66
|
+
function isCheckerReason(reason) {
|
|
67
|
+
return reason.startsWith(CHECKER_NAMESPACE);
|
|
68
|
+
}
|
|
69
|
+
|
|
53
70
|
/** `eslint(no-debugger)` is how oxlint's JSON spells `eslint/no-debugger`. */
|
|
54
71
|
function ruleOf(code) {
|
|
55
72
|
const match = /^(?<plugin>[\w-]+)\((?<rule>[^)]+)\)$/u.exec(code ?? '');
|
|
@@ -208,14 +225,54 @@ if (import.meta.main) {
|
|
|
208
225
|
exit(rules.length > 0 ? 1 : 0);
|
|
209
226
|
}
|
|
210
227
|
|
|
211
|
-
|
|
228
|
+
/*
|
|
229
|
+
* A checker report against a file the checker never entered yet is an
|
|
230
|
+
* upgrade, not a debt list: refusing it rule by rule would print, on a
|
|
231
|
+
* first install, exactly as many refusals as the checker has findings —
|
|
232
|
+
* every one of them the same gesture ('typescript baseline') said once.
|
|
233
|
+
*/
|
|
234
|
+
const enrolling =
|
|
235
|
+
checkerPath !== undefined &&
|
|
236
|
+
!Object.keys(baseline).some((rule) => rule.startsWith(CHECKER_NAMESPACE));
|
|
237
|
+
|
|
238
|
+
let oxlintBroken;
|
|
239
|
+
let checkerBroken;
|
|
240
|
+
let enrolNotice;
|
|
212
241
|
|
|
213
|
-
|
|
242
|
+
if (enrolling) {
|
|
243
|
+
const findings = diagnosticsOf(checkerReport).length;
|
|
244
|
+
oxlintBroken = judge(countsOf(report), baseline);
|
|
245
|
+
checkerBroken = [];
|
|
246
|
+
enrolNotice =
|
|
247
|
+
findings > 0
|
|
248
|
+
? `${BASELINE} records one reporter — run 'typescript baseline' once to enrol @jterrazz/test's ${findings} finding(s)`
|
|
249
|
+
: undefined;
|
|
250
|
+
} else {
|
|
251
|
+
const broken = judge(counts, baseline);
|
|
252
|
+
oxlintBroken = broken.filter((reason) => !isCheckerReason(reason));
|
|
253
|
+
checkerBroken = broken.filter((reason) => isCheckerReason(reason));
|
|
254
|
+
enrolNotice = undefined;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const reason of oxlintBroken) {
|
|
214
258
|
stdout.write(`baseline-ratchet ${BASELINE} ${reason}\n`);
|
|
215
259
|
}
|
|
216
|
-
if (
|
|
260
|
+
if (oxlintBroken.length > 0) {
|
|
217
261
|
stdout.write("Run 'typescript baseline' to record where the project actually stands.\n");
|
|
218
262
|
}
|
|
219
263
|
|
|
220
|
-
|
|
264
|
+
for (const reason of checkerBroken) {
|
|
265
|
+
stderr.write(`baseline-ratchet ${BASELINE} ${reason}\n`);
|
|
266
|
+
}
|
|
267
|
+
if (checkerBroken.length > 0) {
|
|
268
|
+
stderr.write("Run 'typescript baseline' to record where the project actually stands.\n");
|
|
269
|
+
}
|
|
270
|
+
if (enrolNotice !== undefined) {
|
|
271
|
+
stderr.write(`${enrolNotice}\n`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const brokenTotal =
|
|
275
|
+
oxlintBroken.length + checkerBroken.length + (enrolNotice === undefined ? 0 : 1);
|
|
276
|
+
|
|
277
|
+
exit(brokenTotal > 0 ? 1 : 0);
|
|
221
278
|
}
|
|
@@ -75,7 +75,8 @@ const hasReason = (tail) => (tail.split(REASON)[1] ?? '').replace(/\*\/\s*$/u, '
|
|
|
75
75
|
*
|
|
76
76
|
* A JS plugin's rules never appear in `--print-config`, so a directive naming
|
|
77
77
|
* one is unverifiable — and the gate says nothing rather than calling a live
|
|
78
|
-
* rule dead.
|
|
78
|
+
* rule dead. The namespaces returned are therefore the ones the printed config
|
|
79
|
+
* can ANSWER for: the plugins it loaded, and never a JS plugin's.
|
|
79
80
|
*/
|
|
80
81
|
function resolveConfig(root, oxlint) {
|
|
81
82
|
let printed;
|
|
@@ -89,21 +90,33 @@ function resolveConfig(root, oxlint) {
|
|
|
89
90
|
return null;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
let
|
|
93
|
+
let resolved;
|
|
93
94
|
try {
|
|
94
|
-
|
|
95
|
-
rules = { ...resolved.rules };
|
|
96
|
-
/* A rule armed only in an override — a test-file rule, a layer's
|
|
97
|
-
* `no-restricted-imports` — is live for the files it names. */
|
|
98
|
-
for (const override of resolved.overrides ?? []) {
|
|
99
|
-
for (const [name, level] of Object.entries(override.rules ?? {})) {
|
|
100
|
-
rules[name] ??= level;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
95
|
+
resolved = JSON.parse(printed);
|
|
103
96
|
} catch {
|
|
104
97
|
return null;
|
|
105
98
|
}
|
|
106
99
|
|
|
100
|
+
const rules = { ...resolved.rules };
|
|
101
|
+
/* A rule armed only in an override — a test-file rule, a layer's
|
|
102
|
+
* `no-restricted-imports` — is live for the files it names. */
|
|
103
|
+
for (const override of resolved.overrides ?? []) {
|
|
104
|
+
for (const [name, level] of Object.entries(override.rules ?? {})) {
|
|
105
|
+
rules[name] ??= level;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { namespaces: namespacesOf(resolved, rules), rules };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The namespaces the printed config can ANSWER for. Where it loads a JS plugin,
|
|
114
|
+
* a namespace outside the plugins it printed is that plugin's, and the map holds
|
|
115
|
+
* at most the few of its rules a `rules` or `allow` entry happened to name —
|
|
116
|
+
* never the live ones. Such a namespace is dropped rather than read as evidence
|
|
117
|
+
* that every OTHER rule of it is off.
|
|
118
|
+
*/
|
|
119
|
+
function namespacesOf(resolved, rules) {
|
|
107
120
|
const namespaces = new Set();
|
|
108
121
|
for (const name of Object.keys(rules)) {
|
|
109
122
|
if (name.includes('/')) {
|
|
@@ -111,7 +124,13 @@ function resolveConfig(root, oxlint) {
|
|
|
111
124
|
}
|
|
112
125
|
}
|
|
113
126
|
|
|
114
|
-
|
|
127
|
+
if ((resolved.jsPlugins?.length ?? 0) === 0) {
|
|
128
|
+
return namespaces;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const loaded = new Set(resolved.plugins);
|
|
132
|
+
|
|
133
|
+
return new Set([...namespaces].filter((namespace) => loaded.has(namespace)));
|
|
115
134
|
}
|
|
116
135
|
|
|
117
136
|
/** Where a named rule stands for this project: `live`, `dead`, or `unverifiable`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jterrazz/typescript",
|
|
3
|
-
"version": "10.2.
|
|
3
|
+
"version": "10.2.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
|
|
6
6
|
"repository": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"typescript": "^6.0.0"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
|
-
"@jterrazz/test": "^15.
|
|
87
|
+
"@jterrazz/test": "^15.3.0",
|
|
88
88
|
"@types/node": "^26.1.1",
|
|
89
89
|
"vitest": "^4.1.10"
|
|
90
90
|
},
|