@nielspeter/eess 0.4.0 → 0.5.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 +94 -1
- package/dist/baseline-generator.d.ts +9 -0
- package/dist/baseline-generator.d.ts.map +1 -1
- package/dist/baseline-generator.js +9 -0
- package/dist/baseline-generator.js.map +1 -1
- package/dist/baseline.d.ts +1 -1
- package/dist/cardinality.d.ts.map +1 -1
- package/dist/cardinality.js +20 -5
- package/dist/cardinality.js.map +1 -1
- package/dist/cli-config.d.ts +59 -0
- package/dist/cli-config.d.ts.map +1 -0
- package/dist/cli-config.js +84 -0
- package/dist/cli-config.js.map +1 -0
- package/dist/collect-result.d.ts +94 -0
- package/dist/collect-result.d.ts.map +1 -0
- package/dist/collect-result.js +142 -0
- package/dist/collect-result.js.map +1 -0
- package/dist/combinators.d.ts +14 -0
- package/dist/combinators.d.ts.map +1 -1
- package/dist/combinators.js +53 -16
- package/dist/combinators.js.map +1 -1
- package/dist/condition.d.ts +37 -0
- package/dist/condition.d.ts.map +1 -1
- package/dist/condition.js +48 -1
- package/dist/condition.js.map +1 -1
- package/dist/correspondence.d.ts +30 -0
- package/dist/correspondence.d.ts.map +1 -1
- package/dist/correspondence.js +36 -1
- package/dist/correspondence.js.map +1 -1
- package/dist/dedupe-config-findings.d.ts.map +1 -1
- package/dist/dedupe-config-findings.js +27 -0
- package/dist/dedupe-config-findings.js.map +1 -1
- package/dist/diff-aware.d.ts +8 -9
- package/dist/diff-aware.d.ts.map +1 -1
- package/dist/diff-aware.js +38 -17
- package/dist/diff-aware.js.map +1 -1
- package/dist/emitter-findings.d.ts +167 -0
- package/dist/emitter-findings.d.ts.map +1 -0
- package/dist/emitter-findings.js +237 -0
- package/dist/emitter-findings.js.map +1 -0
- package/dist/errors.d.ts +48 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +61 -0
- package/dist/errors.js.map +1 -1
- package/dist/exclusion-comments.d.ts +36 -1
- package/dist/exclusion-comments.d.ts.map +1 -1
- package/dist/exclusion-comments.js +185 -37
- package/dist/exclusion-comments.js.map +1 -1
- package/dist/execute-rule.d.ts +12 -3
- package/dist/execute-rule.d.ts.map +1 -1
- package/dist/execute-rule.js +184 -7
- package/dist/execute-rule.js.map +1 -1
- package/dist/file-walk.d.ts +22 -0
- package/dist/file-walk.d.ts.map +1 -0
- package/dist/file-walk.js +51 -0
- package/dist/file-walk.js.map +1 -0
- package/dist/format-json.d.ts +13 -0
- package/dist/format-json.d.ts.map +1 -1
- package/dist/format-json.js +24 -0
- package/dist/format-json.js.map +1 -1
- package/dist/index.d.ts +14 -46
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -41
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +55 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +92 -0
- package/dist/internal.js.map +1 -0
- package/dist/mask-non-comment.d.ts +49 -0
- package/dist/mask-non-comment.d.ts.map +1 -0
- package/dist/mask-non-comment.js +261 -0
- package/dist/mask-non-comment.js.map +1 -0
- package/dist/owns-empty-discovery.d.ts.map +1 -1
- package/dist/owns-empty-discovery.js +12 -4
- package/dist/owns-empty-discovery.js.map +1 -1
- package/dist/path-suffix.d.ts +57 -0
- package/dist/path-suffix.d.ts.map +1 -0
- package/dist/path-suffix.js +73 -0
- package/dist/path-suffix.js.map +1 -0
- package/dist/predicate.d.ts +12 -0
- package/dist/predicate.d.ts.map +1 -1
- package/dist/predicate.js +20 -1
- package/dist/predicate.js.map +1 -1
- package/dist/preset-dispatch.d.ts +6 -34
- package/dist/preset-dispatch.d.ts.map +1 -1
- package/dist/preset-dispatch.js +30 -52
- package/dist/preset-dispatch.js.map +1 -1
- package/dist/report.d.ts +31 -7
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +174 -15
- package/dist/report.js.map +1 -1
- package/dist/rule-builder-like.d.ts +8 -2
- package/dist/rule-builder-like.d.ts.map +1 -1
- package/dist/rule-builder.d.ts +15 -0
- package/dist/rule-builder.d.ts.map +1 -1
- package/dist/rule-builder.js +41 -36
- package/dist/rule-builder.js.map +1 -1
- package/dist/rule-description.d.ts +50 -0
- package/dist/rule-description.d.ts.map +1 -1
- package/dist/rule-description.js +58 -0
- package/dist/rule-description.js.map +1 -1
- package/dist/selection-memo.d.ts +1 -3
- package/dist/selection-memo.d.ts.map +1 -1
- package/dist/selection-memo.js +1 -3
- package/dist/selection-memo.js.map +1 -1
- package/dist/silent-exclusion.d.ts +12 -0
- package/dist/silent-exclusion.d.ts.map +1 -1
- package/dist/silent-exclusion.js +22 -0
- package/dist/silent-exclusion.js.map +1 -1
- package/dist/terminal-builder.d.ts +29 -15
- package/dist/terminal-builder.d.ts.map +1 -1
- package/dist/terminal-builder.js +97 -51
- package/dist/terminal-builder.js.map +1 -1
- package/dist/vacuity-findings.d.ts +0 -6
- package/dist/vacuity-findings.d.ts.map +1 -1
- package/dist/vacuity-findings.js +27 -57
- package/dist/vacuity-findings.js.map +1 -1
- package/dist/violation.d.ts +22 -1
- package/dist/violation.d.ts.map +1 -1
- package/dist/violation.js.map +1 -1
- package/dist/watch.d.ts +71 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +129 -0
- package/dist/watch.js.map +1 -0
- package/package.json +6 -1
|
@@ -1,10 +1,55 @@
|
|
|
1
|
+
import { maskNonCommentSpans, maskMarkdownCodeSpans } from './mask-non-comment.js';
|
|
1
2
|
// Single-line: // eess-exclude <rule-id>[, <rule-id>]: <reason>
|
|
2
3
|
// Single-line without reason: // eess-exclude <rule-id>
|
|
3
|
-
|
|
4
|
+
// The directive must OPEN its comment (bug 0154). Masking alone took this file's
|
|
5
|
+
// own false-positive count from 12 to 7; the rest were genuine line comments that
|
|
6
|
+
// merely DESCRIBE the grammar — like the line above this one. No lexical rule can
|
|
7
|
+
// separate those, but anchoring to the start of the comment body separates all of
|
|
8
|
+
// them from every real directive, including the trailing `code // eess-exclude …`
|
|
9
|
+
// form.
|
|
10
|
+
//
|
|
11
|
+
// Rejected alternative: validating the rule-id's shape. It rejects `<rule-id>`
|
|
12
|
+
// and still accepts `// see eess-exclude a/b: why`, and it couples this parser to
|
|
13
|
+
// an id grammar nothing else enforces.
|
|
14
|
+
const SINGLE_LINE_RE = /^[ \t]*eess-exclude[ \t]+(.+)/;
|
|
15
|
+
/**
|
|
16
|
+
* The body of a line's FIRST `//` comment, or undefined if it has none.
|
|
17
|
+
*
|
|
18
|
+
* The directive must open the comment, and it must be the comment the line
|
|
19
|
+
* actually starts — not any `//` on the line. A grammar description carries two:
|
|
20
|
+
*
|
|
21
|
+
* // Single-line: // eess-exclude <rule-id>: <reason>
|
|
22
|
+
*
|
|
23
|
+
* Anchoring to `//` alone matches the second one and reads the file's own
|
|
24
|
+
* documentation as 8 live waivers. Taking the first `//` and requiring the
|
|
25
|
+
* directive at the start of its body separates every description from every real
|
|
26
|
+
* directive, including the trailing `code // eess-exclude …` form, whose first
|
|
27
|
+
* `//` is the directive's own.
|
|
28
|
+
*/
|
|
29
|
+
const CODE_LIKE = /\.(?:[cm]?[jt]sx?|vue|svelte)$/i;
|
|
30
|
+
const MARKDOWN_LIKE = /\.(?:md|markdown|mdx)$/i;
|
|
31
|
+
/**
|
|
32
|
+
* The HTML-comment forms exist for text dialects whose sources have no `//` —
|
|
33
|
+
* markdown corpora and the like. In a `.ts` file `// <!-- eess-exclude … -->` is
|
|
34
|
+
* prose describing the grammar, and reading it accounted for the last 2 of this
|
|
35
|
+
* very file's own false hits (bug 0154).
|
|
36
|
+
*/
|
|
37
|
+
function htmlFormsApply(filePath) {
|
|
38
|
+
return !CODE_LIKE.test(filePath);
|
|
39
|
+
}
|
|
40
|
+
function firstCommentBody(line) {
|
|
41
|
+
const at = line.indexOf('//');
|
|
42
|
+
return at === -1 ? undefined : line.slice(at + 2);
|
|
43
|
+
}
|
|
4
44
|
// Block start: // eess-exclude-start <rule-id>[, <rule-id>]: <reason>
|
|
5
|
-
|
|
45
|
+
// The trailing group is OPTIONAL. A bare `// eess-exclude-start` (no ids, no
|
|
46
|
+
// reason) used to match nothing at all — so it pushed no frame, said nothing,
|
|
47
|
+
// and the next `-end` closed the ENCLOSING block. Same two symptoms as the
|
|
48
|
+
// reason-free case, through a shape the reason-free fix did not reach: it is a
|
|
49
|
+
// bracket the reader can see, so it must consume the `-end` written for it.
|
|
50
|
+
const BLOCK_START_RE = /^[ \t]*eess-exclude-start(?:[ \t]+(.*))?[ \t]*$/;
|
|
6
51
|
// Block end: // eess-exclude-end
|
|
7
|
-
const BLOCK_END_RE =
|
|
52
|
+
const BLOCK_END_RE = /^[ \t]*eess-exclude-end\b/;
|
|
8
53
|
// HTML-comment forms — the same directives for text dialects whose sources
|
|
9
54
|
// aren't `//`-commented (markdown corpora, and any future text dialect).
|
|
10
55
|
// <!-- eess-exclude <rule-id>[, <rule-id>]: <reason> -->
|
|
@@ -37,19 +82,24 @@ function parseRuleIdsAndReason(content) {
|
|
|
37
82
|
}
|
|
38
83
|
/** Handle a block-end directive line. */
|
|
39
84
|
function handleBlockEnd(openBlocks, exclusions, warnings, filePath, lineNum) {
|
|
40
|
-
|
|
85
|
+
// Bug 0158: pop the INNERMOST frame. Closing every open block meant an inner
|
|
86
|
+
// `-end` silently ended the outer one, so the outer waiver stopped applying
|
|
87
|
+
// partway through the region it was written for — two wrong results from one
|
|
88
|
+
// input, and neither reported.
|
|
89
|
+
const frame = openBlocks.pop();
|
|
90
|
+
if (frame === undefined) {
|
|
41
91
|
warnings.push({
|
|
42
92
|
message: `eess-exclude-end without matching start`,
|
|
43
93
|
file: filePath,
|
|
44
94
|
line: lineNum,
|
|
95
|
+
kind: 'malformed',
|
|
45
96
|
});
|
|
46
97
|
return;
|
|
47
98
|
}
|
|
48
|
-
for (const
|
|
99
|
+
for (const comment of frame) {
|
|
49
100
|
comment.endLine = lineNum;
|
|
50
101
|
exclusions.push(comment);
|
|
51
102
|
}
|
|
52
|
-
openBlocks.clear();
|
|
53
103
|
}
|
|
54
104
|
/** Emit undocumented-exclusion warnings for each rule ID when no reason is given. */
|
|
55
105
|
function warnUndocumented(warnings, ruleIds, directive, filePath, lineNum) {
|
|
@@ -60,32 +110,85 @@ function warnUndocumented(warnings, ruleIds, directive, filePath, lineNum) {
|
|
|
60
110
|
` Fix: Add a reason — // ${directive} ${ruleId}: <why>`,
|
|
61
111
|
file: filePath,
|
|
62
112
|
line: lineNum,
|
|
113
|
+
kind: 'undocumented',
|
|
63
114
|
});
|
|
64
115
|
}
|
|
65
116
|
}
|
|
66
117
|
/** Handle a block-start directive line. */
|
|
67
118
|
function handleBlockStart(content, openBlocks, warnings, filePath, lineNum) {
|
|
68
|
-
|
|
119
|
+
const { ruleIds, reason } = parseRuleIdsAndReason(content);
|
|
120
|
+
if (ruleIds.length === 0) {
|
|
121
|
+
// A `-start` naming no rule at all. It still consumes its `-end` (see the
|
|
122
|
+
// regex comment), but it is malformed and saying nothing about it would be
|
|
123
|
+
// the silence this whole grammar is written to refuse.
|
|
69
124
|
warnings.push({
|
|
70
|
-
message: `
|
|
125
|
+
message: `eess-exclude-start names no rule id. ` +
|
|
126
|
+
`Fix: write // eess-exclude-start <rule-id>: <why>, or delete the directive ` +
|
|
127
|
+
`and its matching -end.`,
|
|
71
128
|
file: filePath,
|
|
72
129
|
line: lineNum,
|
|
130
|
+
kind: 'malformed',
|
|
73
131
|
});
|
|
132
|
+
openBlocks.push(ruleIds.map((ruleId) => ({
|
|
133
|
+
ruleId,
|
|
134
|
+
reason,
|
|
135
|
+
file: filePath,
|
|
136
|
+
line: lineNum,
|
|
137
|
+
isBlock: true,
|
|
138
|
+
})));
|
|
74
139
|
return;
|
|
75
140
|
}
|
|
76
|
-
const { ruleIds, reason } = parseRuleIdsAndReason(content);
|
|
77
141
|
if (reason === '') {
|
|
78
142
|
warnUndocumented(warnings, ruleIds, 'eess-exclude-start', filePath, lineNum);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
143
|
+
// Push an EMPTY frame rather than returning. Refusing the waiver and
|
|
144
|
+
// refusing the frame are different things: `-start`/`-end` is a bracket
|
|
145
|
+
// language, so a `-start` the reader can see must consume the `-end` the
|
|
146
|
+
// reader wrote for it, whatever we decide about its reason.
|
|
147
|
+
//
|
|
148
|
+
// Returning here — as this did when bug 0158's two halves shipped together —
|
|
149
|
+
// let the next `-end` pop the OUTER frame, so a valid enclosing waiver
|
|
150
|
+
// stopped early and a balanced file reported "end without matching start".
|
|
151
|
+
// That is precisely the frame-mangling 0158's nesting half removed,
|
|
152
|
+
// reintroduced by its reason-required half. Empty means it suppresses
|
|
153
|
+
// nothing, which is the refusal; present means the brackets still pair.
|
|
154
|
+
openBlocks.push(ruleIds.map((ruleId) => ({
|
|
82
155
|
ruleId,
|
|
83
156
|
reason,
|
|
84
157
|
file: filePath,
|
|
85
158
|
line: lineNum,
|
|
86
159
|
isBlock: true,
|
|
160
|
+
})));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// A rule re-opened while already open is warned but still applied. The likeliest
|
|
164
|
+
// cause is a missing `-end`, which the author wants to know; refusing it is what
|
|
165
|
+
// produced the early-close bug. Ported from `eess-ts` when the parsers were
|
|
166
|
+
// unified (ADR-012) — the kernel had no such warning, so `eess-md`,
|
|
167
|
+
// `eess-mermaid` and `eess-gherkin` never got it.
|
|
168
|
+
const alreadyOpen = new Set(openBlocks.flat().map((c) => c.ruleId));
|
|
169
|
+
for (const ruleId of ruleIds) {
|
|
170
|
+
if (!alreadyOpen.has(ruleId))
|
|
171
|
+
continue;
|
|
172
|
+
warnings.push({
|
|
173
|
+
message: `eess-exclude-start for '${ruleId}' is already open — ` +
|
|
174
|
+
`the enclosing block covers this region. Fix: remove this directive, ` +
|
|
175
|
+
`or close the enclosing block first if the nesting was unintended.`,
|
|
176
|
+
file: filePath,
|
|
177
|
+
line: lineNum,
|
|
178
|
+
kind: 'malformed',
|
|
87
179
|
});
|
|
88
180
|
}
|
|
181
|
+
// Bug 0158: nesting is supported rather than refused. Blocks are a STACK, not
|
|
182
|
+
// a map keyed by rule id — the map dropped an inner `-start` outright, and a
|
|
183
|
+
// second start for the same id overwrote the first. One `-start` pushes one
|
|
184
|
+
// frame, whatever number of ids it names.
|
|
185
|
+
openBlocks.push(ruleIds.map((ruleId) => ({
|
|
186
|
+
ruleId,
|
|
187
|
+
reason,
|
|
188
|
+
file: filePath,
|
|
189
|
+
line: lineNum,
|
|
190
|
+
isBlock: true,
|
|
191
|
+
})));
|
|
89
192
|
}
|
|
90
193
|
/** Handle a single-line exclude directive. */
|
|
91
194
|
function handleSingleLine(content, exclusions, warnings, filePath, lineNum) {
|
|
@@ -93,6 +196,21 @@ function handleSingleLine(content, exclusions, warnings, filePath, lineNum) {
|
|
|
93
196
|
if (content.startsWith('-start') || content.startsWith('-end'))
|
|
94
197
|
return;
|
|
95
198
|
const { ruleIds, reason } = parseRuleIdsAndReason(content);
|
|
199
|
+
// Bug 0158: the grammar documents `<rule-id>: <reason>`, and a reason-free
|
|
200
|
+
// directive must not silence a real finding while the build exits 0.
|
|
201
|
+
//
|
|
202
|
+
// **How that is enforced changed in ADR-012, and the change is the point.**
|
|
203
|
+
// This used to `return` — refusing the waiver — so the author saw the original
|
|
204
|
+
// violation and a line on stderr about their directive. `eess-ts` had always
|
|
205
|
+
// done the opposite (bug 0039): apply the waiver, and let `execute-rule`
|
|
206
|
+
// replace the suppressed finding with an UNSUPPRESSABLE one saying the waiver
|
|
207
|
+
// states no reason. Both end red; the second tells the author what to fix
|
|
208
|
+
// instead of showing them the thing they were waiving.
|
|
209
|
+
//
|
|
210
|
+
// Unifying the two parsers forced a choice, and `eess-ts`'s is the better
|
|
211
|
+
// design, so the kernel adopts it — which means the other four dialects gain
|
|
212
|
+
// it. It is safe here because the kernel's own `execute-rule` now does the
|
|
213
|
+
// promotion; applying without promoting would be the fail-open.
|
|
96
214
|
if (reason === '') {
|
|
97
215
|
warnUndocumented(warnings, ruleIds, 'eess-exclude', filePath, lineNum);
|
|
98
216
|
}
|
|
@@ -106,51 +224,81 @@ function handleSingleLine(content, exclusions, warnings, filePath, lineNum) {
|
|
|
106
224
|
});
|
|
107
225
|
}
|
|
108
226
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
227
|
+
export function parseExclusionComments(sourceText, filePath, options) {
|
|
228
|
+
// Bug 0154: read directives from MASKED text, never the raw source. Strings,
|
|
229
|
+
// templates, regex literals and block comments are blanked first — a directive
|
|
230
|
+
// written inside one is prose, and reading it silently waived a real finding on
|
|
231
|
+
// the next line. Masking is length- and line-preserving, so every position
|
|
232
|
+
// reported below is still the position in the original file.
|
|
233
|
+
// Mask with the lexer that matches the file. Running the JS/TS one over
|
|
234
|
+
// markdown made a stray backtick swallow every directive after it — the same
|
|
235
|
+
// question (is this text an example or an instance?) needs the host language's
|
|
236
|
+
// answer, not one language's answer everywhere.
|
|
237
|
+
//
|
|
238
|
+
// Routed on MARKDOWN specifically, not on "not code". The first version used
|
|
239
|
+
// `!CODE_LIKE`, which handed `.mmd`, `.feature`, `.yml` and everything else to
|
|
240
|
+
// the markdown lexer — and a `.mmd` node label is a quoted string, which the
|
|
241
|
+
// JS masker blanks and the markdown one does not. That is the very mistake this
|
|
242
|
+
// comment describes, committed by the fix for it. Anything neither code nor
|
|
243
|
+
// markdown keeps the conservative masker: over-masking hides a directive
|
|
244
|
+
// (loud), under-masking invents one (silent).
|
|
245
|
+
const defaultMask = CODE_LIKE.test(filePath)
|
|
246
|
+
? maskNonCommentSpans
|
|
247
|
+
: MARKDOWN_LIKE.test(filePath)
|
|
248
|
+
? maskMarkdownCodeSpans
|
|
249
|
+
: maskNonCommentSpans;
|
|
250
|
+
// The dialect's masker runs FIRST and the default runs over its output, so the
|
|
251
|
+
// default's protection cannot be opted out of (ADR-012). A dialect supplies
|
|
252
|
+
// accuracy; it cannot supply permission.
|
|
253
|
+
const injected = options?.mask;
|
|
254
|
+
const masked = injected ? defaultMask(injected(sourceText, filePath)) : defaultMask(sourceText);
|
|
255
|
+
const lines = masked.split('\n');
|
|
256
|
+
const htmlApply = htmlFormsApply(filePath);
|
|
122
257
|
const exclusions = [];
|
|
123
258
|
const warnings = [];
|
|
124
|
-
const openBlocks =
|
|
259
|
+
const openBlocks = [];
|
|
125
260
|
for (let i = 0; i < lines.length; i++) {
|
|
126
261
|
const line = lines[i];
|
|
127
262
|
if (line === undefined)
|
|
128
263
|
continue;
|
|
129
264
|
const lineNum = i + 1;
|
|
130
265
|
// Check block end first (before start/single so we don't match -start as single)
|
|
131
|
-
|
|
266
|
+
// The `//` forms read the FIRST comment's body; the HTML forms read the
|
|
267
|
+
// whole line, since they are for text dialects with no `//` at all.
|
|
268
|
+
const body = firstCommentBody(line);
|
|
269
|
+
if ((body !== undefined && BLOCK_END_RE.test(body)) ||
|
|
270
|
+
(htmlApply && HTML_BLOCK_END_RE.test(line))) {
|
|
132
271
|
handleBlockEnd(openBlocks, exclusions, warnings, filePath, lineNum);
|
|
133
272
|
continue;
|
|
134
273
|
}
|
|
135
274
|
// Check block start
|
|
136
|
-
const startMatch = BLOCK_START_RE.exec(
|
|
137
|
-
|
|
138
|
-
|
|
275
|
+
const startMatch = (body === undefined ? null : BLOCK_START_RE.exec(body)) ??
|
|
276
|
+
(htmlApply ? HTML_BLOCK_START_RE.exec(line) : null);
|
|
277
|
+
// `startMatch !== null`, not `startMatch?.[1]`. The capture is optional now, so
|
|
278
|
+
// a bare `// eess-exclude-start` matches with group 1 undefined — and testing
|
|
279
|
+
// the GROUP threw that line away again, which is the same "no frame, silent,
|
|
280
|
+
// enclosing block closes early" defect one level up from the regex.
|
|
281
|
+
if (startMatch !== null) {
|
|
282
|
+
handleBlockStart(startMatch[1] ?? '', openBlocks, warnings, filePath, lineNum);
|
|
139
283
|
continue;
|
|
140
284
|
}
|
|
141
285
|
// Check single-line exclude (must not match block directives)
|
|
142
|
-
const singleMatch = SINGLE_LINE_RE.exec(
|
|
286
|
+
const singleMatch = (body === undefined ? null : SINGLE_LINE_RE.exec(body)) ??
|
|
287
|
+
(htmlApply ? HTML_SINGLE_RE.exec(line) : null);
|
|
143
288
|
if (singleMatch?.[1]) {
|
|
144
289
|
handleSingleLine(singleMatch[1], exclusions, warnings, filePath, lineNum);
|
|
145
290
|
}
|
|
146
291
|
}
|
|
147
292
|
// Any unclosed blocks are errors
|
|
148
|
-
for (const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
293
|
+
for (const frame of openBlocks) {
|
|
294
|
+
for (const comment of frame) {
|
|
295
|
+
warnings.push({
|
|
296
|
+
message: `eess-exclude-start without matching end for rule '${comment.ruleId}'`,
|
|
297
|
+
file: filePath,
|
|
298
|
+
line: comment.line,
|
|
299
|
+
kind: 'malformed',
|
|
300
|
+
});
|
|
301
|
+
}
|
|
154
302
|
}
|
|
155
303
|
return { exclusions, warnings };
|
|
156
304
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exclusion-comments.js","sourceRoot":"","sources":["../src/exclusion-comments.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"exclusion-comments.js","sourceRoot":"","sources":["../src/exclusion-comments.ts"],"names":[],"mappings":"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"}
|
package/dist/execute-rule.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ArchViolation } from './violation.js';
|
|
2
|
+
import { type CollectResult } from './collect-result.js';
|
|
2
3
|
import type { CheckOptions } from './check-options.js';
|
|
3
4
|
import type { RuleMetadata } from './rule-metadata.js';
|
|
4
5
|
/**
|
|
@@ -6,8 +7,17 @@ import type { RuleMetadata } from './rule-metadata.js';
|
|
|
6
7
|
* Shared across all builder types (RuleBuilder, SliceRuleBuilder,
|
|
7
8
|
* SchemaRuleBuilder, ResolverRuleBuilder, PairFinalBuilder, SmellBuilder).
|
|
8
9
|
*/
|
|
9
|
-
interface ExecuteRuleContext {
|
|
10
|
+
export interface ExecuteRuleContext {
|
|
10
11
|
reason?: string;
|
|
12
|
+
/**
|
|
13
|
+
* This rule's own sentence, deferred (bug 0258).
|
|
14
|
+
*
|
|
15
|
+
* A thunk rather than a string because `filterContext()` runs on every
|
|
16
|
+
* terminal call and this is only read when an id-less rule turns out to have
|
|
17
|
+
* an exclusion comment it cannot honour — the same shape `facts()` already
|
|
18
|
+
* uses for `describeRule` a few lines above it.
|
|
19
|
+
*/
|
|
20
|
+
describe?: () => string;
|
|
11
21
|
metadata?: RuleMetadata;
|
|
12
22
|
exclusions?: (string | RegExp)[];
|
|
13
23
|
silentIndices?: Set<number>;
|
|
@@ -28,10 +38,9 @@ export declare function applyFilters(violations: ArchViolation[], ctx: ExecuteRu
|
|
|
28
38
|
/**
|
|
29
39
|
* Execute the terminal "check" action: apply options, format, throw on violations.
|
|
30
40
|
*/
|
|
31
|
-
export declare function executeCheck(violations:
|
|
41
|
+
export declare function executeCheck(violations: CollectResult, ctx: ExecuteRuleContext, options?: CheckOptions): void;
|
|
32
42
|
/**
|
|
33
43
|
* Execute the terminal "warn" action: apply options, format, log to stderr.
|
|
34
44
|
*/
|
|
35
45
|
export declare function executeWarn(violations: ArchViolation[], ctx: ExecuteRuleContext, options?: CheckOptions): void;
|
|
36
|
-
export {};
|
|
37
46
|
//# sourceMappingURL=execute-rule.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute-rule.d.ts","sourceRoot":"","sources":["../src/execute-rule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"execute-rule.d.ts","sourceRoot":"","sources":["../src/execute-rule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,qBAAqB,CAAA;AAEvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AActD;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,UAAU,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;IAChC,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,aAAa,EAAE,EAC3B,GAAG,EAAE,kBAAkB,GACtB,aAAa,EAAE,CAmRjB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,aAAa,EACzB,GAAG,EAAE,kBAAkB,EACvB,OAAO,CAAC,EAAE,YAAY,GACrB,IAAI,CA+BN;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,aAAa,EAAE,EAC3B,GAAG,EAAE,kBAAkB,EACvB,OAAO,CAAC,EAAE,YAAY,GACrB,IAAI,CA8BN"}
|
package/dist/execute-rule.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { collectResult } from './collect-result.js';
|
|
2
3
|
import { severityFor } from './violation.js';
|
|
3
4
|
import { ArchRuleError } from './errors.js';
|
|
4
5
|
import { formatViolations } from './format.js';
|
|
@@ -6,6 +7,7 @@ import { formatViolationsJson } from './format-json.js';
|
|
|
6
7
|
import { formatViolationsGitHub } from './format-github.js';
|
|
7
8
|
import { reportViolations } from './report.js';
|
|
8
9
|
import { parseExclusionComments, isExcludedByComment } from './exclusion-comments.js';
|
|
10
|
+
import { UNSUPPRESSABLE } from './unsuppressable.js';
|
|
9
11
|
import { writeStderr } from './stderr.js';
|
|
10
12
|
import { activeNotice } from './diff-disclosure.js';
|
|
11
13
|
import { recordCommentSuppression } from './comment-suppression.js';
|
|
@@ -94,15 +96,41 @@ export function applyFilters(violations, ctx) {
|
|
|
94
96
|
v.docs = ctx.metadata.docs;
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
|
-
// Scan source files for inline exclusion comments
|
|
98
|
-
//
|
|
99
|
-
|
|
99
|
+
// Scan source files for inline exclusion comments.
|
|
100
|
+
//
|
|
101
|
+
// **Not gated on `ctx.metadata?.id` any more (bug 0255).** It used to be, and
|
|
102
|
+
// that made the worst case of an inert directive the quietest: with no
|
|
103
|
+
// `.rule({ id })` a comment can never match — `isExcludedByComment` returns
|
|
104
|
+
// false without a `ruleId` — and because the block was skipped, the file was
|
|
105
|
+
// never even parsed, so nothing could notice. An adopter following the
|
|
106
|
+
// documented sanction recipe got the same red build and no explanation.
|
|
107
|
+
//
|
|
108
|
+
// The cost is bounded the same way it always was: only files that already
|
|
109
|
+
// produced a violation are read. A rule with no id now pays one parse per
|
|
110
|
+
// violating file to be able to say why the sanction did nothing.
|
|
111
|
+
if (result.length > 0) {
|
|
100
112
|
const filePaths = new Set(result.map((v) => v.file));
|
|
113
|
+
const undocumented = [];
|
|
101
114
|
const allComments = [...filePaths].flatMap((filePath) => {
|
|
102
115
|
try {
|
|
103
116
|
const sourceText = fs.readFileSync(filePath, 'utf-8');
|
|
104
117
|
const parseResult = parseExclusionComments(sourceText, filePath);
|
|
105
118
|
for (const warning of parseResult.warnings) {
|
|
119
|
+
// An undocumented exclusion is well-formed and APPLIES, so a stderr
|
|
120
|
+
// line would let a waiver nobody justified pass with exit 0. It
|
|
121
|
+
// becomes an unsuppressable finding below instead — ADR-009 rule 3's
|
|
122
|
+
// corollary, and bug 0039's design, which until ADR-012 only `eess-ts`
|
|
123
|
+
// implemented. The malformed shapes decline to create the exclusion at
|
|
124
|
+
// all, so the original violation still fires and the build is already
|
|
125
|
+
// red; stderr is the right weight for those.
|
|
126
|
+
if (warning.kind === 'undocumented' && ctx.metadata?.id !== undefined) {
|
|
127
|
+
// The promotion below builds an unsuppressable finding keyed on the
|
|
128
|
+
// rule id. Without one there is nothing to key it on — and the
|
|
129
|
+
// directive is inert anyway, which the inert report says with a
|
|
130
|
+
// remedy the author can act on. So it falls through to stderr.
|
|
131
|
+
undocumented.push(warning);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
106
134
|
writeStderr(`[eess] ${warning.message}`);
|
|
107
135
|
}
|
|
108
136
|
return parseResult.exclusions;
|
|
@@ -115,16 +143,152 @@ export function applyFilters(violations, ctx) {
|
|
|
115
143
|
return [];
|
|
116
144
|
}
|
|
117
145
|
});
|
|
118
|
-
|
|
119
|
-
|
|
146
|
+
const ruleId = ctx.metadata?.id;
|
|
147
|
+
if (allComments.length > 0 && ruleId !== undefined) {
|
|
148
|
+
// Which comments actually did something, so the ones that did nothing can
|
|
149
|
+
// be named. Reuses `isExcludedByComment` one comment at a time rather than
|
|
150
|
+
// re-deriving the match, so this can never drift from the real rule.
|
|
151
|
+
const spent = new Set();
|
|
120
152
|
result = result.filter((v) => {
|
|
121
153
|
if (v.bypassFilters === true)
|
|
122
154
|
return true;
|
|
123
155
|
const excluded = isExcludedByComment(v, allComments);
|
|
124
|
-
if (excluded)
|
|
156
|
+
if (excluded) {
|
|
125
157
|
recordCommentSuppression(ruleId, v.file);
|
|
158
|
+
for (const c of allComments)
|
|
159
|
+
if (isExcludedByComment(v, [c]))
|
|
160
|
+
spent.add(c);
|
|
161
|
+
}
|
|
126
162
|
return !excluded;
|
|
127
163
|
});
|
|
164
|
+
// A directive naming THIS rule that suppressed nothing. Same shape and the
|
|
165
|
+
// same weight as the `.excluding()` "Unused exclusion" line above — that
|
|
166
|
+
// mechanism has warned about a stale pattern since bug 0044; the comment
|
|
167
|
+
// form never did, which is bug 0255.
|
|
168
|
+
//
|
|
169
|
+
// **The wording is domain-neutral on purpose.** The first version explained
|
|
170
|
+
// the next-line scope in terms of markdown table rows, because that is the
|
|
171
|
+
// corpus bug 0255 was filed against. This function is the kernel's, and
|
|
172
|
+
// `eess-mermaid` and `eess-gherkin` run it unforked — so a stale directive
|
|
173
|
+
// in a `.mmd` or `.feature` file was told it sat "inside a markdown table".
|
|
174
|
+
// Review caught it, and caught the tell: the hand-port into `eess-ts`
|
|
175
|
+
// silently dropped the table clause because it made no sense there, which
|
|
176
|
+
// is the evidence it never belonged at the shared layer. The table nuance
|
|
177
|
+
// now lives in `docs/markdown.md`, where the dialect that has tables is
|
|
178
|
+
// documented.
|
|
179
|
+
//
|
|
180
|
+
// Scoped to this rule's id on purpose: a file may carry directives for many
|
|
181
|
+
// rules, and one naming another rule matched nothing here for a good
|
|
182
|
+
// reason. Without that scope this would fire once per unrelated directive
|
|
183
|
+
// per rule.
|
|
184
|
+
for (const c of allComments) {
|
|
185
|
+
if (c.ruleId !== ruleId || spent.has(c))
|
|
186
|
+
continue;
|
|
187
|
+
writeStderr(`[eess] Exclusion comment for '${ruleId}' at ${c.file}:${String(c.line)} ` +
|
|
188
|
+
`suppressed nothing. It may be stale, or out of reach: a single-line ` +
|
|
189
|
+
`directive covers only the NEXT line. eess-exclude-start/-end covers a ` +
|
|
190
|
+
`region instead.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (allComments.length > 0) {
|
|
194
|
+
// No rule id, so no comment here can ever match: `isExcludedByComment`
|
|
195
|
+
// refuses without one.
|
|
196
|
+
//
|
|
197
|
+
// **This branch cannot say whose directive it is looking at, and the first
|
|
198
|
+
// version pretended otherwise.** It named each comment's own rule id and
|
|
199
|
+
// told the reader to add `.rule({ id: <that id> })` — but from inside one
|
|
200
|
+
// rule's execution there is no way to know whether that id already belongs
|
|
201
|
+
// to a different, working rule. Review reproduced the harm: a directive
|
|
202
|
+
// correctly waiving `other/rule` in a file where an id-less rule also
|
|
203
|
+
// fired produced advice to claim `other/rule` for the id-less one, which
|
|
204
|
+
// nothing prevents and which would collide two rules on one id.
|
|
205
|
+
//
|
|
206
|
+
// So it states the fact and leaves the id to the author. One line per
|
|
207
|
+
// file, not per comment, for the same reason its sibling scopes by id: a
|
|
208
|
+
// file may carry directives for many rules and none of them is evidence
|
|
209
|
+
// about this one.
|
|
210
|
+
const byFile = new Map();
|
|
211
|
+
for (const c of allComments) {
|
|
212
|
+
const lines = byFile.get(c.file);
|
|
213
|
+
if (lines)
|
|
214
|
+
lines.push(c.line);
|
|
215
|
+
else
|
|
216
|
+
byFile.set(c.file, [c.line]);
|
|
217
|
+
}
|
|
218
|
+
// Name the rule by its `.because()` reason when it has one (bug 0258).
|
|
219
|
+
// An id-less rule has no id to name, so several id-less chains over one
|
|
220
|
+
// file printed byte-identical lines and a reader could not tell which
|
|
221
|
+
// chain needed the id. The reason is a discriminator that already exists:
|
|
222
|
+
// `.because()` works without `.rule({ id })`, and `ctx.reason` is stamped
|
|
223
|
+
// onto violations a few lines above this. Whitespace is collapsed because
|
|
224
|
+
// the reason is prose and may wrap, and this report is deliberately one
|
|
225
|
+
// line per file. A rule with neither id nor reason is genuinely anonymous
|
|
226
|
+
// and the message stays exactly as it was.
|
|
227
|
+
// Name the rule, preferring its own sentence over its reason.
|
|
228
|
+
//
|
|
229
|
+
// The first version of this used `.because()` alone, on the premise that
|
|
230
|
+
// no rule description was reachable here. Review measured that false:
|
|
231
|
+
// every builder implements `describeRule()`, `filterContext()` is a method
|
|
232
|
+
// on the class that has it, and the kernel already names an id-less rule
|
|
233
|
+
// this way for its assertion-less finding a couple of hundred lines above.
|
|
234
|
+
// `.because()` is optional prose — the chains this diagnostic targets
|
|
235
|
+
// often have neither an id nor a reason — so leaning on it alone was a
|
|
236
|
+
// weaker floor for the same cost.
|
|
237
|
+
//
|
|
238
|
+
// `'unnamed'` is `TerminalBuilder`'s own fallback for a rule with no id;
|
|
239
|
+
// naming a rule "unnamed" tells the reader nothing, so it counts as absent
|
|
240
|
+
// and the reason takes over. Whitespace is collapsed either way: both are
|
|
241
|
+
// prose that may wrap, and this report is one line per file.
|
|
242
|
+
// Collapse to one line, and escape the delimiter. A `.because()` reason is
|
|
243
|
+
// arbitrary prose, so one containing a quoted term rendered as
|
|
244
|
+
// `("ignore the "legacy" path")` — three quotes before the paren, and no
|
|
245
|
+
// way for a reader to see where the label ends. Escaping keeps the
|
|
246
|
+
// author's words exact where replacing them would not.
|
|
247
|
+
const oneLine = (t) => t.replace(/\s+/g, ' ').trim().replace(/"/g, '\\"');
|
|
248
|
+
const described = ctx.describe?.();
|
|
249
|
+
const label = described !== undefined && described !== 'unnamed' && described !== ''
|
|
250
|
+
? oneLine(described)
|
|
251
|
+
: ctx.reason === undefined
|
|
252
|
+
? undefined
|
|
253
|
+
: oneLine(ctx.reason);
|
|
254
|
+
const named = label === undefined || label === '' ? '' : ` ("${label}")`;
|
|
255
|
+
for (const [file, lines] of byFile) {
|
|
256
|
+
const where = lines.length === 1 ? `line ${String(lines[0])}` : `lines ${lines.join(', ')}`;
|
|
257
|
+
writeStderr(`[eess] This rule${named} declares no id, so no exclusion comment can apply to it — ` +
|
|
258
|
+
`a comment matches a violation by rule id. ${file} has a directive at ${where}. ` +
|
|
259
|
+
`If one was meant for this rule, give the rule an id with .rule({ id: '<your-id>' }); ` +
|
|
260
|
+
`directives naming other rules are not this rule's to honour.`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// The waiver applied; this takes the suppressed finding's place, so the
|
|
264
|
+
// author is told what to fix rather than shown the violation they waived.
|
|
265
|
+
//
|
|
266
|
+
// Unsuppressable, because a suppression mechanism that can suppress the
|
|
267
|
+
// complaint about itself is not a mechanism.
|
|
268
|
+
//
|
|
269
|
+
// `undocumented` is only ever populated when the rule HAS an id (the push
|
|
270
|
+
// above is guarded on it), so this loop is unreachable without one — the
|
|
271
|
+
// local re-binding is what tells the compiler that after bug 0255 removed
|
|
272
|
+
// the outer `ctx.metadata?.id` gate.
|
|
273
|
+
for (const warning of undocumented) {
|
|
274
|
+
if (ruleId === undefined)
|
|
275
|
+
continue;
|
|
276
|
+
result.push({
|
|
277
|
+
rule: ruleId,
|
|
278
|
+
ruleId,
|
|
279
|
+
element: `${ruleId}@${warning.file}:${String(warning.line)}`,
|
|
280
|
+
file: warning.file,
|
|
281
|
+
line: warning.line,
|
|
282
|
+
message: `This exclusion states no reason, so nothing records why the rule is ` +
|
|
283
|
+
`waived here — and it is silently suppressing a real finding.`,
|
|
284
|
+
suggestion: `Add a reason: // eess-exclude ${ruleId}: <why>. ` +
|
|
285
|
+
`A reason is prose and nothing verifies it, so this raises the cost of a ` +
|
|
286
|
+
`suppression rather than preventing one — the audience is the reviewer ` +
|
|
287
|
+
`reading the diff. If the exemption is not justifiable, delete it and fix ` +
|
|
288
|
+
`the finding instead. ${UNSUPPRESSABLE}`,
|
|
289
|
+
severity: 'error',
|
|
290
|
+
bypassFilters: true,
|
|
291
|
+
});
|
|
128
292
|
}
|
|
129
293
|
}
|
|
130
294
|
return result;
|
|
@@ -133,6 +297,16 @@ export function applyFilters(violations, ctx) {
|
|
|
133
297
|
* Execute the terminal "check" action: apply options, format, throw on violations.
|
|
134
298
|
*/
|
|
135
299
|
export function executeCheck(violations, ctx, options) {
|
|
300
|
+
// The evidence survives every filter below. Baseline and `--changed` remove
|
|
301
|
+
// violations, never units examined: a run that examined 900 files and had its
|
|
302
|
+
// findings baselined away examined 900 files, and reporting 0 there would be
|
|
303
|
+
// the denominator lie bug 0174 is about.
|
|
304
|
+
const evidence = {
|
|
305
|
+
examined: violations.examined,
|
|
306
|
+
sourceEmpty: violations.sourceEmpty,
|
|
307
|
+
declaredEmpty: violations.declaredEmpty,
|
|
308
|
+
deadGlob: violations.deadGlob,
|
|
309
|
+
};
|
|
136
310
|
let filtered = applyFilters(violations, ctx);
|
|
137
311
|
if (options?.baseline) {
|
|
138
312
|
filtered = options.baseline.filterNew(filtered);
|
|
@@ -146,7 +320,10 @@ export function executeCheck(violations, ctx, options) {
|
|
|
146
320
|
}
|
|
147
321
|
if (filtered.length > 0) {
|
|
148
322
|
// One emitter for both paths (plan 0070) — text/json/github, then throw.
|
|
149
|
-
reportViolations(filtered,
|
|
323
|
+
reportViolations(collectResult(filtered, evidence), {
|
|
324
|
+
format: options?.format,
|
|
325
|
+
reason: ctx.reason,
|
|
326
|
+
});
|
|
150
327
|
throw new ArchRuleError(filtered, ctx.reason);
|
|
151
328
|
}
|
|
152
329
|
}
|