@shivaedev/quality 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/CHANGELOG.md +22 -0
- package/README.md +119 -29
- package/biome/declarations.json +17 -0
- package/biome/plugins/ambient-runtime.grit +18 -0
- package/biome/plugins/effect-fn-spans.grit +7 -0
- package/biome/preset.json +602 -0
- package/dist/baseline/compare.d.ts +8 -9
- package/dist/baseline/compare.d.ts.map +1 -1
- package/dist/baseline/compare.js +4 -4
- package/dist/baseline/compare.js.map +1 -1
- package/dist/baseline/format.d.ts +4 -4
- package/dist/baseline/format.d.ts.map +1 -1
- package/dist/baseline/format.js +3 -2
- package/dist/baseline/format.js.map +1 -1
- package/dist/baseline/guard.d.ts +3 -3
- package/dist/baseline/guard.d.ts.map +1 -1
- package/dist/baseline/guard.js +6 -5
- package/dist/baseline/guard.js.map +1 -1
- package/dist/baseline/legacy.d.ts +3 -3
- package/dist/baseline/legacy.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/baseline/update.d.ts +8 -9
- package/dist/baseline/update.d.ts.map +1 -1
- package/dist/baseline/update.js +10 -9
- package/dist/baseline/update.js.map +1 -1
- package/dist/biome/report.d.ts +33 -0
- package/dist/biome/report.d.ts.map +1 -0
- package/dist/biome/report.js +25 -0
- package/dist/biome/report.js.map +1 -0
- package/dist/biome/run.d.ts +7 -0
- package/dist/biome/run.d.ts.map +1 -0
- package/dist/biome/run.js +16 -0
- package/dist/biome/run.js.map +1 -0
- package/dist/cli/args.d.ts +6 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -4
- 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 +3 -3
- package/dist/cli/baseline.js.map +1 -1
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/fix.d.ts +4 -0
- package/dist/cli/fix.d.ts.map +1 -0
- package/dist/cli/fix.js +19 -0
- package/dist/cli/fix.js.map +1 -0
- package/dist/cli/lint.js +1 -1
- package/dist/cli/lint.js.map +1 -1
- package/dist/cli/run-main.d.ts.map +1 -1
- package/dist/cli/run-main.js +7 -3
- package/dist/cli/run-main.js.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/decode.d.ts +4 -4
- package/dist/config/decode.js +12 -12
- package/dist/config/decode.js.map +1 -1
- package/dist/config/load.d.ts +3 -3
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +1 -1
- package/dist/config/load.js.map +1 -1
- package/dist/config/resolve.d.ts +3 -5
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +11 -5
- package/dist/config/resolve.js.map +1 -1
- package/dist/config.d.ts +9 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/decoded.d.ts +1 -1
- package/dist/decoded.d.ts.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/evaluate.d.ts +8 -9
- package/dist/engine/evaluate.d.ts.map +1 -1
- package/dist/engine/evaluate.js +4 -4
- package/dist/engine/evaluate.js.map +1 -1
- package/dist/engine/run-rules.d.ts +4 -3
- package/dist/engine/run-rules.d.ts.map +1 -1
- package/dist/engine/run-rules.js +3 -3
- package/dist/engine/run-rules.js.map +1 -1
- package/dist/engine/session.d.ts +3 -3
- package/dist/engine/session.d.ts.map +1 -1
- package/dist/engine/violation.d.ts +11 -3
- package/dist/engine/violation.d.ts.map +1 -1
- package/dist/engine/violation.js +6 -2
- package/dist/engine/violation.js.map +1 -1
- package/dist/exceptions/registry.d.ts +7 -8
- package/dist/exceptions/registry.d.ts.map +1 -1
- package/dist/exceptions/registry.js +6 -6
- package/dist/exceptions/registry.js.map +1 -1
- package/dist/git/base.d.ts +1 -1
- package/dist/git/base.d.ts.map +1 -1
- package/dist/git/base.js.map +1 -1
- package/dist/git/command.d.ts +3 -3
- package/dist/git/command.d.ts.map +1 -1
- 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 +1 -1
- package/dist/git/history.js.map +1 -1
- package/dist/imports/ambient.d.ts +7 -0
- package/dist/imports/ambient.d.ts.map +1 -0
- package/dist/imports/ambient.js +44 -0
- package/dist/imports/ambient.js.map +1 -0
- package/dist/imports/components.d.ts +2 -0
- package/dist/imports/components.d.ts.map +1 -0
- package/dist/imports/components.js +63 -0
- package/dist/imports/components.js.map +1 -0
- package/dist/imports/extract.d.ts +10 -0
- package/dist/imports/extract.d.ts.map +1 -0
- package/dist/imports/extract.js +100 -0
- package/dist/imports/extract.js.map +1 -0
- package/dist/imports/fences/dsl.d.ts +18 -0
- package/dist/imports/fences/dsl.d.ts.map +1 -0
- package/dist/imports/fences/dsl.js +14 -0
- package/dist/imports/fences/dsl.js.map +1 -0
- package/dist/imports/fences/evaluate.d.ts +12 -0
- package/dist/imports/fences/evaluate.d.ts.map +1 -0
- package/dist/imports/fences/evaluate.js +55 -0
- package/dist/imports/fences/evaluate.js.map +1 -0
- package/dist/imports/fences/match.d.ts +17 -0
- package/dist/imports/fences/match.d.ts.map +1 -0
- package/dist/imports/fences/match.js +106 -0
- package/dist/imports/fences/match.js.map +1 -0
- package/dist/imports/fences/model.d.ts +60 -0
- package/dist/imports/fences/model.d.ts.map +1 -0
- package/dist/imports/fences/model.js +2 -0
- package/dist/imports/fences/model.js.map +1 -0
- package/dist/imports/fences/policy.d.ts +5 -0
- package/dist/imports/fences/policy.d.ts.map +1 -0
- package/dist/imports/fences/policy.js +59 -0
- package/dist/imports/fences/policy.js.map +1 -0
- package/dist/imports/fences/reach.d.ts +12 -0
- package/dist/imports/fences/reach.d.ts.map +1 -0
- package/dist/imports/fences/reach.js +39 -0
- package/dist/imports/fences/reach.js.map +1 -0
- package/dist/imports/fences/selectors.d.ts +11 -0
- package/dist/imports/fences/selectors.d.ts.map +1 -0
- package/dist/imports/fences/selectors.js +27 -0
- package/dist/imports/fences/selectors.js.map +1 -0
- package/dist/imports/fences/vocabulary.d.ts +9 -0
- package/dist/imports/fences/vocabulary.d.ts.map +1 -0
- package/dist/imports/fences/vocabulary.js +40 -0
- package/dist/imports/fences/vocabulary.js.map +1 -0
- package/dist/imports/generated.d.ts +6 -0
- package/dist/imports/generated.d.ts.map +1 -0
- package/dist/imports/generated.js +78 -0
- package/dist/imports/generated.js.map +1 -0
- package/dist/imports/glob.d.ts +2 -0
- package/dist/imports/glob.d.ts.map +1 -0
- package/dist/imports/glob.js +125 -0
- package/dist/imports/glob.js.map +1 -0
- package/dist/imports/graph.d.ts +23 -0
- package/dist/imports/graph.d.ts.map +1 -0
- package/dist/imports/graph.js +56 -0
- package/dist/imports/graph.js.map +1 -0
- package/dist/imports/paths.d.ts +2 -0
- package/dist/imports/paths.d.ts.map +1 -0
- package/dist/imports/paths.js +24 -0
- package/dist/imports/paths.js.map +1 -0
- package/dist/imports/pnpm-workspace.d.ts +2 -0
- package/dist/imports/pnpm-workspace.d.ts.map +1 -0
- package/dist/imports/pnpm-workspace.js +122 -0
- package/dist/imports/pnpm-workspace.js.map +1 -0
- package/dist/imports/projects.d.ts +10 -0
- package/dist/imports/projects.d.ts.map +1 -0
- package/dist/imports/projects.js +83 -0
- package/dist/imports/projects.js.map +1 -0
- package/dist/imports/resolve.d.ts +16 -0
- package/dist/imports/resolve.d.ts.map +1 -0
- package/dist/imports/resolve.js +88 -0
- package/dist/imports/resolve.js.map +1 -0
- package/dist/imports/workspace.d.ts +10 -0
- package/dist/imports/workspace.d.ts.map +1 -0
- package/dist/imports/workspace.js +55 -0
- package/dist/imports/workspace.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/inventory/collect.d.ts +6 -6
- package/dist/inventory/collect.d.ts.map +1 -1
- package/dist/inventory/collect.js.map +1 -1
- package/dist/manifests/sorted.d.ts +14 -0
- package/dist/manifests/sorted.d.ts.map +1 -0
- package/dist/manifests/sorted.js +39 -0
- package/dist/manifests/sorted.js.map +1 -0
- package/dist/report/guard.d.ts +1 -1
- package/dist/report/guard.d.ts.map +1 -1
- package/dist/report/guard.js +1 -1
- package/dist/report/guard.js.map +1 -1
- package/dist/report/render.d.ts +2 -2
- package/dist/report/render.d.ts.map +1 -1
- package/dist/report/render.js.map +1 -1
- package/dist/report/stale.d.ts +2 -2
- package/dist/report/stale.d.ts.map +1 -1
- package/dist/report/stale.js.map +1 -1
- package/dist/rule.d.ts +12 -11
- package/dist/rule.d.ts.map +1 -1
- package/dist/rule.js.map +1 -1
- package/dist/rules/biome.d.ts +4 -0
- package/dist/rules/biome.d.ts.map +1 -0
- package/dist/rules/biome.js +40 -0
- package/dist/rules/biome.js.map +1 -0
- package/dist/rules/built-in.d.ts +8 -10
- package/dist/rules/built-in.d.ts.map +1 -1
- package/dist/rules/built-in.js +12 -0
- package/dist/rules/built-in.js.map +1 -1
- package/dist/rules/comments/kinds.d.ts +1 -2
- package/dist/rules/comments/kinds.d.ts.map +1 -1
- package/dist/rules/comments/kinds.js +7 -16
- package/dist/rules/comments/kinds.js.map +1 -1
- package/dist/rules/comments/max-per-file.d.ts +1 -1
- package/dist/rules/comments/max-per-file.js +11 -11
- package/dist/rules/comments/max-per-file.js.map +1 -1
- package/dist/rules/comments/no-banner.js +2 -2
- package/dist/rules/comments/no-banner.js.map +1 -1
- package/dist/rules/comments/no-environment-pragma.d.ts +2 -0
- package/dist/rules/comments/no-environment-pragma.d.ts.map +1 -0
- package/dist/rules/comments/no-environment-pragma.js +8 -0
- package/dist/rules/comments/no-environment-pragma.js.map +1 -0
- package/dist/rules/comments/no-jsdoc.js +3 -3
- package/dist/rules/comments/no-jsdoc.js.map +1 -1
- package/dist/rules/comments/no-line-reference.js +2 -2
- package/dist/rules/comments/no-line-reference.js.map +1 -1
- package/dist/rules/comments/no-pr-reference.js +6 -6
- package/dist/rules/comments/no-pr-reference.js.map +1 -1
- package/dist/rules/comments/no-todo.js +2 -2
- package/dist/rules/comments/no-todo.js.map +1 -1
- package/dist/rules/comments/pattern-rule.d.ts +2 -2
- package/dist/rules/comments/pattern-rule.d.ts.map +1 -1
- package/dist/rules/comments/pattern-rule.js +2 -2
- package/dist/rules/comments/pattern-rule.js.map +1 -1
- package/dist/rules/comments/scan.d.ts +6 -6
- package/dist/rules/comments/scan.d.ts.map +1 -1
- package/dist/rules/comments/scan.js +2 -2
- package/dist/rules/comments/scan.js.map +1 -1
- package/dist/rules/imports/cycles.d.ts +2 -0
- package/dist/rules/imports/cycles.d.ts.map +1 -0
- package/dist/rules/imports/cycles.js +38 -0
- package/dist/rules/imports/cycles.js.map +1 -0
- package/dist/rules/imports/fences.d.ts +5 -0
- package/dist/rules/imports/fences.d.ts.map +1 -0
- package/dist/rules/imports/fences.js +24 -0
- package/dist/rules/imports/fences.js.map +1 -0
- package/dist/rules/imports/resolvable.d.ts +4 -0
- package/dist/rules/imports/resolvable.d.ts.map +1 -0
- package/dist/rules/imports/resolvable.js +22 -0
- package/dist/rules/imports/resolvable.js.map +1 -0
- package/dist/rules/manifests-sorted.d.ts +2 -0
- package/dist/rules/manifests-sorted.d.ts.map +1 -0
- package/dist/rules/manifests-sorted.js +22 -0
- package/dist/rules/manifests-sorted.js.map +1 -0
- package/dist/rules/max-lines.js +4 -4
- package/dist/rules/max-lines.js.map +1 -1
- package/dist/rules/suppressions/biome/configs.d.ts +6 -6
- package/dist/rules/suppressions/biome/configs.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/configs.js +2 -2
- 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 +3 -3
- package/dist/rules/suppressions/biome/declarations.js.map +1 -1
- package/dist/rules/suppressions/biome/json.d.ts +2 -2
- package/dist/rules/suppressions/biome/json.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/json.js +1 -1
- package/dist/rules/suppressions/biome/json.js.map +1 -1
- package/dist/rules/suppressions/biome/resolve.js +3 -3
- package/dist/rules/suppressions/biome/resolve.js.map +1 -1
- package/dist/rules/suppressions/biome/weakenings.d.ts +4 -4
- package/dist/rules/suppressions/biome/weakenings.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/weakenings.js.map +1 -1
- package/dist/rules/suppressions/biome-overrides.d.ts +1 -1
- package/dist/rules/suppressions/biome-overrides.js +4 -4
- 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 +11 -11
- package/dist/rules/suppressions/directives.js.map +1 -1
- package/dist/rules/suppressions/no-double-cast.js +2 -2
- package/dist/rules/suppressions/no-double-cast.js.map +1 -1
- package/dist/rules/suppressions/no-inline.d.ts +1 -7
- package/dist/rules/suppressions/no-inline.d.ts.map +1 -1
- package/dist/rules/suppressions/no-inline.js +7 -30
- 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 +3 -3
- package/dist/rules/suppressions/stylesheet-comments.js.map +1 -1
- package/dist/rules/syntax.js +4 -4
- package/dist/rules/syntax.js.map +1 -1
- package/dist/standard-schema.d.ts +1 -1
- package/dist/standard-schema.d.ts.map +1 -1
- package/dist/vitest.d.ts +3 -0
- package/dist/vitest.d.ts.map +1 -0
- package/dist/vitest.js +20 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +46 -31
- package/src/baseline/compare.ts +10 -15
- package/src/baseline/format.ts +8 -6
- package/src/baseline/guard.ts +11 -10
- package/src/baseline/legacy.ts +3 -3
- package/src/baseline/rewrite.ts +1 -1
- package/src/baseline/update.ts +21 -21
- package/src/biome/report.ts +35 -0
- package/src/biome/run.ts +23 -0
- package/src/cli/args.ts +14 -8
- package/src/cli/baseline.ts +5 -6
- package/src/cli/check.ts +1 -1
- package/src/cli/fix.ts +19 -0
- package/src/cli/lint.ts +1 -1
- package/src/cli/run-main.ts +8 -3
- package/src/cli.ts +3 -0
- package/src/config/decode.ts +12 -12
- package/src/config/load.ts +4 -4
- package/src/config/resolve.ts +15 -11
- package/src/config.ts +9 -9
- package/src/decoded.ts +1 -1
- package/src/engine/baseline-file.ts +1 -1
- package/src/engine/evaluate.ts +14 -16
- package/src/engine/run-rules.ts +8 -7
- package/src/engine/session.ts +3 -3
- package/src/engine/violation.ts +18 -4
- package/src/exceptions/registry.ts +10 -16
- package/src/git/base.ts +2 -2
- package/src/git/command.ts +3 -3
- package/src/git/history.ts +2 -2
- package/src/imports/ambient.ts +54 -0
- package/src/imports/components.ts +80 -0
- package/src/imports/extract.ts +129 -0
- package/src/imports/fences/dsl.ts +33 -0
- package/src/imports/fences/evaluate.ts +75 -0
- package/src/imports/fences/match.ts +148 -0
- package/src/imports/fences/model.ts +42 -0
- package/src/imports/fences/policy.ts +70 -0
- package/src/imports/fences/reach.ts +62 -0
- package/src/imports/fences/selectors.ts +37 -0
- package/src/imports/fences/vocabulary.ts +51 -0
- package/src/imports/generated.ts +95 -0
- package/src/imports/glob.ts +142 -0
- package/src/imports/graph.ts +81 -0
- package/src/imports/paths.ts +28 -0
- package/src/imports/pnpm-workspace.ts +134 -0
- package/src/imports/projects.ts +106 -0
- package/src/imports/resolve.ts +115 -0
- package/src/imports/workspace.ts +76 -0
- package/src/index.ts +3 -0
- package/src/inventory/collect.ts +6 -6
- package/src/manifests/sorted.ts +46 -0
- package/src/report/guard.ts +2 -2
- package/src/report/render.ts +9 -9
- package/src/report/stale.ts +3 -3
- package/src/rule.ts +13 -12
- package/src/rules/biome.ts +47 -0
- package/src/rules/built-in.ts +12 -0
- package/src/rules/comments/kinds.ts +11 -21
- package/src/rules/comments/max-per-file.ts +13 -13
- package/src/rules/comments/no-banner.ts +2 -2
- package/src/rules/comments/no-environment-pragma.ts +9 -0
- package/src/rules/comments/no-jsdoc.ts +3 -3
- package/src/rules/comments/no-line-reference.ts +2 -2
- package/src/rules/comments/no-pr-reference.ts +6 -6
- package/src/rules/comments/no-todo.ts +2 -2
- package/src/rules/comments/pattern-rule.ts +6 -6
- package/src/rules/comments/scan.ts +13 -13
- package/src/rules/imports/cycles.ts +44 -0
- package/src/rules/imports/fences.ts +27 -0
- package/src/rules/imports/resolvable.ts +23 -0
- package/src/rules/manifests-sorted.ts +23 -0
- package/src/rules/max-lines.ts +6 -6
- package/src/rules/suppressions/biome/configs.ts +12 -12
- package/src/rules/suppressions/biome/declarations.ts +7 -7
- package/src/rules/suppressions/biome/json.ts +3 -3
- package/src/rules/suppressions/biome/resolve.ts +3 -3
- package/src/rules/suppressions/biome/weakenings.ts +18 -19
- package/src/rules/suppressions/biome-overrides.ts +7 -7
- package/src/rules/suppressions/directives.ts +12 -12
- package/src/rules/suppressions/no-double-cast.ts +4 -4
- package/src/rules/suppressions/no-inline.ts +13 -37
- package/src/rules/suppressions/stylesheet-comments.ts +4 -4
- package/src/rules/syntax.ts +4 -4
- package/src/standard-schema.ts +1 -1
- package/src/vitest.ts +22 -0
- package/tsconfig/base.json +21 -21
- package/tsconfig/package.json +2 -2
package/src/cli/args.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { WarningDetail } from "../report/render.ts";
|
|
|
4
4
|
|
|
5
5
|
export type Command =
|
|
6
6
|
| { readonly _tag: "Lint"; readonly config: string | undefined; readonly warnings: WarningDetail }
|
|
7
|
-
| { readonly _tag: "
|
|
7
|
+
| { readonly _tag: "Fix"; readonly config: string | undefined }
|
|
8
|
+
| { readonly _tag: "BaselineWrite"; readonly config: string | undefined; readonly rules: readonly string[] }
|
|
8
9
|
| { readonly _tag: "BaselinePrune"; readonly config: string | undefined; readonly against: string | undefined }
|
|
9
10
|
| { readonly _tag: "BaselineCheck"; readonly config: string | undefined; readonly against: string | undefined }
|
|
10
11
|
| { readonly _tag: "BaselineTighten"; readonly config: string | undefined; readonly staged: boolean }
|
|
@@ -15,6 +16,7 @@ export type Parsed = { readonly _tag: "Parsed"; readonly command: Command } | {
|
|
|
15
16
|
|
|
16
17
|
export const USAGE = `Usage:
|
|
17
18
|
quality lint [--config <file>] [--warnings summary|all]
|
|
19
|
+
quality fix [--config <file>]
|
|
18
20
|
quality baseline write [--config <file>] [--rule <id>]...
|
|
19
21
|
quality baseline prune [--config <file>] [--against <ref>]
|
|
20
22
|
quality baseline tighten [--config <file>] [--staged]
|
|
@@ -23,6 +25,7 @@ export const USAGE = `Usage:
|
|
|
23
25
|
|
|
24
26
|
lint Run every rule. Exits 1 on an error-level violation, a file over its baseline, a stale registry entry
|
|
25
27
|
or a baseline entry for a rule that is off or unknown.
|
|
28
|
+
fix Apply Biome's safe fixes, assist actions and formatting.
|
|
26
29
|
baseline write Record current error-level violations. Creates the baseline, or records the named rules again, replacing their entries.
|
|
27
30
|
baseline prune Drop fixed debt, lower entries to what is left and carry entries to files git saw move. Never adds or raises an entry.
|
|
28
31
|
baseline tighten Prune only the entries of files changed since HEAD, or with --staged, in the index.
|
|
@@ -51,15 +54,16 @@ type Values = ReturnType<typeof parseArgs<{ options: typeof OPTIONS; allowPositi
|
|
|
51
54
|
|
|
52
55
|
type Option = Exclude<keyof Values, "config" | "help">;
|
|
53
56
|
|
|
54
|
-
const COMMAND_OPTIONS:
|
|
57
|
+
const COMMAND_OPTIONS: readonly Option[] = ["against", "from", "rule", "staged", "warnings"];
|
|
55
58
|
|
|
56
|
-
const ACCEPTS: Readonly<Record<string,
|
|
57
|
-
lint: ["warnings"],
|
|
58
|
-
"baseline write": ["rule"],
|
|
59
|
-
"baseline prune": ["against"],
|
|
60
|
-
"baseline tighten": ["staged"],
|
|
59
|
+
const ACCEPTS: Readonly<Record<string, readonly Option[]>> = {
|
|
61
60
|
"baseline check": ["against"],
|
|
62
61
|
"baseline migrate": ["from"],
|
|
62
|
+
"baseline prune": ["against"],
|
|
63
|
+
"baseline tighten": ["staged"],
|
|
64
|
+
"baseline write": ["rule"],
|
|
65
|
+
fix: [],
|
|
66
|
+
lint: ["warnings"],
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
const misplaced = (name: string, values: Values): string | undefined => {
|
|
@@ -80,6 +84,8 @@ const commandFor = (name: string, values: Values): Parsed => {
|
|
|
80
84
|
? parsed({ _tag: "Lint", config, warnings })
|
|
81
85
|
: usage(`--warnings takes summary or all, not "${warnings}".`);
|
|
82
86
|
}
|
|
87
|
+
case "fix":
|
|
88
|
+
return parsed({ _tag: "Fix", config });
|
|
83
89
|
case "baseline write":
|
|
84
90
|
return parsed({ _tag: "BaselineWrite", config, rules: values.rule ?? [] });
|
|
85
91
|
case "baseline prune":
|
|
@@ -93,7 +99,7 @@ const commandFor = (name: string, values: Values): Parsed => {
|
|
|
93
99
|
}
|
|
94
100
|
};
|
|
95
101
|
|
|
96
|
-
export const parseCommand = (args:
|
|
102
|
+
export const parseCommand = (args: readonly string[]): Parsed => {
|
|
97
103
|
const result = Result.try({
|
|
98
104
|
catch: (error) => (error instanceof Error ? error.message : String(error)),
|
|
99
105
|
try: () => parseArgs({ allowPositionals: true, args: [...args], options: OPTIONS, strict: true }),
|
package/src/cli/baseline.ts
CHANGED
|
@@ -9,24 +9,23 @@ import { SetupFailure } from "../failure.ts";
|
|
|
9
9
|
import { writeText } from "../inventory/filesystem.ts";
|
|
10
10
|
import { plural } from "../report/plural.ts";
|
|
11
11
|
|
|
12
|
-
const save = (session: Session, entries:
|
|
12
|
+
const save = (session: Session, entries: readonly BaselineEntry[]): Effect.Effect<void, SetupFailure, FileSystem.FileSystem> =>
|
|
13
13
|
Effect.mapError(
|
|
14
14
|
writeText(join(session.config.root, session.config.baseline), rewriteBaseline(session.baseline.raw, session.baseline.entries, entries)),
|
|
15
15
|
(failure) => new SetupFailure({ message: failure.message }),
|
|
16
16
|
);
|
|
17
17
|
|
|
18
|
-
const unregistered = (session: Session) =>
|
|
19
|
-
applyRegistry(session.violations, session.registry, session.config.levels, session.config.unregistrable).kept;
|
|
18
|
+
const unregistered = (session: Session) => applyRegistry(session.violations, session.registry, session.config).kept;
|
|
20
19
|
|
|
21
20
|
export const writeBaseline = (
|
|
22
21
|
cwd: string,
|
|
23
22
|
config: string | undefined,
|
|
24
|
-
rules:
|
|
23
|
+
rules: readonly string[],
|
|
25
24
|
): Effect.Effect<void, SetupFailure, FileSystem.FileSystem> =>
|
|
26
25
|
Effect.gen(function* () {
|
|
27
26
|
const session = yield* openSession(cwd, config);
|
|
28
27
|
const existing = session.baseline.raw === undefined ? undefined : session.baseline.entries;
|
|
29
|
-
const adoption = adopt(existing, rules, unregistered(session), session.config
|
|
28
|
+
const adoption = adopt(existing, rules, unregistered(session), session.config);
|
|
30
29
|
if (adoption._tag === "Refused") {
|
|
31
30
|
return yield* new SetupFailure({ message: `baseline write refused:\n${adoption.reasons.map((reason) => ` - ${reason}`).join("\n")}` });
|
|
32
31
|
}
|
|
@@ -57,7 +56,7 @@ export const shrinkBaseline = <Requirements>(
|
|
|
57
56
|
if (session.baseline.raw === undefined) {
|
|
58
57
|
return yield* Console.log(`quality: no baseline at ${session.config.baseline}; nothing to lower.`);
|
|
59
58
|
}
|
|
60
|
-
const pruned = prune(session.baseline.entries, unregistered(session), session.config
|
|
59
|
+
const pruned = prune(session.baseline.entries, unregistered(session), session.config, yield* scopeOf(session));
|
|
61
60
|
if (pruned.removed === 0 && pruned.lowered === 0 && pruned.moved === 0) {
|
|
62
61
|
return yield* Console.log(`quality: ${session.config.baseline} is current.`);
|
|
63
62
|
}
|
package/src/cli/check.ts
CHANGED
|
@@ -17,7 +17,7 @@ const baseEntries = (
|
|
|
17
17
|
config: ResolvedConfig,
|
|
18
18
|
base: Base,
|
|
19
19
|
path: string,
|
|
20
|
-
): Effect.Effect<
|
|
20
|
+
): Effect.Effect<readonly BaselineEntry[], SetupFailure, FileSystem.FileSystem | Git> =>
|
|
21
21
|
Effect.gen(function* () {
|
|
22
22
|
const raw = yield* readAt(config.root, base.commit, path);
|
|
23
23
|
if (raw !== undefined) {
|
package/src/cli/fix.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Console, Effect, type FileSystem } from "effect";
|
|
2
|
+
import { biomeReport } from "../biome/report.ts";
|
|
3
|
+
import { loadConfig } from "../config/load.ts";
|
|
4
|
+
import { SetupFailure } from "../failure.ts";
|
|
5
|
+
import { sortManifests } from "../manifests/sorted.ts";
|
|
6
|
+
import { plural } from "../report/plural.ts";
|
|
7
|
+
|
|
8
|
+
export function fix(cwd: string, config: string | undefined): Effect.Effect<void, SetupFailure, FileSystem.FileSystem> {
|
|
9
|
+
return Effect.gen(function* () {
|
|
10
|
+
const { root } = yield* loadConfig(cwd, config);
|
|
11
|
+
const sorted = yield* Effect.mapError(sortManifests(root), (failure) => new SetupFailure({ message: failure.message }));
|
|
12
|
+
yield* Console.log(`quality: sort-package-json rewrote ${plural(sorted, "manifest")}.`);
|
|
13
|
+
const report = yield* Effect.tryPromise({
|
|
14
|
+
catch: (cause) => new SetupFailure({ message: cause instanceof Error ? cause.message : String(cause) }),
|
|
15
|
+
try: () => biomeReport(root, ["check", "--write"]),
|
|
16
|
+
});
|
|
17
|
+
yield* Console.log(`quality: Biome rewrote ${plural(report.summary.changed, "file")}.`);
|
|
18
|
+
});
|
|
19
|
+
}
|
package/src/cli/lint.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const lint = (
|
|
|
11
11
|
): Effect.Effect<void, SetupFailure | GateFailed, FileSystem.FileSystem> =>
|
|
12
12
|
Effect.gen(function* () {
|
|
13
13
|
const session = yield* openSession(cwd, config);
|
|
14
|
-
const outcome = evaluate(session.violations, session.registry, session.baseline.entries, session.config
|
|
14
|
+
const outcome = evaluate(session.violations, session.registry, session.baseline.entries, session.config);
|
|
15
15
|
yield* Console.log(
|
|
16
16
|
render(outcome, {
|
|
17
17
|
baseline: session.config.baseline,
|
package/src/cli/run-main.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import process from "node:process";
|
|
1
2
|
import * as NodeChildProcessSpawner from "@effect/platform-node/NodeChildProcessSpawner";
|
|
2
3
|
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
3
4
|
import * as NodePath from "@effect/platform-node/NodePath";
|
|
@@ -20,11 +21,15 @@ const explain = (cause: Cause.Cause<unknown>): Effect.Effect<void> => {
|
|
|
20
21
|
const exitCodeOf = (error: unknown): number =>
|
|
21
22
|
typeof error === "object" && error !== null && Runtime.errorExitCode in error ? Runtime.getErrorExitCode(error) : COULD_NOT_RUN;
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
+
const exitCode = (exit: Exit.Exit<unknown, unknown>): number => {
|
|
24
25
|
if (Exit.isSuccess(exit)) {
|
|
25
|
-
return
|
|
26
|
+
return 0;
|
|
26
27
|
}
|
|
27
|
-
return
|
|
28
|
+
return Cause.hasInterruptsOnly(exit.cause) ? INTERRUPTED : exitCodeOf(Cause.squash(exit.cause));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const teardown: Runtime.Teardown = (exit, onExit) => {
|
|
32
|
+
process.stdout.write("", () => process.stderr.write("", () => onExit(exitCode(exit))));
|
|
28
33
|
};
|
|
29
34
|
|
|
30
35
|
const services = NodeChildProcessSpawner.layer.pipe(Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)));
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Console, Effect } from "effect";
|
|
|
4
4
|
import { parseCommand, USAGE } from "./cli/args.ts";
|
|
5
5
|
import { writeBaseline } from "./cli/baseline.ts";
|
|
6
6
|
import { checkBaseline } from "./cli/check.ts";
|
|
7
|
+
import { fix } from "./cli/fix.ts";
|
|
7
8
|
import { lint } from "./cli/lint.ts";
|
|
8
9
|
import { migrateBaseline } from "./cli/migrate.ts";
|
|
9
10
|
import { runMain } from "./cli/run-main.ts";
|
|
@@ -22,6 +23,8 @@ const program = Effect.gen(function* () {
|
|
|
22
23
|
return yield* Console.log(USAGE);
|
|
23
24
|
case "Lint":
|
|
24
25
|
return yield* lint(process.cwd(), command.config, command.warnings);
|
|
26
|
+
case "Fix":
|
|
27
|
+
return yield* fix(process.cwd(), command.config);
|
|
25
28
|
case "BaselineWrite":
|
|
26
29
|
return yield* writeBaseline(process.cwd(), command.config, command.rules);
|
|
27
30
|
case "BaselinePrune":
|
package/src/config/decode.ts
CHANGED
|
@@ -3,14 +3,14 @@ import { type Decoded, decodeWith } from "../decoded.ts";
|
|
|
3
3
|
import type { Rule } from "../rule.ts";
|
|
4
4
|
|
|
5
5
|
const isRule = (value: unknown): value is Rule =>
|
|
6
|
-
typeof value === "object"
|
|
7
|
-
value !== null
|
|
8
|
-
"id" in value
|
|
9
|
-
typeof value.id === "string"
|
|
10
|
-
"description" in value
|
|
11
|
-
typeof value.description === "string"
|
|
12
|
-
"configure" in value
|
|
13
|
-
typeof value.configure === "function";
|
|
6
|
+
typeof value === "object"
|
|
7
|
+
&& value !== null
|
|
8
|
+
&& "id" in value
|
|
9
|
+
&& typeof value.id === "string"
|
|
10
|
+
&& "description" in value
|
|
11
|
+
&& typeof value.description === "string"
|
|
12
|
+
&& "configure" in value
|
|
13
|
+
&& typeof value.configure === "function";
|
|
14
14
|
|
|
15
15
|
const Level = Schema.Literals(["error", "warn", "off"]);
|
|
16
16
|
|
|
@@ -19,14 +19,14 @@ const Setting = Schema.Union([Level, Schema.Struct({ level: Schema.optionalKey(L
|
|
|
19
19
|
const LocalRule = Schema.declare(isRule, { expected: "a rule made with defineRule" });
|
|
20
20
|
|
|
21
21
|
const ConfigInput = Schema.Struct({
|
|
22
|
-
|
|
22
|
+
adopt: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
23
|
+
baseline: Schema.optionalKey(Schema.String),
|
|
23
24
|
exclude: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
24
25
|
extensions: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
25
|
-
registry: Schema.optionalKey(Schema.String),
|
|
26
|
-
baseline: Schema.optionalKey(Schema.String),
|
|
27
|
-
adopt: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
28
26
|
local: Schema.optionalKey(Schema.Array(LocalRule)),
|
|
27
|
+
registry: Schema.optionalKey(Schema.String),
|
|
29
28
|
rules: Schema.optionalKey(Schema.Record(Schema.String, Setting)),
|
|
29
|
+
sources: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
export type ConfigInput = typeof ConfigInput.Type;
|
package/src/config/load.ts
CHANGED
|
@@ -11,11 +11,11 @@ import { type ResolvedRules, resolveRules } from "./resolve.ts";
|
|
|
11
11
|
const DEFAULT_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
12
12
|
|
|
13
13
|
export interface ResolvedConfig extends ResolvedRules, InventoryScope {
|
|
14
|
-
readonly
|
|
14
|
+
readonly adopt: readonly string[];
|
|
15
|
+
readonly baseline: string;
|
|
15
16
|
readonly file: string;
|
|
16
17
|
readonly registry: string;
|
|
17
|
-
readonly
|
|
18
|
-
readonly adopt: ReadonlyArray<string>;
|
|
18
|
+
readonly root: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const messageOf = (cause: unknown): string => (cause instanceof Error ? cause.message : String(cause));
|
|
@@ -23,8 +23,8 @@ const messageOf = (cause: unknown): string => (cause instanceof Error ? cause.me
|
|
|
23
23
|
const importDefault = (file: string): Effect.Effect<unknown, SetupFailure> =>
|
|
24
24
|
Effect.flatMap(
|
|
25
25
|
Effect.tryPromise({
|
|
26
|
-
try: async (): Promise<unknown> => import(pathToFileURL(file).href),
|
|
27
26
|
catch: (cause) => new SetupFailure({ message: `cannot load ${file}: ${messageOf(cause)}` }),
|
|
27
|
+
try: async (): Promise<unknown> => import(pathToFileURL(file).href),
|
|
28
28
|
}),
|
|
29
29
|
(loaded) =>
|
|
30
30
|
typeof loaded === "object" && loaded !== null && "default" in loaded
|
package/src/config/resolve.ts
CHANGED
|
@@ -2,14 +2,13 @@ import { Effect } from "effect";
|
|
|
2
2
|
import type { Level } from "../config.ts";
|
|
3
3
|
import type { Decoded } from "../decoded.ts";
|
|
4
4
|
import type { ActiveRule } from "../engine/run-rules.ts";
|
|
5
|
+
import { covers, type RuleIndex } from "../engine/violation.ts";
|
|
5
6
|
import type { Rule } from "../rule.ts";
|
|
6
7
|
import { builtInRules } from "../rules/built-in.ts";
|
|
7
8
|
import type { ConfigInput } from "./decode.ts";
|
|
8
9
|
|
|
9
|
-
export interface ResolvedRules {
|
|
10
|
-
readonly active:
|
|
11
|
-
readonly levels: ReadonlyMap<string, Level>;
|
|
12
|
-
readonly unregistrable: ReadonlySet<string>;
|
|
10
|
+
export interface ResolvedRules extends RuleIndex {
|
|
11
|
+
readonly active: readonly ActiveRule[];
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
type Setting = NonNullable<ConfigInput["rules"]>[string];
|
|
@@ -20,7 +19,7 @@ const levelOf = (setting: Setting | undefined): Level => (typeof setting === "st
|
|
|
20
19
|
|
|
21
20
|
const optionsOf = (setting: Setting | undefined): unknown => (typeof setting === "object" ? setting.options : undefined);
|
|
22
21
|
|
|
23
|
-
const duplicateIssues = (rules:
|
|
22
|
+
const duplicateIssues = (rules: readonly Rule[]): readonly string[] =>
|
|
24
23
|
rules.flatMap((rule, index) =>
|
|
25
24
|
rules.findIndex((other) => other.id === rule.id) === index ? [] : [`local: rule id "${rule.id}" is already defined`],
|
|
26
25
|
);
|
|
@@ -35,7 +34,7 @@ const activate = (rule: Rule, setting: Setting | undefined): Effect.Effect<Decod
|
|
|
35
34
|
(configured): Decoded<ActiveRule | undefined> =>
|
|
36
35
|
configured._tag === "Invalid"
|
|
37
36
|
? { _tag: "Invalid", issues: configured.issues.map((issue) => `rules.${rule.id}.options: ${issue}`) }
|
|
38
|
-
: { _tag: "Valid", value: { check: configured.check, description: rule.description, id: rule.id, level } },
|
|
37
|
+
: { _tag: "Valid", value: { check: configured.check, description: rule.description, family: rule.family === true, id: rule.id, level } },
|
|
39
38
|
),
|
|
40
39
|
Effect.catch((failure: string) =>
|
|
41
40
|
Effect.succeed<Decoded<ActiveRule | undefined>>({ _tag: "Invalid", issues: [`rules.${rule.id}.options: validation threw: ${failure}`] }),
|
|
@@ -45,12 +44,17 @@ const activate = (rule: Rule, setting: Setting | undefined): Effect.Effect<Decod
|
|
|
45
44
|
|
|
46
45
|
export const resolveRules = (config: ConfigInput): Effect.Effect<Decoded<ResolvedRules>> =>
|
|
47
46
|
Effect.gen(function* () {
|
|
48
|
-
const rules:
|
|
47
|
+
const rules: readonly Rule[] = [...builtInRules, ...(config.local ?? [])];
|
|
49
48
|
const settings = config.rules ?? {};
|
|
50
49
|
const known = new Set(rules.map((rule) => rule.id));
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const families = new Set(rules.filter((rule) => rule.family === true).map((rule) => rule.id));
|
|
51
|
+
const adoptable = (id: string): boolean => known.has(id) || [...families].some((family) => covers(family, id));
|
|
52
|
+
const unknown = [
|
|
53
|
+
...Object.keys(settings)
|
|
54
|
+
.filter((id) => !known.has(id))
|
|
55
|
+
.map((id) => `rules.${id}`),
|
|
56
|
+
...(config.adopt ?? []).filter((id) => !adoptable(id)).map((id) => `adopt.${id}`),
|
|
57
|
+
].map((path) => `${path}: no built-in or local rule has this id`);
|
|
54
58
|
const activated = yield* Effect.forEach(rules, (rule) => activate(rule, settings[rule.id]));
|
|
55
59
|
const issues = [...duplicateIssues(rules), ...unknown, ...activated.flatMap((result) => (result._tag === "Invalid" ? result.issues : []))];
|
|
56
60
|
if (issues.length > 0) {
|
|
@@ -59,5 +63,5 @@ export const resolveRules = (config: ConfigInput): Effect.Effect<Decoded<Resolve
|
|
|
59
63
|
const active = activated.flatMap((result) => (result._tag === "Valid" && result.value !== undefined ? [result.value] : []));
|
|
60
64
|
const levels = new Map(rules.map((rule) => [rule.id, levelOf(settings[rule.id])]));
|
|
61
65
|
const unregistrable = new Set(rules.filter((rule) => rule.registrable === false).map((rule) => rule.id));
|
|
62
|
-
return { _tag: "Valid", value: { active, levels, unregistrable } };
|
|
66
|
+
return { _tag: "Valid", value: { active, families, levels, unregistrable } };
|
|
63
67
|
});
|
package/src/config.ts
CHANGED
|
@@ -7,23 +7,23 @@ export type RuleSetting<Input> = Level | { readonly level?: Level; readonly opti
|
|
|
7
7
|
|
|
8
8
|
type InputOf<Candidate> = Candidate extends Rule<string, infer Input> ? Input : never;
|
|
9
9
|
|
|
10
|
-
export type RuleSettings<Rules extends
|
|
10
|
+
export type RuleSettings<Rules extends readonly Rule[]> = {
|
|
11
11
|
readonly [Candidate in Rules[number] as Candidate["id"]]?: RuleSetting<InputOf<Candidate>>;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export type BuiltInRules = typeof builtInRules;
|
|
15
15
|
|
|
16
|
-
type IdOf<Rules extends
|
|
16
|
+
type IdOf<Rules extends readonly Rule[]> = Rules[number]["id"];
|
|
17
17
|
|
|
18
|
-
export interface QualityConfig<Local extends
|
|
19
|
-
readonly
|
|
20
|
-
readonly exclude?: ReadonlyArray<string>;
|
|
21
|
-
readonly extensions?: ReadonlyArray<string>;
|
|
22
|
-
readonly registry?: string;
|
|
18
|
+
export interface QualityConfig<Local extends readonly Rule[] = readonly Rule[]> {
|
|
19
|
+
readonly adopt?: readonly IdOf<readonly [...BuiltInRules, ...Local]>[];
|
|
23
20
|
readonly baseline?: string;
|
|
24
|
-
readonly
|
|
21
|
+
readonly exclude?: readonly string[];
|
|
22
|
+
readonly extensions?: readonly string[];
|
|
25
23
|
readonly local?: Local;
|
|
24
|
+
readonly registry?: string;
|
|
26
25
|
readonly rules?: RuleSettings<readonly [...BuiltInRules, ...Local]>;
|
|
26
|
+
readonly sources?: readonly string[];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export const defineConfig = <const Local extends
|
|
29
|
+
export const defineConfig = <const Local extends readonly Rule[] = readonly []>(config: QualityConfig<Local>): QualityConfig<Local> => config;
|
package/src/decoded.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Effect } from "effect";
|
|
|
2
2
|
import { SetupFailure } from "./failure.ts";
|
|
3
3
|
import { describeIssue, type StandardSchemaV1 } from "./standard-schema.ts";
|
|
4
4
|
|
|
5
|
-
export type Decoded<Value> = { readonly _tag: "Valid"; readonly value: Value } | { readonly _tag: "Invalid"; readonly issues:
|
|
5
|
+
export type Decoded<Value> = { readonly _tag: "Valid"; readonly value: Value } | { readonly _tag: "Invalid"; readonly issues: readonly string[] };
|
|
6
6
|
|
|
7
7
|
export const decodeWith = async <Output>(schema: StandardSchemaV1<unknown, Output>, value: unknown): Promise<Decoded<Output>> => {
|
|
8
8
|
const result = await schema["~standard"].validate(value);
|
|
@@ -8,8 +8,8 @@ import { SetupFailure } from "../failure.ts";
|
|
|
8
8
|
import { readOptionalText } from "../inventory/filesystem.ts";
|
|
9
9
|
|
|
10
10
|
export interface BaselineFile {
|
|
11
|
+
readonly entries: readonly BaselineEntry[];
|
|
11
12
|
readonly raw: string | undefined;
|
|
12
|
-
readonly entries: ReadonlyArray<BaselineEntry>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const readInput = (root: string, path: string): Effect.Effect<string | undefined, SetupFailure, FileSystem.FileSystem> =>
|
package/src/engine/evaluate.ts
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
import { applyBaseline, type Regression, type StaleBaselineEntry } from "../baseline/compare.ts";
|
|
2
2
|
import type { BaselineEntry } from "../baseline/format.ts";
|
|
3
|
-
import type { Level } from "../config.ts";
|
|
4
3
|
import { applyRegistry, type RegistryEntry, type StaleRegistryEntry } from "../exceptions/registry.ts";
|
|
5
|
-
import { byLocation, type Violation } from "./violation.ts";
|
|
4
|
+
import { byLocation, type RuleIndex, type Violation } from "./violation.ts";
|
|
6
5
|
|
|
7
6
|
export interface Outcome {
|
|
8
|
-
readonly errors: ReadonlyArray<Violation>;
|
|
9
|
-
readonly warnings: ReadonlyArray<Violation>;
|
|
10
|
-
readonly regressions: ReadonlyArray<Regression>;
|
|
11
|
-
readonly staleBaseline: ReadonlyArray<StaleBaselineEntry>;
|
|
12
|
-
readonly looseBaseline: ReadonlyArray<StaleBaselineEntry>;
|
|
13
|
-
readonly staleRegistry: ReadonlyArray<StaleRegistryEntry>;
|
|
14
7
|
readonly baselined: number;
|
|
8
|
+
readonly errors: readonly Violation[];
|
|
9
|
+
readonly looseBaseline: readonly StaleBaselineEntry[];
|
|
15
10
|
readonly registered: number;
|
|
11
|
+
readonly regressions: readonly Regression[];
|
|
12
|
+
readonly staleBaseline: readonly StaleBaselineEntry[];
|
|
13
|
+
readonly staleRegistry: readonly StaleRegistryEntry[];
|
|
14
|
+
readonly warnings: readonly Violation[];
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export const evaluate = (
|
|
19
|
-
violations:
|
|
20
|
-
registry:
|
|
21
|
-
baseline:
|
|
22
|
-
|
|
23
|
-
unregistrable: ReadonlySet<string>,
|
|
18
|
+
violations: readonly Violation[],
|
|
19
|
+
registry: readonly RegistryEntry[],
|
|
20
|
+
baseline: readonly BaselineEntry[],
|
|
21
|
+
rules: RuleIndex,
|
|
24
22
|
): Outcome => {
|
|
25
|
-
const registered = applyRegistry(violations, registry,
|
|
26
|
-
const baselined = applyBaseline(registered.kept, baseline,
|
|
23
|
+
const registered = applyRegistry(violations, registry, rules);
|
|
24
|
+
const baselined = applyBaseline(registered.kept, baseline, rules);
|
|
27
25
|
const kept = [...baselined.kept].sort(byLocation);
|
|
28
26
|
return {
|
|
29
27
|
baselined: baselined.baselined,
|
|
30
28
|
errors: kept.filter((violation) => violation.level === "error"),
|
|
31
29
|
looseBaseline: baselined.loose,
|
|
32
|
-
regressions: baselined.regressions,
|
|
33
30
|
registered: registered.registered,
|
|
31
|
+
regressions: baselined.regressions,
|
|
34
32
|
staleBaseline: baselined.stale,
|
|
35
33
|
staleRegistry: registered.stale,
|
|
36
34
|
warnings: kept.filter((violation) => violation.level === "warn"),
|
package/src/engine/run-rules.ts
CHANGED
|
@@ -6,34 +6,35 @@ import type { Finding, Findings, RuleInputs } from "../rule.ts";
|
|
|
6
6
|
import type { ActiveLevel, Violation } from "./violation.ts";
|
|
7
7
|
|
|
8
8
|
export interface ActiveRule {
|
|
9
|
-
readonly
|
|
9
|
+
readonly check: (inputs: RuleInputs) => Promise<Findings>;
|
|
10
10
|
readonly description: string;
|
|
11
|
+
readonly family: boolean;
|
|
12
|
+
readonly id: string;
|
|
11
13
|
readonly level: ActiveLevel;
|
|
12
|
-
readonly check: (inputs: RuleInputs) => Promise<Findings>;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const messageOf = (cause: unknown): string => (cause instanceof Error ? cause.message : String(cause));
|
|
16
17
|
|
|
17
18
|
// Registry and baseline entries match on the path, so every finding names its file the same way.
|
|
18
|
-
const repositoryPath = (root: string, file: string): string => posix(isAbsolute(file) ? relative(root, file) : file).replace(/^(\.\/)
|
|
19
|
+
const repositoryPath = (root: string, file: string): string => posix(isAbsolute(file) ? relative(root, file) : file).replace(/^(\.\/)+/u, "");
|
|
19
20
|
|
|
20
21
|
const violationOf = (rule: ActiveRule, root: string, finding: Finding): Violation => ({
|
|
21
22
|
...finding,
|
|
22
23
|
file: repositoryPath(root, finding.file),
|
|
23
24
|
level: rule.level,
|
|
24
|
-
rule: rule.id,
|
|
25
|
+
rule: rule.family && finding.subject !== undefined ? `${rule.id}/${finding.subject}` : rule.id,
|
|
25
26
|
});
|
|
26
27
|
|
|
27
|
-
const runRule = (rule: ActiveRule, inputs: RuleInputs): Effect.Effect<
|
|
28
|
+
const runRule = (rule: ActiveRule, inputs: RuleInputs): Effect.Effect<readonly Violation[], SetupFailure> =>
|
|
28
29
|
Effect.map(
|
|
29
30
|
Effect.tryPromise({
|
|
30
|
-
try: () => rule.check(inputs),
|
|
31
31
|
catch: (cause) => new SetupFailure({ message: `rule ${rule.id} failed: ${messageOf(cause)}` }),
|
|
32
|
+
try: () => rule.check(inputs),
|
|
32
33
|
}),
|
|
33
34
|
(findings) => findings.map((finding) => violationOf(rule, inputs.root, finding)),
|
|
34
35
|
);
|
|
35
36
|
|
|
36
|
-
export const runRules = (rules:
|
|
37
|
+
export const runRules = (rules: readonly ActiveRule[], inputs: RuleInputs): Effect.Effect<readonly Violation[], SetupFailure> =>
|
|
37
38
|
Effect.map(
|
|
38
39
|
Effect.forEach(rules, (rule) => runRule(rule, inputs), { concurrency: "unbounded" }),
|
|
39
40
|
(results) => results.flat(),
|
package/src/engine/session.ts
CHANGED
|
@@ -11,11 +11,11 @@ import { runRules } from "./run-rules.ts";
|
|
|
11
11
|
import type { Violation } from "./violation.ts";
|
|
12
12
|
|
|
13
13
|
export interface Session {
|
|
14
|
+
readonly baseline: BaselineFile;
|
|
14
15
|
readonly config: ResolvedConfig;
|
|
15
16
|
readonly inventory: Inventory;
|
|
16
|
-
readonly
|
|
17
|
-
readonly
|
|
18
|
-
readonly baseline: BaselineFile;
|
|
17
|
+
readonly registry: readonly RegistryEntry[];
|
|
18
|
+
readonly violations: readonly Violation[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const setup = <Value, Requirements>(
|
package/src/engine/violation.ts
CHANGED
|
@@ -4,8 +4,8 @@ import type { Finding } from "../rule.ts";
|
|
|
4
4
|
export type ActiveLevel = "error" | "warn";
|
|
5
5
|
|
|
6
6
|
export interface Violation extends Finding {
|
|
7
|
-
readonly rule: string;
|
|
8
7
|
readonly level: ActiveLevel;
|
|
8
|
+
readonly rule: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export const keyOf = (rule: string, file: string): string => `${rule}\u0000${file}`;
|
|
@@ -13,7 +13,7 @@ export const keyOf = (rule: string, file: string): string => `${rule}\u0000${fil
|
|
|
13
13
|
export const byLocation = (left: Violation, right: Violation): number =>
|
|
14
14
|
left.rule.localeCompare(right.rule) || left.file.localeCompare(right.file) || (left.line ?? 0) - (right.line ?? 0);
|
|
15
15
|
|
|
16
|
-
export const groupBy = <Item>(items:
|
|
16
|
+
export const groupBy = <Item>(items: readonly Item[], key: (item: Item) => string): ReadonlyMap<string, readonly Item[]> => {
|
|
17
17
|
const groups = new Map<string, Item[]>();
|
|
18
18
|
for (const item of items) {
|
|
19
19
|
const group = groups.get(key(item));
|
|
@@ -26,8 +26,22 @@ export const groupBy = <Item>(items: ReadonlyArray<Item>, key: (item: Item) => s
|
|
|
26
26
|
return groups;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
export
|
|
30
|
-
|
|
29
|
+
export interface RuleIndex {
|
|
30
|
+
readonly families: ReadonlySet<string>;
|
|
31
|
+
readonly levels: ReadonlyMap<string, Level>;
|
|
32
|
+
readonly unregistrable: ReadonlySet<string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const covers = (name: string, rule: string): boolean => rule === name || rule.startsWith(`${name}/`);
|
|
36
|
+
|
|
37
|
+
const ownerOf = (index: RuleIndex, rule: string): string => [...index.families].find((family) => covers(family, rule)) ?? rule;
|
|
38
|
+
|
|
39
|
+
export const levelOf = (index: RuleIndex, rule: string): Level | undefined => index.levels.get(ownerOf(index, rule));
|
|
40
|
+
|
|
41
|
+
export const registrable = (index: RuleIndex, rule: string): boolean => !index.unregistrable.has(ownerOf(index, rule));
|
|
42
|
+
|
|
43
|
+
export const unusedEntryProblem = (index: RuleIndex, rule: string, unused: string): string => {
|
|
44
|
+
const level = levelOf(index, rule);
|
|
31
45
|
if (level === undefined) {
|
|
32
46
|
return "names no known rule";
|
|
33
47
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
|
-
import type { Level } from "../config.ts";
|
|
3
2
|
import { type Decoded, decodeWith } from "../decoded.ts";
|
|
4
|
-
import { unusedEntryProblem, type Violation } from "../engine/violation.ts";
|
|
3
|
+
import { type RuleIndex, registrable, unusedEntryProblem, type Violation } from "../engine/violation.ts";
|
|
5
4
|
|
|
6
5
|
const RegistryEntry = Schema.Struct({
|
|
7
|
-
rule: Schema.NonEmptyString,
|
|
8
6
|
file: Schema.NonEmptyString,
|
|
7
|
+
reason: Schema.String.check(Schema.isPattern(/\S/u, { expected: "a reason that says why the exception is permanent" })),
|
|
8
|
+
rule: Schema.NonEmptyString,
|
|
9
9
|
subject: Schema.optionalKey(Schema.NonEmptyString),
|
|
10
|
-
reason: Schema.String.check(Schema.isPattern(/\S/, { expected: "a reason that says why the exception is permanent" })),
|
|
11
10
|
});
|
|
12
11
|
|
|
13
12
|
export type RegistryEntry = typeof RegistryEntry.Type;
|
|
@@ -16,7 +15,7 @@ const standard = Schema.toStandardSchemaV1(Schema.fromJsonString(Schema.Array(Re
|
|
|
16
15
|
parseOptions: { errors: "all", onExcessProperty: "error" },
|
|
17
16
|
});
|
|
18
17
|
|
|
19
|
-
export const decodeRegistry = (raw: string | undefined): Promise<Decoded<
|
|
18
|
+
export const decodeRegistry = (raw: string | undefined): Promise<Decoded<readonly RegistryEntry[]>> =>
|
|
20
19
|
raw === undefined ? Promise.resolve({ _tag: "Valid", value: [] }) : decodeWith(standard, raw);
|
|
21
20
|
|
|
22
21
|
export interface StaleRegistryEntry {
|
|
@@ -25,9 +24,9 @@ export interface StaleRegistryEntry {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export interface RegistryCheck {
|
|
28
|
-
readonly kept:
|
|
27
|
+
readonly kept: readonly Violation[];
|
|
29
28
|
readonly registered: number;
|
|
30
|
-
readonly stale:
|
|
29
|
+
readonly stale: readonly StaleRegistryEntry[];
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
const covers = (entry: RegistryEntry, violation: Violation): boolean =>
|
|
@@ -35,18 +34,13 @@ const covers = (entry: RegistryEntry, violation: Violation): boolean =>
|
|
|
35
34
|
|
|
36
35
|
const UNREGISTRABLE = "names a rule that takes no exceptions. Fix the code, or baseline the violations while the repository adopts the rule";
|
|
37
36
|
|
|
38
|
-
const coveringEntry = (entries:
|
|
37
|
+
const coveringEntry = (entries: readonly RegistryEntry[], violation: Violation): RegistryEntry | undefined => {
|
|
39
38
|
const matching = entries.filter((entry) => covers(entry, violation));
|
|
40
39
|
return matching.find((entry) => entry.subject !== undefined) ?? matching[0];
|
|
41
40
|
};
|
|
42
41
|
|
|
43
|
-
export const applyRegistry = (
|
|
44
|
-
|
|
45
|
-
entries: ReadonlyArray<RegistryEntry>,
|
|
46
|
-
levels: ReadonlyMap<string, Level>,
|
|
47
|
-
unregistrable: ReadonlySet<string>,
|
|
48
|
-
): RegistryCheck => {
|
|
49
|
-
const usable = entries.filter((entry) => !unregistrable.has(entry.rule));
|
|
42
|
+
export const applyRegistry = (violations: readonly Violation[], entries: readonly RegistryEntry[], rules: RuleIndex): RegistryCheck => {
|
|
43
|
+
const usable = entries.filter((entry) => registrable(rules, entry.rule));
|
|
50
44
|
const used = new Set<RegistryEntry>();
|
|
51
45
|
const kept = violations.filter((violation) => {
|
|
52
46
|
const entry = coveringEntry(usable, violation);
|
|
@@ -59,7 +53,7 @@ export const applyRegistry = (
|
|
|
59
53
|
.filter((entry) => !used.has(entry))
|
|
60
54
|
.map((entry) => ({
|
|
61
55
|
entry,
|
|
62
|
-
problem:
|
|
56
|
+
problem: registrable(rules, entry.rule) ? unusedEntryProblem(rules, entry.rule, "matches no violation") : UNREGISTRABLE,
|
|
63
57
|
}));
|
|
64
58
|
return { kept, registered: violations.length - kept.length, stale };
|
|
65
59
|
};
|
package/src/git/base.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { SetupFailure } from "../failure.ts";
|
|
|
3
3
|
import { type Git, git } from "./command.ts";
|
|
4
4
|
|
|
5
5
|
export interface Base {
|
|
6
|
-
readonly ref: string;
|
|
7
6
|
readonly commit: string;
|
|
7
|
+
readonly ref: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const FALLBACK_BRANCHES = ["origin/main", "origin/master"];
|
|
@@ -36,7 +36,7 @@ const isShallow = (root: string): Effect.Effect<boolean, SetupFailure, Git> =>
|
|
|
36
36
|
const mergeBase = (root: string, ref: string): Effect.Effect<string | undefined, SetupFailure, Git> =>
|
|
37
37
|
Effect.map(git(root, ["merge-base", "HEAD", ref]), (result) => (result.code === 0 ? result.stdout.trim() : undefined));
|
|
38
38
|
|
|
39
|
-
const remoteOf = (root: string, ref: string): Effect.Effect<
|
|
39
|
+
const remoteOf = (root: string, ref: string): Effect.Effect<readonly string[], SetupFailure, Git> =>
|
|
40
40
|
Effect.gen(function* () {
|
|
41
41
|
const name = (yield* git(root, ["rev-parse", "--symbolic-full-name", ref])).stdout.trim();
|
|
42
42
|
const remotes = (yield* git(root, ["remote"])).stdout.split("\n");
|