@shivaedev/quality 0.3.1 → 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 +46 -0
- package/README.md +187 -30
- 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 +21 -10
- package/dist/rules/suppressions/biome/configs.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/configs.js +58 -22
- package/dist/rules/suppressions/biome/configs.js.map +1 -1
- package/dist/rules/suppressions/biome/declarations.d.ts +18 -0
- package/dist/rules/suppressions/biome/declarations.d.ts.map +1 -0
- package/dist/rules/suppressions/biome/declarations.js +20 -0
- package/dist/rules/suppressions/biome/declarations.js.map +1 -0
- 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.d.ts +3 -0
- package/dist/rules/suppressions/biome/resolve.d.ts.map +1 -0
- package/dist/rules/suppressions/biome/resolve.js +70 -0
- package/dist/rules/suppressions/biome/resolve.js.map +1 -0
- package/dist/rules/suppressions/biome/weakenings.d.ts +5 -5
- package/dist/rules/suppressions/biome/weakenings.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/weakenings.js +41 -33
- 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.d.ts.map +1 -1
- package/dist/rules/suppressions/biome-overrides.js +35 -27
- 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 +48 -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 +99 -30
- package/src/rules/suppressions/biome/declarations.ts +37 -0
- package/src/rules/suppressions/biome/json.ts +3 -3
- package/src/rules/suppressions/biome/resolve.ts +86 -0
- package/src/rules/suppressions/biome/weakenings.ts +64 -48
- package/src/rules/suppressions/biome-overrides.ts +45 -36
- 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 +35 -0
- package/tsconfig/package.json +10 -0
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");
|
package/src/git/command.ts
CHANGED
|
@@ -4,13 +4,13 @@ import { SetupFailure } from "../failure.ts";
|
|
|
4
4
|
|
|
5
5
|
export interface GitResult {
|
|
6
6
|
readonly code: number;
|
|
7
|
-
readonly stdout: string;
|
|
8
7
|
readonly stderr: string;
|
|
8
|
+
readonly stdout: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export type Git = ChildProcessSpawner.ChildProcessSpawner;
|
|
12
12
|
|
|
13
|
-
export const git = (cwd: string, args:
|
|
13
|
+
export const git = (cwd: string, args: readonly string[], env?: Readonly<Record<string, string>>): Effect.Effect<GitResult, SetupFailure, Git> =>
|
|
14
14
|
Effect.scoped(
|
|
15
15
|
Effect.gen(function* () {
|
|
16
16
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
@@ -23,7 +23,7 @@ export const git = (cwd: string, args: ReadonlyArray<string>, env?: Readonly<Rec
|
|
|
23
23
|
}),
|
|
24
24
|
).pipe(Effect.mapError((error) => new SetupFailure({ message: `cannot run git: ${error.message}` })));
|
|
25
25
|
|
|
26
|
-
export const gitOrFail = (cwd: string, args:
|
|
26
|
+
export const gitOrFail = (cwd: string, args: readonly string[]): Effect.Effect<string, SetupFailure, Git> =>
|
|
27
27
|
Effect.flatMap(git(cwd, args), (result) =>
|
|
28
28
|
result.code === 0
|
|
29
29
|
? Effect.succeed(result.stdout)
|
package/src/git/history.ts
CHANGED
|
@@ -20,7 +20,7 @@ const parse = (output: string): Changes => {
|
|
|
20
20
|
const moves = new Map<string, string>();
|
|
21
21
|
for (let index = 0; index < fields.length - 1; ) {
|
|
22
22
|
const status = fields[index] ?? "";
|
|
23
|
-
const paths = fields.slice(index + 1, index + (/^[RC]
|
|
23
|
+
const paths = fields.slice(index + 1, index + (/^[RC]/u.test(status) ? 3 : 2));
|
|
24
24
|
for (const path of paths) {
|
|
25
25
|
files.add(path);
|
|
26
26
|
}
|
|
@@ -34,7 +34,7 @@ const parse = (output: string): Changes => {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
// Paths are relative to the root, and only tracked files count: a move is seen once both of its sides are staged.
|
|
37
|
-
export const changesSince = (root: string, revision:
|
|
37
|
+
export const changesSince = (root: string, revision: readonly string[]): Effect.Effect<Changes, SetupFailure, Git> =>
|
|
38
38
|
Effect.map(
|
|
39
39
|
gitOrFail(root, ["diff", "--no-color", "--no-ext-diff", "--find-renames", "--name-status", "-z", "--relative", ...revision, "--"]),
|
|
40
40
|
parse,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
export type Ambient = (specifier: string, projectFiles: ReadonlySet<string>) => boolean;
|
|
4
|
+
|
|
5
|
+
const WILDCARD = "*";
|
|
6
|
+
|
|
7
|
+
function isPattern(name: string): boolean {
|
|
8
|
+
return name !== WILDCARD && name.split(WILDCARD).length <= 2;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function declaredIn(source: ts.SourceFile): readonly string[] {
|
|
12
|
+
if (ts.isExternalModule(source)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
return source.statements.flatMap((statement) =>
|
|
16
|
+
ts.isModuleDeclaration(statement) && ts.isStringLiteral(statement.name) && isPattern(statement.name.text) ? [statement.name.text] : [],
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function wildcardMatches(pattern: string, value: string): boolean {
|
|
21
|
+
const [head = "", ...rest] = pattern.split(WILDCARD);
|
|
22
|
+
const tail = rest.pop();
|
|
23
|
+
if (tail === undefined) {
|
|
24
|
+
return pattern === value;
|
|
25
|
+
}
|
|
26
|
+
const end = value.length - tail.length;
|
|
27
|
+
if (end < head.length || !value.startsWith(head) || !value.endsWith(tail)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
let at = head.length;
|
|
31
|
+
for (const middle of rest) {
|
|
32
|
+
const found = value.indexOf(middle, at);
|
|
33
|
+
if (found === -1 || found + middle.length > end) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
at = found + middle.length;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isBare(specifier: string): boolean {
|
|
42
|
+
return !(specifier.startsWith(".") || specifier.startsWith("/"));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A module declared in a script, not a module, is ambient: it exists for the files of the project that includes the script.
|
|
46
|
+
export function ambientModules(sources: readonly { readonly path: string; readonly syntax: ts.SourceFile }[]): Ambient {
|
|
47
|
+
const declarations = sources.flatMap(({ path, syntax }) => {
|
|
48
|
+
const patterns = declaredIn(syntax);
|
|
49
|
+
return patterns.length === 0 ? [] : [{ path, patterns }];
|
|
50
|
+
});
|
|
51
|
+
return (specifier, projectFiles) =>
|
|
52
|
+
isBare(specifier)
|
|
53
|
+
&& declarations.some(({ path, patterns }) => projectFiles.has(path) && patterns.some((pattern) => wildcardMatches(pattern, specifier)));
|
|
54
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
interface Frame {
|
|
2
|
+
next: number;
|
|
3
|
+
readonly node: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface Search {
|
|
7
|
+
readonly adjacency: ReadonlyMap<string, readonly string[]>;
|
|
8
|
+
readonly found: string[][];
|
|
9
|
+
readonly index: Map<string, number>;
|
|
10
|
+
readonly low: Map<string, number>;
|
|
11
|
+
readonly open: Set<string>;
|
|
12
|
+
readonly stack: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function byPath(left: string, right: string): number {
|
|
16
|
+
return left.localeCompare(right);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function enter(search: Search, frames: Frame[], node: string): void {
|
|
20
|
+
const index = search.index.size;
|
|
21
|
+
search.index.set(node, index);
|
|
22
|
+
search.low.set(node, index);
|
|
23
|
+
search.stack.push(node);
|
|
24
|
+
search.open.add(node);
|
|
25
|
+
frames.push({ next: 0, node });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function lowOf(search: Search, node: string): number {
|
|
29
|
+
return search.low.get(node) ?? 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function close(search: Search, node: string): void {
|
|
33
|
+
if (lowOf(search, node) !== search.index.get(node)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const component: string[] = [];
|
|
37
|
+
for (let member = search.stack.pop(); member !== undefined; member = member === node ? undefined : search.stack.pop()) {
|
|
38
|
+
search.open.delete(member);
|
|
39
|
+
component.push(member);
|
|
40
|
+
}
|
|
41
|
+
const loops = (search.adjacency.get(node) ?? []).includes(node);
|
|
42
|
+
if (component.length > 1 || loops) {
|
|
43
|
+
search.found.push(component.sort(byPath));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function step(search: Search, frames: Frame[], frame: Frame): void {
|
|
48
|
+
const neighbour = (search.adjacency.get(frame.node) ?? [])[frame.next];
|
|
49
|
+
if (neighbour === undefined) {
|
|
50
|
+
frames.pop();
|
|
51
|
+
const parent = frames.at(-1);
|
|
52
|
+
if (parent !== undefined) {
|
|
53
|
+
search.low.set(parent.node, Math.min(lowOf(search, parent.node), lowOf(search, frame.node)));
|
|
54
|
+
}
|
|
55
|
+
close(search, frame.node);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
frame.next += 1;
|
|
59
|
+
if (!search.index.has(neighbour)) {
|
|
60
|
+
enter(search, frames, neighbour);
|
|
61
|
+
} else if (search.open.has(neighbour)) {
|
|
62
|
+
search.low.set(frame.node, Math.min(lowOf(search, frame.node), search.index.get(neighbour) ?? 0));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Tarjan's algorithm with an explicit stack, so a long import chain cannot overflow the call stack.
|
|
67
|
+
export function cyclicComponents(adjacency: ReadonlyMap<string, readonly string[]>): readonly (readonly string[])[] {
|
|
68
|
+
const search: Search = { adjacency, found: [], index: new Map(), low: new Map(), open: new Set(), stack: [] };
|
|
69
|
+
for (const node of [...adjacency.keys()].sort(byPath)) {
|
|
70
|
+
if (search.index.has(node)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const frames: Frame[] = [];
|
|
74
|
+
enter(search, frames, node);
|
|
75
|
+
for (let frame = frames.at(-1); frame !== undefined; frame = frames.at(-1)) {
|
|
76
|
+
step(search, frames, frame);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return search.found.sort((left, right) => byPath(left[0] ?? "", right[0] ?? ""));
|
|
80
|
+
}
|