@descent-vtt/spec-guard 0.5.1 → 0.6.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/CHANGELOG.md +377 -283
- package/README.md +1009 -868
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/graph.d.ts +124 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +241 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/layers.d.ts +72 -0
- package/dist/layers.d.ts.map +1 -0
- package/dist/layers.js +102 -0
- package/dist/layers.js.map +1 -0
- package/dist/parser.d.ts +25 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +178 -1
- package/dist/parser.js.map +1 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +62 -5
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts +8 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +238 -40
- package/dist/runner.js.map +1 -1
- package/dist/types.d.ts +60 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +73 -73
package/dist/layers.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The layering rule behind `@assert-layers`.
|
|
3
|
+
*
|
|
4
|
+
* Pure, and built on almost nothing new. A layer is a pattern in the language
|
|
5
|
+
* `module=` and `exclude` already speak; a file belongs to a layer when its path
|
|
6
|
+
* matches, and a reference points into one when any name it can denote does -
|
|
7
|
+
* see `referenceForms`. That is why layering needs no resolver and works in
|
|
8
|
+
* every language spec-guard reads: it never asks which file a reference *is*,
|
|
9
|
+
* only which areas it could be about.
|
|
10
|
+
*
|
|
11
|
+
* Cycles are the opposite case, and live in `graph.ts` - see ADR-0011.
|
|
12
|
+
*/
|
|
13
|
+
import { type ModuleReference } from './imports.js';
|
|
14
|
+
/**
|
|
15
|
+
* Every name a reference can be matched under.
|
|
16
|
+
*
|
|
17
|
+
* The specifier as written and the module it resolves to, as `module=` tries -
|
|
18
|
+
* and, for the two languages whose modules are files, the files that module can
|
|
19
|
+
* denote. Without the last, a layer that names a file could never be reached:
|
|
20
|
+
* `src/runner.ts` is where the file is, `./runner.js` is what imports it, and
|
|
21
|
+
* the first rule this project wrote with file-sized layers passed while its
|
|
22
|
+
* order was reversed, because no reference matched any layer at all.
|
|
23
|
+
*
|
|
24
|
+
* Names only. Nothing here asks whether a candidate exists, which is why a layer
|
|
25
|
+
* outside the assertion's target still works: `../infrastructure/db.js` reaches
|
|
26
|
+
* `src/infrastructure/db.ts` whether or not the walk ever went there.
|
|
27
|
+
*/
|
|
28
|
+
export declare function referenceForms(specifier: string, importingFile: string): string[];
|
|
29
|
+
export interface LayerInput {
|
|
30
|
+
/** Root-relative path of the importing file. */
|
|
31
|
+
file: string;
|
|
32
|
+
references: readonly ModuleReference[];
|
|
33
|
+
}
|
|
34
|
+
export interface LayerViolation {
|
|
35
|
+
file: string;
|
|
36
|
+
/** The layer the file belongs to, as an index into the order. */
|
|
37
|
+
from: number;
|
|
38
|
+
/** The latest layer its reference reaches, also an index. */
|
|
39
|
+
to: number;
|
|
40
|
+
/** The first reference, in source order, that reaches a later layer. */
|
|
41
|
+
reference: ModuleReference;
|
|
42
|
+
}
|
|
43
|
+
export interface LayerReport {
|
|
44
|
+
/** Files in scope matched by each layer, by index. */
|
|
45
|
+
members: number[];
|
|
46
|
+
/** Files in scope that no layer matches, and which are therefore unconstrained. */
|
|
47
|
+
unassigned: string[];
|
|
48
|
+
/** Files matched by more than one layer, with every layer that matched. */
|
|
49
|
+
ambiguous: Array<{
|
|
50
|
+
file: string;
|
|
51
|
+
layers: number[];
|
|
52
|
+
}>;
|
|
53
|
+
/** At most one per file. */
|
|
54
|
+
violations: LayerViolation[];
|
|
55
|
+
}
|
|
56
|
+
/** A function from a path to the indices of every layer that matches it. */
|
|
57
|
+
export declare function layerMatcher(order: readonly string[]): (relativePath: string) => number[];
|
|
58
|
+
/**
|
|
59
|
+
* Checks each file's references against the order.
|
|
60
|
+
*
|
|
61
|
+
* A file may import from its own layer and from layers listed before it. The
|
|
62
|
+
* unit of violation is the file, as it is for every import assertion: one
|
|
63
|
+
* import statement split into two is not twice the problem.
|
|
64
|
+
*
|
|
65
|
+
* A file two layers claim is not checked for violations - which layer's rule
|
|
66
|
+
* applies is exactly the thing that is unknown - and is reported instead. So is
|
|
67
|
+
* every file no layer claims, because a shared utility and a directory someone
|
|
68
|
+
* forgot to assign look identical from here and only the author can say which a
|
|
69
|
+
* count describes.
|
|
70
|
+
*/
|
|
71
|
+
export declare function checkLayers(inputs: readonly LayerInput[], order: readonly string[], includeTypes: boolean): LayerReport;
|
|
72
|
+
//# sourceMappingURL=layers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../src/layers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAGnE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAKjF;AAED,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mFAAmF;IACnF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,2EAA2E;IAC3E,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACrD,4BAA4B;IAC5B,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,EAAE,CAIzF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,YAAY,EAAE,OAAO,GAAG,WAAW,CA6CvH"}
|
package/dist/layers.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The layering rule behind `@assert-layers`.
|
|
3
|
+
*
|
|
4
|
+
* Pure, and built on almost nothing new. A layer is a pattern in the language
|
|
5
|
+
* `module=` and `exclude` already speak; a file belongs to a layer when its path
|
|
6
|
+
* matches, and a reference points into one when any name it can denote does -
|
|
7
|
+
* see `referenceForms`. That is why layering needs no resolver and works in
|
|
8
|
+
* every language spec-guard reads: it never asks which file a reference *is*,
|
|
9
|
+
* only which areas it could be about.
|
|
10
|
+
*
|
|
11
|
+
* Cycles are the opposite case, and live in `graph.ts` - see ADR-0011.
|
|
12
|
+
*/
|
|
13
|
+
import { createExcludeMatcher } from './glob.js';
|
|
14
|
+
import { candidates, isGraphFile } from './graph.js';
|
|
15
|
+
import { resolveModule } from './imports.js';
|
|
16
|
+
import { languageFor } from './polyglot.js';
|
|
17
|
+
/**
|
|
18
|
+
* Every name a reference can be matched under.
|
|
19
|
+
*
|
|
20
|
+
* The specifier as written and the module it resolves to, as `module=` tries -
|
|
21
|
+
* and, for the two languages whose modules are files, the files that module can
|
|
22
|
+
* denote. Without the last, a layer that names a file could never be reached:
|
|
23
|
+
* `src/runner.ts` is where the file is, `./runner.js` is what imports it, and
|
|
24
|
+
* the first rule this project wrote with file-sized layers passed while its
|
|
25
|
+
* order was reversed, because no reference matched any layer at all.
|
|
26
|
+
*
|
|
27
|
+
* Names only. Nothing here asks whether a candidate exists, which is why a layer
|
|
28
|
+
* outside the assertion's target still works: `../infrastructure/db.js` reaches
|
|
29
|
+
* `src/infrastructure/db.ts` whether or not the walk ever went there.
|
|
30
|
+
*/
|
|
31
|
+
export function referenceForms(specifier, importingFile) {
|
|
32
|
+
const module = resolveModule(specifier, importingFile);
|
|
33
|
+
if (isGraphFile(importingFile))
|
|
34
|
+
return [specifier, ...candidates(module)];
|
|
35
|
+
if (languageFor(importingFile) === 'python')
|
|
36
|
+
return [specifier, module, `${module}.py`, `${module}/__init__.py`];
|
|
37
|
+
return [specifier, module];
|
|
38
|
+
}
|
|
39
|
+
/** A function from a path to the indices of every layer that matches it. */
|
|
40
|
+
export function layerMatcher(order) {
|
|
41
|
+
const matchers = order.map((pattern) => createExcludeMatcher([pattern]));
|
|
42
|
+
return (relativePath) => matchers.flatMap((matches, layer) => (matches(relativePath) ? [layer] : []));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Checks each file's references against the order.
|
|
46
|
+
*
|
|
47
|
+
* A file may import from its own layer and from layers listed before it. The
|
|
48
|
+
* unit of violation is the file, as it is for every import assertion: one
|
|
49
|
+
* import statement split into two is not twice the problem.
|
|
50
|
+
*
|
|
51
|
+
* A file two layers claim is not checked for violations - which layer's rule
|
|
52
|
+
* applies is exactly the thing that is unknown - and is reported instead. So is
|
|
53
|
+
* every file no layer claims, because a shared utility and a directory someone
|
|
54
|
+
* forgot to assign look identical from here and only the author can say which a
|
|
55
|
+
* count describes.
|
|
56
|
+
*/
|
|
57
|
+
export function checkLayers(inputs, order, includeTypes) {
|
|
58
|
+
const layersOf = layerMatcher(order);
|
|
59
|
+
// Every file in a directory imports the same few modules, and each reference
|
|
60
|
+
// is matched under some twenty names, so the same name is matched against the
|
|
61
|
+
// same patterns thousands of times on a large tree. The answer depends on the
|
|
62
|
+
// name alone. Measured on 6,977 files of node_modules: see ADR-0011.
|
|
63
|
+
const seen = new Map();
|
|
64
|
+
const reachedBy = (name) => {
|
|
65
|
+
let layers = seen.get(name);
|
|
66
|
+
if (layers === undefined) {
|
|
67
|
+
layers = layersOf(name);
|
|
68
|
+
seen.set(name, layers);
|
|
69
|
+
}
|
|
70
|
+
return layers;
|
|
71
|
+
};
|
|
72
|
+
const members = order.map(() => 0);
|
|
73
|
+
const unassigned = [];
|
|
74
|
+
const ambiguous = [];
|
|
75
|
+
const violations = [];
|
|
76
|
+
for (const { file, references } of inputs) {
|
|
77
|
+
const own = layersOf(file);
|
|
78
|
+
for (const layer of own)
|
|
79
|
+
members[layer] = members[layer] + 1;
|
|
80
|
+
if (own.length === 0) {
|
|
81
|
+
unassigned.push(file);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (own.length > 1) {
|
|
85
|
+
ambiguous.push({ file, layers: own });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const from = own[0];
|
|
89
|
+
for (const reference of references) {
|
|
90
|
+
if (reference.typeOnly && !includeTypes)
|
|
91
|
+
continue;
|
|
92
|
+
const reached = referenceForms(reference.specifier, file).flatMap(reachedBy);
|
|
93
|
+
const to = Math.max(from, ...reached);
|
|
94
|
+
if (to > from) {
|
|
95
|
+
violations.push({ file, from, to, reference });
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { members, unassigned, ambiguous, violations };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=layers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layers.js","sourceRoot":"","sources":["../src/layers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,aAAa,EAAwB,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,aAAqB;IACrE,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IACvD,IAAI,WAAW,CAAC,aAAa,CAAC;QAAE,OAAO,CAAC,SAAS,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1E,IAAI,WAAW,CAAC,aAAa,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,GAAG,MAAM,cAAc,CAAC,CAAC;IACjH,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAC7B,CAAC;AA6BD,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,oBAAoB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,YAAY,EAAE,EAAE,CACtB,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACjF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,MAA6B,EAAE,KAAwB,EAAE,YAAqB;IACxG,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACrC,6EAA6E;IAC7E,8EAA8E;IAC9E,8EAA8E;IAC9E,qEAAqE;IACrE,MAAM,IAAI,GAAG,IAAI,GAAG,EAA6B,CAAC;IAClD,MAAM,SAAS,GAAG,CAAC,IAAY,EAAqB,EAAE;QACpD,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,SAAS,GAA6B,EAAE,CAAC;IAC/C,MAAM,UAAU,GAAqB,EAAE,CAAC;IAExC,KAAK,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,MAAM,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,GAAG;YAAE,OAAO,CAAC,KAAK,CAAC,GAAI,OAAO,CAAC,KAAK,CAAY,GAAG,CAAC,CAAC;QACzE,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnB,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YACtC,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAW,CAAC;QAC9B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,IAAI,SAAS,CAAC,QAAQ,IAAI,CAAC,YAAY;gBAAE,SAAS;YAClD,MAAM,OAAO,GAAG,cAAc,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC7E,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,CAAC;YACtC,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;gBACd,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC/C,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AACxD,CAAC"}
|
package/dist/parser.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* error rather than silently skipped - a typo must never turn into a
|
|
15
15
|
* silently-passing invariant.
|
|
16
16
|
*/
|
|
17
|
-
import type { DirectiveKind, ParseResult } from './types.js';
|
|
17
|
+
import type { DirectiveKind, ParseResult, SpecStatus } from './types.js';
|
|
18
18
|
/** Every directive spec-guard understands. */
|
|
19
19
|
export declare const KINDS: Set<string>;
|
|
20
20
|
/**
|
|
@@ -26,6 +26,30 @@ export declare const KINDS: Set<string>;
|
|
|
26
26
|
* from any kind and nothing would have said so.
|
|
27
27
|
*/
|
|
28
28
|
export declare const ALLOWED_ATTRIBUTES: Record<DirectiveKind, ReadonlySet<string>>;
|
|
29
|
+
/**
|
|
30
|
+
* Status words that withhold a document's directives from execution.
|
|
31
|
+
*
|
|
32
|
+
* The words that mean "not in force" in Nygard's ADR template and in MADR's
|
|
33
|
+
* between them, plus `draft`, which is the one people actually type. The list
|
|
34
|
+
* is closed and short on purpose: every word added to it is another way for a
|
|
35
|
+
* document to go dark, and a rule that stops being enforced without anyone
|
|
36
|
+
* deciding so is the failure this tool exists to prevent. Anything else - an
|
|
37
|
+
* unrecognised word, a misspelling, no status at all - stays in force. See
|
|
38
|
+
* ADR-0010.
|
|
39
|
+
*/
|
|
40
|
+
export declare const INACTIVE_STATUSES: ReadonlySet<string>;
|
|
41
|
+
/**
|
|
42
|
+
* The lifecycle status a Markdown document declares about itself.
|
|
43
|
+
*
|
|
44
|
+
* Three spellings are recognised because three are in use, including two in
|
|
45
|
+
* this repository's own ADRs: YAML front-matter (MADR), a `## Status` section
|
|
46
|
+
* (Nygard), and a bold `**Status:**` label. Front-matter wins when present -
|
|
47
|
+
* it is machine-readable metadata rather than a convention read out of prose.
|
|
48
|
+
*
|
|
49
|
+
* Code is masked first, so a document that documents this syntax inside a
|
|
50
|
+
* fence - this project's README does - is not read as declaring a status.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseStatus(source: string): SpecStatus | undefined;
|
|
29
53
|
export interface ParseContext {
|
|
30
54
|
/** Absolute path of the spec file. */
|
|
31
55
|
file: string;
|
package/dist/parser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAGV,aAAa,EACb,WAAW,EAEX,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB,8CAA8C;AAC9C,eAAO,MAAM,KAAK,aAQhB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAuEzE,CAAC;AAIF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAMhD,CAAC;AA+HH;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAElE;AAWD,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAwD/C;AAMD,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUrE;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,WAAW,CAmDlF"}
|
package/dist/parser.js
CHANGED
|
@@ -22,6 +22,8 @@ export const KINDS = new Set([
|
|
|
22
22
|
'assert-present',
|
|
23
23
|
'assert-import-absence',
|
|
24
24
|
'assert-import-count',
|
|
25
|
+
'assert-import-cycle',
|
|
26
|
+
'assert-layers',
|
|
25
27
|
]);
|
|
26
28
|
/**
|
|
27
29
|
* Which attributes each directive accepts.
|
|
@@ -87,7 +89,181 @@ export const ALLOWED_ATTRIBUTES = {
|
|
|
87
89
|
'allow-empty',
|
|
88
90
|
'reason',
|
|
89
91
|
]),
|
|
92
|
+
// No baseline: a baseline lists files, and a cycle is not a file. Exempting
|
|
93
|
+
// the files of today's cycle would exempt a new one among the same files.
|
|
94
|
+
'assert-import-cycle': new Set(['target', 'exclude', 'types', 'expected', 'max', 'allow-empty', 'reason']),
|
|
95
|
+
'assert-layers': new Set([
|
|
96
|
+
'target',
|
|
97
|
+
'order',
|
|
98
|
+
'exclude',
|
|
99
|
+
'types',
|
|
100
|
+
'expected',
|
|
101
|
+
'max',
|
|
102
|
+
'allow-empty',
|
|
103
|
+
'baseline',
|
|
104
|
+
'ratchet',
|
|
105
|
+
'reason',
|
|
106
|
+
]),
|
|
90
107
|
};
|
|
108
|
+
/* --------------------------------------------------------------- the status */
|
|
109
|
+
/**
|
|
110
|
+
* Status words that withhold a document's directives from execution.
|
|
111
|
+
*
|
|
112
|
+
* The words that mean "not in force" in Nygard's ADR template and in MADR's
|
|
113
|
+
* between them, plus `draft`, which is the one people actually type. The list
|
|
114
|
+
* is closed and short on purpose: every word added to it is another way for a
|
|
115
|
+
* document to go dark, and a rule that stops being enforced without anyone
|
|
116
|
+
* deciding so is the failure this tool exists to prevent. Anything else - an
|
|
117
|
+
* unrecognised word, a misspelling, no status at all - stays in force. See
|
|
118
|
+
* ADR-0010.
|
|
119
|
+
*/
|
|
120
|
+
export const INACTIVE_STATUSES = new Set([
|
|
121
|
+
'draft',
|
|
122
|
+
'proposed',
|
|
123
|
+
'rejected',
|
|
124
|
+
'deprecated',
|
|
125
|
+
'superseded',
|
|
126
|
+
]);
|
|
127
|
+
/** `---\n...\n---` at the very top of the file, and nowhere else. */
|
|
128
|
+
const FRONTMATTER_RE = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
129
|
+
/**
|
|
130
|
+
* The three ways the key is spelled: `**Status**:`, `**Status:**`, `Status:`.
|
|
131
|
+
*
|
|
132
|
+
* Written as alternatives rather than as optional emphasis on either side of
|
|
133
|
+
* the colon, which is what it used to be - and an optional `**` after the colon
|
|
134
|
+
* cannot tell the key's closing marker from the value's opening one, so
|
|
135
|
+
* `Status: **Draft**` reached the report as "Draft**". Here emphasis after the
|
|
136
|
+
* colon is only consumed when it closes emphasis that opened the key. The
|
|
137
|
+
* colon itself is mandatory, which keeps "Status reports are..." from being
|
|
138
|
+
* read as metadata. Leading whitespace in the value is left to `toStatus`,
|
|
139
|
+
* which trims it anyway.
|
|
140
|
+
*/
|
|
141
|
+
const STATUS_LABEL_RE = /^[ \t]{0,3}(?:(\*\*|__)status(?:\1[ \t]*:|[ \t]*:\1)|status[ \t]*:)(.*)/i;
|
|
142
|
+
/**
|
|
143
|
+
* The value a YAML scalar holds: a quoted one up to its closing quote, a plain
|
|
144
|
+
* one up to a comment.
|
|
145
|
+
*
|
|
146
|
+
* Not a YAML parser, and it does not need to be one - it reads one line. But
|
|
147
|
+
* it has to read that line the way YAML does. MADR's own template quotes the
|
|
148
|
+
* status, and a quote left in leaves no first word to read; a trailing
|
|
149
|
+
* `# decided at review` left in is part of the label; and a regex anchored at
|
|
150
|
+
* both ends of a quoted value read `"proposed" # a comment` as no status at
|
|
151
|
+
* all, which keeps a withdrawn proposal in force.
|
|
152
|
+
*/
|
|
153
|
+
function yamlScalar(raw) {
|
|
154
|
+
const text = raw.trim();
|
|
155
|
+
const quoted = /^(["'])(.*?)\1/.exec(text);
|
|
156
|
+
return quoted ? quoted[2] : text.replace(/\s#.*/, '');
|
|
157
|
+
}
|
|
158
|
+
/** An ATX heading whose entire text is "Status". */
|
|
159
|
+
const STATUS_HEADING_RE = /^[ \t]{0,3}#{1,6}[ \t]+status[ \t]*#*[ \t]*$/i;
|
|
160
|
+
/** Any ATX heading of level 2 or deeper - where a document's preamble ends. */
|
|
161
|
+
const SECTION_HEADING_RE = /^[ \t]{0,3}#{2,6}[ \t]/;
|
|
162
|
+
/**
|
|
163
|
+
* Turns a status line into a status, or into nothing.
|
|
164
|
+
*
|
|
165
|
+
* The value is the first run of letters, so "Accepted (0.3.0)." and
|
|
166
|
+
* "Superseded by ADR-0007" normalise to one word while the line as written
|
|
167
|
+
* survives for the report - a reader shown "superseded" learns much less than
|
|
168
|
+
* one shown what it was superseded by.
|
|
169
|
+
*/
|
|
170
|
+
function toStatus(raw, source) {
|
|
171
|
+
const trimmed = raw.trim();
|
|
172
|
+
// The word is read through any leading emphasis, so `**Superseded** by
|
|
173
|
+
// ADR-0007` is superseded rather than nothing at all - which is what a rule
|
|
174
|
+
// that required the markers to balance made of it.
|
|
175
|
+
const word = /^[*_]*([a-zA-Z]+)/.exec(trimmed);
|
|
176
|
+
if (!word)
|
|
177
|
+
return undefined;
|
|
178
|
+
// The label loses emphasis only when it wraps the whole line. `**Draft**` is
|
|
179
|
+
// a status written in bold and reads better without the markers; "Superseded
|
|
180
|
+
// by *ADR-0007*" is a sentence with emphasis inside it, and taking one marker
|
|
181
|
+
// off each end would put a half-mangled line in the report.
|
|
182
|
+
const label = trimmed.replace(/^(\*{1,2}|_{1,2})(.*)\1$/, '$2');
|
|
183
|
+
const value = word[1].toLowerCase();
|
|
184
|
+
return { value, label, source, active: !INACTIVE_STATUSES.has(value) };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Lines, whichever way the file ends them.
|
|
188
|
+
*
|
|
189
|
+
* Splitting on "\n" alone leaves a carriage return on every line of a CRLF
|
|
190
|
+
* document, and a trailing "\r" defeats every `$` below - so a Windows-checkout
|
|
191
|
+
* ADR declared no status at all, silently, which is the one failure mode this
|
|
192
|
+
* feature must not have. Found by running the parser over this repository's own
|
|
193
|
+
* ADR-0003, which happened to be CRLF on disk.
|
|
194
|
+
*/
|
|
195
|
+
function toLines(masked) {
|
|
196
|
+
return masked.split(/\r?\n/);
|
|
197
|
+
}
|
|
198
|
+
function fromFrontmatter(masked) {
|
|
199
|
+
const block = FRONTMATTER_RE.exec(masked);
|
|
200
|
+
if (!block)
|
|
201
|
+
return undefined;
|
|
202
|
+
for (const line of toLines(block[1])) {
|
|
203
|
+
const found = STATUS_LABEL_RE.exec(line);
|
|
204
|
+
// Read as YAML here and nowhere else: quotes and `#` are syntax in
|
|
205
|
+
// front-matter and characters in a sentence.
|
|
206
|
+
if (found)
|
|
207
|
+
return toStatus(yamlScalar(found[2]), 'frontmatter');
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* A `## Status` section, whose value is the first line of prose under it.
|
|
213
|
+
*
|
|
214
|
+
* A section with nothing but the next heading under it declares no status.
|
|
215
|
+
* There is no guard for that here: every ATX heading begins with `#`, and a
|
|
216
|
+
* value that does not begin with a letter is already no status at all. The
|
|
217
|
+
* guard this used to carry could not decide anything, which is a different
|
|
218
|
+
* thing from a guard that is merely never hit.
|
|
219
|
+
*/
|
|
220
|
+
function fromHeading(lines) {
|
|
221
|
+
const start = lines.findIndex((line) => STATUS_HEADING_RE.test(line));
|
|
222
|
+
if (start === -1)
|
|
223
|
+
return undefined;
|
|
224
|
+
for (let index = start + 1; index < lines.length; index++) {
|
|
225
|
+
const candidate = lines[index].trim();
|
|
226
|
+
if (candidate.length === 0)
|
|
227
|
+
continue;
|
|
228
|
+
return toStatus(candidate, 'heading');
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* A `**Status:** accepted` line in the document's preamble.
|
|
234
|
+
*
|
|
235
|
+
* Bounded to the preamble - everything before the first `##` - because this
|
|
236
|
+
* form is a line of prose with a colon in it, and a tool that accepts one
|
|
237
|
+
* anywhere in a long document will eventually find one in a sentence.
|
|
238
|
+
*/
|
|
239
|
+
function fromLabel(lines) {
|
|
240
|
+
for (const line of lines) {
|
|
241
|
+
if (SECTION_HEADING_RE.test(line))
|
|
242
|
+
return undefined;
|
|
243
|
+
const found = STATUS_LABEL_RE.exec(line);
|
|
244
|
+
if (found)
|
|
245
|
+
return toStatus(found[2], 'label');
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The lifecycle status a Markdown document declares about itself.
|
|
251
|
+
*
|
|
252
|
+
* Three spellings are recognised because three are in use, including two in
|
|
253
|
+
* this repository's own ADRs: YAML front-matter (MADR), a `## Status` section
|
|
254
|
+
* (Nygard), and a bold `**Status:**` label. Front-matter wins when present -
|
|
255
|
+
* it is machine-readable metadata rather than a convention read out of prose.
|
|
256
|
+
*
|
|
257
|
+
* Code is masked first, so a document that documents this syntax inside a
|
|
258
|
+
* fence - this project's README does - is not read as declaring a status.
|
|
259
|
+
*/
|
|
260
|
+
export function parseStatus(source) {
|
|
261
|
+
return statusOf(maskCode(source));
|
|
262
|
+
}
|
|
263
|
+
function statusOf(masked) {
|
|
264
|
+
const lines = toLines(masked);
|
|
265
|
+
return fromFrontmatter(masked) ?? fromHeading(lines) ?? fromLabel(lines);
|
|
266
|
+
}
|
|
91
267
|
const DIRECTIVE_RE = /<!--\s*@([a-zA-Z][\w-]*)([\s\S]*?)-->/g;
|
|
92
268
|
const ATTRIBUTE_RE = /([a-zA-Z][\w-]*)(?:\s*=\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\s"'=<>`]+)))?/g;
|
|
93
269
|
/**
|
|
@@ -210,6 +386,7 @@ export function parseDirectives(source, context) {
|
|
|
210
386
|
}
|
|
211
387
|
directives.push({ kind: kind, attributes, location, raw });
|
|
212
388
|
}
|
|
213
|
-
|
|
389
|
+
const status = statusOf(masked);
|
|
390
|
+
return status ? { directives, errors, status } : { directives, errors };
|
|
214
391
|
}
|
|
215
392
|
//# sourceMappingURL=parser.js.map
|
package/dist/parser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
|
package/dist/reporter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAKD,QAAA,MAAM,IAAI;aACR,KAAK;aACL,IAAI;aACJ,GAAG;aACH,GAAG;aACH,KAAK;aACL,MAAM;aACN,IAAI;aACJ,OAAO;aACP,IAAI;aACJ,IAAI;CACI,CAAC;AAEX,KAAK,KAAK,GAAG,MAAM,OAAO,IAAI,CAAC;AAE/B,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,MAAM,CAI1F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC3B,IAAI,EAAE,OAAO,GAAG,SAAS,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,QAAQ,kBAAmB,GAAG,OAAO,CAGzG;
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAKD,QAAA,MAAM,IAAI;aACR,KAAK;aACL,IAAI;aACJ,GAAG;aACH,GAAG;aACH,KAAK;aACL,MAAM;aACN,IAAI;aACJ,OAAO;aACP,IAAI;aACJ,IAAI;CACI,CAAC;AAEX,KAAK,KAAK,GAAG,MAAM,OAAO,IAAI,CAAC;AAE/B,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,MAAM,CAI1F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC3B,IAAI,EAAE,OAAO,GAAG,SAAS,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,QAAQ,kBAAmB,GAAG,OAAO,CAGzG;AA8JD,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,SAAI,GAAG,MAAM,CAmGjG;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAezD;AAED,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAmDpD;AAiED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAwFzF"}
|
package/dist/reporter.js
CHANGED
|
@@ -49,8 +49,8 @@ export function shouldUseAscii(env = process.env, platform = process.platform) {
|
|
|
49
49
|
}
|
|
50
50
|
function symbols(ascii) {
|
|
51
51
|
return ascii
|
|
52
|
-
? { pass: '+', fail: 'x', warn: '!', more: '...' }
|
|
53
|
-
: { pass: '✔', fail: '✖', warn: '⚠', more: '…' };
|
|
52
|
+
? { pass: '+', fail: 'x', warn: '!', skip: 'o', more: '...' }
|
|
53
|
+
: { pass: '✔', fail: '✖', warn: '⚠', skip: '○', more: '…' };
|
|
54
54
|
}
|
|
55
55
|
function formatDuration(ms) {
|
|
56
56
|
return ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(2)}s`;
|
|
@@ -141,7 +141,11 @@ function formatFailure(result, paint, glyphs, maxSnippets) {
|
|
|
141
141
|
function formatPass(result, paint, glyphs) {
|
|
142
142
|
const detail = result.kind === 'assert-present'
|
|
143
143
|
? result.files.join(', ')
|
|
144
|
-
:
|
|
144
|
+
: result.symbol === undefined
|
|
145
|
+
? // A cycle or layer rule has no symbol to quote, and its description
|
|
146
|
+
// already says what it is about and where.
|
|
147
|
+
`${result.description} ${paint(`(${countLabel(result.actual, result.kind === 'assert-import-cycle' ? 'cycle' : 'violating file')})`, 'dim')}`
|
|
148
|
+
: `"${result.symbol}" ${paint(`(${countLabel(result.actual, 'match')})`, 'dim')} in ${result.targets.join(', ')}`;
|
|
145
149
|
return `${paint(glyphs.pass, 'green')} ${paint(formatLocation(result), 'dim')} ${paint(`@${result.kind}`, 'dim')} ${detail}`;
|
|
146
150
|
}
|
|
147
151
|
function formatError(error, paint, glyphs) {
|
|
@@ -209,16 +213,34 @@ export function formatReport(report, options, maxSnippets = 5) {
|
|
|
209
213
|
lines.push(`${paint(glyphs.warn, 'yellow')} ${paint(note, 'yellow')}`);
|
|
210
214
|
lines.push('');
|
|
211
215
|
}
|
|
216
|
+
// Named, not totalled. A count of withheld rules tells a reader that some
|
|
217
|
+
// part of their specification stopped being enforced without telling them
|
|
218
|
+
// which part, and the whole reason to report this at all is that a rule
|
|
219
|
+
// going quiet is indistinguishable from a rule passing.
|
|
220
|
+
for (const spec of report.inactiveSpecs) {
|
|
221
|
+
const detail = spec.directives === 0
|
|
222
|
+
? 'no directives to execute'
|
|
223
|
+
: `${countLabel(spec.directives, 'assertion')} not executed`;
|
|
224
|
+
lines.push(paint(`${glyphs.skip} ${spec.file} is ${spec.label} - ${detail}`, 'dim'));
|
|
225
|
+
}
|
|
226
|
+
if (report.inactiveSpecs.length > 0)
|
|
227
|
+
lines.push('');
|
|
212
228
|
const parts = [
|
|
213
229
|
paint(`${report.summary.passed} passed`, 'green'),
|
|
214
230
|
failures.length > 0 ? paint(`${report.summary.failed} failed`, 'red', 'bold') : null,
|
|
215
231
|
report.errors.length > 0 ? paint(`${report.errors.length} invalid`, 'yellow') : null,
|
|
216
232
|
report.summary.skipped > 0 ? paint(`${report.summary.skipped} skipped`, 'dim') : null,
|
|
233
|
+
report.summary.inactive > 0 ? paint(`${report.summary.inactive} not in force`, 'dim') : null,
|
|
217
234
|
paint(formatDuration(report.durationMs), 'dim'),
|
|
218
235
|
].filter((part) => part !== null);
|
|
219
236
|
lines.push(parts.join(paint(' · ', 'dim')));
|
|
220
237
|
if (report.ok) {
|
|
221
|
-
|
|
238
|
+
// "Every assertion holds" over zero assertions is true and useless, and it
|
|
239
|
+
// is the exact sentence someone reads as proof their specification is
|
|
240
|
+
// being enforced. A run that verified nothing has to say so instead.
|
|
241
|
+
lines.push(report.summary.total > 0
|
|
242
|
+
? paint(`${glyphs.pass} every spec assertion holds`, 'green')
|
|
243
|
+
: paint(`${glyphs.warn} no assertion was executed, so nothing was verified`, 'yellow'));
|
|
222
244
|
}
|
|
223
245
|
return lines.join('\n');
|
|
224
246
|
}
|
|
@@ -302,6 +324,7 @@ export function formatJson(report) {
|
|
|
302
324
|
raw: error.raw,
|
|
303
325
|
})),
|
|
304
326
|
warnings: report.warnings,
|
|
327
|
+
inactiveSpecs: report.inactiveSpecs,
|
|
305
328
|
}, null, 2);
|
|
306
329
|
}
|
|
307
330
|
/* --------------------------------------------------------------------- sarif */
|
|
@@ -323,8 +346,28 @@ const SARIF_RULES = [
|
|
|
323
346
|
{ id: 'assert-present', text: 'A file or directory the specification says must exist.' },
|
|
324
347
|
{ id: 'assert-import-absence', text: 'A dependency one part of the codebase must not have.' },
|
|
325
348
|
{ id: 'assert-import-count', text: 'A dependency count one part of the codebase must hold to.' },
|
|
349
|
+
{ id: 'assert-import-cycle', text: 'A set of files that depend on each other, directly or through others.' },
|
|
350
|
+
{ id: 'assert-layers', text: 'A file importing from a layer the architecture places above it.' },
|
|
326
351
|
{ id: 'invalid-directive', text: 'A directive that could not be parsed, so nothing was checked.' },
|
|
327
352
|
];
|
|
353
|
+
/**
|
|
354
|
+
* SARIF's slot for "something happened during this run that is not a finding".
|
|
355
|
+
*
|
|
356
|
+
* A document that is not in force produces no result, and a format that only
|
|
357
|
+
* carries results would show a clean code-scanning page for a repository whose
|
|
358
|
+
* rules had gone dormant - the same silence the human report refuses. This is
|
|
359
|
+
* the standard's own answer to that: a note-level execution notification,
|
|
360
|
+
* which GitHub surfaces as run information rather than as an alert.
|
|
361
|
+
*/
|
|
362
|
+
function inactiveNotifications(report) {
|
|
363
|
+
return report.inactiveSpecs.map((spec) => ({
|
|
364
|
+
level: 'note',
|
|
365
|
+
message: {
|
|
366
|
+
text: `${spec.file} is ${spec.label}, so ` +
|
|
367
|
+
`${spec.directives === 1 ? 'its 1 assertion was' : `its ${spec.directives} assertions were`} not executed.`,
|
|
368
|
+
},
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
328
371
|
function sarifLocation(uri, line, column, text) {
|
|
329
372
|
const location = {
|
|
330
373
|
physicalLocation: { artifactLocation: { uri }, region: { startLine: line, startColumn: column } },
|
|
@@ -371,7 +414,12 @@ export function formatSarif(report, options = {}) {
|
|
|
371
414
|
specGuardAssertion: fingerprint([
|
|
372
415
|
result.location.relativeFile,
|
|
373
416
|
result.kind,
|
|
374
|
-
|
|
417
|
+
// The description only where there is nothing else to tell two
|
|
418
|
+
// assertions apart. A cycle or layer rule names no symbol and no
|
|
419
|
+
// file, so two on one target - `types="ignore"` beside the default -
|
|
420
|
+
// would share an identity and be merged into one alert. Every kind
|
|
421
|
+
// that has a symbol or a file list keeps the fingerprint it had.
|
|
422
|
+
result.symbol ?? (result.files.length > 0 ? result.files.join(',') : result.description),
|
|
375
423
|
result.targets.join(','),
|
|
376
424
|
]),
|
|
377
425
|
},
|
|
@@ -396,6 +444,15 @@ export function formatSarif(report, options = {}) {
|
|
|
396
444
|
version: '2.1.0',
|
|
397
445
|
runs: [
|
|
398
446
|
{
|
|
447
|
+
// Present only when there is something to say. An empty invocations
|
|
448
|
+
// block is noise in every consumer that renders one.
|
|
449
|
+
...(report.inactiveSpecs.length > 0
|
|
450
|
+
? {
|
|
451
|
+
invocations: [
|
|
452
|
+
{ executionSuccessful: report.ok, toolExecutionNotifications: inactiveNotifications(report) },
|
|
453
|
+
],
|
|
454
|
+
}
|
|
455
|
+
: {}),
|
|
399
456
|
tool: {
|
|
400
457
|
driver: {
|
|
401
458
|
name: 'spec-guard',
|