@nielspeter/eess-ts 0.2.1 → 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 +61 -35
- 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 -24
- 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,664 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { subjectOf } from '@nielspeter/eess';
|
|
5
|
+
import { discoverIdentityRoot, normalizeIdentityText, toPortablePath } from '@nielspeter/eess';
|
|
6
|
+
import { writeStderr } from '@nielspeter/eess';
|
|
7
|
+
import { descriptionChangeFinding, unmatchedBaselineFinding } from './baseline-diagnostics.js';
|
|
8
|
+
/**
|
|
9
|
+
* Identity-hash format version.
|
|
10
|
+
*
|
|
11
|
+
* 1 — sha256(rule::element::message) verbatim; absolute paths leak in.
|
|
12
|
+
* 2 — the repository root is replaced with a token first, so identity is
|
|
13
|
+
* portable across checkouts (bug 0010).
|
|
14
|
+
*
|
|
15
|
+
* Note that v2 is byte-identical to v1 for any violation whose fields contain
|
|
16
|
+
* no path, so most v1 baselines keep matching. The version alone is therefore
|
|
17
|
+
* NOT grounds to fail — see `unmatchedBaselineFinding`, which fires on the
|
|
18
|
+
* measurement instead.
|
|
19
|
+
* 4 — bug 0012. Metric findings gained a producer-set `identity`
|
|
20
|
+
* (`file::element::metric`) and an accepted `measured` value, so every entry
|
|
21
|
+
* for the ten size and complexity conditions hashes differently. The v3
|
|
22
|
+
* precedent applies exactly: `hashViolation`'s FORMULA is untouched and one
|
|
23
|
+
* of its inputs moved, which is the case the 0.23.0 withdrawal established
|
|
24
|
+
* is worth a bump only when the identity of existing entries actually
|
|
25
|
+
* changes. It does here, for that family and no other — a baseline with no
|
|
26
|
+
* metric entries is byte-identical and keeps matching.
|
|
27
|
+
*
|
|
28
|
+
*/
|
|
29
|
+
// Stays 2 through 0.23.0, deliberately. Accumulate (bug 0020) lengthens
|
|
30
|
+
// `buildRuleDescription()` for a rule derived off a held rule and for a
|
|
31
|
+
// pre-`.should()` `satisfy()`, and the description is hashed — so those entries
|
|
32
|
+
// stop matching. That is a change in the hash's *input*, not in how it is
|
|
33
|
+
// computed: `hashViolation` below never reads this constant.
|
|
34
|
+
//
|
|
35
|
+
// 0.23.0 drafted a bump to 3 to signal it and two independent reviews measured
|
|
36
|
+
// that as a defect. It matches no entry differently, and the only thing it
|
|
37
|
+
// changes is which `cause` sentence `unmatchedBaselineFinding` picks — so every
|
|
38
|
+
// user holding a pre-0.23.0 baseline that matched nothing for an unrelated
|
|
39
|
+
// reason would be told the format was "the likely cause", which cannot be true,
|
|
40
|
+
// while the branch naming the cause that usually is (a differently-resolved
|
|
41
|
+
// root) became unreachable. Bump this only when `hashViolation` changes.
|
|
42
|
+
//
|
|
43
|
+
// The unmatched *entry* still cannot be diagnosed — see bug 0027; that is the
|
|
44
|
+
// gap the bump was reaching for and did not close.
|
|
45
|
+
//
|
|
46
|
+
// 3 — dependency findings carry a producer-set `identity`, so `hashViolation`
|
|
47
|
+
// computes a different string for all of them (bug 0028). This bump satisfies
|
|
48
|
+
// the rule above, where 0.23.0's attempt did not: the *formula* changed, not
|
|
49
|
+
// just one of its inputs.
|
|
50
|
+
//
|
|
51
|
+
// Why it had to: a dependency message carries the basename and the resolved
|
|
52
|
+
// target and nothing else, so two edges from one file to one module hashed
|
|
53
|
+
// identically. Measured on this repo's barrel after v0.28.0 made barrels
|
|
54
|
+
// dependency-bearing — 114 findings, 87 identities, 46.5% of findings sharing
|
|
55
|
+
// one with a sibling. `identity` adds the imported names, which discriminate
|
|
56
|
+
// and survive code moving where a line number would not.
|
|
57
|
+
//
|
|
58
|
+
// **It invalidates existing baselines for every dependency finding**, not only
|
|
59
|
+
// the colliding ones, because identity replaces `element::message` wholesale.
|
|
60
|
+
// No printed text changes, which is why bug 0028 first recorded this as needing
|
|
61
|
+
// no migration treatment — that conflated text-stability with baseline-stability
|
|
62
|
+
// and was wrong. The bump is what lets `unmatchedBaselineFinding` name the real
|
|
63
|
+
// cause instead of guessing at a re-resolved root.
|
|
64
|
+
//
|
|
65
|
+
// 5 — plan 0104. `beFreeOfCycles` gains a producer-set `identity` PER INTERNAL EDGE
|
|
66
|
+
// (`cycle-edge::${from}->${to}`), replacing the old per-component `cycle::${members}`
|
|
67
|
+
// scheme wholesale — so every existing cycle finding's identity changes, whether or
|
|
68
|
+
// not its underlying edges did. The v3/v4 precedent applies exactly: `hashViolation`'s
|
|
69
|
+
// FORMULA is untouched and one of its inputs moved, and the identity of existing
|
|
70
|
+
// entries actually changes, for that family and no other — a baseline with no cycle
|
|
71
|
+
// entries is byte-identical and keeps matching. Distinct prefix (`cycle-edge::` vs.
|
|
72
|
+
// `cycle::`) also means an old-format entry cannot accidentally still match a
|
|
73
|
+
// new-format finding by coincidence, independent of this version bump.
|
|
74
|
+
export const HASH_VERSION = 5;
|
|
75
|
+
/**
|
|
76
|
+
* Units whose meaning has never changed, so an entry that predates unit
|
|
77
|
+
* stamping is still safely comparable against one.
|
|
78
|
+
*
|
|
79
|
+
* `code-lines` is deliberately absent, and that absence is the entire fix for
|
|
80
|
+
* [bug 0171](../../../../work/bugs/0171-a-metric-unit-change-silently-loosens-every-baselined-ratchet.md).
|
|
81
|
+
* An unstamped `lines` entry was written when `linesOfCode` returned a SPAN, so
|
|
82
|
+
* its number is denominated in something the tool no longer produces —
|
|
83
|
+
* measured, roughly three times the current value on this repo's own source.
|
|
84
|
+
* Comparing across that does not report a regression; it silently raises the
|
|
85
|
+
* ceiling and keeps the build green while a class triples.
|
|
86
|
+
*
|
|
87
|
+
* A metric added here later is asserting "this counts what it always counted".
|
|
88
|
+
*/
|
|
89
|
+
const MEANING_NEVER_CHANGED = new Set([
|
|
90
|
+
'methods',
|
|
91
|
+
'parameters',
|
|
92
|
+
'properties',
|
|
93
|
+
'named-exports',
|
|
94
|
+
'complexity',
|
|
95
|
+
]);
|
|
96
|
+
/**
|
|
97
|
+
* Whether a stored measurement may be compared against a current one.
|
|
98
|
+
*
|
|
99
|
+
* Fails CLOSED: anything other than a demonstrable match is incomparable, and
|
|
100
|
+
* an incomparable entry stops suppressing. The alternative — assume they agree —
|
|
101
|
+
* is how a ratchet loosens without anyone being told (ADR-010: a pass is
|
|
102
|
+
* constructed from evidence, and "probably the same unit" is not evidence).
|
|
103
|
+
*/
|
|
104
|
+
function measurementComparable(stored, current) {
|
|
105
|
+
// Both stamped and equal, or neither stamped (a producer that predates units
|
|
106
|
+
// on both sides — the honest pre-0171 reading).
|
|
107
|
+
if (stored === current)
|
|
108
|
+
return true;
|
|
109
|
+
// Stored predates stamping. Comparable only where the meaning cannot have moved.
|
|
110
|
+
if (stored === undefined)
|
|
111
|
+
return current !== undefined && MEANING_NEVER_CHANGED.has(current);
|
|
112
|
+
// Stored says one thing and this run says another. Never comparable.
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Compute a stable hash for a violation.
|
|
117
|
+
*
|
|
118
|
+
* Uses rule + element + message as identity. This survives:
|
|
119
|
+
* - Line number changes (code moved)
|
|
120
|
+
* - Unrelated code changes in the same file
|
|
121
|
+
* - **The checkout's absolute location** — see `root` below
|
|
122
|
+
*
|
|
123
|
+
* Does NOT survive:
|
|
124
|
+
* - Rule DESCRIPTION changes — the assembled `that … should …` text, i.e. editing
|
|
125
|
+
* the rule's predicates or conditions. **Not** `.because()`: that sets
|
|
126
|
+
* `because`, which is not hashed. The old wording said "(rewording
|
|
127
|
+
* .because())" and it was false — measured, two violations differing only in
|
|
128
|
+
* `because` hash identically, and so do two differing only in `suggestion`.
|
|
129
|
+
* The distinction is load-bearing: it is what makes correcting a preset's
|
|
130
|
+
* remedy text a patch rather than a change that invalidates every consumer's
|
|
131
|
+
* baseline (bug 0017, whose own defect was a remedy claiming something it
|
|
132
|
+
* could not do — sitting in the docstring a compat auditor would check).
|
|
133
|
+
* - Element renames (class renamed)
|
|
134
|
+
* - Message text changes (condition wording updated)
|
|
135
|
+
*
|
|
136
|
+
* This is intentional — if the rule or element changes,
|
|
137
|
+
* the violation should be re-evaluated.
|
|
138
|
+
*
|
|
139
|
+
* @param root - Repository/workspace root. Every occurrence of it inside the
|
|
140
|
+
* rule, element and message is replaced with a stable token before hashing.
|
|
141
|
+
* Producers interpolate absolute paths into those fields, so without a root
|
|
142
|
+
* the identity encodes the checkout directory and a baseline written on one
|
|
143
|
+
* machine matches nothing on another (bug 0010). Omitting it preserves the
|
|
144
|
+
* pre-0.19 hash and is only correct when no field contains a path.
|
|
145
|
+
*/
|
|
146
|
+
// eess-exclude eess/no-unused-exports: consumed by the test suite; the build tsconfig this gate reads excludes tests, so `src` is the only usage it can see
|
|
147
|
+
export function hashViolation(violation, root) {
|
|
148
|
+
const scrub = (text) => root === undefined ? text : normalizeIdentityText(text, root);
|
|
149
|
+
// A producer that sets `identity` has declared its own canonical form, which
|
|
150
|
+
// supersedes both element and message — see ArchViolation.identity. Without
|
|
151
|
+
// one, the composed string is byte-identical to the pre-0.19 input, so a
|
|
152
|
+
// violation whose fields never contained a path keeps its old hash.
|
|
153
|
+
// ONE definition, shared with `disambiguateIdentities` — see `subjectOf`. It used to be
|
|
154
|
+
// spelled out here and copied there, guarded by a test asserting the copies agree; the copy
|
|
155
|
+
// is gone because `helpers/` may import from `core/` and a shared definition cannot diverge.
|
|
156
|
+
const content = `${scrub(violation.rule)}::${scrub(subjectOf(violation))}`;
|
|
157
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The violation's **subject**: what was found, independent of which rule found it.
|
|
161
|
+
*
|
|
162
|
+
* `hashViolation` above is `rule::element::message`, so editing a rule's
|
|
163
|
+
* predicates or conditions — or accumulating them, which v0.23.0 made happen for
|
|
164
|
+
* a rule derived off a held rule — changes the identity of violations that did
|
|
165
|
+
* not change at all. Those entries stop matching and their already-accepted
|
|
166
|
+
* violations report as **new**, with nothing saying why (bug 0027).
|
|
167
|
+
*
|
|
168
|
+
* This is the differently-derived value that tells the two cases apart:
|
|
169
|
+
*
|
|
170
|
+
* | baseline entry did not match because… | subject present in the run? |
|
|
171
|
+
* | ------------------------------------- | --------------------------- |
|
|
172
|
+
* | the violation was fixed | no — stay silent, this is success |
|
|
173
|
+
* | the rule's description changed | yes — say so, and regenerate |
|
|
174
|
+
*
|
|
175
|
+
* Bug 0027's own suggested signal was "an entry whose `rule` string appears under
|
|
176
|
+
* a different hash", and it cannot work: the rule string is precisely what
|
|
177
|
+
* changed. Measured before this was built.
|
|
178
|
+
*/
|
|
179
|
+
export function hashSubject(violation, root) {
|
|
180
|
+
const scrub = (text) => root === undefined ? text : normalizeIdentityText(text, root);
|
|
181
|
+
return createHash('sha256')
|
|
182
|
+
.update(scrub(subjectOf(violation)))
|
|
183
|
+
.digest('hex')
|
|
184
|
+
.slice(0, 16);
|
|
185
|
+
}
|
|
186
|
+
/** Forward slashes so the recorded root reads the same on Windows and CI. */
|
|
187
|
+
function toPosix(value) {
|
|
188
|
+
return value.replaceAll('\\', '/');
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Load a baseline from a JSON file.
|
|
192
|
+
*
|
|
193
|
+
* @param baselinePath - Path to the baseline JSON file
|
|
194
|
+
* @param options - See \{@link BaselineOptions\}
|
|
195
|
+
* @returns A Baseline object for use with check(\{ baseline \})
|
|
196
|
+
*/
|
|
197
|
+
export function withBaseline(baselinePath, options = {}) {
|
|
198
|
+
const resolved = path.resolve(baselinePath);
|
|
199
|
+
const baselineDir = path.dirname(resolved);
|
|
200
|
+
const root = options.root !== undefined ? path.resolve(options.root) : discoverIdentityRoot(baselineDir);
|
|
201
|
+
if (!fs.existsSync(resolved)) {
|
|
202
|
+
// No baseline file = no known violations = all violations are new
|
|
203
|
+
return new Baseline(new Set(), root, HASH_VERSION);
|
|
204
|
+
}
|
|
205
|
+
const raw = fs.readFileSync(resolved, 'utf-8');
|
|
206
|
+
const parsed = JSON.parse(raw);
|
|
207
|
+
if (!parsed ||
|
|
208
|
+
typeof parsed !== 'object' ||
|
|
209
|
+
!('violations' in parsed) ||
|
|
210
|
+
!Array.isArray(parsed.violations)) {
|
|
211
|
+
writeStderr(`[eess] Invalid baseline file format at ${resolved} — treating as empty`);
|
|
212
|
+
return new Baseline(new Set(), root, HASH_VERSION);
|
|
213
|
+
}
|
|
214
|
+
const hashVersion = 'hashVersion' in parsed && typeof parsed.hashVersion === 'number' ? parsed.hashVersion : 1;
|
|
215
|
+
// A recorded root wins over re-discovery: it is what the hashes in this file
|
|
216
|
+
// were actually built against. Re-deriving here is what lets a machine
|
|
217
|
+
// without `.git` disagree with the machine that wrote the file, and that
|
|
218
|
+
// disagreement produces no signal at all (bug 0010, review C2). An explicit
|
|
219
|
+
// `options.root` still wins over both — the caller is overriding on purpose.
|
|
220
|
+
const recordedRoot = 'root' in parsed && typeof parsed.root === 'string'
|
|
221
|
+
? path.resolve(baselineDir, parsed.root)
|
|
222
|
+
: undefined;
|
|
223
|
+
const effectiveRoot = options.root !== undefined ? root : (recordedRoot ?? root);
|
|
224
|
+
const hashes = new Set();
|
|
225
|
+
// subject hash -> the rule description recorded for it. Only what the
|
|
226
|
+
// description-change diagnosis needs, so a large baseline does not carry a
|
|
227
|
+
// second copy of every entry. Entries written before 0.24.0 have no subject
|
|
228
|
+
// and simply do not appear here.
|
|
229
|
+
const subjects = new Map();
|
|
230
|
+
/** hash -> accepted measurement and its unit, for metric findings (bugs 0012, 0171). */
|
|
231
|
+
const accepted = new Map();
|
|
232
|
+
// Annotated as `readonly unknown[]`, not iterated directly: `parsed.violations`
|
|
233
|
+
// is `any[]` after the `Array.isArray` check, and ADR-005 bars both `any` and
|
|
234
|
+
// the `as` that would otherwise be needed to re-narrow it. Assigning to this
|
|
235
|
+
// type is allowed and hands each element back as `unknown`.
|
|
236
|
+
const rawEntries = parsed.violations;
|
|
237
|
+
for (const entry of rawEntries) {
|
|
238
|
+
if (entry === null || typeof entry !== 'object')
|
|
239
|
+
continue;
|
|
240
|
+
if ('hash' in entry && typeof entry.hash === 'string')
|
|
241
|
+
hashes.add(entry.hash);
|
|
242
|
+
if ('subject' in entry &&
|
|
243
|
+
typeof entry.subject === 'string' &&
|
|
244
|
+
'rule' in entry &&
|
|
245
|
+
typeof entry.rule === 'string') {
|
|
246
|
+
subjects.set(entry.subject, entry.rule);
|
|
247
|
+
}
|
|
248
|
+
// Bug 0012: the measurement this entry accepted. Absent on every non-metric
|
|
249
|
+
// entry and on anything written before this shipped, which `isKnown` reads
|
|
250
|
+
// as "no ratchet recorded" rather than as zero.
|
|
251
|
+
if ('hash' in entry &&
|
|
252
|
+
typeof entry.hash === 'string' &&
|
|
253
|
+
'measured' in entry &&
|
|
254
|
+
typeof entry.measured === 'number') {
|
|
255
|
+
accepted.set(entry.hash, {
|
|
256
|
+
value: entry.measured,
|
|
257
|
+
// Absent on any entry written before bug 0171 — deliberately left
|
|
258
|
+
// `undefined` rather than defaulted, because a guess here is exactly
|
|
259
|
+
// the silent re-denomination this field exists to prevent.
|
|
260
|
+
unit: 'measuredUnit' in entry && typeof entry.measuredUnit === 'string'
|
|
261
|
+
? entry.measuredUnit
|
|
262
|
+
: undefined,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return new Baseline(hashes, effectiveRoot, hashVersion, resolved, subjects, accepted);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Generate a baseline file from a list of violations.
|
|
270
|
+
*
|
|
271
|
+
* Call this to create/update the baseline:
|
|
272
|
+
* ```typescript
|
|
273
|
+
* const violations = collectAllViolations(rules)
|
|
274
|
+
* generateBaseline(violations, 'arch-baseline.json')
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
export function generateBaseline(violations, outputPath, options = {}) {
|
|
278
|
+
const resolved = path.resolve(outputPath);
|
|
279
|
+
const baselineDir = path.dirname(resolved);
|
|
280
|
+
const root = options.root !== undefined ? path.resolve(options.root) : discoverIdentityRoot(baselineDir);
|
|
281
|
+
// Config-level meta-findings (empty selector/discovery) must never be
|
|
282
|
+
// baselined away — they carry bypassFilters and are re-kept by filterNew
|
|
283
|
+
// regardless, so writing them in only pollutes the file (plan 0067).
|
|
284
|
+
const entries = violations
|
|
285
|
+
.filter((v) => v.bypassFilters !== true)
|
|
286
|
+
.map((v) => ({
|
|
287
|
+
rule: v.rule,
|
|
288
|
+
// Root-relative, not baseline-relative: the stored path must read the
|
|
289
|
+
// same in every checkout, and `../../` chains encode the baseline file's
|
|
290
|
+
// depth. Forward slashes so a file written on Windows reads on CI.
|
|
291
|
+
file: toPortablePath(v.file, root),
|
|
292
|
+
line: v.line,
|
|
293
|
+
hash: hashViolation(v, root),
|
|
294
|
+
subject: hashSubject(v, root),
|
|
295
|
+
// Bug 0012. Written only for metric findings, so a baseline of ordinary
|
|
296
|
+
// findings is byte-identical to one from before this shipped.
|
|
297
|
+
...(v.measured === undefined ? {} : { measured: v.measured }),
|
|
298
|
+
// Bug 0171: what that number counts, so a later change to the metric
|
|
299
|
+
// cannot silently re-denominate an accepted ceiling.
|
|
300
|
+
...(v.measuredUnit === undefined ? {} : { measuredUnit: v.measuredUnit }),
|
|
301
|
+
}));
|
|
302
|
+
// Read before writing — this is the only moment both sets exist (plan 0071).
|
|
303
|
+
const prior = readPriorHashes(resolved);
|
|
304
|
+
const baseline = {
|
|
305
|
+
generatedAt: new Date().toISOString(),
|
|
306
|
+
hashVersion: HASH_VERSION,
|
|
307
|
+
root: toPosix(path.relative(baselineDir, root)) || '.',
|
|
308
|
+
count: entries.length,
|
|
309
|
+
violations: entries,
|
|
310
|
+
};
|
|
311
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
312
|
+
fs.writeFileSync(resolved, JSON.stringify(baseline, null, 2) + '\n');
|
|
313
|
+
const written = new Set(entries.map((e) => e.hash));
|
|
314
|
+
return {
|
|
315
|
+
before: prior?.count,
|
|
316
|
+
after: entries.length,
|
|
317
|
+
added: prior === undefined
|
|
318
|
+
? entries.length
|
|
319
|
+
: [...written].filter((h) => !prior.hashes.has(h)).length,
|
|
320
|
+
removed: prior === undefined ? 0 : [...prior.hashes].filter((h) => !written.has(h)).length,
|
|
321
|
+
priorHashVersion: prior?.hashVersion,
|
|
322
|
+
priorUnreadable: prior?.unreadable ?? false,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* The hashes of the baseline file about to be overwritten.
|
|
327
|
+
*
|
|
328
|
+
* `undefined` when there is no prior file — the first-run case, which must read
|
|
329
|
+
* as "41 new entries", never as "+41, −0 against an empty baseline".
|
|
330
|
+
*
|
|
331
|
+
* Deliberately tolerant in the same shape as {@link withBaseline}, and for the
|
|
332
|
+
* same reason: a prior file that cannot be parsed must not throw here, because
|
|
333
|
+
* the write is the user's whole purpose. But it is reported rather than treated
|
|
334
|
+
* as absent — silently calling a corrupt baseline "no prior baseline" would
|
|
335
|
+
* print `(+78, −0)` and hide that 41 accepted findings just stopped being
|
|
336
|
+
* accepted.
|
|
337
|
+
*/
|
|
338
|
+
function readPriorHashes(resolved) {
|
|
339
|
+
if (!fs.existsSync(resolved))
|
|
340
|
+
return undefined;
|
|
341
|
+
let parsed;
|
|
342
|
+
try {
|
|
343
|
+
parsed = JSON.parse(fs.readFileSync(resolved, 'utf-8'));
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
// `unreadable` carries the cause, so the finding can say whether the file is
|
|
347
|
+
// malformed or simply absent — two different remedies (regenerate vs create).
|
|
348
|
+
return {
|
|
349
|
+
hashes: new Set(),
|
|
350
|
+
count: 0,
|
|
351
|
+
hashVersion: undefined,
|
|
352
|
+
unreadable: true,
|
|
353
|
+
unreadableReason: error instanceof Error ? error.message : String(error),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (!parsed ||
|
|
357
|
+
typeof parsed !== 'object' ||
|
|
358
|
+
!('violations' in parsed) ||
|
|
359
|
+
!Array.isArray(parsed.violations)) {
|
|
360
|
+
return { hashes: new Set(), count: 0, hashVersion: undefined, unreadable: true };
|
|
361
|
+
}
|
|
362
|
+
const hashVersion = 'hashVersion' in parsed && typeof parsed.hashVersion === 'number' ? parsed.hashVersion : 1;
|
|
363
|
+
// Same `readonly unknown[]` narrowing as `withBaseline`, for the ADR-005
|
|
364
|
+
// reason documented there.
|
|
365
|
+
const rawEntries = parsed.violations;
|
|
366
|
+
const hashes = new Set();
|
|
367
|
+
for (const entry of rawEntries) {
|
|
368
|
+
if (entry === null || typeof entry !== 'object')
|
|
369
|
+
continue;
|
|
370
|
+
if ('hash' in entry && typeof entry.hash === 'string')
|
|
371
|
+
hashes.add(entry.hash);
|
|
372
|
+
}
|
|
373
|
+
// `count` is the entry count, not `hashes.size`: two entries can share a hash
|
|
374
|
+
// (bug 0028, measured at 17% in this repo), and reporting the deduplicated
|
|
375
|
+
// number as "41 entries" would disagree with the file the reader can open.
|
|
376
|
+
return { hashes, count: rawEntries.length, hashVersion, unreadable: false };
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* The one-line delta, e.g. `41 → 78 entries (+37, −0)`.
|
|
380
|
+
*
|
|
381
|
+
* Kept beside {@link BaselineDelta} rather than in the CLI so the `baseline`
|
|
382
|
+
* command and any programmatic caller print the same sentence.
|
|
383
|
+
*/
|
|
384
|
+
export function formatBaselineDelta(delta) {
|
|
385
|
+
const plural = (n) => (n === 1 ? 'entry' : 'entries');
|
|
386
|
+
if (delta.priorUnreadable) {
|
|
387
|
+
return (`Baseline replaced: ${String(delta.after)} ${plural(delta.after)} written. ` +
|
|
388
|
+
`The previous file could not be read as a baseline, so this is not a delta — ` +
|
|
389
|
+
`whatever it accepted is no longer accepted. Check it in git history if that matters.`);
|
|
390
|
+
}
|
|
391
|
+
if (delta.before === undefined) {
|
|
392
|
+
return `Baseline created: ${String(delta.after)} ${plural(delta.after)} accepted (no previous baseline).`;
|
|
393
|
+
}
|
|
394
|
+
const line = `Baseline updated: ${String(delta.before)} → ${String(delta.after)} ${plural(delta.after)} ` +
|
|
395
|
+
`(+${String(delta.added)}, −${String(delta.removed)}).`;
|
|
396
|
+
// A full replacement — nothing carried over — means something different from a
|
|
397
|
+
// delta of the same magnitude, and the reader's next action is not the same:
|
|
398
|
+
// "you accepted 78 new findings" versus "the identities all changed, so this
|
|
399
|
+
// says nothing about what you accepted".
|
|
400
|
+
//
|
|
401
|
+
// **Keyed on the measurement, not on the version.** A version mismatch looks
|
|
402
|
+
// like the obvious trigger and is not one: v2 is byte-identical to v1 for any
|
|
403
|
+
// violation whose fields contain no path (see HASH_VERSION), so a v1 baseline
|
|
404
|
+
// usually keeps matching entirely — and a message asserting "none of its
|
|
405
|
+
// identities could be compared" beside `(+0, −0)` would be plainly false. The
|
|
406
|
+
// overlap is the differently-derived value (ADR-009 rule 5); the version is
|
|
407
|
+
// offered as a likely cause only once the measurement has established there
|
|
408
|
+
// is something to explain.
|
|
409
|
+
if (delta.before > 0 && delta.added === delta.after && delta.removed === delta.before) {
|
|
410
|
+
const cause = delta.priorHashVersion !== undefined && delta.priorHashVersion !== HASH_VERSION
|
|
411
|
+
? ` The identity format changed (v${String(delta.priorHashVersion)} → v${String(HASH_VERSION)}), which is the usual cause.`
|
|
412
|
+
: '';
|
|
413
|
+
return (`${line} No entry survived: every prior identity is gone and every entry written is new, ` +
|
|
414
|
+
`so this delta does not tell you what changed in your code.${cause}`);
|
|
415
|
+
}
|
|
416
|
+
if (delta.added > 0) {
|
|
417
|
+
return `${line} The +${String(delta.added)} are findings this file now accepts that it did not before.`;
|
|
418
|
+
}
|
|
419
|
+
return line;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* A loaded baseline. Passed to check(\{ baseline \}) to filter known violations.
|
|
423
|
+
*/
|
|
424
|
+
export class Baseline {
|
|
425
|
+
knownHashes;
|
|
426
|
+
root;
|
|
427
|
+
hashVersion;
|
|
428
|
+
sourcePath;
|
|
429
|
+
knownSubjects;
|
|
430
|
+
acceptedMeasurements;
|
|
431
|
+
// `Baseline` is an exported type and `new Baseline(hashes, root)` is a
|
|
432
|
+
// documented call, so folding these into an options object is a breaking
|
|
433
|
+
// change and not this plan's to make. Every parameter after the second is
|
|
434
|
+
// optional and defaulted, so the call sites that matter take two.
|
|
435
|
+
//
|
|
436
|
+
// The directive is the LAST line before the declaration on purpose: a
|
|
437
|
+
// single-line waiver covers exactly the next line, so prose placed under it
|
|
438
|
+
// consumes the waiver instead of the code (measured, right here).
|
|
439
|
+
// eess-exclude eess/max-parameters: published constructor; an options object would break it
|
|
440
|
+
constructor(knownHashes, root, hashVersion = HASH_VERSION, sourcePath,
|
|
441
|
+
/**
|
|
442
|
+
* Subject hash -> the rule description recorded against it. Empty for a
|
|
443
|
+
* baseline written before 0.24.0, which disables the description-change
|
|
444
|
+
* diagnosis rather than guessing at it (bug 0027).
|
|
445
|
+
*/
|
|
446
|
+
knownSubjects = new Map(),
|
|
447
|
+
/**
|
|
448
|
+
* Hash -> the measurement accepted for it, for metric findings (bug 0012).
|
|
449
|
+
* Absent for every non-metric entry and for baselines written before this
|
|
450
|
+
* shipped, where equality of identity remains the right test.
|
|
451
|
+
*/
|
|
452
|
+
acceptedMeasurements = new Map()) {
|
|
453
|
+
this.knownHashes = knownHashes;
|
|
454
|
+
this.root = root;
|
|
455
|
+
this.hashVersion = hashVersion;
|
|
456
|
+
this.sourcePath = sourcePath;
|
|
457
|
+
this.knownSubjects = knownSubjects;
|
|
458
|
+
this.acceptedMeasurements = acceptedMeasurements;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Check if a violation is known (exists in the baseline).
|
|
462
|
+
* Known violations are filtered out — they don't cause failures.
|
|
463
|
+
*/
|
|
464
|
+
/**
|
|
465
|
+
* Whether the baseline holds an entry with this violation's identity.
|
|
466
|
+
*
|
|
467
|
+
* Distinct from {@link isKnown}, which also asks whether a metric finding is
|
|
468
|
+
* still within the measurement that entry accepted. A regressed metric has an
|
|
469
|
+
* entry and is not known.
|
|
470
|
+
*/
|
|
471
|
+
hasEntry(violation) {
|
|
472
|
+
return this.knownHashes.has(hashViolation(violation, this.root));
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Whether this violation is already accepted by the baseline.
|
|
476
|
+
*
|
|
477
|
+
* Identity answers "is this the same finding?"; for a metric finding that is
|
|
478
|
+
* not enough, so a known hash is additionally required to be **no worse** than
|
|
479
|
+
* the accepted measurement — improving a metric stays green, regressing past
|
|
480
|
+
* the accepted value fails (bug 0012).
|
|
481
|
+
*/
|
|
482
|
+
isKnown(violation) {
|
|
483
|
+
const hash = hashViolation(violation, this.root);
|
|
484
|
+
if (!this.knownHashes.has(hash))
|
|
485
|
+
return false;
|
|
486
|
+
// Bug 0012: a metric finding is known only while it is **no worse** than
|
|
487
|
+
// what was accepted. Identity answers "is this the same finding?"; a metric
|
|
488
|
+
// needs "is it worse than what we accepted?", which is a comparison.
|
|
489
|
+
//
|
|
490
|
+
// Both sides must be present. A metric violation matched against an entry
|
|
491
|
+
// written before this shipped has no accepted value to compare with, and
|
|
492
|
+
// the honest reading of that is the pre-0012 one — the entry was accepted,
|
|
493
|
+
// so it stays accepted until the baseline is regenerated. Treating a
|
|
494
|
+
// missing value as 0 would fail every metric finding in an older baseline
|
|
495
|
+
// and call it a regression.
|
|
496
|
+
const accepted = this.acceptedMeasurements.get(hash);
|
|
497
|
+
if (violation.measured === undefined || accepted === undefined)
|
|
498
|
+
return true;
|
|
499
|
+
// Bug 0171: the entry's number and this run's number must count the same
|
|
500
|
+
// thing before `<=` means anything. When they do not, the finding is NOT
|
|
501
|
+
// known — reporting it is the only honest answer, because the accepted
|
|
502
|
+
// ceiling is denominated in a unit the tool no longer produces.
|
|
503
|
+
// `staleMeasurementFinding` supplies the cause and the remedy so this does
|
|
504
|
+
// not read as fresh rot in the code (ADR-009 rule 2).
|
|
505
|
+
if (!measurementComparable(accepted.unit, violation.measuredUnit))
|
|
506
|
+
return false;
|
|
507
|
+
return violation.measured <= accepted.value;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* What the diagnoses in `baseline-diagnostics.ts` need, stated explicitly.
|
|
511
|
+
*
|
|
512
|
+
* An object rather than `this`, because the fields are `private readonly` and
|
|
513
|
+
* a diagnosis should depend on the facts it reads, not on the class that
|
|
514
|
+
* happens to hold them.
|
|
515
|
+
*/
|
|
516
|
+
facts() {
|
|
517
|
+
return {
|
|
518
|
+
knownHashes: this.knownHashes,
|
|
519
|
+
knownSubjects: this.knownSubjects,
|
|
520
|
+
root: this.root,
|
|
521
|
+
sourcePath: this.sourcePath,
|
|
522
|
+
hashVersion: this.hashVersion,
|
|
523
|
+
isKnown: (violation) => this.isKnown(violation),
|
|
524
|
+
hasEntry: (violation) => this.hasEntry(violation),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Filter out known violations, returning only new ones.
|
|
529
|
+
*
|
|
530
|
+
* Config-level meta-findings (empty selector/discovery) are never baselined
|
|
531
|
+
* away — a regenerated baseline must not silence them (ADR-008; plan 0067).
|
|
532
|
+
*
|
|
533
|
+
* A baseline that matches **nothing** looks identical to "every accepted
|
|
534
|
+
* violation regressed at once", so that case is reported as what it is —
|
|
535
|
+
* see \{@link unmatchedBaselineFinding\}.
|
|
536
|
+
*/
|
|
537
|
+
filterNew(violations) {
|
|
538
|
+
const kept = [];
|
|
539
|
+
let matched = 0;
|
|
540
|
+
let matchable = 0;
|
|
541
|
+
for (const violation of violations) {
|
|
542
|
+
if (violation.bypassFilters === true) {
|
|
543
|
+
kept.push(violation);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
matchable += 1;
|
|
547
|
+
// `matched` counts entries the baseline RECOGNISED, not violations it
|
|
548
|
+
// suppressed, and the two differ for a metric finding that regressed past
|
|
549
|
+
// its accepted value (bug 0012). Counting suppression instead made a
|
|
550
|
+
// single-entry baseline whose one metric got worse report "no entry
|
|
551
|
+
// survived: every prior identity is gone" — a false cause, since the
|
|
552
|
+
// identity matched perfectly and only the number moved.
|
|
553
|
+
if (this.hasEntry(violation))
|
|
554
|
+
matched += 1;
|
|
555
|
+
if (this.isKnown(violation))
|
|
556
|
+
continue;
|
|
557
|
+
kept.push(violation);
|
|
558
|
+
}
|
|
559
|
+
// The specific diagnosis SUPERSEDES the generic one, and not merely for
|
|
560
|
+
// tidiness: it disproves it. `unmatchedBaselineFinding` fires on
|
|
561
|
+
// `matched === 0` and, in the same-version case, tells the reader the likely
|
|
562
|
+
// cause is a differently-resolved repository root. A detected description
|
|
563
|
+
// change means a stored SUBJECT matched — and subjects are scrubbed with the
|
|
564
|
+
// same root as hashes — so the root is demonstrably resolving consistently
|
|
565
|
+
// and the root explanation is false. Reporting both would put two
|
|
566
|
+
// contradictory causes in one run, which is the ADR-009 rule 2 defect the
|
|
567
|
+
// withdrawn HASH_VERSION bump already committed once in this area.
|
|
568
|
+
const facts = this.facts();
|
|
569
|
+
const descriptionChange = descriptionChangeFinding(facts, violations);
|
|
570
|
+
const finding = descriptionChange ?? unmatchedBaselineFinding(facts, matched, matchable);
|
|
571
|
+
// Additive, not exclusive: a stale unit and a renamed rule are independent
|
|
572
|
+
// causes that can both be true in one upgrade, and each carries its own
|
|
573
|
+
// remedy. The `??` above is a different situation — there, one diagnosis
|
|
574
|
+
// DISPROVES the other.
|
|
575
|
+
const stale = this.staleMeasurementFinding(violations);
|
|
576
|
+
const meta = [];
|
|
577
|
+
if (stale !== undefined)
|
|
578
|
+
meta.push(stale);
|
|
579
|
+
if (finding !== undefined)
|
|
580
|
+
meta.push(finding);
|
|
581
|
+
return meta.length === 0 ? kept : [...meta, ...kept];
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* The stale-unit measurements in this run, grouped by the unit pair that
|
|
585
|
+
* changed — so one upgrade is described once however many elements it
|
|
586
|
+
* touched, with the identities carried inside.
|
|
587
|
+
*
|
|
588
|
+
* Extracted from {@link staleMeasurementFinding} because the four
|
|
589
|
+
* disqualifying cases are most of that method's branching, the same reason
|
|
590
|
+
* {@link renamedRuleFor} sits beside {@link descriptionChangeFinding}.
|
|
591
|
+
*/
|
|
592
|
+
staleMeasurementsByUnit(violations) {
|
|
593
|
+
const stale = new Map();
|
|
594
|
+
for (const violation of violations) {
|
|
595
|
+
const accepted = this.incomparableAcceptedFor(violation);
|
|
596
|
+
if (accepted === undefined)
|
|
597
|
+
continue;
|
|
598
|
+
const key = `${accepted.unit ?? '(unrecorded)'} -> ${violation.measuredUnit ?? '(none)'}`;
|
|
599
|
+
const named = stale.get(key) ?? [];
|
|
600
|
+
named.push(`${violation.element} (accepted ${String(accepted.value)}, now ${String(violation.measured ?? 0)})`);
|
|
601
|
+
stale.set(key, named);
|
|
602
|
+
}
|
|
603
|
+
return stale;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* The accepted measurement this violation matches but CANNOT be compared
|
|
607
|
+
* against, or `undefined` when there is no such conflict — which covers the
|
|
608
|
+
* ordinary cases too: not a metric finding, no matching entry, or a unit that
|
|
609
|
+
* still means what it meant.
|
|
610
|
+
*/
|
|
611
|
+
incomparableAcceptedFor(violation) {
|
|
612
|
+
if (violation.bypassFilters === true || violation.measured === undefined)
|
|
613
|
+
return undefined;
|
|
614
|
+
const accepted = this.acceptedMeasurements.get(hashViolation(violation, this.root));
|
|
615
|
+
if (accepted === undefined)
|
|
616
|
+
return undefined;
|
|
617
|
+
if (measurementComparable(accepted.unit, violation.measuredUnit))
|
|
618
|
+
return undefined;
|
|
619
|
+
return accepted;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* A meta-finding for baselined metric entries whose accepted measurement is
|
|
623
|
+
* denominated in a unit this version no longer produces — bug 0171.
|
|
624
|
+
*
|
|
625
|
+
* Without it, the upgrade that changed a metric reports the affected classes
|
|
626
|
+
* as ordinary new violations. The author sees findings against code they did
|
|
627
|
+
* not touch, on a rule they had already baselined, with nothing connecting
|
|
628
|
+
* that to the release note — the "eess started reporting something new"
|
|
629
|
+
* failure ADR-009 rule 2 exists to prevent.
|
|
630
|
+
*
|
|
631
|
+
* This fires where `unmatchedBaselineFinding` structurally cannot: those
|
|
632
|
+
* entries MATCH. Identity is intact and only the unit moved, so `matched` is
|
|
633
|
+
* non-zero and that check stays silent by design.
|
|
634
|
+
*/
|
|
635
|
+
staleMeasurementFinding(violations) {
|
|
636
|
+
const stale = this.staleMeasurementsByUnit(violations);
|
|
637
|
+
if (stale.size === 0)
|
|
638
|
+
return undefined;
|
|
639
|
+
const where = this.sourcePath ?? 'the baseline file';
|
|
640
|
+
const total = [...stale.values()].reduce((n, xs) => n + xs.length, 0);
|
|
641
|
+
// Identities, never a bare total (ADR-009 rule 4).
|
|
642
|
+
const detail = [...stale.entries()]
|
|
643
|
+
.map(([units, names]) => `\n ${units}:${names.map((n) => `\n ${n}`).join('')}`)
|
|
644
|
+
.join('');
|
|
645
|
+
return {
|
|
646
|
+
rule: 'eess-ts: baseline',
|
|
647
|
+
element: 'baseline',
|
|
648
|
+
file: '',
|
|
649
|
+
line: 0,
|
|
650
|
+
message: `Baseline at ${where} accepted ${String(total)} metric ${total === 1 ? 'finding' : 'findings'} ` +
|
|
651
|
+
`under a measurement this version no longer produces, so ${total === 1 ? 'it is' : 'they are'} ` +
|
|
652
|
+
`being reported rather than silently re-accepted. This is not new rot in your code — the ` +
|
|
653
|
+
`metric changed what it counts:${detail}`,
|
|
654
|
+
because: 'An accepted ceiling is a number in a unit. Comparing this run against a ceiling recorded in a different unit does not measure a regression — it moves the bar, and the build stays green while the code gets worse.',
|
|
655
|
+
suggestion: `Check that each element above is genuinely acceptable at its NEW number, then regenerate: \`npx eess-ts baseline <your-rule-files> --output ${where}\`. Re-accepting without reading it re-baselines whatever drift the old unit was hiding.`,
|
|
656
|
+
bypassFilters: true,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
/** Number of known violations in the baseline */
|
|
660
|
+
get size() {
|
|
661
|
+
return this.knownHashes.size;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
//# sourceMappingURL=baseline.js.map
|