@nielspeter/eess-ts 0.2.0 → 0.4.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/README.md +87 -34
- package/dist/builders/call-rule-builder.d.ts +66 -26
- package/dist/builders/call-rule-builder.d.ts.map +1 -1
- package/dist/builders/call-rule-builder.js +73 -31
- package/dist/builders/call-rule-builder.js.map +1 -1
- package/dist/builders/class-rule-builder.d.ts +125 -26
- package/dist/builders/class-rule-builder.d.ts.map +1 -1
- package/dist/builders/class-rule-builder.js +134 -30
- package/dist/builders/class-rule-builder.js.map +1 -1
- package/dist/builders/correspondence-builder.d.ts +287 -0
- package/dist/builders/correspondence-builder.d.ts.map +1 -0
- package/dist/builders/correspondence-builder.js +393 -0
- package/dist/builders/correspondence-builder.js.map +1 -0
- package/dist/builders/correspondence-findings.d.ts +104 -0
- package/dist/builders/correspondence-findings.d.ts.map +1 -0
- package/dist/builders/correspondence-findings.js +301 -0
- package/dist/builders/correspondence-findings.js.map +1 -0
- package/dist/builders/cross-layer-builder.d.ts +99 -16
- package/dist/builders/cross-layer-builder.d.ts.map +1 -1
- package/dist/builders/cross-layer-builder.js +181 -35
- package/dist/builders/cross-layer-builder.js.map +1 -1
- package/dist/builders/function-rule-builder.d.ts +144 -25
- package/dist/builders/function-rule-builder.d.ts.map +1 -1
- package/dist/builders/function-rule-builder.js +173 -26
- package/dist/builders/function-rule-builder.js.map +1 -1
- package/dist/builders/jsx-rule-builder.d.ts +70 -14
- package/dist/builders/jsx-rule-builder.d.ts.map +1 -1
- package/dist/builders/jsx-rule-builder.js +73 -14
- package/dist/builders/jsx-rule-builder.js.map +1 -1
- package/dist/builders/module-rule-builder.d.ts +2 -2
- package/dist/builders/module-rule-builder.d.ts.map +1 -1
- package/dist/builders/module-rule-builder.js +12 -3
- package/dist/builders/module-rule-builder.js.map +1 -1
- package/dist/builders/scoped-function-rule-builder.d.ts +11 -0
- package/dist/builders/scoped-function-rule-builder.d.ts.map +1 -1
- package/dist/builders/scoped-function-rule-builder.js +12 -0
- package/dist/builders/scoped-function-rule-builder.js.map +1 -1
- package/dist/builders/slice-discovery-message.d.ts +36 -0
- package/dist/builders/slice-discovery-message.d.ts.map +1 -0
- package/dist/builders/slice-discovery-message.js +181 -0
- package/dist/builders/slice-discovery-message.js.map +1 -0
- package/dist/builders/slice-rule-builder.d.ts +151 -12
- package/dist/builders/slice-rule-builder.d.ts.map +1 -1
- package/dist/builders/slice-rule-builder.js +242 -35
- package/dist/builders/slice-rule-builder.js.map +1 -1
- package/dist/builders/type-rule-builder.d.ts +95 -24
- package/dist/builders/type-rule-builder.d.ts.map +1 -1
- package/dist/builders/type-rule-builder.js +105 -29
- package/dist/builders/type-rule-builder.js.map +1 -1
- package/dist/{helpers → builders}/within.d.ts +2 -2
- package/dist/builders/within.d.ts.map +1 -0
- package/dist/{helpers → builders}/within.js +1 -1
- package/dist/builders/within.js.map +1 -0
- package/dist/cli/commands/baseline.d.ts +3 -2
- package/dist/cli/commands/baseline.d.ts.map +1 -1
- package/dist/cli/commands/baseline.js +68 -6
- package/dist/cli/commands/baseline.js.map +1 -1
- package/dist/cli/commands/check.d.ts +7 -2
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +241 -45
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +29 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +224 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +2 -1
- package/dist/cli/commands/explain.d.ts.map +1 -1
- package/dist/cli/commands/explain.js +19 -10
- package/dist/cli/commands/explain.js.map +1 -1
- package/dist/cli/commands/init.d.ts +9 -3
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +162 -137
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/import-rule-module.d.ts +29 -0
- package/dist/cli/import-rule-module.d.ts.map +1 -0
- package/dist/cli/import-rule-module.js +60 -0
- package/dist/cli/import-rule-module.js.map +1 -0
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +98 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/load-rules.d.ts +6 -11
- package/dist/cli/load-rules.d.ts.map +1 -1
- package/dist/cli/load-rules.js +21 -29
- package/dist/cli/load-rules.js.map +1 -1
- package/dist/cli/resolve-config.d.ts.map +1 -1
- package/dist/cli/resolve-config.js +24 -14
- package/dist/cli/resolve-config.js.map +1 -1
- package/dist/cli/rule-file-findings.d.ts +178 -0
- package/dist/cli/rule-file-findings.d.ts.map +1 -0
- package/dist/cli/rule-file-findings.js +293 -0
- package/dist/cli/rule-file-findings.js.map +1 -0
- package/dist/cli/watch.d.ts +20 -7
- package/dist/cli/watch.d.ts.map +1 -1
- package/dist/cli/watch.js +39 -14
- package/dist/cli/watch.js.map +1 -1
- package/dist/conditions/body-analysis-function.d.ts.map +1 -1
- package/dist/conditions/body-analysis-function.js +16 -7
- package/dist/conditions/body-analysis-function.js.map +1 -1
- package/dist/conditions/body-analysis-module.d.ts.map +1 -1
- package/dist/conditions/body-analysis-module.js +14 -9
- package/dist/conditions/body-analysis-module.js.map +1 -1
- package/dist/conditions/body-analysis.d.ts.map +1 -1
- package/dist/conditions/body-analysis.js +16 -7
- package/dist/conditions/body-analysis.js.map +1 -1
- package/dist/conditions/call.d.ts.map +1 -1
- package/dist/conditions/call.js +38 -17
- package/dist/conditions/call.js.map +1 -1
- package/dist/conditions/catch-analysis.d.ts +2 -1
- package/dist/conditions/catch-analysis.d.ts.map +1 -1
- package/dist/conditions/catch-analysis.js.map +1 -1
- package/dist/conditions/cross-layer.d.ts +2 -10
- package/dist/conditions/cross-layer.d.ts.map +1 -1
- package/dist/conditions/cross-layer.js +227 -23
- package/dist/conditions/cross-layer.js.map +1 -1
- package/dist/conditions/dependency.d.ts +35 -6
- package/dist/conditions/dependency.d.ts.map +1 -1
- package/dist/conditions/dependency.js +299 -45
- package/dist/conditions/dependency.js.map +1 -1
- package/dist/conditions/exports.d.ts.map +1 -1
- package/dist/conditions/exports.js +10 -0
- package/dist/conditions/exports.js.map +1 -1
- package/dist/conditions/function.d.ts.map +1 -1
- package/dist/conditions/function.js +19 -5
- package/dist/conditions/function.js.map +1 -1
- package/dist/conditions/helpers.d.ts +2 -1
- package/dist/conditions/helpers.d.ts.map +1 -1
- package/dist/conditions/helpers.js +4 -1
- package/dist/conditions/helpers.js.map +1 -1
- package/dist/conditions/jsx.d.ts.map +1 -1
- package/dist/conditions/jsx.js +7 -2
- package/dist/conditions/jsx.js.map +1 -1
- package/dist/conditions/match-identity.d.ts +43 -0
- package/dist/conditions/match-identity.d.ts.map +1 -0
- package/dist/conditions/match-identity.js +43 -0
- package/dist/conditions/match-identity.js.map +1 -0
- package/dist/conditions/members.d.ts.map +1 -1
- package/dist/conditions/members.js +17 -4
- package/dist/conditions/members.js.map +1 -1
- package/dist/conditions/reverse-dependency.d.ts +18 -5
- package/dist/conditions/reverse-dependency.d.ts.map +1 -1
- package/dist/conditions/reverse-dependency.js +109 -92
- package/dist/conditions/reverse-dependency.js.map +1 -1
- package/dist/conditions/slice.d.ts +86 -2
- package/dist/conditions/slice.d.ts.map +1 -1
- package/dist/conditions/slice.js +234 -53
- package/dist/conditions/slice.js.map +1 -1
- package/dist/conditions/structural.d.ts.map +1 -1
- package/dist/conditions/structural.js +16 -4
- package/dist/conditions/structural.js.map +1 -1
- package/dist/core/check-all.d.ts +19 -0
- package/dist/core/check-all.d.ts.map +1 -0
- package/dist/core/check-all.js +96 -0
- package/dist/core/check-all.js.map +1 -0
- package/dist/core/combinators.d.ts +48 -0
- package/dist/core/combinators.d.ts.map +1 -0
- package/dist/core/combinators.js +56 -0
- package/dist/core/combinators.js.map +1 -0
- package/dist/core/descendant-cache.d.ts +36 -0
- package/dist/core/descendant-cache.d.ts.map +1 -0
- package/dist/core/descendant-cache.js +119 -0
- package/dist/core/descendant-cache.js.map +1 -0
- package/dist/core/diagnose.d.ts +234 -0
- package/dist/core/diagnose.d.ts.map +1 -0
- package/dist/core/diagnose.js +382 -0
- package/dist/core/diagnose.js.map +1 -0
- package/dist/core/disk-set.d.ts +48 -0
- package/dist/core/disk-set.d.ts.map +1 -0
- package/dist/core/disk-set.js +218 -0
- package/dist/core/disk-set.js.map +1 -0
- package/dist/core/element-cache.d.ts +75 -0
- package/dist/core/element-cache.d.ts.map +1 -0
- package/dist/core/element-cache.js +32 -0
- package/dist/core/element-cache.js.map +1 -0
- package/dist/core/empty-project-advice.d.ts +75 -0
- package/dist/core/empty-project-advice.d.ts.map +1 -0
- package/dist/core/empty-project-advice.js +61 -0
- package/dist/core/empty-project-advice.js.map +1 -0
- package/dist/core/exclusion-comments.d.ts +54 -0
- package/dist/core/exclusion-comments.d.ts.map +1 -0
- package/dist/core/exclusion-comments.js +310 -0
- package/dist/core/exclusion-comments.js.map +1 -0
- package/dist/core/execute-rule.d.ts +150 -0
- package/dist/core/execute-rule.d.ts.map +1 -0
- package/dist/core/execute-rule.js +580 -0
- package/dist/core/execute-rule.js.map +1 -0
- package/dist/core/glob-diagnosis.d.ts +78 -0
- package/dist/core/glob-diagnosis.d.ts.map +1 -0
- package/dist/core/glob-diagnosis.js +134 -0
- package/dist/core/glob-diagnosis.js.map +1 -0
- package/dist/core/glob-evaluator.d.ts +48 -0
- package/dist/core/glob-evaluator.d.ts.map +1 -0
- package/dist/core/glob-evaluator.js +89 -0
- package/dist/core/glob-evaluator.js.map +1 -0
- package/dist/core/import-candidates.d.ts +60 -0
- package/dist/core/import-candidates.d.ts.map +1 -0
- package/dist/core/import-candidates.js +93 -0
- package/dist/core/import-candidates.js.map +1 -0
- package/dist/core/import-options.d.ts +55 -2
- package/dist/core/import-options.d.ts.map +1 -1
- package/dist/core/import-options.js +64 -0
- package/dist/core/import-options.js.map +1 -1
- package/dist/core/line-index.d.ts +3 -0
- package/dist/core/line-index.d.ts.map +1 -0
- package/dist/core/line-index.js +229 -0
- package/dist/core/line-index.js.map +1 -0
- package/dist/core/metric-violation.d.ts +86 -0
- package/dist/core/metric-violation.d.ts.map +1 -0
- package/dist/core/metric-violation.js +132 -0
- package/dist/core/metric-violation.js.map +1 -0
- package/dist/core/module-edges.d.ts +361 -0
- package/dist/core/module-edges.d.ts.map +1 -0
- package/dist/core/module-edges.js +592 -0
- package/dist/core/module-edges.js.map +1 -0
- package/dist/core/object-literal-functions.d.ts +42 -0
- package/dist/core/object-literal-functions.d.ts.map +1 -0
- package/dist/core/object-literal-functions.js +75 -0
- package/dist/core/object-literal-functions.js.map +1 -0
- package/dist/core/orphan-exclusions.d.ts +44 -0
- package/dist/core/orphan-exclusions.d.ts.map +1 -0
- package/dist/core/orphan-exclusions.js +214 -0
- package/dist/core/orphan-exclusions.js.map +1 -0
- package/dist/core/pair-condition.d.ts +32 -3
- package/dist/core/pair-condition.d.ts.map +1 -1
- package/dist/core/path-universe.d.ts +54 -0
- package/dist/core/path-universe.d.ts.map +1 -0
- package/dist/core/path-universe.js +65 -0
- package/dist/core/path-universe.js.map +1 -0
- package/dist/core/per-root-compiler-options.d.ts +20 -0
- package/dist/core/per-root-compiler-options.d.ts.map +1 -0
- package/dist/core/per-root-compiler-options.js +57 -0
- package/dist/core/per-root-compiler-options.js.map +1 -0
- package/dist/core/project-relative.d.ts +98 -0
- package/dist/core/project-relative.d.ts.map +1 -0
- package/dist/core/project-relative.js +185 -0
- package/dist/core/project-relative.js.map +1 -0
- package/dist/core/project.d.ts +7 -1
- package/dist/core/project.d.ts.map +1 -1
- package/dist/core/project.js +24 -0
- package/dist/core/project.js.map +1 -1
- package/dist/core/rule-builder.d.ts +282 -0
- package/dist/core/rule-builder.d.ts.map +1 -0
- package/dist/core/rule-builder.js +445 -0
- package/dist/core/rule-builder.js.map +1 -0
- package/dist/core/rule-declaration.d.ts +32 -0
- package/dist/core/rule-declaration.d.ts.map +1 -0
- package/dist/core/rule-declaration.js +126 -0
- package/dist/core/rule-declaration.js.map +1 -0
- package/dist/core/terminal-builder.d.ts +625 -0
- package/dist/core/terminal-builder.d.ts.map +1 -0
- package/dist/core/terminal-builder.js +787 -0
- package/dist/core/terminal-builder.js.map +1 -0
- package/dist/core/terminal-execution.d.ts +23 -0
- package/dist/core/terminal-execution.d.ts.map +1 -0
- package/dist/core/terminal-execution.js +71 -0
- package/dist/core/terminal-execution.js.map +1 -0
- package/dist/core/vacuity-diagnosis.d.ts +103 -0
- package/dist/core/vacuity-diagnosis.d.ts.map +1 -0
- package/dist/core/vacuity-diagnosis.js +415 -0
- package/dist/core/vacuity-diagnosis.js.map +1 -0
- package/dist/core/violation.d.ts +16 -11
- package/dist/core/violation.d.ts.map +1 -1
- package/dist/core/violation.js +33 -13
- package/dist/core/violation.js.map +1 -1
- package/dist/graphql/index.js +1 -1
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/resolver-rule-builder.d.ts +98 -23
- package/dist/graphql/resolver-rule-builder.d.ts.map +1 -1
- package/dist/graphql/resolver-rule-builder.js +151 -21
- package/dist/graphql/resolver-rule-builder.js.map +1 -1
- package/dist/graphql/schema-loader.d.ts +21 -0
- package/dist/graphql/schema-loader.d.ts.map +1 -1
- package/dist/graphql/schema-loader.js +81 -12
- package/dist/graphql/schema-loader.js.map +1 -1
- package/dist/graphql/schema-rule-builder.d.ts +57 -20
- package/dist/graphql/schema-rule-builder.d.ts.map +1 -1
- package/dist/graphql/schema-rule-builder.js +101 -25
- package/dist/graphql/schema-rule-builder.js.map +1 -1
- package/dist/helpers/baseline-diagnostics.d.ts +45 -0
- package/dist/helpers/baseline-diagnostics.d.ts.map +1 -0
- package/dist/helpers/baseline-diagnostics.js +168 -0
- package/dist/helpers/baseline-diagnostics.js.map +1 -0
- package/dist/helpers/baseline-generator.d.ts +19 -0
- package/dist/helpers/baseline-generator.d.ts.map +1 -0
- package/dist/helpers/baseline-generator.js +18 -0
- package/dist/helpers/baseline-generator.js.map +1 -0
- package/dist/helpers/baseline.d.ts +336 -0
- package/dist/helpers/baseline.d.ts.map +1 -0
- package/dist/helpers/baseline.js +664 -0
- package/dist/helpers/baseline.js.map +1 -0
- package/dist/helpers/body-traversal.d.ts +27 -2
- package/dist/helpers/body-traversal.d.ts.map +1 -1
- package/dist/helpers/body-traversal.js +122 -16
- package/dist/helpers/body-traversal.js.map +1 -1
- package/dist/helpers/callback-extractor.d.ts.map +1 -1
- package/dist/helpers/callback-extractor.js +48 -47
- package/dist/helpers/callback-extractor.js.map +1 -1
- package/dist/helpers/complexity.d.ts +13 -5
- package/dist/helpers/complexity.d.ts.map +1 -1
- package/dist/helpers/complexity.js +15 -6
- package/dist/helpers/complexity.js.map +1 -1
- package/dist/helpers/diff-aware.d.ts +44 -0
- package/dist/helpers/diff-aware.d.ts.map +1 -0
- package/dist/helpers/diff-aware.js +84 -0
- package/dist/helpers/diff-aware.js.map +1 -0
- package/dist/helpers/matchers.d.ts +68 -7
- package/dist/helpers/matchers.d.ts.map +1 -1
- package/dist/helpers/matchers.js +114 -20
- package/dist/helpers/matchers.js.map +1 -1
- package/dist/helpers/slice-graph.d.ts +100 -8
- package/dist/helpers/slice-graph.d.ts.map +1 -1
- package/dist/helpers/slice-graph.js +126 -34
- package/dist/helpers/slice-graph.js.map +1 -1
- package/dist/helpers/tarjan.d.ts +0 -14
- package/dist/helpers/tarjan.d.ts.map +1 -1
- package/dist/helpers/tarjan.js +29 -16
- package/dist/helpers/tarjan.js.map +1 -1
- package/dist/index.d.ts +56 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -13
- package/dist/index.js.map +1 -1
- package/dist/models/arch-call.d.ts +3 -2
- package/dist/models/arch-call.d.ts.map +1 -1
- package/dist/models/arch-call.js.map +1 -1
- package/dist/models/arch-function.d.ts +28 -6
- package/dist/models/arch-function.d.ts.map +1 -1
- package/dist/models/arch-function.js +106 -3
- package/dist/models/arch-function.js.map +1 -1
- package/dist/models/arch-jsx-element.js +2 -2
- package/dist/models/arch-jsx-element.js.map +1 -1
- package/dist/models/slice.d.ts +33 -9
- package/dist/models/slice.d.ts.map +1 -1
- package/dist/models/slice.js +129 -19
- package/dist/models/slice.js.map +1 -1
- package/dist/predicates/call.d.ts.map +1 -1
- package/dist/predicates/call.js +5 -0
- package/dist/predicates/call.js.map +1 -1
- package/dist/predicates/identity.d.ts +21 -0
- package/dist/predicates/identity.d.ts.map +1 -1
- package/dist/predicates/identity.js +75 -3
- package/dist/predicates/identity.js.map +1 -1
- package/dist/predicates/index.d.ts +1 -1
- package/dist/predicates/index.d.ts.map +1 -1
- package/dist/predicates/index.js +1 -1
- package/dist/predicates/index.js.map +1 -1
- package/dist/predicates/metrics.d.ts +4 -2
- package/dist/predicates/metrics.d.ts.map +1 -1
- package/dist/predicates/metrics.js +4 -2
- package/dist/predicates/metrics.js.map +1 -1
- package/dist/predicates/module.d.ts +4 -12
- package/dist/predicates/module.d.ts.map +1 -1
- package/dist/predicates/module.js +57 -37
- package/dist/predicates/module.js.map +1 -1
- package/dist/presets/agent-guardrails.d.ts +22 -15
- package/dist/presets/agent-guardrails.d.ts.map +1 -1
- package/dist/presets/agent-guardrails.js +124 -19
- package/dist/presets/agent-guardrails.js.map +1 -1
- package/dist/presets/boundaries.d.ts +45 -2
- package/dist/presets/boundaries.d.ts.map +1 -1
- package/dist/presets/boundaries.js +132 -23
- package/dist/presets/boundaries.js.map +1 -1
- package/dist/presets/data-layer.d.ts +22 -2
- package/dist/presets/data-layer.d.ts.map +1 -1
- package/dist/presets/data-layer.js +58 -16
- package/dist/presets/data-layer.js.map +1 -1
- package/dist/presets/index.d.ts +7 -5
- package/dist/presets/index.d.ts.map +1 -1
- package/dist/presets/index.js +9 -3
- package/dist/presets/index.js.map +1 -1
- package/dist/presets/layered.d.ts +46 -2
- package/dist/presets/layered.d.ts.map +1 -1
- package/dist/presets/layered.js +66 -29
- package/dist/presets/layered.js.map +1 -1
- package/dist/presets/recommended.d.ts +80 -9
- package/dist/presets/recommended.d.ts.map +1 -1
- package/dist/presets/recommended.js +25 -23
- package/dist/presets/recommended.js.map +1 -1
- package/dist/presets/shared.d.ts +270 -2
- package/dist/presets/shared.d.ts.map +1 -1
- package/dist/presets/shared.js +338 -4
- package/dist/presets/shared.js.map +1 -1
- package/dist/rules/code-quality.d.ts.map +1 -1
- package/dist/rules/code-quality.js +15 -6
- package/dist/rules/code-quality.js.map +1 -1
- package/dist/rules/hygiene.d.ts +1 -1
- package/dist/rules/hygiene.js +1 -1
- package/dist/rules/metrics-function.d.ts +2 -1
- package/dist/rules/metrics-function.d.ts.map +1 -1
- package/dist/rules/metrics-function.js +33 -6
- package/dist/rules/metrics-function.js.map +1 -1
- package/dist/rules/metrics.d.ts +2 -1
- package/dist/rules/metrics.d.ts.map +1 -1
- package/dist/rules/metrics.js +39 -9
- package/dist/rules/metrics.js.map +1 -1
- package/dist/smells/duplicate-bodies.d.ts +58 -5
- package/dist/smells/duplicate-bodies.d.ts.map +1 -1
- package/dist/smells/duplicate-bodies.js +137 -56
- package/dist/smells/duplicate-bodies.js.map +1 -1
- package/dist/smells/fingerprint.d.ts +8 -1
- package/dist/smells/fingerprint.d.ts.map +1 -1
- package/dist/smells/fingerprint.js +21 -3
- package/dist/smells/fingerprint.js.map +1 -1
- package/dist/smells/inconsistent-siblings.d.ts +128 -7
- package/dist/smells/inconsistent-siblings.d.ts.map +1 -1
- package/dist/smells/inconsistent-siblings.js +288 -86
- package/dist/smells/inconsistent-siblings.js.map +1 -1
- package/dist/smells/sibling-files.d.ts +47 -0
- package/dist/smells/sibling-files.d.ts.map +1 -0
- package/dist/smells/sibling-files.js +104 -0
- package/dist/smells/sibling-files.js.map +1 -0
- package/dist/smells/similar-pairs.d.ts +27 -0
- package/dist/smells/similar-pairs.d.ts.map +1 -0
- package/dist/smells/similar-pairs.js +52 -0
- package/dist/smells/similar-pairs.js.map +1 -0
- package/dist/smells/smell-builder.d.ts +57 -12
- package/dist/smells/smell-builder.d.ts.map +1 -1
- package/dist/smells/smell-builder.js +111 -14
- package/dist/smells/smell-builder.js.map +1 -1
- package/dist/tsconfig/index.d.ts +19 -0
- package/dist/tsconfig/index.d.ts.map +1 -0
- package/dist/tsconfig/index.js +20 -0
- package/dist/tsconfig/index.js.map +1 -0
- package/dist/tsconfig/tsconfig-builder.d.ts +42 -9
- package/dist/tsconfig/tsconfig-builder.d.ts.map +1 -1
- package/dist/tsconfig/tsconfig-builder.js +64 -16
- package/dist/tsconfig/tsconfig-builder.js.map +1 -1
- package/package.json +3 -2
- package/dist/core/module-dependencies.d.ts +0 -28
- package/dist/core/module-dependencies.d.ts.map +0 -1
- package/dist/core/module-dependencies.js +0 -35
- package/dist/core/module-dependencies.js.map +0 -1
- package/dist/helpers/within.d.ts.map +0 -1
- package/dist/helpers/within.js.map +0 -1
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
import { collectWithAssertionGuard as runWithGuard } from './terminal-execution.js';
|
|
2
|
+
import { zeroSubjectsAdviceOf, zeroSubjectsViolationOf } from './vacuity-diagnosis.js';
|
|
3
|
+
import { severityFor, subjectOf } from '@nielspeter/eess';
|
|
4
|
+
import { isSilent } from '@nielspeter/eess';
|
|
5
|
+
import { executeCheck, executeWarn, applyFilters } from './execute-rule.js';
|
|
6
|
+
import { shallowClone } from '@nielspeter/eess';
|
|
7
|
+
/**
|
|
8
|
+
* Declaring both emptiness assertions is a contradiction, and 0069's appendix
|
|
9
|
+
* says it "must fail at build time, not silently pick one".
|
|
10
|
+
*
|
|
11
|
+
* Thrown when the chain is built rather than reported as a finding: this is not
|
|
12
|
+
* a property of the codebase under test, it is a rule that cannot be evaluated,
|
|
13
|
+
* and the author is standing right there.
|
|
14
|
+
*
|
|
15
|
+
* `TypeError`, following `combinators.ts`'s "cannot mix Predicate objects and
|
|
16
|
+
* TypeMatcher functions" — the same class of build-time misuse. A bare `Error`
|
|
17
|
+
* was written first and this project's own `quality/typed-errors` rule rejected
|
|
18
|
+
* it, which is the third time in this plan's implementation that the dogfooding
|
|
19
|
+
* caught the new code.
|
|
20
|
+
*/
|
|
21
|
+
const CONTRADICTION = '.expectEmpty() and .expectNonEmpty() on the same rule contradict each other — ' +
|
|
22
|
+
'a selection cannot be required to be both empty and non-empty. Keep the one you mean.';
|
|
23
|
+
/**
|
|
24
|
+
* True when two or more of this batch's RAW violations would share one
|
|
25
|
+
* `subjectOf()` before `disambiguateIdentities()` (`applyFilters()`'s own
|
|
26
|
+
* first step) repairs the collision with a positional `#1`/`#2` suffix —
|
|
27
|
+
* plan 0090's own review found this, reproduced: a rule whose FIXED violation
|
|
28
|
+
* held the bare subject and whose NEW violation lands on the same position a
|
|
29
|
+
* PRIOR run's `#1` occupied gets the identical accepted-list identity the
|
|
30
|
+
* fixed one held — a genuinely new finding silently absorbed as accepted,
|
|
31
|
+
* because the identity `accepted` was built from was never stable, only
|
|
32
|
+
* positionally stable. That is bug 0084's exact swap-blindness, reintroduced
|
|
33
|
+
* through the identity primitive `accepted` is built on rather than through a
|
|
34
|
+
* bare count.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately coarse: this does not try to isolate which violations
|
|
37
|
+
* collided and escalate only those — `violations()` escalates the WHOLE
|
|
38
|
+
* batch once this is true, because a batch whose identities are not reliably
|
|
39
|
+
* unique is a batch `accepted` cannot safely reason about AT ALL, and
|
|
40
|
+
* silently trusting the non-colliding remainder risks the same false
|
|
41
|
+
* confidence in a smaller blast radius. The remedy is on the rule, not the
|
|
42
|
+
* mechanism: narrow the selector or qualify the condition's message so
|
|
43
|
+
* violations do not collide (`ArchViolation.identity`'s own doc comment
|
|
44
|
+
* states this contract), not to make the escalation more surgical.
|
|
45
|
+
*
|
|
46
|
+
* Computed independently of `identityCollisions()`/`resetIdentityCollisions()`
|
|
47
|
+
* (`violation.ts`) — that channel is global, mutable, disclosure-only state
|
|
48
|
+
* for a different consumer (a test asserting a specific producer collided);
|
|
49
|
+
* reading or resetting it from inside `violations()`, a widely-called,
|
|
50
|
+
* side-effect-free accessor, would couple this to a different feature's
|
|
51
|
+
* instrumentation. This is a pure recomputation instead, using the same
|
|
52
|
+
* `rule::subject` grouping key `disambiguateIdentities()` itself groups on.
|
|
53
|
+
*/
|
|
54
|
+
function hasIdentityCollision(violations) {
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
for (const v of violations) {
|
|
57
|
+
const key = `${v.rule}::${subjectOf(v)}`;
|
|
58
|
+
if (seen.has(key))
|
|
59
|
+
return true;
|
|
60
|
+
seen.add(key);
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The declaration half of every rule builder: what the author states.
|
|
66
|
+
*
|
|
67
|
+
* Owns `because`, `rule`, `excluding`, `describeRule`, `asSeverity`, the two
|
|
68
|
+
* emptiness assertions and the filter state — everything a chain accumulates
|
|
69
|
+
* BEFORE anything runs. {@link TerminalBuilder} extends this with the terminals
|
|
70
|
+
* and the execution behind them; a builder subclasses that one and inherits both
|
|
71
|
+
* halves, so the split is in this file rather than in their contracts.
|
|
72
|
+
*
|
|
73
|
+
* `RuleBuilder<T>` extends this too, as of plan 0069. It used not to, and the
|
|
74
|
+
* old docstring said so plainly: "RuleBuilder does NOT extend this because it
|
|
75
|
+
* predates it." The cost was not the duplicated methods, it was that every
|
|
76
|
+
* safety feature added to one root silently did not reach the builders on the
|
|
77
|
+
* other — which is what bug 0013 cost. Anything that must hold for all thirteen
|
|
78
|
+
* builders now has exactly one place to live.
|
|
79
|
+
*/
|
|
80
|
+
class RuleDeclaration {
|
|
81
|
+
_reason;
|
|
82
|
+
_metadata;
|
|
83
|
+
_severity;
|
|
84
|
+
/**
|
|
85
|
+
* Plan 0090 — the debt half of a `warn`. `undefined` means an ADVISORY
|
|
86
|
+
* warning: permanent, no ceiling, exactly today's `.asSeverity('warn')`.
|
|
87
|
+
* Set means DEFERRED: a violation whose `subjectOf()` is in this list stays
|
|
88
|
+
* `warn`; anything not in it escalates to `error` in `violations()` — a new
|
|
89
|
+
* finding this list did not accept. See `asSeverity()`'s own doc comment.
|
|
90
|
+
*/
|
|
91
|
+
_acceptedWarnings;
|
|
92
|
+
// `protected`, not `private`. `RuleBuilder` declared these `protected` before
|
|
93
|
+
// the single-root refactor and both classes are public exports, so narrowing
|
|
94
|
+
// them is a compile break for an external subclass — the same argument that
|
|
95
|
+
// kept `globs()` concrete rather than abstract.
|
|
96
|
+
_exclusions = [];
|
|
97
|
+
_silentIndices = new Set();
|
|
98
|
+
/**
|
|
99
|
+
* The declared-empty grammar — plan 0097, and [ts-archunit ADR-010](https://github.com/nielspeter/ts-archunit/blob/main/adr/010-the-extension-surface-is-a-contract.md)
|
|
100
|
+
* rule 3(a).
|
|
101
|
+
*
|
|
102
|
+
* These lived on `RuleBuilder<T>`, so the smell family — the one bug 0066 is
|
|
103
|
+
* filed against — could not reach them: that bug listed "is `.expectEmpty()`
|
|
104
|
+
* reachable on a smell builder at all?" under Not measured, and the answer was
|
|
105
|
+
* no. ADR-010 part 3 requires every family's grammar to expose a declaration
|
|
106
|
+
* path, so they belong on the root every family shares.
|
|
107
|
+
*
|
|
108
|
+
* Booleans rather than registry membership, deliberately. `shallowClone` in
|
|
109
|
+
* `copy()` carries an own property for free on every chain step, where a
|
|
110
|
+
* `WeakSet` keyed on the builder would be lost at the first `copy()` — and the
|
|
111
|
+
* threat model differs from the one that forced the cardinality registry,
|
|
112
|
+
* which guards user-CONSTRUCTIBLE condition objects. These sit behind a
|
|
113
|
+
* sanctioned method on a class whose only other audience is ADR-010's
|
|
114
|
+
* contract.
|
|
115
|
+
*/
|
|
116
|
+
_requireNonEmpty = false;
|
|
117
|
+
_expectEmpty = false;
|
|
118
|
+
/**
|
|
119
|
+
* Assert that the predicate chain matches at least one subject. If the
|
|
120
|
+
* filtered subject set is empty, the rule FAILS with a config-level
|
|
121
|
+
* meta-finding instead of passing vacuously — the "0 === 0" false-green
|
|
122
|
+
* ADR-008 forbids. Opt-in: legitimately-empty selections (e.g. "no
|
|
123
|
+
* repositories yet") stay green without it. Built on the materialized
|
|
124
|
+
* subject set (plan 0064); the finding bypasses diff/baseline (plan 0067).
|
|
125
|
+
*/
|
|
126
|
+
expectNonEmpty() {
|
|
127
|
+
if (this._expectEmpty)
|
|
128
|
+
throw new TypeError(CONTRADICTION);
|
|
129
|
+
const next = this.copy();
|
|
130
|
+
next._requireNonEmpty = true;
|
|
131
|
+
return next;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Assert that this selector matches **nothing**, and fail the day it matches
|
|
135
|
+
* something.
|
|
136
|
+
*
|
|
137
|
+
* Plan 0074 (R3b). Since an empty selection is now a failure by default, a
|
|
138
|
+
* rule whose selection is legitimately empty needs a way to say so — and
|
|
139
|
+
* 0069's appendix rejected `.allowEmpty()` for being "one word, silent
|
|
140
|
+
* forever, typo or not, and nothing revisits it". This is the shape that
|
|
141
|
+
* survived review, and the difference is that it is an **assertion**:
|
|
142
|
+
*
|
|
143
|
+
* ```ts
|
|
144
|
+
* classes(p).that().haveDecorator('Deprecated')
|
|
145
|
+
* .expectEmpty() // nothing is deprecated yet
|
|
146
|
+
* .should().beExported()
|
|
147
|
+
*
|
|
148
|
+
* // the day someone deprecates a class:
|
|
149
|
+
* // FAIL: .expectEmpty() asserted 0 subjects, found 1
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* An agent that reaches for this to silence a real typo gets a different
|
|
153
|
+
* failure the moment the typo is fixed, and until then the intent is stated
|
|
154
|
+
* in the rule where a reader sees it — rather than in a baseline, or nowhere.
|
|
155
|
+
*
|
|
156
|
+
* Symmetric with {@link expectNonEmpty} for the rule builders. A family may **refuse** the zero-arg form where a whole-rule notion of empty has no meaning — `CrossProjectBuilder` throws, because it declares per side — so a consumer walking `TerminalBuilder[]` must not call it unguarded. Exactly symmetric, and the two together mean
|
|
157
|
+
* the empty/non-empty question is always answerable from the rule text.
|
|
158
|
+
* Declaring both is a contradiction and throws here rather than silently
|
|
159
|
+
* picking one.
|
|
160
|
+
*/
|
|
161
|
+
expectEmpty() {
|
|
162
|
+
if (this._requireNonEmpty)
|
|
163
|
+
throw new TypeError(CONTRADICTION);
|
|
164
|
+
const next = this.copy();
|
|
165
|
+
next._expectEmpty = true;
|
|
166
|
+
return next;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* True when this rule's emptiness is DECLARED — plan 0097, consumed by 0098.
|
|
170
|
+
*
|
|
171
|
+
* The base answer is the whole-rule flag. It is `protected` and overridable
|
|
172
|
+
* because a family whose declaration is not whole-rule must be able to say so:
|
|
173
|
+
* `CrossProjectBuilder` declares per SIDE and refuses the zero-arg form
|
|
174
|
+
* entirely, so `_expectEmpty` is unreachable there and this default would
|
|
175
|
+
* answer `false` for a rule whose every side the author declared — reporting a
|
|
176
|
+
* finding that tells them to declare what they declared, which is ADR-008
|
|
177
|
+
* rule 2's loop.
|
|
178
|
+
*
|
|
179
|
+
* PUBLIC, and forced rather than chosen — the same reason `assertsSomething()`
|
|
180
|
+
* is: `DiagnosableRule` is a structural interface, and a protected member
|
|
181
|
+
* cannot satisfy it. Plan 0096's preview is the first reader, and a preview
|
|
182
|
+
* that ignored the declaration would report a finding on a rule the gate will
|
|
183
|
+
* accept — over-reporting against the very thing it previews, which is the
|
|
184
|
+
* rule 5 violation inside the migration that plan warns about.
|
|
185
|
+
*
|
|
186
|
+
* It exists now, ahead of the floor that reads it, for a reason worth stating:
|
|
187
|
+
* a private version of this lived on `CrossProjectBuilder` and was deleted
|
|
188
|
+
* as dead code, correctly — but the deletion also removed the only expression
|
|
189
|
+
* of the concept, so the override 0098 needs would have gone missing SILENTLY
|
|
190
|
+
* rather than as the compile error a narrowed-visibility clash would have
|
|
191
|
+
* produced. Declaring it here makes the coupling loud again.
|
|
192
|
+
*/
|
|
193
|
+
declaresEmpty() {
|
|
194
|
+
return this._expectEmpty;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* How THIS family spells the declaration, for a remedy that names a real call.
|
|
198
|
+
*
|
|
199
|
+
* The sibling of `assertionAdvice()`, and it exists for the same reason: a
|
|
200
|
+
* remedy is only verified to remediate if following it works, and the generic
|
|
201
|
+
* `.expectEmpty()` is a `TypeError` on `CrossProjectBuilder`, which declares
|
|
202
|
+
* per side. Advice that names the one form the reader cannot call is ADR-008
|
|
203
|
+
* rule 2's failure with extra confidence.
|
|
204
|
+
*
|
|
205
|
+
* Overriding `expectEmpty()` and not this leaves a remedy that throws, so the
|
|
206
|
+
* classification census in `evidence-at-every-seam.test.ts` requires both
|
|
207
|
+
* together rather than trusting the next author to remember.
|
|
208
|
+
*/
|
|
209
|
+
emptyDeclarationAdvice() {
|
|
210
|
+
// The AUTHOR states it; core does not guess. This branched on
|
|
211
|
+
// `id.startsWith('preset/')`, which core cannot verify — false for a
|
|
212
|
+
// hand-written `.rule({ id: 'preset/...' })`, for a third-party preset that
|
|
213
|
+
// never extended `PresetBaseOptions`, and for one that forwards `overrides`
|
|
214
|
+
// but not `expectEmpty`. The method exists precisely to stop advice naming a
|
|
215
|
+
// call the reader cannot make, so deriving it from a string prefix gave up
|
|
216
|
+
// the discipline it was added to enforce.
|
|
217
|
+
return this._metadata?.declarationSpelling ?? '.expectEmpty()';
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Attach a human-readable rationale to the rule.
|
|
221
|
+
* Included in violation messages when `.check()` throws.
|
|
222
|
+
*/
|
|
223
|
+
because(reason) {
|
|
224
|
+
const next = this.copy();
|
|
225
|
+
next._reason = reason;
|
|
226
|
+
return next;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Attach rich metadata to the rule.
|
|
230
|
+
* Provides educational context in violation output: why, how to fix, docs link.
|
|
231
|
+
*
|
|
232
|
+
* If `metadata.because` is set, it also sets the reason (same as `.because()`).
|
|
233
|
+
*/
|
|
234
|
+
rule(metadata) {
|
|
235
|
+
const next = this.copy();
|
|
236
|
+
next._metadata = metadata;
|
|
237
|
+
if (metadata.because) {
|
|
238
|
+
next._reason = metadata.because;
|
|
239
|
+
}
|
|
240
|
+
return next;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Exclude specific violations from reporting by matching against
|
|
244
|
+
* the violation's `element`, `file`, or `message` fields.
|
|
245
|
+
*
|
|
246
|
+
* Matched violations are silently suppressed. Use for permanent,
|
|
247
|
+
* intentional exceptions — not for temporary violations (use baseline for those).
|
|
248
|
+
*
|
|
249
|
+
* Patterns are matched against all three fields. Prefer anchored regexes
|
|
250
|
+
* or full string matches over short substrings, especially for `message`
|
|
251
|
+
* matching, to avoid accidentally suppressing unrelated violations whose
|
|
252
|
+
* messages happen to contain the same text.
|
|
253
|
+
*
|
|
254
|
+
* Emits a warning if an exclusion matches zero violations — so renamed
|
|
255
|
+
* or deleted exceptions don't silently stay in the rule.
|
|
256
|
+
*
|
|
257
|
+
* A configuration meta-finding — one that reports the rule enforces
|
|
258
|
+
* nothing — is never excludable, and an exclusion that would have matched
|
|
259
|
+
* one is reported as refused rather than as stale.
|
|
260
|
+
*
|
|
261
|
+
* For narrowing a rule's scope at the predicate phase (so the rule never
|
|
262
|
+
* evaluates the excluded element), use `satisfy(not(<predicate>))` instead.
|
|
263
|
+
* See the "Excluding a file from a rule's scope" recipe in docs/recipes.md.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* // Exclude by element name (fully qualified)
|
|
267
|
+
* .excluding('Asset.getImageUrl')
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* // Exclude by file path (regex anchored to suffix)
|
|
271
|
+
* .excluding(/repositories\/index\.ts$/)
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* // Multiple exclusions, mixed forms
|
|
275
|
+
* .excluding('Asset.getImageUrl', /\/legacy\//, /generated/)
|
|
276
|
+
*/
|
|
277
|
+
excluding(...patterns) {
|
|
278
|
+
const next = this.copy();
|
|
279
|
+
for (const p of patterns) {
|
|
280
|
+
if (isSilent(p)) {
|
|
281
|
+
next._exclusions.push(p.pattern);
|
|
282
|
+
next._silentIndices.add(next._exclusions.length - 1);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
next._exclusions.push(p);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return next;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Return a structured description of this rule without executing it.
|
|
292
|
+
* Used by the `explain` CLI subcommand.
|
|
293
|
+
*/
|
|
294
|
+
describeRule() {
|
|
295
|
+
return {
|
|
296
|
+
rule: this._metadata?.id ?? 'unnamed',
|
|
297
|
+
id: this._metadata?.id,
|
|
298
|
+
because: this._reason,
|
|
299
|
+
suggestion: this._metadata?.suggestion,
|
|
300
|
+
docs: this._metadata?.docs,
|
|
301
|
+
imperative: this._metadata?.imperative ?? this._reason,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Set this rule's severity, returning a COPY — the held builder is never
|
|
306
|
+
* mutated.
|
|
307
|
+
*
|
|
308
|
+
* `options.accepted` applies only to `'warn'` and names the violations already
|
|
309
|
+
* accepted at that level; it is cleared when the level is `'error'`, so a rule
|
|
310
|
+
* escalated back to error cannot carry a stale allowlist that would silently
|
|
311
|
+
* excuse the very findings the escalation was for.
|
|
312
|
+
*
|
|
313
|
+
* A configuration finding ignores this entirely (`severityFor`): its severity
|
|
314
|
+
* is not the author's to lower.
|
|
315
|
+
*/
|
|
316
|
+
asSeverity(level, options) {
|
|
317
|
+
const next = this.copy();
|
|
318
|
+
next._severity = level;
|
|
319
|
+
next._acceptedWarnings = level === 'warn' ? options?.accepted : undefined;
|
|
320
|
+
return next;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Whether this rule asserts anything about what it selects (plan 0070).
|
|
324
|
+
*
|
|
325
|
+
* `false` means the rule can never fail, and as of 0.23.0
|
|
326
|
+
* `collectWithAssertionGuard()` turns that into an unsuppressable
|
|
327
|
+
* configuration finding on every terminal; `diagnose()` / `doctor` report it
|
|
328
|
+
* without running the rule. 0.22.0 shipped this hook with nothing at runtime
|
|
329
|
+
* reading it, because the gate drafted for that release wrote through a
|
|
330
|
+
* bespoke stderr channel that bypassed the formatter, the JSON payload, the
|
|
331
|
+
* annotation path and the exit code — a review found a defect at each of those
|
|
332
|
+
* seams, so the channel was withdrawn and the hook shipped on its own. The
|
|
333
|
+
* finding form reaches all four surfaces by construction, which is what the
|
|
334
|
+
* withdrawal bought.
|
|
335
|
+
*
|
|
336
|
+
* Concrete with a `true` default rather than abstract: both roots are public
|
|
337
|
+
* exports, so an abstract member is a compile break for an external subclass
|
|
338
|
+
* (the `globs()` argument). The default makes a new builder EXEMPT by
|
|
339
|
+
* default — the opposite polarity from `globs()`'s empty default, which only
|
|
340
|
+
* makes a builder invisible. The classification test in
|
|
341
|
+
* `tests/core/assertion-gate.test.ts` is what forces the decision for every
|
|
342
|
+
* exported builder.
|
|
343
|
+
*
|
|
344
|
+
* Public, not protected — `diagnose()` duck-types it through
|
|
345
|
+
* `DiagnosableRule`, and a protected member cannot satisfy a structural
|
|
346
|
+
* interface. Same forcing as `assertsSomething` on `RuleBuilder`, which was
|
|
347
|
+
* already shipped public.
|
|
348
|
+
*/
|
|
349
|
+
assertsSomething() {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* The remedy for this builder's assertion-less state, as one string.
|
|
354
|
+
*
|
|
355
|
+
* The "one string, one place" channel: `diagnose()`'s advice and the
|
|
356
|
+
* finding's message and `suggestion` all read this method, so the diagnostic
|
|
357
|
+
* a consumer runs before upgrading and the failure they get after cannot
|
|
358
|
+
* disagree — plan 0070 round 2 measured an earlier design shipping two
|
|
359
|
+
* diverging texts for one state.
|
|
360
|
+
*
|
|
361
|
+
* Public for the same `DiagnosableRule` duck-typing reason as
|
|
362
|
+
* `assertsSomething` — plan 0070 drafted this `protected`, and a protected
|
|
363
|
+
* member cannot satisfy the structural interface `diagnose()` consumes.
|
|
364
|
+
*/
|
|
365
|
+
assertionAdvice() {
|
|
366
|
+
return 'this rule asserts nothing, so it can never fail. Add an assertion, or delete the rule.';
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Every glob declaration this rule makes, as independent trees.
|
|
370
|
+
*
|
|
371
|
+
* One entry per independent declaration, because each one dies on its own:
|
|
372
|
+
* a rule whose selector is satisfiable and whose discovery glob is not has
|
|
373
|
+
* exactly one fault, and reporting them as one tree would lose that.
|
|
374
|
+
*
|
|
375
|
+
* Concrete with an empty default rather than abstract. Making it abstract
|
|
376
|
+
* would enumerate every builder at compile time — genuinely attractive, and
|
|
377
|
+
* how the census refactor did it — but `RuleBuilder` and `TerminalBuilder`
|
|
378
|
+
* are both public exports, so an abstract member is a compile break for
|
|
379
|
+
* anyone who has subclassed them. R2a is the release people install in order
|
|
380
|
+
* to MEASURE before R3 flips anything; it cannot be the one that fails to
|
|
381
|
+
* compile. The vacuity that `abstract` would have caught is caught instead
|
|
382
|
+
* by a test that reflects over both entry points and fails a `return []`
|
|
383
|
+
* stub, which the compiler could not have done anyway.
|
|
384
|
+
*/
|
|
385
|
+
globs() {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* The project this rule was built against, when it has one.
|
|
390
|
+
*
|
|
391
|
+
* Concrete returning `undefined` rather than abstract, for the reason
|
|
392
|
+
* `globs()` above records: both roots are public exports, so an abstract
|
|
393
|
+
* member is a compile break for an external subclass. `RuleBuilder`
|
|
394
|
+
* overrides it with the non-optional form.
|
|
395
|
+
*
|
|
396
|
+
* The glob gate needs it, and a builder that cannot name its project simply
|
|
397
|
+
* has its globs left unchecked — the same fallback `diagnose()` takes, and
|
|
398
|
+
* the honest one: satisfiability is meaningless without a path universe to
|
|
399
|
+
* take it against.
|
|
400
|
+
*/
|
|
401
|
+
getProject() {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Selector globs that can never match — plan 0074 (R3b), the flip.
|
|
406
|
+
*
|
|
407
|
+
* 0069's decision table, not reopened here: a **selector** glob that is
|
|
408
|
+
* unsatisfiable means the rule can never have subjects, so it certifies
|
|
409
|
+
* nothing and passing is a lie. `discovery` already fails (0067-D, and the
|
|
410
|
+
* slice builders own their own message). `condition` and `exclusion` are
|
|
411
|
+
* **never** faults — a condition glob matching nothing is indistinguishable
|
|
412
|
+
* from an armed tripwire that has not fired, and plan 0072 got that wrong
|
|
413
|
+
* twice before it stayed written down.
|
|
414
|
+
*
|
|
415
|
+
* Negative polarity is excluded by `isDeadGlobTree` itself: `not(dead)`
|
|
416
|
+
* over-selects rather than under-selecting, so it cannot be dead.
|
|
417
|
+
*
|
|
418
|
+
* This is the same computation `diagnose()` performs — deliberately, and
|
|
419
|
+
* from the same functions, so the pre-flight and the gate can never disagree
|
|
420
|
+
* about what is dead. `doctor` told adopters what R3b would fail on; if the
|
|
421
|
+
* two used separate logic that promise would be worth nothing.
|
|
422
|
+
*/
|
|
423
|
+
/**
|
|
424
|
+
* Whether any condition on this rule declares emptiness as its passing state.
|
|
425
|
+
*
|
|
426
|
+
* Concrete `false` default for the reason `globs()` records — an abstract
|
|
427
|
+
* member breaks external subclasses. `RuleBuilder` overrides it by reading
|
|
428
|
+
* its own conditions; the builders that take their condition as a
|
|
429
|
+
* constructor argument have no cardinality condition to declare.
|
|
430
|
+
*
|
|
431
|
+
* **PUBLIC since plan 0098**, for the same structural reason as
|
|
432
|
+
* `assertsSomething()` and `declaresEmpty()`: `diagnose()` reads it through a
|
|
433
|
+
* structural interface and a protected member cannot satisfy one. It became a
|
|
434
|
+
* reader the moment the rule-builder family started reporting evidence —
|
|
435
|
+
* `.should().notExist()` examines zero subjects *because that is what it
|
|
436
|
+
* asserts*, and previewing it as a fault tells the author their working rule
|
|
437
|
+
* is broken. Caught by this repo's own 53-rule suite, on the one rule in it
|
|
438
|
+
* that ends in `.notExist()`.
|
|
439
|
+
*
|
|
440
|
+
* Deliberately NOT folded into `declaresEmpty()`, though both suppress the
|
|
441
|
+
* same finding. They are different facts and plan 0099 needs them apart: its
|
|
442
|
+
* expiry branch reads the declaration flag alone, because `.notExist()` over a
|
|
443
|
+
* selection that has grown is the condition doing its job, never a declaration
|
|
444
|
+
* that has expired.
|
|
445
|
+
*/
|
|
446
|
+
assertsCardinality() {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* The narrowing THIS family applied, when it can name it — plan 0099.
|
|
451
|
+
*
|
|
452
|
+
* ADR-010 part 4 wants the zero-examined remedy to name the **actual excluder,
|
|
453
|
+
* including internal defaults**, because "fix your filters" to a user who wrote
|
|
454
|
+
* none sends an agent looking for filters that do not exist.
|
|
455
|
+
*
|
|
456
|
+
* The root cannot know this: only the family holds its own thresholds. Review
|
|
457
|
+
* measured the cost of leaving it out — `agentGuardrails({ src: '**\/lib/**',
|
|
458
|
+
* noCopyPaste: true })` over two one-line functions, a **correctly configured**
|
|
459
|
+
* rule pointing at real code, hard-failed with "widen it until it matches
|
|
460
|
+
* something" while the true cause was `minLines(5)`, a default the author never
|
|
461
|
+
* wrote and which that preset exposes no knob for.
|
|
462
|
+
*
|
|
463
|
+
* Returns `undefined` when a family has nothing specific to say, and the caller
|
|
464
|
+
* falls back to naming the possibility rather than asserting a cause it cannot
|
|
465
|
+
* verify.
|
|
466
|
+
*/
|
|
467
|
+
narrowingHint() {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* What this family counts in `examined` — plan 0099.
|
|
472
|
+
*
|
|
473
|
+
* `CollectResult.examined` is unit-typed per family (subjects, bodies, pairs,
|
|
474
|
+
* keys), so a message that prints a FILE count and then says the rule examined
|
|
475
|
+
* "0 of them" commits a category error. Measured on this repo: a rule whose
|
|
476
|
+
* glob matched essentially all 616 files and whose NAME predicate matched none
|
|
477
|
+
* printed "loaded 616 files, and this rule examined 0 of them" — sending the
|
|
478
|
+
* reader to widen a glob that was already maximal.
|
|
479
|
+
*/
|
|
480
|
+
examinedUnitNoun() {
|
|
481
|
+
return 'subjects';
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* An independent copy of this builder.
|
|
485
|
+
*
|
|
486
|
+
* **A held selection is immutable** (bug 0016). Every method that adds to a
|
|
487
|
+
* builder returns a copy instead of mutating `this`, so
|
|
488
|
+
*
|
|
489
|
+
* ```ts
|
|
490
|
+
* const repositories = classes(p).that().extend('BaseRepository')
|
|
491
|
+
* repositories.that().haveNameEndingWith('Legacy').should().notExist().check()
|
|
492
|
+
* repositories.should().beExported().check() // still ALL repositories
|
|
493
|
+
* ```
|
|
494
|
+
*
|
|
495
|
+
* works. Before this, narrowing a held selection edited it in place: the
|
|
496
|
+
* second rule silently inherited `Legacy` and reported on a subset — or on
|
|
497
|
+
* nothing, and then passed. Same for `.excluding()`, which leaked a
|
|
498
|
+
* suppression into every later rule off the same selection, and `.rule()`,
|
|
499
|
+
* which leaked the id that `// eess-exclude <id>` comments are matched
|
|
500
|
+
* against (`execute-rule.ts` gates the whole comment scan on `metadata.id`)
|
|
501
|
+
* and that a preset's severity `overrides` are keyed on — so a later rule
|
|
502
|
+
* inherited a suppression channel it never opted into.
|
|
503
|
+
*
|
|
504
|
+
* Not baselines: `hashViolation` keys on the rule *description*, never on
|
|
505
|
+
* `metadata.id`. An earlier version of this docstring said baselines and a
|
|
506
|
+
* `--rule` filter were keyed on the id. Neither is true, and `--rule` does
|
|
507
|
+
* not exist — the only occurrences of it in the repo were three copies of
|
|
508
|
+
* this sentence. ADR-009 rule 2: a failure may not assert a cause it cannot
|
|
509
|
+
* verify, and that includes naming a flag the CLI does not have.
|
|
510
|
+
*
|
|
511
|
+
* Cost is one object per chain link, against a ts-morph walk. Irrelevant.
|
|
512
|
+
*/
|
|
513
|
+
copy() {
|
|
514
|
+
const clone = shallowClone(this);
|
|
515
|
+
clone.adoptFilterState(this);
|
|
516
|
+
clone._metadata = this._metadata ? { ...this._metadata } : undefined;
|
|
517
|
+
return clone;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Give this builder independent copies of another's filter state.
|
|
521
|
+
*
|
|
522
|
+
* `copy()` shallow-copies every field, so without this a copy would share
|
|
523
|
+
* its parent's exclusion array by reference and `.excluding()` on one would
|
|
524
|
+
* silently mutate the other. The copy lives here rather than inline in
|
|
525
|
+
* `copy()` because the knowledge of what needs duplicating belongs with the
|
|
526
|
+
* fields — the same reason every other state-holding class overrides
|
|
527
|
+
* `copy()` instead of `copy()` knowing about their fields.
|
|
528
|
+
*/
|
|
529
|
+
adoptFilterState(source) {
|
|
530
|
+
this._exclusions = [...source._exclusions];
|
|
531
|
+
this._silentIndices = new Set(source._silentIndices);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* The terminals — `check()`, `warn()`, `violations()` — and the execution behind
|
|
536
|
+
* them.
|
|
537
|
+
*
|
|
538
|
+
* Split from {@link RuleDeclaration} because a builder does two jobs: it collects
|
|
539
|
+
* a declaration, and it runs it. Both halves were one class of 372 code lines,
|
|
540
|
+
* and the gate that measures class size was right about it.
|
|
541
|
+
*
|
|
542
|
+
* A subclass still extends `TerminalBuilder` and inherits both halves, so the ten
|
|
543
|
+
* builders that do are untouched — the split is in this file, not in their
|
|
544
|
+
* contracts.
|
|
545
|
+
*/
|
|
546
|
+
export class TerminalBuilder extends RuleDeclaration {
|
|
547
|
+
/**
|
|
548
|
+
* Does this builder diagnose its own empty discovery?
|
|
549
|
+
*
|
|
550
|
+
* `false` — the gate reports a dead `discovery` glob, which is the fix for
|
|
551
|
+
* [ts-archunit bug 0040](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0040-a-crosslayer-rule-reports-nothing-when-its-layer-resolves-nothing.md)'s
|
|
552
|
+
* silence half: two of three cross-layer conditions reported **nothing** when a
|
|
553
|
+
* layer resolved to no files.
|
|
554
|
+
*
|
|
555
|
+
* `true` — the builder owns it, and the gate stays out. Two builders override,
|
|
556
|
+
* and for different reasons, which is the thing to understand before adding a
|
|
557
|
+
* third:
|
|
558
|
+
*
|
|
559
|
+
* - `SliceRuleBuilder` returns a constant `true`, because its discovery
|
|
560
|
+
* semantics are **not** per-tree and cannot be expressed by a position filter
|
|
561
|
+
* (below). Nothing about that varies by condition.
|
|
562
|
+
* - `PairFinalBuilder` **asks its condition**, via the registry in
|
|
563
|
+
* `owns-empty-discovery.ts` (plan 0081). There, ownership does vary by
|
|
564
|
+
* condition, and a blanket `true` once suppressed the gate for the one
|
|
565
|
+
* condition that did not self-report — which is why this used to say
|
|
566
|
+
* "`SliceRuleBuilder` is the one that does" and was wrong.
|
|
567
|
+
*
|
|
568
|
+
* A hand-maintained roster of which builders override is the defect class plan
|
|
569
|
+
* 0081 was filed to remove, so this names the two shapes rather than the two
|
|
570
|
+
* classes:
|
|
571
|
+
*
|
|
572
|
+
* | builder | one dead tree among live ones |
|
|
573
|
+
* | --- | --- |
|
|
574
|
+
* | `crossLayer` | a **fault** — that layer's pairs are unchecked |
|
|
575
|
+
* | slice `assignedFrom` | **legitimate** — a slice with no files yet is a real shape |
|
|
576
|
+
*
|
|
577
|
+
* `slice-rule-builder.ts` records that second guard being withdrawn before
|
|
578
|
+
* release for firing on real projects: "a layer not created yet, and the
|
|
579
|
+
* `strict-boundaries` scaffold itself". Slice already handles the all-empty case
|
|
580
|
+
* with a better message than the gate's, so it owns both halves.
|
|
581
|
+
*
|
|
582
|
+
* Declared, never named from the outside. A list of exceptions in the gate is
|
|
583
|
+
* an unchecked claim about who owns what — which is exactly the comment this
|
|
584
|
+
* plan was filed to correct.
|
|
585
|
+
*/
|
|
586
|
+
ownsDiscoveryDiagnosis() {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Execute the rule with the given severity.
|
|
591
|
+
* `.severity('error')` is equivalent to `.check()`.
|
|
592
|
+
* `.severity('warn')` is equivalent to `.warn()`.
|
|
593
|
+
*/
|
|
594
|
+
severity(level) {
|
|
595
|
+
if (level === 'error') {
|
|
596
|
+
this.check();
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
this.warn();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* A rule that asserts nothing about what it selects cannot fail, so it is
|
|
604
|
+
* reported as a configuration finding (bug 0019, plan 0070).
|
|
605
|
+
*
|
|
606
|
+
* **Gate-first**, ahead of `collectViolations()`, for three measured reasons:
|
|
607
|
+
* an assertion-less rule cannot produce a legitimate finding, so running it
|
|
608
|
+
* buys nothing but a full AST walk; `CrossProjectBuilder.collectViolations`
|
|
609
|
+
* throws before returning, so a gate placed after it would never run for that
|
|
610
|
+
* builder and its `RangeError` would escape the CLI's `ArchRuleError`-only
|
|
611
|
+
* catch, dropping every remaining rule file; and the alternative ordering —
|
|
612
|
+
* let an existing `bypassFilters` finding win — only functions for rules that
|
|
613
|
+
* opted into `.expectNonEmpty()`, which is the opt-in this whole plan exists
|
|
614
|
+
* because nobody uses.
|
|
615
|
+
*
|
|
616
|
+
* The consequence, accepted: a rule with a dead glob AND no condition reports
|
|
617
|
+
* the missing assertion only. That is the right root cause — no selector
|
|
618
|
+
* makes an assertion-less rule capable of failing — and the selector fault
|
|
619
|
+
* resurfaces on the next run, once there is something to assert.
|
|
620
|
+
*
|
|
621
|
+
* `bypassFilters` makes it a configuration finding: `error` severity
|
|
622
|
+
* regardless of `.asSeverity('warn')`, refused by `.excluding()`, and skipped
|
|
623
|
+
* by diff and baseline. See `severityFor` and ADR-009 rule 1.
|
|
624
|
+
*/
|
|
625
|
+
collectWithAssertionGuard() {
|
|
626
|
+
return runWithGuard(this.asRun());
|
|
627
|
+
}
|
|
628
|
+
/** This rule, reduced to what `terminal-execution.ts` needs to run it. */
|
|
629
|
+
asRun() {
|
|
630
|
+
return {
|
|
631
|
+
facts: this.facts(),
|
|
632
|
+
expectsEmpty: this._expectEmpty,
|
|
633
|
+
assertsSomething: () => this.assertsSomething(),
|
|
634
|
+
ownsDiscoveryDiagnosis: () => this.ownsDiscoveryDiagnosis(),
|
|
635
|
+
collectViolations: () => this.collectViolations(),
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* What `applyFilters` needs, in one place.
|
|
640
|
+
*
|
|
641
|
+
* `violations()`, `check()` and `warn()` each rebuilt this object literally —
|
|
642
|
+
* three copies of the same four fields, which is three places to forget one.
|
|
643
|
+
*/
|
|
644
|
+
filterContext() {
|
|
645
|
+
return {
|
|
646
|
+
reason: this._reason,
|
|
647
|
+
metadata: this._metadata,
|
|
648
|
+
exclusions: this._exclusions,
|
|
649
|
+
silentIndices: this._silentIndices,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/** This rule's own account of itself, for `vacuity-diagnosis.ts`. */
|
|
653
|
+
facts() {
|
|
654
|
+
return {
|
|
655
|
+
ruleClass: { name: this.constructor.name },
|
|
656
|
+
describeRule: () => this.describeRule(),
|
|
657
|
+
assertionAdvice: () => this.assertionAdvice(),
|
|
658
|
+
emptyDeclarationAdvice: () => this.emptyDeclarationAdvice(),
|
|
659
|
+
examinedUnitNoun: () => this.examinedUnitNoun(),
|
|
660
|
+
narrowingHint: () => this.narrowingHint(),
|
|
661
|
+
getProject: () => this.getProject(),
|
|
662
|
+
globs: () => this.globs(),
|
|
663
|
+
assertsCardinality: () => this.assertsCardinality(),
|
|
664
|
+
declaresEmpty: () => this.declaresEmpty(),
|
|
665
|
+
zeroSubjectsAdvice: () => this.zeroSubjectsAdvice(),
|
|
666
|
+
zeroSubjectsViolation: (project) => this.zeroSubjectsViolation(project),
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Execute the rule and return violations after exclusion filtering.
|
|
671
|
+
* Does not throw — use for programmatic access (presets, aggregation).
|
|
672
|
+
*/
|
|
673
|
+
violations() {
|
|
674
|
+
const raw = this.collectWithAssertionGuard();
|
|
675
|
+
const filtered = applyFilters(raw, this.filterContext());
|
|
676
|
+
const sev = this._severity ?? 'error';
|
|
677
|
+
// Computed from `raw`, not `filtered`: `applyFilters()` already ran
|
|
678
|
+
// `disambiguateIdentities()` by this point, which REPAIRS a colliding
|
|
679
|
+
// subject with a positional `#1`/`#2` suffix — see `hasIdentityCollision`'s
|
|
680
|
+
// own doc comment for why checking post-repair identities would miss
|
|
681
|
+
// exactly the case this exists to catch. Only computed when it can matter.
|
|
682
|
+
const unsafe = sev === 'warn' && this._acceptedWarnings !== undefined && hasIdentityCollision(raw);
|
|
683
|
+
return filtered.map((v) => ({
|
|
684
|
+
...v,
|
|
685
|
+
severity: severityFor(v, unsafe ? 'error' : this.fallbackSeverityFor(v, sev)),
|
|
686
|
+
}));
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Plan 0090's escalation, per violation. A DEFERRED warning (`_acceptedWarnings`
|
|
690
|
+
* set) stays `warn` only for a violation whose `subjectOf()` is in the accepted
|
|
691
|
+
* list; anything else — a genuinely new finding — escalates to `error`, which is
|
|
692
|
+
* the whole point: an accepted list that never fails on something new is
|
|
693
|
+
* indistinguishable from an advisory warning, and bug 0084 is what that costs.
|
|
694
|
+
* An ADVISORY warning (`_acceptedWarnings` `undefined`) is unaffected — `sev`
|
|
695
|
+
* passes through unchanged, exactly today's behaviour.
|
|
696
|
+
*/
|
|
697
|
+
fallbackSeverityFor(v, sev) {
|
|
698
|
+
if (sev !== 'warn' || this._acceptedWarnings === undefined)
|
|
699
|
+
return sev;
|
|
700
|
+
return this._acceptedWarnings.includes(subjectOf(v)) ? 'warn' : 'error';
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Execute the rule and throw `ArchRuleError` if any violations are found.
|
|
704
|
+
* This is the primary terminal method — use in test assertions.
|
|
705
|
+
*
|
|
706
|
+
* @param options - Optional baseline, diff filtering, and output format
|
|
707
|
+
*/
|
|
708
|
+
check(options) {
|
|
709
|
+
const violations = this.collectWithAssertionGuard();
|
|
710
|
+
executeCheck(violations, this.filterContext(), options);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Execute the rule and log violations to stderr. Does not throw.
|
|
714
|
+
* Use for rules that should warn but not fail CI.
|
|
715
|
+
*
|
|
716
|
+
* @param options - Optional baseline, diff filtering, and output format
|
|
717
|
+
*/
|
|
718
|
+
warn(options) {
|
|
719
|
+
const violations = this.collectWithAssertionGuard();
|
|
720
|
+
executeWarn(violations, this.filterContext(), options);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* The advice for a DEFERRED warning whose accepted list no longer covers
|
|
724
|
+
* everything it currently finds — plan 0090.
|
|
725
|
+
*
|
|
726
|
+
* PUBLIC and zero-arg, following `inertAdvice()`/`zeroSubjectsAdvice()`'s
|
|
727
|
+
* precedent exactly: read structurally by `diagnose()`, so `doctor`'s preview
|
|
728
|
+
* and `checkAll()`/CLI `check`'s eventual failure carry the same evidence by
|
|
729
|
+
* construction. Returns `''` for an advisory warning (`_acceptedWarnings`
|
|
730
|
+
* `undefined`), for a rule at `'error'` severity, and for a deferred warning
|
|
731
|
+
* whose current findings are all still within `accepted` — a working, healthy
|
|
732
|
+
* deferral is not a problem `doctor` should report, only a breached one is.
|
|
733
|
+
*
|
|
734
|
+
* Calls `this.violations()` rather than re-deriving "is this accepted" a
|
|
735
|
+
* second time: that already applies the exact escalation this reports on, so
|
|
736
|
+
* the two can never disagree about which violations breached the list.
|
|
737
|
+
*
|
|
738
|
+
* Also checks `hasIdentityCollision()` separately, on its own
|
|
739
|
+
* `collectWithAssertionGuard()` pass — not to decide WHETHER to report (that
|
|
740
|
+
* is already folded into `.violations()`'s own escalation, which forces the
|
|
741
|
+
* whole batch to `error` when unsafe), but to choose the RIGHT message: a
|
|
742
|
+
* collision means `accepted` cannot be trusted at all here, which is a
|
|
743
|
+
* different, more urgent fact than "this specific finding is new".
|
|
744
|
+
*/
|
|
745
|
+
deferredWarningAdvice() {
|
|
746
|
+
if (this._severity !== 'warn' || this._acceptedWarnings === undefined)
|
|
747
|
+
return '';
|
|
748
|
+
const breaching = this.violations().filter((v) => v.severity === 'error');
|
|
749
|
+
if (breaching.length === 0)
|
|
750
|
+
return '';
|
|
751
|
+
const described = this.describeRule();
|
|
752
|
+
const name = described.id || described.rule || this.constructor.name;
|
|
753
|
+
if (hasIdentityCollision(this.collectWithAssertionGuard())) {
|
|
754
|
+
return (`"${name}" is a deferred warning, but its findings are not reliably identifiable: two or ` +
|
|
755
|
+
`more share one subject (rule + element + message, with no producer-set \`identity\`), so ` +
|
|
756
|
+
`the repair that keeps them distinct assigns a POSITIONAL "#1"/"#2" suffix — not stable ` +
|
|
757
|
+
`across runs, so a fixed finding and a genuinely new one can land on the same suffix and ` +
|
|
758
|
+
`\`accepted\` would silently treat the new one as already-known debt. Every finding here is ` +
|
|
759
|
+
`escalated to error until this is fixed: qualify the condition's message, or set ` +
|
|
760
|
+
`ArchViolation.identity explicitly, so each finding's subject is unique on its own.`);
|
|
761
|
+
}
|
|
762
|
+
const subjects = breaching.map((v) => subjectOf(v));
|
|
763
|
+
return (`"${name}" is a deferred warning (accepted: ${String(this._acceptedWarnings.length)} finding` +
|
|
764
|
+
`${this._acceptedWarnings.length === 1 ? '' : 's'}), and ${String(breaching.length)} current ` +
|
|
765
|
+
`finding${breaching.length === 1 ? '' : 's'} ${breaching.length === 1 ? 'is' : 'are'} not in ` +
|
|
766
|
+
`that list — a new finding this deferral did not accept, which will fail at check() time: ` +
|
|
767
|
+
`${subjects.join(', ')}. Either fix it, or extend \`accepted\` if it is genuinely more debt of ` +
|
|
768
|
+
`the same kind you already deferred.`);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* The advice for a rule that examined zero units — plan 0099.
|
|
772
|
+
*
|
|
773
|
+
* PUBLIC and zero-arg, following `assertionAdvice()`'s precedent exactly: it is
|
|
774
|
+
* read structurally by `diagnose()` so `doctor` and the gate carry the same
|
|
775
|
+
* string **by construction**. Plan 0070 added that seam after the two were
|
|
776
|
+
* measured diverging; review measured this pair diverging the same way, with
|
|
777
|
+
* `diagnose()` still closing "A later release makes this state fail at check
|
|
778
|
+
* time" in the release that makes it fail.
|
|
779
|
+
*/
|
|
780
|
+
zeroSubjectsAdvice() {
|
|
781
|
+
return zeroSubjectsAdviceOf(this.facts());
|
|
782
|
+
}
|
|
783
|
+
zeroSubjectsViolation(project) {
|
|
784
|
+
return zeroSubjectsViolationOf(this.facts(), project);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
//# sourceMappingURL=terminal-builder.js.map
|