@shivaedev/quality 0.4.0 → 0.5.1
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 +32 -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 +7 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +12 -5
- 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 +21 -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 +16 -8
- package/src/cli/baseline.ts +5 -6
- package/src/cli/check.ts +1 -1
- package/src/cli/fix.ts +23 -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 +48 -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
|
@@ -2,39 +2,29 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import { SUPPRESSIONS } from "../suppressions/directives.ts";
|
|
3
3
|
import type { SourceComment } from "./scan.ts";
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
"@vitest-environment",
|
|
7
|
-
"@vitest-environment-options",
|
|
8
|
-
"@jest-environment",
|
|
9
|
-
"@jsx",
|
|
10
|
-
"@jsxFrag",
|
|
11
|
-
"@jsxImportSource",
|
|
12
|
-
"@jsxRuntime",
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
export const Pragmas = Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_PRAGMAS)));
|
|
5
|
+
export const Pragmas = Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed<readonly string[]>([])));
|
|
16
6
|
|
|
17
|
-
const DIRECTIVES:
|
|
7
|
+
const DIRECTIVES: readonly RegExp[] = [
|
|
18
8
|
...SUPPRESSIONS,
|
|
19
|
-
/^@ts
|
|
20
|
-
/^biome-ignore
|
|
21
|
-
/^(?:eslint|oxlint|stylelint)-(?:disable|enable)
|
|
22
|
-
/^[#@]__(?:PURE|NO_SIDE_EFFECTS)__
|
|
23
|
-
/^(?:c8|v8|istanbul) ignore
|
|
9
|
+
/^@ts-/u,
|
|
10
|
+
/^biome-ignore/u,
|
|
11
|
+
/^(?:eslint|oxlint|stylelint)-(?:disable|enable)/u,
|
|
12
|
+
/^[#@]__(?:PURE|NO_SIDE_EFFECTS)__/u,
|
|
13
|
+
/^(?:c8|v8|istanbul) ignore/u,
|
|
24
14
|
];
|
|
25
15
|
|
|
26
|
-
const TRIPLE_SLASH = /^\/\/\/\s
|
|
16
|
+
const TRIPLE_SLASH = /^\/\/\/\s*</u;
|
|
27
17
|
|
|
28
|
-
const contentOf = (comment: SourceComment):
|
|
18
|
+
const contentOf = (comment: SourceComment): readonly string[] => comment.body.filter((line) => line !== "");
|
|
29
19
|
|
|
30
|
-
const startsWithTag = (line: string, tags:
|
|
20
|
+
const startsWithTag = (line: string, tags: readonly string[]): boolean =>
|
|
31
21
|
tags.some((tag) => line === tag || line.startsWith(`${tag} `) || line.startsWith(`${tag}\t`));
|
|
32
22
|
|
|
33
23
|
export const textOf = (comment: SourceComment): string => comment.body.join("\n");
|
|
34
24
|
|
|
35
25
|
export const isJsdoc = (comment: SourceComment): boolean => comment.kind === "block" && comment.text.startsWith("/**") && comment.text !== "/**/";
|
|
36
26
|
|
|
37
|
-
export const isPragma = (comment: SourceComment, pragmas:
|
|
27
|
+
export const isPragma = (comment: SourceComment, pragmas: readonly string[]): boolean => {
|
|
38
28
|
const content = contentOf(comment);
|
|
39
29
|
return content.length > 0 && content.every((line) => startsWithTag(line, pragmas));
|
|
40
30
|
};
|
|
@@ -5,32 +5,32 @@ import { isDirective, isPragma, Pragmas } from "./kinds.ts";
|
|
|
5
5
|
import { commentsOf, type SourceComment } from "./scan.ts";
|
|
6
6
|
|
|
7
7
|
const MaxPerFileOptions = Schema.Struct({
|
|
8
|
-
max: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).pipe(Schema.withDecodingDefaultKey(Effect.succeed(2))),
|
|
9
8
|
allow: Pragmas,
|
|
9
|
+
max: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).pipe(Schema.withDecodingDefaultKey(Effect.succeed(2))),
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
const continues = (previous: SourceComment | undefined, comment: SourceComment): boolean =>
|
|
13
|
-
previous !== undefined
|
|
14
|
-
previous.kind === "line"
|
|
15
|
-
comment.kind === "line"
|
|
16
|
-
previous.ownLine
|
|
17
|
-
comment.ownLine
|
|
18
|
-
comment.line === previous.endLine + 1;
|
|
13
|
+
previous !== undefined
|
|
14
|
+
&& previous.kind === "line"
|
|
15
|
+
&& comment.kind === "line"
|
|
16
|
+
&& previous.ownLine
|
|
17
|
+
&& comment.ownLine
|
|
18
|
+
&& comment.line === previous.endLine + 1;
|
|
19
19
|
|
|
20
|
-
const commentBlocks = (comments:
|
|
20
|
+
const commentBlocks = (comments: readonly SourceComment[]): readonly SourceComment[] =>
|
|
21
21
|
comments.filter((comment, index) => !continues(comments[index - 1], comment));
|
|
22
22
|
|
|
23
|
-
const overCap = (file: SourceFile, max: number, allow:
|
|
24
|
-
const counted = commentsOf(file).filter((comment) => !isDirective(comment)
|
|
23
|
+
const overCap = (file: SourceFile, max: number, allow: readonly string[]): readonly Finding[] => {
|
|
24
|
+
const counted = commentsOf(file).filter((comment) => !(isDirective(comment) || isPragma(comment, allow)));
|
|
25
25
|
const blocks = commentBlocks(counted);
|
|
26
26
|
const first = blocks[max];
|
|
27
27
|
return first === undefined
|
|
28
28
|
? []
|
|
29
29
|
: [
|
|
30
30
|
{
|
|
31
|
+
count: blocks.length - max,
|
|
31
32
|
file: file.path,
|
|
32
33
|
line: first.line,
|
|
33
|
-
count: blocks.length - max,
|
|
34
34
|
message: `${plural(blocks.length, "comment", "comments")} against a limit of ${max}.`,
|
|
35
35
|
threshold: max,
|
|
36
36
|
},
|
|
@@ -38,9 +38,9 @@ const overCap = (file: SourceFile, max: number, allow: ReadonlyArray<string>): R
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
export const maxPerFile = defineRule({
|
|
41
|
-
|
|
41
|
+
check: ({ options, sources }) => sources.flatMap((file) => overCap(file, options.max, options.allow)),
|
|
42
42
|
description:
|
|
43
43
|
"A file that needs more than a few comments does too much, or its comments narrate the code. Delete the comments that restate the code, then split the file.",
|
|
44
|
+
id: "comments/max-per-file",
|
|
44
45
|
options: Schema.toStandardSchemaV1(MaxPerFileOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
45
|
-
check: ({ options, sources }) => sources.flatMap((file) => overCap(file, options.max, options.allow)),
|
|
46
46
|
});
|
|
@@ -3,8 +3,8 @@ import { definePatternRule } from "./pattern-rule.ts";
|
|
|
3
3
|
const DIVIDER = "[-=*#~_+/\\\\─━═┄┈]{3,}";
|
|
4
4
|
|
|
5
5
|
export const noBanner = definePatternRule({
|
|
6
|
-
id: "comments/no-banner",
|
|
7
6
|
description: "Banners, dividers and regions mark a file that does several jobs. Split the file along those sections instead.",
|
|
8
|
-
|
|
7
|
+
id: "comments/no-banner",
|
|
9
8
|
message: (match) => `Banner or divider: "${match}".`,
|
|
9
|
+
patterns: [new RegExp(`^${DIVIDER}|${DIVIDER}$`, "mu"), /^#(?:end)?region\b/mu],
|
|
10
10
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { definePatternRule } from "./pattern-rule.ts";
|
|
2
|
+
|
|
3
|
+
export const noEnvironmentPragma = definePatternRule({
|
|
4
|
+
description:
|
|
5
|
+
"A test file gets its environment from its name, not from a pragma: `*.dom.test.ts` runs in a DOM, every other test in Node. `@shivaedev/quality/vitest` sets up the Vitest projects that read the name.",
|
|
6
|
+
id: "comments/no-environment-pragma",
|
|
7
|
+
message: (match) => `Sets the test environment with "${match}". Name the file *.dom.test.ts or *.dom.test.tsx instead.`,
|
|
8
|
+
patterns: [/^@(?:vitest|jest)-environment(?:-options)?(?=\s|$)/mu],
|
|
9
|
+
});
|
|
@@ -6,13 +6,13 @@ import { commentsOf } from "./scan.ts";
|
|
|
6
6
|
const NoJsdocOptions = Schema.Struct({ allow: Pragmas });
|
|
7
7
|
|
|
8
8
|
export const noJsdoc = defineRule({
|
|
9
|
-
id: "comments/no-jsdoc",
|
|
10
|
-
description: "Names and types already say what a JSDoc block repeats. Delete it, and keep a reason the code cannot show as a short // comment.",
|
|
11
|
-
options: Schema.toStandardSchemaV1(NoJsdocOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
12
9
|
check: ({ options, sources }) =>
|
|
13
10
|
sources.flatMap((file) =>
|
|
14
11
|
commentsOf(file)
|
|
15
12
|
.filter((comment) => isJsdoc(comment) && !isPragma(comment, options.allow) && !isDirective(comment))
|
|
16
13
|
.map((comment) => ({ file: file.path, line: comment.line, message: "JSDoc block." })),
|
|
17
14
|
),
|
|
15
|
+
description: "Names and types already say what a JSDoc block repeats. Delete it, and keep a reason the code cannot show as a short // comment.",
|
|
16
|
+
id: "comments/no-jsdoc",
|
|
17
|
+
options: Schema.toStandardSchemaV1(NoJsdocOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
18
18
|
});
|
|
@@ -4,8 +4,8 @@ const SOURCE_EXTENSION =
|
|
|
4
4
|
"(?:[cm]?[jt]sx?|json5?|mdx?|css|scss|less|html?|vue|svelte|astro|ya?ml|toml|sql|prisma|graphql|gql|go|rs|py|rb|java|kt|swift|sh)";
|
|
5
5
|
|
|
6
6
|
export const noLineReference = definePatternRule({
|
|
7
|
-
id: "comments/no-line-reference",
|
|
8
7
|
description: "A line number goes stale with the next edit. Name the function, type, test or setting instead.",
|
|
9
|
-
|
|
8
|
+
id: "comments/no-line-reference",
|
|
10
9
|
message: (match) => `Refers to a line: "${match}".`,
|
|
10
|
+
patterns: [new RegExp(`[\\w./@-]*\\.${SOURCE_EXTENSION}(?::\\d+|#L\\d+)`, "iu"), /(?<![\w-])lines?\s+\d+/iu],
|
|
11
11
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { definePatternRule } from "./pattern-rule.ts";
|
|
2
2
|
|
|
3
3
|
export const noPrReference = definePatternRule({
|
|
4
|
-
id: "comments/no-pr-reference",
|
|
5
4
|
description: "History belongs in version control, not in the code. State the reason itself, so the comment holds without the link.",
|
|
5
|
+
id: "comments/no-pr-reference",
|
|
6
|
+
message: (match) => `Refers to a pull request or issue: "${match}".`,
|
|
6
7
|
patterns: [
|
|
7
|
-
/\b(?:PR|MR|pull request|merge request|issue|ticket)s?\s*#?\d+\b/
|
|
8
|
-
/\/(?:pull|pulls|issues|merge_requests)\/\d
|
|
9
|
-
/\bGH-\d+\b
|
|
10
|
-
/(?<![\w&/#-])#\d+\b
|
|
8
|
+
/\b(?:PR|MR|pull request|merge request|issue|ticket)s?\s*#?\d+\b/iu,
|
|
9
|
+
/\/(?:pull|pulls|issues|merge_requests)\/\d+/u,
|
|
10
|
+
/\bGH-\d+\b/u,
|
|
11
|
+
/(?<![\w&/#-])#\d+\b/u,
|
|
11
12
|
],
|
|
12
|
-
message: (match) => `Refers to a pull request or issue: "${match}".`,
|
|
13
13
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { definePatternRule } from "./pattern-rule.ts";
|
|
2
2
|
|
|
3
3
|
export const noTodo = definePatternRule({
|
|
4
|
-
id: "comments/no-todo",
|
|
5
4
|
description: "A TODO in the code is never done. Do the work now, or track it where the repository tracks work.",
|
|
6
|
-
|
|
5
|
+
id: "comments/no-todo",
|
|
7
6
|
message: (match) => `Marks unfinished work: "${match}".`,
|
|
7
|
+
patterns: [/\b(?:TODO|FIXME|XXX)\b/u, /@todo\b/iu],
|
|
8
8
|
});
|
|
@@ -3,26 +3,26 @@ import { isDirective, textOf } from "./kinds.ts";
|
|
|
3
3
|
import { commentsOf, type SourceComment } from "./scan.ts";
|
|
4
4
|
|
|
5
5
|
interface PatternRule<Id extends string> {
|
|
6
|
-
readonly id: Id;
|
|
7
6
|
readonly description: string;
|
|
8
|
-
readonly
|
|
7
|
+
readonly id: Id;
|
|
9
8
|
readonly message: (match: string) => string;
|
|
9
|
+
readonly patterns: readonly RegExp[];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const firstMatch = (comment: SourceComment, patterns:
|
|
12
|
+
const firstMatch = (comment: SourceComment, patterns: readonly RegExp[]): string | undefined => {
|
|
13
13
|
const text = textOf(comment);
|
|
14
14
|
return patterns.map((pattern) => pattern.exec(text)?.[0]).find((match) => match !== undefined);
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export const definePatternRule = <const Id extends string>({ description, id, message, patterns }: PatternRule<Id>): Rule<Id, undefined> =>
|
|
18
18
|
defineRule({
|
|
19
|
-
id,
|
|
20
|
-
description,
|
|
21
19
|
check: ({ sources }) =>
|
|
22
20
|
sources.flatMap((file) =>
|
|
23
|
-
commentsOf(file).flatMap((comment):
|
|
21
|
+
commentsOf(file).flatMap((comment): readonly Finding[] => {
|
|
24
22
|
const match = isDirective(comment) ? undefined : firstMatch(comment, patterns);
|
|
25
23
|
return match === undefined ? [] : [{ file: file.path, line: comment.line, message: message(match) }];
|
|
26
24
|
}),
|
|
27
25
|
),
|
|
26
|
+
description,
|
|
27
|
+
id,
|
|
28
28
|
});
|
|
@@ -3,20 +3,20 @@ import type { SourceFile } from "../../rule.ts";
|
|
|
3
3
|
import { parse } from "../syntax.ts";
|
|
4
4
|
|
|
5
5
|
export interface SourceComment {
|
|
6
|
+
readonly body: readonly string[];
|
|
7
|
+
readonly endLine: number;
|
|
6
8
|
readonly kind: "block" | "line";
|
|
7
|
-
readonly text: string;
|
|
8
|
-
readonly body: ReadonlyArray<string>;
|
|
9
9
|
readonly line: number;
|
|
10
|
-
readonly endLine: number;
|
|
11
10
|
readonly ownLine: boolean;
|
|
11
|
+
readonly text: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const DECLARATION = /\.d\.[cm]?ts
|
|
14
|
+
const DECLARATION = /\.d\.[cm]?ts$/u;
|
|
15
15
|
|
|
16
|
-
const commentRanges = (source: ts.SourceFile):
|
|
16
|
+
const commentRanges = (source: ts.SourceFile): readonly ts.CommentRange[] => {
|
|
17
17
|
const ranges = new Map<number, ts.CommentRange>();
|
|
18
|
-
const jsxText:
|
|
19
|
-
const add = (found:
|
|
18
|
+
const jsxText: ts.Node[] = [];
|
|
19
|
+
const add = (found: readonly ts.CommentRange[] | undefined) => {
|
|
20
20
|
for (const range of found ?? []) {
|
|
21
21
|
ranges.set(range.pos, range);
|
|
22
22
|
}
|
|
@@ -37,13 +37,13 @@ const commentRanges = (source: ts.SourceFile): ReadonlyArray<ts.CommentRange> =>
|
|
|
37
37
|
.sort((left, right) => left.pos - right.pos);
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
export const bodyOf = (kind: SourceComment["kind"], text: string):
|
|
40
|
+
export const bodyOf = (kind: SourceComment["kind"], text: string): readonly string[] =>
|
|
41
41
|
kind === "line"
|
|
42
42
|
? [text.slice(2).trim()]
|
|
43
43
|
: text
|
|
44
44
|
.slice(2, -2)
|
|
45
45
|
.split("\n")
|
|
46
|
-
.map((line) => line.trim().replace(/^\*(?=\s|$)\s
|
|
46
|
+
.map((line) => line.trim().replace(/^\*(?=\s|$)\s*/u, ""));
|
|
47
47
|
|
|
48
48
|
const toComment = (source: ts.SourceFile, range: ts.CommentRange): SourceComment => {
|
|
49
49
|
const kind = range.kind === ts.SyntaxKind.SingleLineCommentTrivia ? "line" : "block";
|
|
@@ -60,14 +60,14 @@ const toComment = (source: ts.SourceFile, range: ts.CommentRange): SourceComment
|
|
|
60
60
|
};
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const scanned = new WeakMap<SourceFile,
|
|
63
|
+
const scanned = new WeakMap<SourceFile, readonly SourceComment[]>();
|
|
64
64
|
|
|
65
|
-
const scan = (file: SourceFile):
|
|
65
|
+
const scan = (file: SourceFile): readonly SourceComment[] => {
|
|
66
66
|
const source = parse(file);
|
|
67
67
|
return source === undefined ? [] : commentRanges(source).map((range) => toComment(source, range));
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
export const scanComments = (file: SourceFile):
|
|
70
|
+
export const scanComments = (file: SourceFile): readonly SourceComment[] => {
|
|
71
71
|
const cached = scanned.get(file);
|
|
72
72
|
if (cached !== undefined) {
|
|
73
73
|
return cached;
|
|
@@ -77,4 +77,4 @@ export const scanComments = (file: SourceFile): ReadonlyArray<SourceComment> =>
|
|
|
77
77
|
return comments;
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
export const commentsOf = (file: SourceFile):
|
|
80
|
+
export const commentsOf = (file: SourceFile): readonly SourceComment[] => (DECLARATION.test(file.path) ? [] : scanComments(file));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cyclicComponents } from "../../imports/components.ts";
|
|
2
|
+
import { type ImportEdge, importGraph } from "../../imports/graph.ts";
|
|
3
|
+
import { shortestPath } from "../../imports/paths.ts";
|
|
4
|
+
import { defineRule, type Finding } from "../../rule.ts";
|
|
5
|
+
|
|
6
|
+
type RuntimeEdge = ImportEdge & { readonly to: { readonly kind: "file"; readonly path: string } };
|
|
7
|
+
|
|
8
|
+
function isRuntime(edge: ImportEdge): edge is RuntimeEdge {
|
|
9
|
+
return !edge.type && edge.kind === "import" && edge.to.kind === "file";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function runtimeAdjacency(edges: readonly RuntimeEdge[]): ReadonlyMap<string, readonly string[]> {
|
|
13
|
+
const adjacency = new Map<string, string[]>();
|
|
14
|
+
for (const edge of edges) {
|
|
15
|
+
adjacency.set(edge.from, [...(adjacency.get(edge.from) ?? []), edge.to.path]);
|
|
16
|
+
}
|
|
17
|
+
return adjacency;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function findingOf(edges: readonly RuntimeEdge[], adjacency: ReadonlyMap<string, readonly string[]>, component: readonly string[]): Finding {
|
|
21
|
+
const [anchor = ""] = component;
|
|
22
|
+
const members = new Set(component);
|
|
23
|
+
function within(node: string): readonly string[] {
|
|
24
|
+
return (adjacency.get(node) ?? []).filter((next) => members.has(next));
|
|
25
|
+
}
|
|
26
|
+
const loop = shortestPath(anchor, within, (node) => node === anchor) ?? component;
|
|
27
|
+
const first = edges.find((edge) => edge.from === anchor && edge.to.path === loop[1]);
|
|
28
|
+
const message =
|
|
29
|
+
component.length === 1
|
|
30
|
+
? `${anchor} imports itself at runtime. Use its own code directly instead of importing it.`
|
|
31
|
+
: `${component.length} modules import each other at runtime: ${component.join(", ")}. One loop: ${loop.join(" -> ")}. Move what they share into a module that imports neither, or import only types with \`import type\`.`;
|
|
32
|
+
return { count: component.length, file: anchor, line: first?.line, message };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const importCycles = defineRule({
|
|
36
|
+
check: async (inputs) => {
|
|
37
|
+
const edges = (await importGraph(inputs)).edges.filter(isRuntime);
|
|
38
|
+
const adjacency = runtimeAdjacency(edges);
|
|
39
|
+
return cyclicComponents(adjacency).map((component) => findingOf(edges, adjacency, component));
|
|
40
|
+
},
|
|
41
|
+
description: "Modules never import each other in a loop at runtime. A group of modules that reach one another counts once for each module in it.",
|
|
42
|
+
id: "imports/cycles",
|
|
43
|
+
registrable: false,
|
|
44
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import type { Fence } from "../../imports/fences/model.ts";
|
|
3
|
+
import { compilePolicy } from "../../imports/fences/policy.ts";
|
|
4
|
+
import { importGraph } from "../../imports/graph.ts";
|
|
5
|
+
import { defineRule } from "../../rule.ts";
|
|
6
|
+
|
|
7
|
+
function isFence(value: unknown): value is Fence {
|
|
8
|
+
return typeof value === "object" && value !== null && "_tag" in value && value._tag === "Fence";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const FencesOptions = Schema.Struct({
|
|
12
|
+
fences: Schema.Array(Schema.declare(isFence, { expected: "a fence made with fence()" })).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const importFences = defineRule({
|
|
16
|
+
check: async ({ options, ...inputs }) => {
|
|
17
|
+
if (options.fences.length === 0) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const graph = await importGraph(inputs);
|
|
21
|
+
const policy = compilePolicy(options.fences, { files: inputs.files, packages: graph.packages });
|
|
22
|
+
return policy.flatMap((compiled) => compiled.evaluate(graph));
|
|
23
|
+
},
|
|
24
|
+
description: "Imports respect the fences the repository declares. Move the code to the side of the fence its imports belong to.",
|
|
25
|
+
id: "imports/fences",
|
|
26
|
+
options: Schema.toStandardSchemaV1(FencesOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
27
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { withoutGenerated } from "../../imports/generated.ts";
|
|
3
|
+
import { importGraph } from "../../imports/graph.ts";
|
|
4
|
+
import { defineRule } from "../../rule.ts";
|
|
5
|
+
|
|
6
|
+
const ResolvableOptions = Schema.Struct({
|
|
7
|
+
generated: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const importsResolvable = defineRule({
|
|
11
|
+
check: async ({ options, ...inputs }) => {
|
|
12
|
+
const unresolved = await withoutGenerated(inputs, await importGraph(inputs), options.generated);
|
|
13
|
+
return unresolved.map((request) => ({
|
|
14
|
+
file: request.from,
|
|
15
|
+
line: request.line,
|
|
16
|
+
message: `Cannot resolve "${request.specifier}". Fix the path, install or declare the package, or declare the module in a .d.ts file among the sources.`,
|
|
17
|
+
subject: request.specifier,
|
|
18
|
+
}));
|
|
19
|
+
},
|
|
20
|
+
description: "Every import resolves to a file, a package or a declared module, so no edge of the import graph goes unchecked.",
|
|
21
|
+
id: "imports/resolvable",
|
|
22
|
+
options: Schema.toStandardSchemaV1(ResolvableOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
23
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { manifestsIn, sortingOf } from "../manifests/sorted.ts";
|
|
4
|
+
import { defineRule, type Finding } from "../rule.ts";
|
|
5
|
+
|
|
6
|
+
const MESSAGES = {
|
|
7
|
+
Invalid: "Is not valid JSON.",
|
|
8
|
+
Unsorted: "Keys are not in sort-package-json order. Run `quality fix`.",
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export const manifestsSorted = defineRule({
|
|
12
|
+
check: async ({ readText, root }) => {
|
|
13
|
+
const manifests = await Effect.runPromise(manifestsIn(root).pipe(Effect.provide(NodeFileSystem.layer)));
|
|
14
|
+
const texts = await Promise.all(manifests.map(async (path) => ({ path, text: (await readText(path)) ?? "" })));
|
|
15
|
+
return texts.flatMap(({ path, text }): readonly Finding[] => {
|
|
16
|
+
const sorting = sortingOf(text);
|
|
17
|
+
return sorting._tag === "Sorted" ? [] : [{ file: path, message: MESSAGES[sorting._tag] }];
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
description: "Every package.json in the repository keeps its keys in sort-package-json order. `quality fix` sorts them.",
|
|
21
|
+
id: "manifests/sorted",
|
|
22
|
+
registrable: false,
|
|
23
|
+
});
|
package/src/rules/max-lines.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import ignore from "ignore";
|
|
3
3
|
import { defineRule, type Finding, type SourceFile } from "../rule.ts";
|
|
4
4
|
|
|
5
|
-
const DEFAULT_TEST_FILES:
|
|
6
|
-
const DECLARATION = /\.d\.[cm]?ts
|
|
5
|
+
const DEFAULT_TEST_FILES: readonly string[] = ["*.test.*", "*.spec.*", "test/", "tests/", "__tests__/"];
|
|
6
|
+
const DECLARATION = /\.d\.[cm]?ts$/u;
|
|
7
7
|
|
|
8
8
|
const Limit = Schema.Int.check(Schema.isGreaterThan(0));
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ const MaxLinesOptions = Schema.Struct({
|
|
|
13
13
|
testFiles: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_TEST_FILES))),
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
const oversized = (file: SourceFile, limit: number):
|
|
16
|
+
const oversized = (file: SourceFile, limit: number): readonly Finding[] =>
|
|
17
17
|
file.lines.length <= limit
|
|
18
18
|
? []
|
|
19
19
|
: [
|
|
@@ -26,13 +26,13 @@ const oversized = (file: SourceFile, limit: number): ReadonlyArray<Finding> =>
|
|
|
26
26
|
];
|
|
27
27
|
|
|
28
28
|
export const maxLines = defineRule({
|
|
29
|
-
id: "structure/max-lines",
|
|
30
|
-
description: "Keep each module to one job. Split a long file along its responsibilities; never golf it under the limit.",
|
|
31
|
-
options: Schema.toStandardSchemaV1(MaxLinesOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
32
29
|
check: ({ options, sources }) => {
|
|
33
30
|
const tests = ignore().add([...options.testFiles]);
|
|
34
31
|
return sources
|
|
35
32
|
.filter((file) => !DECLARATION.test(file.path))
|
|
36
33
|
.flatMap((file) => oversized(file, tests.ignores(file.path) ? options.test : options.source));
|
|
37
34
|
},
|
|
35
|
+
description: "Keep each module to one job. Split a long file along its responsibilities; never golf it under the limit.",
|
|
36
|
+
id: "structure/max-lines",
|
|
37
|
+
options: Schema.toStandardSchemaV1(MaxLinesOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
38
38
|
});
|
|
@@ -5,35 +5,35 @@ import { itemsOf, type Json, member, type Parsed, parseJsonc, textOf } from "./j
|
|
|
5
5
|
import { resolvePackage } from "./resolve.ts";
|
|
6
6
|
|
|
7
7
|
export interface Preset {
|
|
8
|
-
readonly
|
|
8
|
+
readonly declared: readonly Declaration[];
|
|
9
9
|
readonly file: string;
|
|
10
10
|
readonly line: number;
|
|
11
|
-
readonly
|
|
11
|
+
readonly specifier: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export interface Layer {
|
|
15
|
-
readonly path: string;
|
|
16
15
|
readonly json: Json;
|
|
16
|
+
readonly path: string;
|
|
17
17
|
readonly preset: Preset | undefined;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface Chain {
|
|
21
21
|
readonly base: string;
|
|
22
|
-
readonly layers:
|
|
22
|
+
readonly layers: readonly Layer[];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
interface Loaded {
|
|
26
|
-
readonly layers:
|
|
27
|
-
readonly problems:
|
|
26
|
+
readonly layers: readonly Layer[];
|
|
27
|
+
readonly problems: readonly Finding[];
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
type ReadText = RuleInputs["readText"];
|
|
31
31
|
|
|
32
|
-
const ROOT_CONFIGS:
|
|
32
|
+
const ROOT_CONFIGS: readonly string[] = ["biome.json", "biome.jsonc"];
|
|
33
33
|
|
|
34
|
-
const NESTED = /(?:^|\/)biome\.jsonc
|
|
34
|
+
const NESTED = /(?:^|\/)biome\.jsonc?$/u;
|
|
35
35
|
|
|
36
|
-
const RELATIVE = /^\.\.?(?:\/|$)
|
|
36
|
+
const RELATIVE = /^\.\.?(?:\/|$)/u;
|
|
37
37
|
|
|
38
38
|
const directoryOf = (path: string): string => (posix.dirname(path) === "." ? "" : posix.dirname(path));
|
|
39
39
|
|
|
@@ -81,7 +81,7 @@ const extended = async (readText: ReadText, from: string, entry: Json): Promise<
|
|
|
81
81
|
return (await readText(relative)) === undefined ? fromPackage(readText, from, entry, specifier) : local(readText, relative);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
const extendsOf = (json: Json):
|
|
84
|
+
const extendsOf = (json: Json): readonly Json[] => itemsOf(member(json, "extends"));
|
|
85
85
|
|
|
86
86
|
const chainOf = async (readText: ReadText, path: string): Promise<Loaded> => {
|
|
87
87
|
const own = await local(readText, path);
|
|
@@ -99,9 +99,9 @@ const rootConfig = async (readText: ReadText): Promise<string | undefined> => {
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
export interface BiomeConfigs {
|
|
102
|
+
readonly chains: readonly Chain[];
|
|
103
|
+
readonly problems: readonly Finding[];
|
|
102
104
|
readonly root: string;
|
|
103
|
-
readonly chains: ReadonlyArray<Chain>;
|
|
104
|
-
readonly problems: ReadonlyArray<Finding>;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export const biomeConfigs = async ({ files, readText }: RuleInputs): Promise<BiomeConfigs> => {
|
|
@@ -2,9 +2,9 @@ import { Schema } from "effect";
|
|
|
2
2
|
import { type Decoded, decodeWith } from "../../../decoded.ts";
|
|
3
3
|
|
|
4
4
|
export const Declaration = Schema.Struct({
|
|
5
|
-
rule: Schema.NonEmptyString,
|
|
6
5
|
includes: Schema.NonEmptyArray(Schema.NonEmptyString),
|
|
7
|
-
reason: Schema.String.check(Schema.isPattern(/\S
|
|
6
|
+
reason: Schema.String.check(Schema.isPattern(/\S/u, { expected: "a reason that says why the scope needs the rule weakened" })),
|
|
7
|
+
rule: Schema.NonEmptyString,
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
export type Declaration = typeof Declaration.Type;
|
|
@@ -15,23 +15,23 @@ const shipped = Schema.toStandardSchemaV1(Schema.fromJsonString(Schema.Array(Dec
|
|
|
15
15
|
parseOptions: { errors: "all", onExcessProperty: "error" },
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
export const decodeShipped = (text: string | undefined): Promise<Decoded<
|
|
18
|
+
export const decodeShipped = (text: string | undefined): Promise<Decoded<readonly Declaration[]>> =>
|
|
19
19
|
text === undefined ? Promise.resolve({ _tag: "Valid", value: [] }) : decodeWith(shipped, text);
|
|
20
20
|
|
|
21
21
|
export interface Scoped {
|
|
22
|
+
readonly includes: readonly string[];
|
|
22
23
|
readonly rule: string;
|
|
23
|
-
readonly includes: ReadonlyArray<string>;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const sameScope = (left:
|
|
26
|
+
const sameScope = (left: readonly string[], right: readonly string[]): boolean =>
|
|
27
27
|
new Set(left).size === new Set(right).size && left.every((glob) => right.includes(glob));
|
|
28
28
|
|
|
29
29
|
export const matches = (declaration: Scoped, weakening: Scoped): boolean =>
|
|
30
30
|
declaration.rule === weakening.rule && sameScope(declaration.includes, weakening.includes);
|
|
31
31
|
|
|
32
32
|
export const scoped = (base: string, glob: string): string => {
|
|
33
|
-
const [, negation = "", path = glob] = /^(!*)(.*)
|
|
33
|
+
const [, negation = "", path = glob] = /^(!*)(.*)$/u.exec(glob) ?? [];
|
|
34
34
|
return base === "" ? glob : `${negation}${base}/${path}`;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
export const scopeText = (includes:
|
|
37
|
+
export const scopeText = (includes: readonly string[]): string => includes.map((glob) => `"${glob}"`).join(", ");
|
|
@@ -2,7 +2,7 @@ import ts from "typescript";
|
|
|
2
2
|
|
|
3
3
|
export type Json =
|
|
4
4
|
| { readonly _tag: "Object"; readonly line: number; readonly entries: ReadonlyMap<string, Json> }
|
|
5
|
-
| { readonly _tag: "Array"; readonly line: number; readonly items:
|
|
5
|
+
| { readonly _tag: "Array"; readonly line: number; readonly items: readonly Json[] }
|
|
6
6
|
| { readonly _tag: "Value"; readonly line: number; readonly value: string | boolean | undefined };
|
|
7
7
|
|
|
8
8
|
export type Parsed = { readonly _tag: "Parsed"; readonly json: Json } | { readonly _tag: "Unreadable"; readonly reason: string };
|
|
@@ -37,7 +37,7 @@ export const parseJsonc = (path: string, text: string): Parsed => {
|
|
|
37
37
|
if (error !== undefined || root === undefined) {
|
|
38
38
|
return {
|
|
39
39
|
_tag: "Unreadable",
|
|
40
|
-
reason: error === undefined ? "it is empty" : ts.flattenDiagnosticMessageText(error.messageText, " ").replace(
|
|
40
|
+
reason: error === undefined ? "it is empty" : ts.flattenDiagnosticMessageText(error.messageText, " ").replace(/\.$/u, ""),
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
return { _tag: "Parsed", json: toJson(source, root) };
|
|
@@ -47,7 +47,7 @@ export const member = (node: Json | undefined, key: string): Json | undefined =>
|
|
|
47
47
|
|
|
48
48
|
export const entriesOf = (node: Json | undefined): ReadonlyArray<readonly [string, Json]> => (node?._tag === "Object" ? [...node.entries] : []);
|
|
49
49
|
|
|
50
|
-
export const itemsOf = (node: Json | undefined):
|
|
50
|
+
export const itemsOf = (node: Json | undefined): readonly Json[] => (node?._tag === "Array" ? node.items : []);
|
|
51
51
|
|
|
52
52
|
export const textOf = (node: Json | undefined): string | undefined =>
|
|
53
53
|
node?._tag === "Value" && typeof node.value === "string" ? node.value : undefined;
|
|
@@ -4,9 +4,9 @@ import { decodeWith } from "../../../decoded.ts";
|
|
|
4
4
|
import type { RuleInputs } from "../../../rule.ts";
|
|
5
5
|
|
|
6
6
|
const Manifest = Schema.Struct({
|
|
7
|
-
name: Schema.optionalKey(Schema.String),
|
|
8
|
-
main: Schema.optionalKey(Schema.String),
|
|
9
7
|
exports: Schema.optionalKey(Schema.Unknown),
|
|
8
|
+
main: Schema.optionalKey(Schema.String),
|
|
9
|
+
name: Schema.optionalKey(Schema.String),
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
type Manifest = typeof Manifest.Type;
|
|
@@ -15,7 +15,7 @@ const manifestSchema = Schema.toStandardSchemaV1(Schema.fromJsonString(Manifest)
|
|
|
15
15
|
|
|
16
16
|
const CONDITIONS: ReadonlySet<string> = new Set(["biome", "default"]);
|
|
17
17
|
|
|
18
|
-
const SPECIFIER = /^((?:@[^/]+\/)?[^/]+)(?:\/(.+))
|
|
18
|
+
const SPECIFIER = /^((?:@[^/]+\/)?[^/]+)(?:\/(.+))?$/u;
|
|
19
19
|
|
|
20
20
|
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
21
|
|