@shivaedev/quality 0.22.0 → 0.24.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 +13 -0
- package/README.md +4 -0
- package/dist/baseline/compare.d.ts +2 -2
- package/dist/baseline/compare.d.ts.map +1 -1
- package/dist/baseline/compare.js +5 -3
- package/dist/baseline/compare.js.map +1 -1
- package/dist/baseline/format.d.ts +5 -5
- package/dist/baseline/format.d.ts.map +1 -1
- package/dist/baseline/format.js +23 -13
- package/dist/baseline/format.js.map +1 -1
- package/dist/baseline/legacy.d.ts +2 -2
- package/dist/baseline/legacy.d.ts.map +1 -1
- package/dist/baseline/legacy.js +4 -4
- package/dist/baseline/legacy.js.map +1 -1
- package/dist/baseline/rewrite.d.ts +1 -1
- package/dist/baseline/rewrite.d.ts.map +1 -1
- package/dist/baseline/rewrite.js +2 -2
- package/dist/baseline/rewrite.js.map +1 -1
- package/dist/baseline/update.d.ts +2 -2
- package/dist/baseline/update.d.ts.map +1 -1
- package/dist/baseline/update.js +24 -18
- package/dist/baseline/update.js.map +1 -1
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +15 -9
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/baseline.d.ts +1 -1
- package/dist/cli/baseline.d.ts.map +1 -1
- package/dist/cli/baseline.js +10 -6
- package/dist/cli/baseline.js.map +1 -1
- package/dist/cli/fix.d.ts +2 -2
- package/dist/cli/fix.d.ts.map +1 -1
- package/dist/cli/fix.js +34 -40
- package/dist/cli/fix.js.map +1 -1
- package/dist/cli/lint.d.ts +1 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/lint.js +1 -1
- package/dist/cli/lint.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +1 -1
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/program.d.ts +9 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +44 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/run-main.d.ts +5 -2
- package/dist/cli/run-main.d.ts.map +1 -1
- package/dist/cli/run-main.js +16 -12
- package/dist/cli/run-main.js.map +1 -1
- package/dist/cli/shrink.d.ts +2 -2
- package/dist/cli/shrink.d.ts.map +1 -1
- package/dist/cli/shrink.js +9 -3
- package/dist/cli/shrink.js.map +1 -1
- package/dist/cli.js +2 -49
- package/dist/cli.js.map +1 -1
- package/dist/config/decode.d.ts +1 -1
- package/dist/config/decode.d.ts.map +1 -1
- package/dist/config/decode.js +13 -9
- package/dist/config/decode.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +26 -20
- package/dist/config/load.js.map +1 -1
- package/dist/config/resolve.d.ts +22 -2
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +18 -10
- package/dist/config/resolve.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/decoded.d.ts +2 -2
- package/dist/decoded.d.ts.map +1 -1
- package/dist/decoded.js +7 -5
- package/dist/decoded.js.map +1 -1
- package/dist/engine/baseline-file.d.ts +1 -1
- package/dist/engine/baseline-file.d.ts.map +1 -1
- package/dist/engine/baseline-file.js +4 -2
- package/dist/engine/baseline-file.js.map +1 -1
- package/dist/engine/evaluate.d.ts +2 -2
- package/dist/engine/evaluate.d.ts.map +1 -1
- package/dist/engine/evaluate.js +5 -3
- package/dist/engine/evaluate.js.map +1 -1
- package/dist/engine/run-rules.d.ts +1 -1
- package/dist/engine/run-rules.d.ts.map +1 -1
- package/dist/engine/run-rules.js +24 -14
- package/dist/engine/run-rules.js.map +1 -1
- package/dist/engine/session.d.ts.map +1 -1
- package/dist/engine/session.js +8 -4
- package/dist/engine/session.js.map +1 -1
- package/dist/engine/violation.d.ts +7 -7
- package/dist/engine/violation.d.ts.map +1 -1
- package/dist/engine/violation.js +22 -10
- package/dist/engine/violation.js.map +1 -1
- package/dist/exceptions/registry.d.ts +2 -2
- package/dist/exceptions/registry.d.ts.map +1 -1
- package/dist/exceptions/registry.js +10 -6
- package/dist/exceptions/registry.js.map +1 -1
- package/dist/git/base.d.ts +1 -2
- package/dist/git/base.d.ts.map +1 -1
- package/dist/git/base.js +22 -10
- package/dist/git/base.js.map +1 -1
- package/dist/git/command.d.ts +2 -2
- package/dist/git/command.d.ts.map +1 -1
- package/dist/git/command.js +13 -9
- package/dist/git/command.js.map +1 -1
- package/dist/git/history.d.ts +1 -1
- package/dist/git/history.d.ts.map +1 -1
- package/dist/git/history.js +5 -3
- package/dist/git/history.js.map +1 -1
- package/dist/inventory/collect.d.ts +1 -1
- package/dist/inventory/collect.d.ts.map +1 -1
- package/dist/inventory/collect.js +7 -5
- package/dist/inventory/collect.js.map +1 -1
- package/dist/inventory/filesystem.d.ts +1 -1
- package/dist/inventory/filesystem.d.ts.map +1 -1
- package/dist/inventory/filesystem.js +11 -7
- package/dist/inventory/filesystem.js.map +1 -1
- package/dist/inventory/ignore-scope.d.ts +4 -4
- package/dist/inventory/ignore-scope.d.ts.map +1 -1
- package/dist/inventory/ignore-scope.js +18 -9
- package/dist/inventory/ignore-scope.js.map +1 -1
- package/dist/manifests/sorted.d.ts +1 -1
- package/dist/manifests/sorted.d.ts.map +1 -1
- package/dist/manifests/sorted.js +14 -16
- package/dist/manifests/sorted.js.map +1 -1
- package/dist/report/plural.d.ts +1 -1
- package/dist/report/plural.d.ts.map +1 -1
- package/dist/report/plural.js +3 -1
- package/dist/report/plural.js.map +1 -1
- package/dist/report/render.d.ts +1 -1
- package/dist/report/render.d.ts.map +1 -1
- package/dist/report/render.js +29 -21
- package/dist/report/render.js.map +1 -1
- package/dist/report/stale.d.ts +1 -1
- package/dist/report/stale.d.ts.map +1 -1
- package/dist/report/stale.js +18 -8
- package/dist/report/stale.js.map +1 -1
- package/dist/rule.d.ts +13 -13
- package/dist/rule.d.ts.map +1 -1
- package/dist/rule.js +10 -8
- package/dist/rule.js.map +1 -1
- package/dist/rules/built-in.d.ts +2 -2
- package/dist/rules/built-in.d.ts.map +1 -1
- package/dist/rules/built-in.js +6 -0
- package/dist/rules/built-in.js.map +1 -1
- package/dist/rules/comments/kinds.d.ts +4 -4
- package/dist/rules/comments/kinds.d.ts.map +1 -1
- package/dist/rules/comments/kinds.js +16 -8
- package/dist/rules/comments/kinds.js.map +1 -1
- package/dist/rules/comments/max-per-file.d.ts.map +1 -1
- package/dist/rules/comments/max-per-file.js +13 -9
- package/dist/rules/comments/max-per-file.js.map +1 -1
- package/dist/rules/comments/pattern-rule.d.ts +1 -1
- package/dist/rules/comments/pattern-rule.d.ts.map +1 -1
- package/dist/rules/comments/pattern-rule.js +12 -10
- package/dist/rules/comments/pattern-rule.js.map +1 -1
- package/dist/rules/comments/scan.d.ts +3 -3
- package/dist/rules/comments/scan.d.ts.map +1 -1
- package/dist/rules/comments/scan.js +23 -19
- package/dist/rules/comments/scan.js.map +1 -1
- package/dist/rules/max-lines.d.ts.map +1 -1
- package/dist/rules/max-lines.js +12 -10
- package/dist/rules/max-lines.js.map +1 -1
- package/dist/rules/maxDepth.d.ts +2 -0
- package/dist/rules/maxDepth.d.ts.map +1 -0
- package/dist/rules/maxDepth.js +20 -0
- package/dist/rules/maxDepth.js.map +1 -0
- package/dist/rules/noBarrel.d.ts +2 -0
- package/dist/rules/noBarrel.d.ts.map +1 -0
- package/dist/rules/noBarrel.js +14 -0
- package/dist/rules/noBarrel.js.map +1 -0
- package/dist/rules/stories/origins.d.ts +10 -0
- package/dist/rules/stories/origins.d.ts.map +1 -0
- package/dist/rules/stories/origins.js +129 -0
- package/dist/rules/stories/origins.js.map +1 -0
- package/dist/rules/stories/prerequisites.d.ts +4 -0
- package/dist/rules/stories/prerequisites.d.ts.map +1 -0
- package/dist/rules/stories/prerequisites.js +32 -0
- package/dist/rules/stories/prerequisites.js.map +1 -0
- package/dist/rules/stories/program.d.ts +5 -0
- package/dist/rules/stories/program.d.ts.map +1 -0
- package/dist/rules/stories/program.js +22 -0
- package/dist/rules/stories/program.js.map +1 -0
- package/dist/rules/stories/specs.d.ts +2 -0
- package/dist/rules/stories/specs.d.ts.map +1 -0
- package/dist/rules/stories/specs.js +80 -0
- package/dist/rules/stories/specs.js.map +1 -0
- package/dist/rules/suppressions/biome/configs.d.ts +1 -1
- package/dist/rules/suppressions/biome/configs.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/configs.js +23 -17
- package/dist/rules/suppressions/biome/configs.js.map +1 -1
- package/dist/rules/suppressions/biome/declarations.d.ts +4 -4
- package/dist/rules/suppressions/biome/declarations.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/declarations.js +15 -7
- package/dist/rules/suppressions/biome/declarations.js.map +1 -1
- package/dist/rules/suppressions/biome/json.d.ts +6 -6
- package/dist/rules/suppressions/biome/json.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/json.js +27 -13
- package/dist/rules/suppressions/biome/json.js.map +1 -1
- package/dist/rules/suppressions/biome/resolve.d.ts +1 -1
- package/dist/rules/suppressions/biome/resolve.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/resolve.js +23 -17
- package/dist/rules/suppressions/biome/resolve.js.map +1 -1
- package/dist/rules/suppressions/biome/weakenings.d.ts +1 -1
- package/dist/rules/suppressions/biome/weakenings.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/weakenings.js +78 -52
- package/dist/rules/suppressions/biome/weakenings.js.map +1 -1
- package/dist/rules/suppressions/biome-overrides.d.ts.map +1 -1
- package/dist/rules/suppressions/biome-overrides.js +45 -33
- package/dist/rules/suppressions/biome-overrides.js.map +1 -1
- package/dist/rules/suppressions/directives.d.ts +1 -1
- package/dist/rules/suppressions/directives.d.ts.map +1 -1
- package/dist/rules/suppressions/directives.js +5 -3
- package/dist/rules/suppressions/directives.js.map +1 -1
- package/dist/rules/suppressions/no-double-cast.d.ts.map +1 -1
- package/dist/rules/suppressions/no-double-cast.js +14 -10
- package/dist/rules/suppressions/no-double-cast.js.map +1 -1
- package/dist/rules/suppressions/no-inline.d.ts.map +1 -1
- package/dist/rules/suppressions/no-inline.js +8 -6
- package/dist/rules/suppressions/no-inline.js.map +1 -1
- package/dist/rules/suppressions/stylesheet-comments.d.ts +1 -1
- package/dist/rules/suppressions/stylesheet-comments.d.ts.map +1 -1
- package/dist/rules/suppressions/stylesheet-comments.js +11 -5
- package/dist/rules/suppressions/stylesheet-comments.js.map +1 -1
- package/dist/rules/syntax.d.ts +1 -1
- package/dist/rules/syntax.d.ts.map +1 -1
- package/dist/rules/syntax.js +2 -2
- package/dist/rules/syntax.js.map +1 -1
- package/dist/standard-schema.d.ts +6 -6
- package/dist/standard-schema.d.ts.map +1 -1
- package/docs/modules.md +19 -1
- package/docs/roadmap.md +1 -1
- package/docs/rules.md +9 -1
- package/package.json +2 -2
- package/src/baseline/compare.ts +5 -3
- package/src/baseline/format.ts +17 -10
- package/src/baseline/legacy.ts +4 -4
- package/src/baseline/rewrite.ts +2 -2
- package/src/baseline/update.ts +20 -16
- package/src/cli/args.ts +15 -10
- package/src/cli/baseline.ts +36 -35
- package/src/cli/fix.ts +39 -42
- package/src/cli/lint.ts +17 -19
- package/src/cli/migrate.ts +20 -21
- package/src/cli/program.ts +50 -0
- package/src/cli/run-main.ts +19 -13
- package/src/cli/shrink.ts +11 -8
- package/src/cli.ts +2 -51
- package/src/config/decode.ts +15 -10
- package/src/config/load.ts +44 -37
- package/src/config/resolve.ts +32 -27
- package/src/config.ts +3 -1
- package/src/decoded.ts +5 -4
- package/src/engine/baseline-file.ts +14 -12
- package/src/engine/evaluate.ts +6 -5
- package/src/engine/run-rules.ts +21 -13
- package/src/engine/session.ts +32 -25
- package/src/engine/violation.ts +22 -11
- package/src/exceptions/registry.ts +10 -8
- package/src/git/base.ts +45 -37
- package/src/git/command.ts +6 -4
- package/src/git/history.ts +5 -4
- package/src/inventory/collect.ts +39 -34
- package/src/inventory/filesystem.ts +38 -30
- package/src/inventory/ignore-scope.ts +20 -10
- package/src/manifests/sorted.ts +18 -18
- package/src/report/plural.ts +3 -1
- package/src/report/render.ts +23 -18
- package/src/report/stale.ts +33 -25
- package/src/rule.ts +25 -19
- package/src/rules/built-in.ts +6 -0
- package/src/rules/comments/kinds.ts +16 -9
- package/src/rules/comments/max-per-file.ts +15 -11
- package/src/rules/comments/pattern-rule.ts +5 -4
- package/src/rules/comments/scan.ts +18 -15
- package/src/rules/max-lines.ts +3 -2
- package/src/rules/maxDepth.ts +24 -0
- package/src/rules/noBarrel.ts +16 -0
- package/src/rules/stories/origins.ts +147 -0
- package/src/rules/stories/prerequisites.ts +37 -0
- package/src/rules/stories/program.ts +29 -0
- package/src/rules/stories/specs.ts +93 -0
- package/src/rules/suppressions/biome/configs.ts +23 -17
- package/src/rules/suppressions/biome/declarations.ts +15 -10
- package/src/rules/suppressions/biome/json.ts +25 -13
- package/src/rules/suppressions/biome/resolve.ts +23 -18
- package/src/rules/suppressions/biome/weakenings.ts +60 -41
- package/src/rules/suppressions/biome-overrides.ts +32 -22
- package/src/rules/suppressions/directives.ts +5 -4
- package/src/rules/suppressions/no-double-cast.ts +14 -10
- package/src/rules/suppressions/no-inline.ts +5 -4
- package/src/rules/suppressions/stylesheet-comments.ts +11 -6
- package/src/rules/syntax.ts +2 -2
- package/src/standard-schema.ts +4 -4
package/src/engine/violation.ts
CHANGED
|
@@ -8,12 +8,15 @@ export interface Violation extends Finding {
|
|
|
8
8
|
readonly rule: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export function keyOf(rule: string, file: string): string {
|
|
12
|
+
return `${rule}\u0000${file}`;
|
|
13
|
+
}
|
|
12
14
|
|
|
13
|
-
export
|
|
14
|
-
left.rule.localeCompare(right.rule) || left.file.localeCompare(right.file) || (left.line ?? 0) - (right.line ?? 0);
|
|
15
|
+
export function byLocation(left: Violation, right: Violation): number {
|
|
16
|
+
return left.rule.localeCompare(right.rule) || left.file.localeCompare(right.file) || (left.line ?? 0) - (right.line ?? 0);
|
|
17
|
+
}
|
|
15
18
|
|
|
16
|
-
export
|
|
19
|
+
export function groupBy<TItem>(items: readonly TItem[], key: (item: TItem) => string): ReadonlyMap<string, readonly TItem[]> {
|
|
17
20
|
const groups = new Map<string, TItem[]>();
|
|
18
21
|
for (const item of items) {
|
|
19
22
|
const group = groups.get(key(item));
|
|
@@ -24,7 +27,7 @@ export const groupBy = <TItem>(items: readonly TItem[], key: (item: TItem) => st
|
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
return groups;
|
|
27
|
-
}
|
|
30
|
+
}
|
|
28
31
|
|
|
29
32
|
export interface RuleIndex {
|
|
30
33
|
readonly families: ReadonlySet<string>;
|
|
@@ -32,18 +35,26 @@ export interface RuleIndex {
|
|
|
32
35
|
readonly unregistrable: ReadonlySet<string>;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
export
|
|
38
|
+
export function covers(name: string, rule: string): boolean {
|
|
39
|
+
return rule === name || rule.startsWith(`${name}/`);
|
|
40
|
+
}
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
function ownerOf(index: RuleIndex, rule: string): string {
|
|
43
|
+
return [...index.families].find((family) => covers(family, rule)) ?? rule;
|
|
44
|
+
}
|
|
38
45
|
|
|
39
|
-
export
|
|
46
|
+
export function levelOf(index: RuleIndex, rule: string): Level | undefined {
|
|
47
|
+
return index.levels.get(ownerOf(index, rule));
|
|
48
|
+
}
|
|
40
49
|
|
|
41
|
-
export
|
|
50
|
+
export function registrable(index: RuleIndex, rule: string): boolean {
|
|
51
|
+
return !index.unregistrable.has(ownerOf(index, rule));
|
|
52
|
+
}
|
|
42
53
|
|
|
43
|
-
export
|
|
54
|
+
export function unusedEntryProblem(index: RuleIndex, rule: string, unused: string): string {
|
|
44
55
|
const level = levelOf(index, rule);
|
|
45
56
|
if (level === undefined) {
|
|
46
57
|
return "names no known rule";
|
|
47
58
|
}
|
|
48
59
|
return level === "off" ? "names a rule that is off" : unused;
|
|
49
|
-
}
|
|
60
|
+
}
|
|
@@ -15,8 +15,9 @@ const standard = Schema.toStandardSchemaV1(Schema.fromJsonString(Schema.Array(Re
|
|
|
15
15
|
parseOptions: { errors: "all", onExcessProperty: "error" },
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
raw === undefined ? Promise.resolve({ _tag: "Valid", value: [] }) : decodeWith(standard, raw);
|
|
18
|
+
export function decodeRegistry(raw: string | undefined): Promise<Decoded<readonly RegistryEntry[]>> {
|
|
19
|
+
return raw === undefined ? Promise.resolve({ _tag: "Valid", value: [] }) : decodeWith(standard, raw);
|
|
20
|
+
}
|
|
20
21
|
|
|
21
22
|
export interface StaleRegistryEntry {
|
|
22
23
|
readonly entry: RegistryEntry;
|
|
@@ -29,17 +30,18 @@ export interface RegistryCheck {
|
|
|
29
30
|
readonly stale: readonly StaleRegistryEntry[];
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
entry.rule === violation.rule && entry.file === violation.file && (entry.subject === undefined || entry.subject === violation.subject);
|
|
33
|
+
function covers(entry: RegistryEntry, violation: Violation): boolean {
|
|
34
|
+
return entry.rule === violation.rule && entry.file === violation.file && (entry.subject === undefined || entry.subject === violation.subject);
|
|
35
|
+
}
|
|
34
36
|
|
|
35
37
|
const UNREGISTRABLE = "names a rule that takes no exceptions. Fix the code, or baseline the violations while the repository adopts the rule";
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
function coveringEntry(entries: readonly RegistryEntry[], violation: Violation): RegistryEntry | undefined {
|
|
38
40
|
const matching = entries.filter((entry) => covers(entry, violation));
|
|
39
41
|
return matching.find((entry) => entry.subject !== undefined) ?? matching[0];
|
|
40
|
-
}
|
|
42
|
+
}
|
|
41
43
|
|
|
42
|
-
export
|
|
44
|
+
export function applyRegistry(violations: readonly Violation[], entries: readonly RegistryEntry[], rules: RuleIndex): RegistryCheck {
|
|
43
45
|
const usable = entries.filter((entry) => registrable(rules, entry.rule));
|
|
44
46
|
const used = new Set<RegistryEntry>();
|
|
45
47
|
const kept = violations.filter((violation) => {
|
|
@@ -56,4 +58,4 @@ export const applyRegistry = (violations: readonly Violation[], entries: readonl
|
|
|
56
58
|
problem: registrable(rules, entry.rule) ? unusedEntryProblem(rules, entry.rule, "matches no violation") : UNREGISTRABLE,
|
|
57
59
|
}));
|
|
58
60
|
return { kept, registered: violations.length - kept.length, stale };
|
|
59
|
-
}
|
|
61
|
+
}
|
package/src/git/base.ts
CHANGED
|
@@ -9,54 +9,62 @@ export interface Base {
|
|
|
9
9
|
|
|
10
10
|
const FALLBACK_BRANCHES = ["origin/main", "origin/master"];
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
function fail(message: string) {
|
|
13
|
+
return Effect.fail(new SetupFailure({ message }));
|
|
14
|
+
}
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
Effect.map(git(root, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]), (result) => result.code === 0);
|
|
16
|
+
function isCommit(root: string, ref: string): Effect.Effect<boolean, SetupFailure, Git> {
|
|
17
|
+
return Effect.map(git(root, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]), (result) => result.code === 0);
|
|
18
|
+
}
|
|
16
19
|
|
|
17
|
-
const defaultBranch = (root: string): Effect.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return candidate;
|
|
26
|
-
}
|
|
20
|
+
const defaultBranch = Effect.fn("Git.defaultBranch")(function* (root: string): Effect.fn.Return<string, SetupFailure, Git> {
|
|
21
|
+
const head = yield* git(root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
|
|
22
|
+
if (head.code === 0) {
|
|
23
|
+
return head.stdout.trim();
|
|
24
|
+
}
|
|
25
|
+
for (const candidate of FALLBACK_BRANCHES) {
|
|
26
|
+
if (yield* isCommit(root, candidate)) {
|
|
27
|
+
return candidate;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
}
|
|
30
|
+
return yield* fail(
|
|
31
|
+
"cannot find the default branch: origin/HEAD, origin/main and origin/master are missing. Pass --against <ref>, or run `git remote set-head origin --auto`.",
|
|
32
|
+
);
|
|
33
|
+
});
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
Effect.map(git(root, ["rev-parse", "--is-shallow-repository"]), (result) => result.stdout.trim() === "true");
|
|
35
|
+
function isShallow(root: string): Effect.Effect<boolean, SetupFailure, Git> {
|
|
36
|
+
return Effect.map(git(root, ["rev-parse", "--is-shallow-repository"]), (result) => result.stdout.trim() === "true");
|
|
37
|
+
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
Effect.map(git(root, ["merge-base", "HEAD", ref]), (result) => (result.code === 0 ? result.stdout.trim() : undefined));
|
|
39
|
+
function mergeBase(root: string, ref: string): Effect.Effect<string | undefined, SetupFailure, Git> {
|
|
40
|
+
return Effect.map(git(root, ["merge-base", "HEAD", ref]), (result) => (result.code === 0 ? result.stdout.trim() : undefined));
|
|
41
|
+
}
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
Effect.flatMap(isShallow(root), (shallow) =>
|
|
43
|
+
function missingMergeBase(root: string, ref: string): Effect.Effect<never, SetupFailure, Git> {
|
|
44
|
+
return Effect.flatMap(isShallow(root), (shallow) =>
|
|
41
45
|
fail(
|
|
42
46
|
shallow
|
|
43
47
|
? `the clone is shallow and does not reach the merge base of HEAD and ${ref}. Fetch the history first: \`git fetch --unshallow\`.`
|
|
44
48
|
: `HEAD and ${ref} share no history.`,
|
|
45
49
|
),
|
|
46
50
|
);
|
|
51
|
+
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
Effect.flatMap(mergeBase(root, ref), (found) => (found === undefined ? missingMergeBase(root, ref) : Effect.succeed(found)));
|
|
53
|
+
function reachMergeBase(root: string, ref: string): Effect.Effect<string, SetupFailure, Git> {
|
|
54
|
+
return Effect.flatMap(mergeBase(root, ref), (found) => (found === undefined ? missingMergeBase(root, ref) : Effect.succeed(found)));
|
|
55
|
+
}
|
|
50
56
|
|
|
51
|
-
export const resolveBase =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return { commit
|
|
62
|
-
}
|
|
57
|
+
export const resolveBase = Effect.fn("Git.resolveBase")(function* (
|
|
58
|
+
root: string,
|
|
59
|
+
against: string | undefined,
|
|
60
|
+
): Effect.fn.Return<Base, SetupFailure, Git> {
|
|
61
|
+
const inside = yield* git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
62
|
+
if (inside.code !== 0 || inside.stdout.trim() !== "true") {
|
|
63
|
+
return yield* fail(`${root} is not in a git work tree; moves are followed through git history.`);
|
|
64
|
+
}
|
|
65
|
+
const ref = against ?? (yield* defaultBranch(root));
|
|
66
|
+
if (!(yield* isCommit(root, ref))) {
|
|
67
|
+
return yield* fail(`${ref} names no commit in this clone. Fetch it first, or pass another --against <ref>.`);
|
|
68
|
+
}
|
|
69
|
+
return { commit: yield* reachMergeBase(root, ref), ref };
|
|
70
|
+
});
|
package/src/git/command.ts
CHANGED
|
@@ -10,8 +10,8 @@ export interface GitResult {
|
|
|
10
10
|
|
|
11
11
|
export type Git = ChildProcessSpawner.ChildProcessSpawner;
|
|
12
12
|
|
|
13
|
-
export
|
|
14
|
-
Effect.scoped(
|
|
13
|
+
export function git(cwd: string, args: readonly string[]): Effect.Effect<GitResult, SetupFailure, Git> {
|
|
14
|
+
return Effect.scoped(
|
|
15
15
|
Effect.gen(function* () {
|
|
16
16
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
17
17
|
const child = yield* spawner.spawn(ChildProcess.make("git", [...args], { cwd, stdin: "ignore" }));
|
|
@@ -22,10 +22,12 @@ export const git = (cwd: string, args: readonly string[]): Effect.Effect<GitResu
|
|
|
22
22
|
return { code: Number(code), stderr, stdout };
|
|
23
23
|
}),
|
|
24
24
|
).pipe(Effect.mapError((error) => new SetupFailure({ message: `cannot run git: ${error.message}` })));
|
|
25
|
+
}
|
|
25
26
|
|
|
26
|
-
export
|
|
27
|
-
Effect.flatMap(git(cwd, args), (result) =>
|
|
27
|
+
export function gitOrFail(cwd: string, args: readonly string[]): Effect.Effect<string, SetupFailure, Git> {
|
|
28
|
+
return Effect.flatMap(git(cwd, args), (result) =>
|
|
28
29
|
result.code === 0
|
|
29
30
|
? Effect.succeed(result.stdout)
|
|
30
31
|
: Effect.fail(new SetupFailure({ message: `git ${args[0]} failed: ${result.stderr.trim() || `exit code ${result.code}`}` })),
|
|
31
32
|
);
|
|
33
|
+
}
|
package/src/git/history.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface Changes {
|
|
|
7
7
|
readonly moves: ReadonlyMap<string, string>;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
function parse(output: string): Changes {
|
|
11
11
|
const fields = output.split("\0");
|
|
12
12
|
const files = new Set<string>();
|
|
13
13
|
const moves = new Map<string, string>();
|
|
@@ -24,11 +24,12 @@ const parse = (output: string): Changes => {
|
|
|
24
24
|
index += paths.length + 1;
|
|
25
25
|
}
|
|
26
26
|
return { files, moves };
|
|
27
|
-
}
|
|
27
|
+
}
|
|
28
28
|
|
|
29
29
|
// Paths are relative to the root, and only tracked files count: a move is seen once both of its sides are staged.
|
|
30
|
-
export
|
|
31
|
-
Effect.map(
|
|
30
|
+
export function changesSince(root: string, revision: readonly string[]): Effect.Effect<Changes, SetupFailure, Git> {
|
|
31
|
+
return Effect.map(
|
|
32
32
|
gitOrFail(root, ["diff", "--no-color", "--no-ext-diff", "--find-renames", "--name-status", "-z", "--relative", ...revision, "--"]),
|
|
33
33
|
parse,
|
|
34
34
|
);
|
|
35
|
+
}
|
package/src/inventory/collect.ts
CHANGED
|
@@ -19,42 +19,47 @@ export interface Inventory {
|
|
|
19
19
|
|
|
20
20
|
const READ_CONCURRENCY = 16;
|
|
21
21
|
|
|
22
|
-
export
|
|
22
|
+
export function linesOf(text: string): readonly string[] {
|
|
23
23
|
const lines = text.split("\n");
|
|
24
24
|
return lines.at(-1) === "" ? lines.slice(0, -1) : lines;
|
|
25
|
-
}
|
|
25
|
+
}
|
|
26
26
|
|
|
27
|
-
const zoneFiles =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
39
|
-
}
|
|
27
|
+
const zoneFiles = Effect.fn("Inventory.zoneFiles")(function* (
|
|
28
|
+
root: string,
|
|
29
|
+
zone: string,
|
|
30
|
+
): Effect.fn.Return<readonly string[], FilesystemFailure, FileSystem.FileSystem> {
|
|
31
|
+
const fs = yield* FileSystem.FileSystem;
|
|
32
|
+
const path = join(root, zone);
|
|
33
|
+
if (posix(relative(root, path)).startsWith("..")) {
|
|
34
|
+
return yield* new FilesystemFailure({ message: `source "${zone}" lies outside the repository at ${root}`, path });
|
|
35
|
+
}
|
|
36
|
+
const info = yield* orWhenAbsent<FileSystem.File.Info | undefined>(path, fs.stat(path), undefined);
|
|
37
|
+
if (info === undefined) {
|
|
38
|
+
return yield* new FilesystemFailure({ message: `source "${zone}" does not exist in ${root}`, path });
|
|
39
|
+
}
|
|
40
|
+
return info.type === "Directory" ? yield* walk(path, yield* scopeAbove(root, path)) : [path];
|
|
41
|
+
});
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
Effect.map(readRequiredText(join(root, path)), (text) => ({ lines: linesOf(text), path, text }));
|
|
43
|
+
function readSource(root: string, path: string): Effect.Effect<SourceFile, FilesystemFailure, FileSystem.FileSystem> {
|
|
44
|
+
return Effect.map(readRequiredText(join(root, path)), (text) => ({ lines: linesOf(text), path, text }));
|
|
45
|
+
}
|
|
43
46
|
|
|
44
|
-
export const collectInventory =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
47
|
+
export const collectInventory = Effect.fn("Inventory.collectInventory")(function* (
|
|
48
|
+
root: string,
|
|
49
|
+
scope: InventoryScope,
|
|
50
|
+
): Effect.fn.Return<Inventory, FilesystemFailure, FileSystem.FileSystem> {
|
|
51
|
+
const excluded = ignore().add([...scope.exclude]);
|
|
52
|
+
const zones = yield* Effect.forEach(scope.sources, (zone) => zoneFiles(root, zone), { concurrency: READ_CONCURRENCY });
|
|
53
|
+
const relativePaths = zones.flat().map((absolute) => posix(relative(root, absolute)));
|
|
54
|
+
const files = [...new Set(relativePaths)]
|
|
55
|
+
.filter((path) => !excluded.ignores(path))
|
|
56
|
+
.sort((left, right) => {
|
|
57
|
+
if (left === right) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
return left < right ? -1 : 1;
|
|
61
|
+
});
|
|
62
|
+
const sourcePaths = files.filter((path) => scope.extensions.some((extension) => path.endsWith(extension)));
|
|
63
|
+
const sources = yield* Effect.forEach(sourcePaths, (path) => readSource(root, path), { concurrency: READ_CONCURRENCY });
|
|
64
|
+
return { files, sources };
|
|
65
|
+
});
|
|
@@ -10,38 +10,46 @@ export class FilesystemFailure extends Data.TaggedError("FilesystemFailure")<{
|
|
|
10
10
|
}> {}
|
|
11
11
|
|
|
12
12
|
// Effect maps ENOTDIR and EISDIR to BadResource; the original errno remains on the cause.
|
|
13
|
-
|
|
13
|
+
function errnoOf(error: PlatformError): string {
|
|
14
14
|
const cause: unknown = error.reason.cause;
|
|
15
15
|
return typeof cause === "object" && cause !== null && "code" in cause && typeof cause.code === "string" ? cause.code : "";
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
new FilesystemFailure({ message: `cannot read ${path}: ${error.message}`, path });
|
|
18
|
+
function failure(path: string, error: PlatformError): FilesystemFailure {
|
|
19
|
+
return new FilesystemFailure({ message: `cannot read ${path}: ${error.message}`, path });
|
|
20
|
+
}
|
|
20
21
|
|
|
21
|
-
export
|
|
22
|
+
export function orWhenAbsent<TValue>(
|
|
22
23
|
path: string,
|
|
23
|
-
action: Effect.Effect<
|
|
24
|
-
whenAbsent:
|
|
25
|
-
): Effect.Effect<
|
|
26
|
-
Effect.catchTag(action, "PlatformError", (error) =>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
24
|
+
action: Effect.Effect<TValue, PlatformError, FileSystem.FileSystem>,
|
|
25
|
+
whenAbsent: TValue,
|
|
26
|
+
): Effect.Effect<TValue, FilesystemFailure, FileSystem.FileSystem> {
|
|
27
|
+
return Effect.catchTag(action, "PlatformError", (error) =>
|
|
28
|
+
ABSENT.has(errnoOf(error)) ? Effect.succeed(whenAbsent) : Effect.fail(failure(path, error)),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const readOptionalText = Effect.fn("Inventory.readOptionalText")(function* (
|
|
33
|
+
path: string,
|
|
34
|
+
): Effect.fn.Return<string | undefined, FilesystemFailure, FileSystem.FileSystem> {
|
|
35
|
+
const fs = yield* FileSystem.FileSystem;
|
|
36
|
+
return yield* orWhenAbsent<string | undefined>(path, fs.readFileString(path), undefined);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const readRequiredText = Effect.fn("Inventory.readRequiredText")(function* (
|
|
40
|
+
path: string,
|
|
41
|
+
): Effect.fn.Return<string, FilesystemFailure, FileSystem.FileSystem> {
|
|
42
|
+
const fs = yield* FileSystem.FileSystem;
|
|
43
|
+
return yield* Effect.mapError(fs.readFileString(path), (error) =>
|
|
44
|
+
ABSENT.has(errnoOf(error)) ? new FilesystemFailure({ message: `required input is missing: ${path}`, path }) : failure(path, error),
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const writeText = Effect.fn("Inventory.writeText")(function* (
|
|
49
|
+
path: string,
|
|
50
|
+
text: string,
|
|
51
|
+
): Effect.fn.Return<void, FilesystemFailure, FileSystem.FileSystem> {
|
|
52
|
+
const fs = yield* FileSystem.FileSystem;
|
|
53
|
+
const written = Effect.andThen(fs.makeDirectory(dirname(path), { recursive: true }), fs.writeFileString(path, text));
|
|
54
|
+
return yield* Effect.mapError(written, (error) => new FilesystemFailure({ message: `cannot write ${path}: ${error.message}`, path }));
|
|
55
|
+
});
|
|
@@ -15,17 +15,23 @@ export interface IgnoreScope {
|
|
|
15
15
|
|
|
16
16
|
export const emptyScope: IgnoreScope = { boundary: undefined, matchers: [] };
|
|
17
17
|
|
|
18
|
-
export
|
|
18
|
+
export function posix(path: string): string {
|
|
19
|
+
return path.replaceAll("\\", "/");
|
|
20
|
+
}
|
|
19
21
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
22
|
+
export function withIgnoreFile(scope: IgnoreScope, base: string, contents: string): IgnoreScope {
|
|
23
|
+
return {
|
|
24
|
+
boundary: scope.boundary,
|
|
25
|
+
matchers: [...scope.matchers, { base, rules: ignore().add(contents) }],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
24
28
|
|
|
25
29
|
// Re-including a directory resets directory rules below that boundary; filename rules still apply.
|
|
26
|
-
export
|
|
30
|
+
export function insideKept(scope: IgnoreScope, directory: string): IgnoreScope {
|
|
31
|
+
return { boundary: directory, matchers: scope.matchers };
|
|
32
|
+
}
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
function matcherVerdict(matcher: IgnoreMatcher, boundary: string | undefined, path: string, directory: boolean): Verdict {
|
|
29
35
|
const anchor = boundary !== undefined && boundary.length > matcher.base.length ? boundary : matcher.base;
|
|
30
36
|
const rel = posix(relative(anchor, path));
|
|
31
37
|
if (rel === "" || rel.startsWith("..")) {
|
|
@@ -37,7 +43,11 @@ const matcherVerdict = (matcher: IgnoreMatcher, boundary: string | undefined, pa
|
|
|
37
43
|
return "ignored";
|
|
38
44
|
}
|
|
39
45
|
return result.unignored ? "kept" : "unknown";
|
|
40
|
-
}
|
|
46
|
+
}
|
|
41
47
|
|
|
42
|
-
export
|
|
43
|
-
|
|
48
|
+
export function verdictFor(scope: IgnoreScope, path: string, directory: boolean): Verdict {
|
|
49
|
+
return (
|
|
50
|
+
scope.matchers.map((matcher) => matcherVerdict(matcher, scope.boundary, path, directory)).findLast((verdict) => verdict !== "unknown")
|
|
51
|
+
?? "unknown"
|
|
52
|
+
);
|
|
53
|
+
}
|
package/src/manifests/sorted.ts
CHANGED
|
@@ -26,21 +26,21 @@ export function sortingOf(text: string): Sorting {
|
|
|
26
26
|
return sorted === text ? { _tag: "Sorted" } : { _tag: "Unsorted", sorted };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
29
|
+
export const sortManifests = Effect.fn("Manifests.sortManifests")(function* (
|
|
30
|
+
root: string,
|
|
31
|
+
): Effect.fn.Return<number, FilesystemFailure, FileSystem.FileSystem> {
|
|
32
|
+
const fs = yield* FileSystem.FileSystem;
|
|
33
|
+
const manifests = yield* manifestsIn(root);
|
|
34
|
+
const written = yield* Effect.forEach(manifests, (path) =>
|
|
35
|
+
Effect.gen(function* () {
|
|
36
|
+
const absolute = join(root, path);
|
|
37
|
+
const sorting = sortingOf(yield* fs.readFileString(absolute));
|
|
38
|
+
if (sorting._tag !== "Unsorted") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
yield* fs.writeFileString(absolute, sorting.sorted);
|
|
42
|
+
return true;
|
|
43
|
+
}).pipe(Effect.mapError((error) => new FilesystemFailure({ message: `cannot sort ${path}: ${error.message}`, path }))),
|
|
44
|
+
);
|
|
45
|
+
return written.filter(Boolean).length;
|
|
46
|
+
});
|
package/src/report/plural.ts
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
export
|
|
1
|
+
export function plural(count: number, singular: string, many = `${singular}s`): string {
|
|
2
|
+
return `${count} ${count === 1 ? singular : many}`;
|
|
3
|
+
}
|
package/src/report/render.ts
CHANGED
|
@@ -16,18 +16,21 @@ export interface ReportContext {
|
|
|
16
16
|
|
|
17
17
|
const LISTED_FILES = 5;
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
function locate(violation: Violation): string {
|
|
20
|
+
return violation.line === undefined ? violation.file : `${violation.file}:${violation.line}`;
|
|
21
|
+
}
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
` ${regression.entry.file} is over its baseline: ${regression.count} against ${regression.entry.count} baselined.`;
|
|
23
|
+
function regressionNote(regression: Regression): string {
|
|
24
|
+
return ` ${regression.entry.file} is over its baseline: ${regression.count} against ${regression.entry.count} baselined.`;
|
|
25
|
+
}
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
function heading(label: string, rule: string, violations: readonly Violation[], context: ReportContext): readonly string[] {
|
|
25
28
|
const description = context.descriptions.get(rule);
|
|
26
29
|
return [`${label} ${rule} (${violations.length})`, ...(description === undefined ? [] : [` ${description}`])];
|
|
27
|
-
}
|
|
30
|
+
}
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
[...groupBy(violations, (violation) => violation.file).values()].flatMap((inFile) => {
|
|
32
|
+
function detailed(violations: readonly Violation[], regressions: ReadonlyMap<string, Regression>): readonly string[] {
|
|
33
|
+
return [...groupBy(violations, (violation) => violation.file).values()].flatMap((inFile) => {
|
|
31
34
|
const first = inFile[0];
|
|
32
35
|
const regression = first === undefined ? undefined : regressions.get(keyOf(first.rule, first.file));
|
|
33
36
|
return [
|
|
@@ -35,33 +38,35 @@ const detailed = (violations: readonly Violation[], regressions: ReadonlyMap<str
|
|
|
35
38
|
...(regression === undefined ? [] : [regressionNote(regression)]),
|
|
36
39
|
];
|
|
37
40
|
});
|
|
41
|
+
}
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
function summarized(violations: readonly Violation[]): readonly string[] {
|
|
40
44
|
const files = [...groupBy(violations, (violation) => violation.file).entries()].sort(([, left], [, right]) => right.length - left.length);
|
|
41
45
|
const listed = files.slice(0, LISTED_FILES).map(([file, inFile]) => `${file} (${inFile.length})`);
|
|
42
46
|
const rest = files.length - listed.length;
|
|
43
47
|
return [` ${listed.join(", ")}${rest > 0 ? `, and ${plural(rest, "more file", "more files")}` : ""}. --warnings all lists each one.`];
|
|
44
|
-
}
|
|
48
|
+
}
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
function ruleSections(
|
|
47
51
|
label: string,
|
|
48
52
|
violations: readonly Violation[],
|
|
49
53
|
context: ReportContext,
|
|
50
54
|
body: (inRule: readonly Violation[]) => readonly string[],
|
|
51
|
-
): readonly string[]
|
|
52
|
-
[...groupBy(violations, (violation) => violation.rule).entries()].map(([rule, inRule]) =>
|
|
55
|
+
): readonly string[] {
|
|
56
|
+
return [...groupBy(violations, (violation) => violation.rule).entries()].map(([rule, inRule]) =>
|
|
53
57
|
[...heading(label, rule, inRule, context), ...body(inRule)].join("\n"),
|
|
54
58
|
);
|
|
59
|
+
}
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
function failures(outcome: Outcome): string {
|
|
57
62
|
const stale = outcome.staleBaseline.length + outcome.staleRegistry.length;
|
|
58
63
|
return [
|
|
59
64
|
...(outcome.errors.length > 0 ? [plural(outcome.errors.length, "error")] : []),
|
|
60
65
|
...(stale > 0 ? [plural(stale, "stale entry", "stale entries")] : []),
|
|
61
66
|
].join(" and ");
|
|
62
|
-
}
|
|
67
|
+
}
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
function summary(outcome: Outcome, context: ReportContext): string {
|
|
65
70
|
const verdict = passes(outcome) ? "quality: passed" : `quality: failed with ${failures(outcome)}`;
|
|
66
71
|
const warnings = outcome.warnings.length > 0 ? `; ${plural(outcome.warnings.length, "warning")} from rules still at warn` : "";
|
|
67
72
|
const hidden = [
|
|
@@ -71,7 +76,7 @@ const summary = (outcome: Outcome, context: ReportContext): string => {
|
|
|
71
76
|
const suppressed =
|
|
72
77
|
hidden.length === 0 ? "" : `; ${hidden.join(" and ")} ${outcome.baselined + outcome.registered === 1 ? "violation" : "violations"} not shown`;
|
|
73
78
|
return `${verdict}${warnings}. ${plural(context.checked, "source file")} checked${suppressed}.`;
|
|
74
|
-
}
|
|
79
|
+
}
|
|
75
80
|
|
|
76
81
|
function rerecord(outcome: Outcome): readonly string[] {
|
|
77
82
|
const rules = [...new Set(outcome.errors.map((violation) => violation.rule))].sort((left, right) => left.localeCompare(right));
|
|
@@ -84,7 +89,7 @@ function rerecord(outcome: Outcome): readonly string[] {
|
|
|
84
89
|
];
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
export
|
|
92
|
+
export function render(outcome: Outcome, context: ReportContext): string {
|
|
88
93
|
const regressions = new Map(outcome.regressions.map((regression) => [keyOf(regression.entry.rule, regression.entry.file), regression]));
|
|
89
94
|
const warningBody = context.warnings === "all" ? (inRule: readonly Violation[]) => detailed(inRule, regressions) : summarized;
|
|
90
95
|
return [
|
|
@@ -94,4 +99,4 @@ export const render = (outcome: Outcome, context: ReportContext): string => {
|
|
|
94
99
|
summary(outcome, context),
|
|
95
100
|
...rerecord(outcome),
|
|
96
101
|
].join("\n\n");
|
|
97
|
-
}
|
|
102
|
+
}
|