@shivaedev/quality 0.6.0 → 0.7.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +16 -38
  3. package/biome/declarations.json +55 -0
  4. package/biome/preset.json +17 -12
  5. package/dist/baseline/legacy.d.ts +0 -1
  6. package/dist/baseline/legacy.d.ts.map +1 -1
  7. package/dist/baseline/legacy.js +0 -1
  8. package/dist/baseline/legacy.js.map +1 -1
  9. package/dist/cli/args.d.ts +1 -6
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/args.js +7 -14
  12. package/dist/cli/args.js.map +1 -1
  13. package/dist/cli/fix.d.ts +8 -2
  14. package/dist/cli/fix.d.ts.map +1 -1
  15. package/dist/cli/fix.js +55 -9
  16. package/dist/cli/fix.js.map +1 -1
  17. package/dist/cli.js +1 -4
  18. package/dist/cli.js.map +1 -1
  19. package/dist/config/decode.d.ts +0 -1
  20. package/dist/config/decode.d.ts.map +1 -1
  21. package/dist/config/decode.js +0 -1
  22. package/dist/config/decode.js.map +1 -1
  23. package/dist/config/load.d.ts +0 -1
  24. package/dist/config/load.d.ts.map +1 -1
  25. package/dist/config/load.js +0 -1
  26. package/dist/config/load.js.map +1 -1
  27. package/dist/config/resolve.d.ts +1 -1
  28. package/dist/config/resolve.d.ts.map +1 -1
  29. package/dist/config/resolve.js +0 -3
  30. package/dist/config/resolve.js.map +1 -1
  31. package/dist/config.d.ts +0 -2
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js.map +1 -1
  34. package/dist/git/base.d.ts.map +1 -1
  35. package/dist/git/base.js +4 -27
  36. package/dist/git/base.js.map +1 -1
  37. package/dist/git/command.d.ts +1 -1
  38. package/dist/git/command.d.ts.map +1 -1
  39. package/dist/git/command.js +2 -2
  40. package/dist/git/command.js.map +1 -1
  41. package/dist/git/history.d.ts +0 -1
  42. package/dist/git/history.d.ts.map +1 -1
  43. package/dist/git/history.js +1 -6
  44. package/dist/git/history.js.map +1 -1
  45. package/dist/report/render.d.ts.map +1 -1
  46. package/dist/report/render.js +11 -0
  47. package/dist/report/render.js.map +1 -1
  48. package/dist/rule.d.ts +2 -1
  49. package/dist/rule.d.ts.map +1 -1
  50. package/dist/rule.js.map +1 -1
  51. package/dist/rules/biome.js +1 -1
  52. package/dist/rules/biome.js.map +1 -1
  53. package/package.json +3 -2
  54. package/src/baseline/legacy.ts +0 -2
  55. package/src/cli/args.ts +8 -16
  56. package/src/cli/fix.ts +72 -10
  57. package/src/cli.ts +1 -4
  58. package/src/config/decode.ts +0 -1
  59. package/src/config/load.ts +0 -2
  60. package/src/config/resolve.ts +1 -3
  61. package/src/config.ts +0 -3
  62. package/src/git/base.ts +4 -31
  63. package/src/git/command.ts +2 -2
  64. package/src/git/history.ts +1 -8
  65. package/src/report/render.ts +12 -0
  66. package/src/rule.ts +3 -2
  67. package/src/rules/biome.ts +1 -1
  68. package/dist/baseline/guard.d.ts +0 -23
  69. package/dist/baseline/guard.d.ts.map +0 -1
  70. package/dist/baseline/guard.js +0 -32
  71. package/dist/baseline/guard.js.map +0 -1
  72. package/dist/cli/check.d.ts +0 -5
  73. package/dist/cli/check.d.ts.map +0 -1
  74. package/dist/cli/check.js +0 -39
  75. package/dist/cli/check.js.map +0 -1
  76. package/dist/report/guard.d.ts +0 -9
  77. package/dist/report/guard.d.ts.map +0 -1
  78. package/dist/report/guard.js +0 -34
  79. package/dist/report/guard.js.map +0 -1
  80. package/src/baseline/guard.ts +0 -48
  81. package/src/cli/check.ts +0 -51
  82. package/src/report/guard.ts +0 -46
@@ -1,48 +0,0 @@
1
- import { covers, groupBy, keyOf } from "../engine/violation.ts";
2
- import type { BaselineEntry } from "./format.ts";
3
-
4
- export type GuardProblem =
5
- | { readonly _tag: "Added"; readonly entry: BaselineEntry }
6
- | { readonly _tag: "Raised"; readonly entry: BaselineEntry; readonly base: BaselineEntry }
7
- | { readonly _tag: "Unadopted"; readonly rule: string; readonly entries: number }
8
- | { readonly _tag: "NothingAdopted"; readonly rule: string };
9
-
10
- export interface GuardResult {
11
- readonly adopted: readonly string[];
12
- readonly moved: number;
13
- readonly problems: readonly GuardProblem[];
14
- }
15
-
16
- export const guardBaseline = (
17
- base: readonly BaselineEntry[],
18
- working: readonly BaselineEntry[],
19
- renames: ReadonlyMap<string, string>,
20
- adopt: readonly string[],
21
- ): GuardResult => {
22
- const before = new Map(base.map((entry) => [keyOf(entry.rule, entry.file), entry]));
23
- const covered = new Set(base.map((entry) => entry.rule));
24
- const byRule = groupBy(working, (entry) => entry.rule);
25
- const problems: GuardProblem[] = [];
26
- let moved = 0;
27
- for (const entry of working.filter((candidate) => covered.has(candidate.rule))) {
28
- const exact = before.get(keyOf(entry.rule, entry.file));
29
- const origin = renames.get(entry.file);
30
- const matched = exact ?? (origin === undefined ? undefined : before.get(keyOf(entry.rule, origin)));
31
- if (matched === undefined) {
32
- problems.push({ _tag: "Added", entry });
33
- } else if (entry.count > matched.count) {
34
- problems.push({ _tag: "Raised", base: matched, entry });
35
- } else if (exact === undefined) {
36
- moved += 1;
37
- }
38
- }
39
- const adopted = (rule: string): boolean => adopt.some((name) => covers(name, rule));
40
- const fresh = [...byRule.entries()].filter(([rule]) => !covered.has(rule));
41
- for (const [rule, entries] of fresh.filter(([rule]) => !adopted(rule))) {
42
- problems.push({ _tag: "Unadopted", entries: entries.length, rule });
43
- }
44
- for (const name of adopt.filter((candidate) => ![...byRule.keys()].some((rule) => covers(candidate, rule)))) {
45
- problems.push({ _tag: "NothingAdopted", rule: name });
46
- }
47
- return { adopted: fresh.map(([rule]) => rule).filter(adopted), moved, problems };
48
- };
package/src/cli/check.ts DELETED
@@ -1,51 +0,0 @@
1
- import { relative } from "node:path";
2
- import { Console, Effect, type FileSystem } from "effect";
3
- import { type BaselineEntry, decodeBaseline } from "../baseline/format.ts";
4
- import { guardBaseline } from "../baseline/guard.ts";
5
- import { convertLegacy, decodeLegacyBaseline, LEGACY_BASELINE, measured } from "../baseline/legacy.ts";
6
- import { loadConfig, type ResolvedConfig } from "../config/load.ts";
7
- import { validOrFail } from "../decoded.ts";
8
- import { readBaseline } from "../engine/baseline-file.ts";
9
- import { scan } from "../engine/session.ts";
10
- import { GateFailed, type SetupFailure } from "../failure.ts";
11
- import { type Base, resolveBase } from "../git/base.ts";
12
- import type { Git } from "../git/command.ts";
13
- import { changesSince, readAt } from "../git/history.ts";
14
- import { renderGuard } from "../report/guard.ts";
15
-
16
- const baseEntries = (
17
- config: ResolvedConfig,
18
- base: Base,
19
- path: string,
20
- ): Effect.Effect<readonly BaselineEntry[], SetupFailure, FileSystem.FileSystem | Git> =>
21
- Effect.gen(function* () {
22
- const raw = yield* readAt(config.root, base.commit, path);
23
- if (raw !== undefined) {
24
- return yield* validOrFail(`${path} at ${base.ref}`, yield* Effect.promise(() => decodeBaseline(raw)));
25
- }
26
- const legacyRaw = yield* readAt(config.root, base.commit, LEGACY_BASELINE);
27
- if (legacyRaw === undefined) {
28
- return [];
29
- }
30
- const legacy = yield* validOrFail(`${LEGACY_BASELINE} at ${base.ref}`, yield* Effect.promise(() => decodeLegacyBaseline(legacyRaw)));
31
- return measured(legacy) ? convertLegacy(legacy, (yield* scan(config)).violations) : legacy;
32
- });
33
-
34
- export const checkBaseline = (
35
- cwd: string,
36
- configPath: string | undefined,
37
- against: string | undefined,
38
- ): Effect.Effect<void, SetupFailure | GateFailed, FileSystem.FileSystem | Git> =>
39
- Effect.gen(function* () {
40
- const config = yield* loadConfig(cwd, configPath);
41
- const working = yield* readBaseline(config);
42
- const base = yield* resolveBase(config.root, against);
43
- const { moves } = yield* changesSince(config.root, [base.commit]);
44
- const renames = new Map([...moves].map(([from, to]) => [to, from]));
45
- const before = yield* baseEntries(config, base, renames.get(config.baseline) ?? config.baseline);
46
- const result = guardBaseline(before, working.entries, renames, config.adopt);
47
- yield* Console.log(renderGuard(result, { base, baseline: config.baseline, config: relative(config.root, config.file) }));
48
- if (result.problems.length > 0) {
49
- return yield* new GateFailed();
50
- }
51
- });
@@ -1,46 +0,0 @@
1
- import type { GuardProblem, GuardResult } from "../baseline/guard.ts";
2
- import type { Base } from "../git/base.ts";
3
- import { plural } from "./plural.ts";
4
-
5
- export interface GuardContext {
6
- readonly base: Base;
7
- readonly baseline: string;
8
- readonly config: string;
9
- }
10
-
11
- const problemLine = (problem: GuardProblem, context: GuardContext): string => {
12
- switch (problem._tag) {
13
- case "Added":
14
- return `${problem.entry.rule} ${problem.entry.file} is new, at ${problem.entry.count}.`;
15
- case "Raised":
16
- return `${problem.entry.rule} ${problem.entry.file} rose to ${problem.entry.count} from ${problem.base.count}.`;
17
- case "Unadopted":
18
- return `${problem.rule} is newly baselined in ${plural(problem.entries, "file")} without being named under \`adopt\` in ${context.config}.`;
19
- case "NothingAdopted":
20
- return `\`adopt\` in ${context.config} names ${problem.rule}, which has nothing baselined. Remove it.`;
21
- }
22
- };
23
-
24
- const against = (base: Base): string => `${base.ref} (merge base ${base.commit.slice(0, 12)})`;
25
-
26
- const held = (result: GuardResult): string =>
27
- [
28
- ...(result.moved > 0 ? [`${plural(result.moved, "entry", "entries")} followed moved files`] : []),
29
- ...(result.adopted.length > 0 ? [`adopted ${result.adopted.join(", ")}`] : []),
30
- ]
31
- .map((part) => `; ${part}`)
32
- .join("");
33
-
34
- export const renderGuard = (result: GuardResult, context: GuardContext): string => {
35
- if (result.problems.length === 0) {
36
- return `quality: ${context.baseline} holds against ${against(context.base)}${held(result)}.`;
37
- }
38
- return [
39
- [
40
- `error ${context.baseline} grew against ${against(context.base)}`,
41
- " The baseline only shrinks. Fix new violations instead of baselining them; a rule enters the baseline for the first time only while `adopt` names it.",
42
- ...result.problems.map((problem) => ` ${problemLine(problem, context)}`),
43
- ].join("\n"),
44
- `quality: baseline check failed with ${plural(result.problems.length, "problem")}.`,
45
- ].join("\n\n");
46
- };