@cplieger/deadset-ts 8.0.0-dev.2 → 8.0.0-dev.21
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 +3 -1
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/alias-chain.js +35 -9
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +274 -496
- package/dist/src/augmentations.js +64 -3
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/bindings.js +382 -0
- package/dist/src/commands/held.js +499 -0
- package/dist/src/commands/read.js +161 -0
- package/dist/src/commands/shell.js +169 -0
- package/dist/src/commands/tools.js +1847 -0
- package/dist/src/component-files.js +3 -2
- package/dist/src/component-layer.js +4 -2
- package/dist/src/config.js +6 -82
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/consumers.js +10 -38
- package/dist/src/contextual-uses.js +57 -26
- package/dist/src/contract/config.generated.js +561 -0
- package/dist/src/contract/contract.generated.js +1370 -0
- package/dist/src/contract/report.generated.js +866 -0
- package/dist/src/dependencies.js +165 -122
- package/dist/src/destructuring.js +1 -19
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +181 -168
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +52 -256
- package/dist/src/{convention-rows.js → entries/convention-rows.js} +170 -30
- package/dist/src/entries/conventions.js +740 -0
- package/dist/src/{html-entries.js → entries/html-entries.js} +12 -42
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +5 -28
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +670 -0
- package/dist/src/entries/test-runners.js +650 -0
- package/dist/src/entries/workflow-steps.js +345 -0
- package/dist/src/exemptions/argument-places.js +131 -0
- package/dist/src/exemptions/calls.js +338 -0
- package/dist/src/exemptions/classes.js +13 -0
- package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
- package/dist/src/exemptions/detectors.js +48 -0
- package/dist/src/{enum-group.js → exemptions/enum-group.js} +9 -16
- package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +7 -11
- package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +76 -54
- package/dist/src/exemptions/generated-file.js +241 -0
- package/dist/src/{injection-container.js → exemptions/injection-container.js} +20 -26
- package/dist/src/exemptions/installed-globals.js +173 -0
- package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
- package/dist/src/exemptions/lifecycle.js +150 -0
- package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
- package/dist/src/exemptions/serialization-contract.js +1276 -0
- package/dist/src/exemptions/sites.js +182 -0
- package/dist/src/{template-field.js → exemptions/template-field.js} +13 -23
- package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
- package/dist/src/exposure.js +400 -0
- package/dist/src/extensions.js +37 -0
- package/dist/src/facts.js +38 -0
- package/dist/src/failure.js +36 -0
- package/dist/src/file-facts.js +3 -26
- package/dist/src/findings/completion.js +109 -56
- package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
- package/dist/src/findings/emitters.js +27 -41
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +3 -2
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +16 -13
- package/dist/src/findings/non-code-artifacts.js +17 -12
- package/dist/src/findings/reads-and-writes.js +86 -27
- package/dist/src/findings/self-check.js +44 -30
- package/dist/src/findings/unused-declarations.js +29 -45
- package/dist/src/findings/visibility-narrowing.js +23 -31
- package/dist/src/findings-pass.js +13 -21
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +67 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/git-ignore.js +39 -26
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/index/values.js +124 -0
- package/dist/src/index.js +2 -2
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/intra-function-parts.js +63 -43
- package/dist/src/inventory/commonjs.js +507 -0
- package/dist/src/inventory/enumerate.js +167 -0
- package/dist/src/inventory/syntax.js +236 -0
- package/dist/src/inventory.js +323 -724
- package/dist/src/javascript-file.js +48 -0
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +126 -107
- package/dist/src/kinds.js +3 -366
- package/dist/src/{components.js → liveness/components.js} +2 -3
- package/dist/src/{graph.js → liveness/graph.js} +52 -28
- package/dist/src/liveness/matrix.js +99 -0
- package/dist/src/liveness/sweep.js +558 -0
- package/dist/src/load/classify.js +289 -0
- package/dist/src/load/diagnostics.js +87 -0
- package/dist/src/load/discover.js +285 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +27 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/package-scope.js +56 -0
- package/dist/src/load/plan.js +313 -0
- package/dist/src/load/specifiers.js +113 -0
- package/dist/src/load/synthesize.js +152 -0
- package/dist/src/load/test-globals.js +207 -0
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +24 -66
- package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
- package/dist/src/{workspace.js → load/workspace.js} +62 -124
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +82 -16
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +134 -0
- package/dist/src/position.js +43 -1
- package/dist/src/print.js +11 -79
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +95 -0
- package/dist/src/ref.js +12 -20
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +237 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +117 -1074
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +57 -41
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +16 -10
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +246 -730
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +22 -24
- package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +6 -15
- package/dist/src/roots/manifest.js +216 -0
- package/dist/src/roots/published-reach.js +412 -0
- package/dist/src/roots/root-set.js +487 -0
- package/dist/src/roots/unseen-loads.js +236 -0
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -343
- package/dist/src/scope.js +38 -115
- package/dist/src/session.js +37 -29
- package/dist/src/source-files.js +6 -10
- package/dist/src/stores.js +4 -2
- package/dist/src/suppress-file.js +25 -28
- package/dist/src/suppress-inline.js +36 -83
- package/dist/src/suppress.js +50 -71
- package/dist/src/symbol-keys.js +92 -88
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +5 -12
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/named-files.js +298 -0
- package/dist/src/text/yaml.js +684 -0
- package/dist/src/type-errors.js +11 -232
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/vendor/unbash/ansi-c.js +132 -0
- package/dist/src/vendor/unbash/arithmetic-parts.js +37 -0
- package/dist/src/vendor/unbash/arithmetic.js +591 -0
- package/dist/src/vendor/unbash/assignment.js +131 -0
- package/dist/src/vendor/unbash/chars.js +48 -0
- package/dist/src/vendor/unbash/command.js +41 -0
- package/dist/src/vendor/unbash/expansion.js +80 -0
- package/dist/src/vendor/unbash/glob.js +268 -0
- package/dist/src/vendor/unbash/heredoc.js +27 -0
- package/dist/src/vendor/unbash/internal-types.js +1 -0
- package/dist/src/vendor/unbash/lexer.js +4274 -0
- package/dist/src/vendor/unbash/limits.js +3 -0
- package/dist/src/vendor/unbash/parser.js +1366 -0
- package/dist/src/vendor/unbash/parts.js +57 -0
- package/dist/src/vendor/unbash/substitution-context.js +8 -0
- package/dist/src/vendor/unbash/traversal.js +212 -0
- package/dist/src/vendor/unbash/types.js +1 -0
- package/dist/src/vendor/unbash/word-value.js +61 -0
- package/dist/src/vendor/unbash/word.js +40 -0
- package/dist/src/verbs/analyze.js +194 -127
- package/dist/src/verbs/describe.js +13 -18
- package/dist/src/verbs/explain.js +85 -139
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +6 -4
- package/dist/src/verbs/print-retained.js +30 -6
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +14 -3
- package/jsr.json +2 -1
- package/package.json +14 -8
- package/src/alias-chain.ts +40 -11
- package/src/amd.ts +130 -0
- package/src/analysis.ts +426 -733
- package/src/augmentations.ts +93 -4
- package/src/automatic-types.ts +12 -30
- package/src/bindings.ts +460 -0
- package/src/commands/held.ts +632 -0
- package/src/commands/read.ts +242 -0
- package/src/commands/shell.ts +228 -0
- package/src/commands/tools.ts +2382 -0
- package/src/component-files.ts +4 -2
- package/src/component-layer.ts +4 -2
- package/src/config.ts +31 -302
- package/src/conformance-record.ts +2 -2
- package/src/consumers.ts +16 -66
- package/src/contextual-uses.ts +66 -34
- package/src/contract/config.generated.ts +821 -0
- package/src/contract/contract.generated.ts +1453 -0
- package/src/contract/report.generated.ts +1241 -0
- package/src/dependencies.ts +223 -128
- package/src/destructuring.ts +1 -31
- package/src/edges.ts +41 -140
- package/src/element-access.ts +190 -187
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +68 -278
- package/src/{convention-rows.ts → entries/convention-rows.ts} +227 -31
- package/src/{conventions.ts → entries/conventions.ts} +367 -583
- package/src/{html-entries.ts → entries/html-entries.ts} +22 -48
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +7 -30
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +858 -0
- package/src/entries/test-runners.ts +835 -0
- package/src/entries/workflow-steps.ts +432 -0
- package/src/exemptions/argument-places.ts +198 -0
- package/src/exemptions/calls.ts +483 -0
- package/src/exemptions/classes.ts +49 -0
- package/src/exemptions/decorator.ts +56 -0
- package/src/exemptions/detectors.ts +75 -0
- package/src/{enum-group.ts → exemptions/enum-group.ts} +10 -19
- package/src/{erased-stores.ts → exemptions/erased-stores.ts} +7 -14
- package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +85 -66
- package/src/exemptions/generated-file.ts +318 -0
- package/src/{injection-container.ts → exemptions/injection-container.ts} +29 -34
- package/src/exemptions/installed-globals.ts +255 -0
- package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
- package/src/exemptions/lifecycle.ts +286 -0
- package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
- package/src/exemptions/serialization-contract.ts +1729 -0
- package/src/exemptions/sites.ts +293 -0
- package/src/{template-field.ts → exemptions/template-field.ts} +22 -32
- package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
- package/src/exposure.ts +492 -0
- package/src/extensions.ts +45 -0
- package/src/facts.ts +46 -0
- package/src/failure.ts +63 -0
- package/src/file-facts.ts +15 -25
- package/src/finding.ts +10 -10
- package/src/findings/boundary.ts +11 -0
- package/src/findings/completion.ts +133 -64
- package/src/findings/dependencies-and-module-machinery.ts +4 -7
- package/src/findings/emitter.ts +9 -10
- package/src/findings/emitters.ts +38 -52
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +4 -3
- package/src/findings/interfaces.ts +7 -9
- package/src/findings/intra-function.ts +16 -14
- package/src/findings/non-code-artifacts.ts +24 -15
- package/src/findings/reads-and-writes.ts +118 -41
- package/src/findings/self-check.ts +51 -38
- package/src/findings/unused-declarations.ts +31 -49
- package/src/findings/visibility-narrowing.ts +36 -40
- package/src/findings-pass.ts +15 -31
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +96 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +50 -26
- package/src/host.ts +12 -5
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/index/values.ts +170 -0
- package/src/index.ts +2 -2
- package/src/indexed-access.ts +570 -527
- package/src/intra-function-parts.ts +73 -44
- package/src/inventory/commonjs.ts +696 -0
- package/src/inventory/enumerate.ts +203 -0
- package/src/inventory/syntax.ts +311 -0
- package/src/inventory.ts +442 -895
- package/src/javascript-file.ts +54 -0
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +133 -122
- package/src/kinds.ts +5 -413
- package/src/{components.ts → liveness/components.ts} +4 -6
- package/src/{graph.ts → liveness/graph.ts} +100 -35
- package/src/liveness/matrix.ts +162 -0
- package/src/liveness/sweep.ts +855 -0
- package/src/load/classify.ts +433 -0
- package/src/load/diagnostics.ts +154 -0
- package/src/load/discover.ts +471 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +194 -0
- package/src/load/load.ts +73 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/package-scope.ts +74 -0
- package/src/load/plan.ts +513 -0
- package/src/load/specifiers.ts +171 -0
- package/src/load/synthesize.ts +230 -0
- package/src/load/test-globals.ts +251 -0
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +41 -77
- package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
- package/src/{workspace.ts → load/workspace.ts} +90 -134
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/partial.ts +3 -2
- package/src/paths.ts +92 -16
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +189 -0
- package/src/position.ts +44 -1
- package/src/print.ts +12 -92
- package/src/product.ts +18 -0
- package/src/query.ts +201 -10
- package/src/ref.ts +12 -20
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +314 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +187 -1450
- package/src/report-schema.ts +68 -0
- package/src/report.ts +103 -226
- package/src/reporters/annotations.ts +22 -11
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -4
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +29 -16
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +284 -1115
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +26 -29
- package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +7 -16
- package/src/roots/manifest.ts +303 -0
- package/src/roots/published-reach.ts +486 -0
- package/src/{roots.ts → roots/root-set.ts} +286 -469
- package/src/roots/unseen-loads.ts +345 -0
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -388
- package/src/scope.ts +46 -138
- package/src/session.ts +87 -55
- package/src/source-files.ts +6 -11
- package/src/stores.ts +15 -3
- package/src/suppress-file.ts +24 -33
- package/src/suppress-inline.ts +55 -91
- package/src/suppress.ts +78 -79
- package/src/symbol-keys.ts +104 -107
- package/src/tests/file-class.ts +135 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +15 -21
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/named-files.ts +388 -0
- package/src/text/yaml.ts +747 -0
- package/src/type-errors.ts +18 -288
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/vendor/unbash/LICENSE +15 -0
- package/src/vendor/unbash/VENDORED.md +16 -0
- package/src/vendor/unbash/ansi-c.ts +132 -0
- package/src/vendor/unbash/arithmetic-parts.ts +53 -0
- package/src/vendor/unbash/arithmetic.ts +641 -0
- package/src/vendor/unbash/assignment.ts +163 -0
- package/src/vendor/unbash/chars.ts +48 -0
- package/src/vendor/unbash/command.ts +47 -0
- package/src/vendor/unbash/expansion.ts +144 -0
- package/src/vendor/unbash/glob.ts +284 -0
- package/src/vendor/unbash/heredoc.ts +34 -0
- package/src/vendor/unbash/internal-types.ts +94 -0
- package/src/vendor/unbash/lexer.ts +4352 -0
- package/src/vendor/unbash/limits.ts +3 -0
- package/src/vendor/unbash/parser.ts +1612 -0
- package/src/vendor/unbash/parts.ts +96 -0
- package/src/vendor/unbash/substitution-context.ts +20 -0
- package/src/vendor/unbash/traversal.ts +182 -0
- package/src/vendor/unbash/types.ts +660 -0
- package/src/vendor/unbash/word-value.ts +64 -0
- package/src/vendor/unbash/word.ts +50 -0
- package/src/verbs/analyze.ts +255 -164
- package/src/verbs/describe.ts +19 -17
- package/src/verbs/explain.ts +93 -169
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +6 -4
- package/src/verbs/print-retained.ts +36 -6
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +2 -8
- package/src/verbs/version.ts +15 -3
- package/dist/src/base-url.js +0 -18
- package/dist/src/calls.js +0 -155
- package/dist/src/conventions.js +0 -937
- package/dist/src/discover.js +0 -468
- package/dist/src/entry-points.js +0 -529
- package/dist/src/exempt-classes.js +0 -75
- package/dist/src/exempt.js +0 -209
- package/dist/src/generated-file.js +0 -95
- package/dist/src/glob.js +0 -65
- package/dist/src/manifest.js +0 -165
- package/dist/src/matrix.js +0 -246
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/roots.js +0 -672
- package/dist/src/serialization-contract.js +0 -611
- package/dist/src/setup-failure.js +0 -23
- package/dist/src/sweep.js +0 -224
- package/dist/src/test-joins.js +0 -101
- package/dist/src/value-flow.js +0 -74
- package/dist/src/version.js +0 -7
- package/dist/src/workflow-steps.js +0 -185
- package/src/base-url.ts +0 -24
- package/src/calls.ts +0 -225
- package/src/decorator.ts +0 -78
- package/src/discover.ts +0 -653
- package/src/entry-points.ts +0 -687
- package/src/exempt-classes.ts +0 -123
- package/src/exempt.ts +0 -323
- package/src/generated-file.ts +0 -111
- package/src/glob.ts +0 -67
- package/src/manifest.ts +0 -234
- package/src/matrix.ts +0 -423
- package/src/pnpm-workspace.ts +0 -135
- package/src/serialization-contract.ts +0 -849
- package/src/setup-failure.ts +0 -39
- package/src/sweep.ts +0 -347
- package/src/test-joins.ts +0 -120
- package/src/value-flow.ts +0 -104
- package/src/version.ts +0 -7
- package/src/workflow-steps.ts +0 -216
|
@@ -1,17 +1,15 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.ts";
|
|
1
2
|
import type { Finding } from "../finding.ts";
|
|
2
|
-
import { OUTSIDE, type Graph } from "../graph.ts";
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
3
|
+
import { OUTSIDE, type Graph } from "../liveness/graph.ts";
|
|
4
|
+
import { WORDS, type InventorySymbol } from "../inventory.ts";
|
|
5
|
+
import type { Candidate } from "../liveness/sweep.ts";
|
|
5
6
|
import { declarationKey } from "../ref.ts";
|
|
6
|
-
import type { RootKind } from "../roots.ts";
|
|
7
|
-
import { runnerCalls } from "../sweep.ts";
|
|
7
|
+
import type { RootKind } from "../roots/root-set.ts";
|
|
8
|
+
import { runnerCalls } from "../liveness/sweep.ts";
|
|
9
|
+
import { exposedTypes, exposes } from "../exposure.ts";
|
|
8
10
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
9
11
|
import { writeOnlyDeclarations } from "./reads-and-writes.ts";
|
|
10
12
|
|
|
11
|
-
const UNNECESSARY_EXPORT = "DS1101";
|
|
12
|
-
const UNREACHABLE_EXPORT = "DS1103";
|
|
13
|
-
const REDUNDANT_EXPORT_KEYWORD = "DS1104";
|
|
14
|
-
|
|
15
13
|
/** How far the references to one declaration reach, from the narrowest. */
|
|
16
14
|
type Reach = "file" | "package" | "outside";
|
|
17
15
|
|
|
@@ -22,18 +20,6 @@ type Reach = "file" | "package" | "outside";
|
|
|
22
20
|
*/
|
|
23
21
|
const PUBLISHING: ReadonlySet<RootKind> = new Set<RootKind>(["manifest-entry", "published-api"]);
|
|
24
22
|
|
|
25
|
-
/** The reader's word for each kind of declaration this family reports. */
|
|
26
|
-
const WORDS: Partial<Readonly<Record<SymbolKind, string>>> = {
|
|
27
|
-
function: "function",
|
|
28
|
-
class: "class",
|
|
29
|
-
interface: "interface",
|
|
30
|
-
type: "type alias",
|
|
31
|
-
enum: "enum",
|
|
32
|
-
namespace: "namespace",
|
|
33
|
-
variable: "variable",
|
|
34
|
-
"export-alias": "re-export",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
23
|
/** Per declaration of the run, the facts both rules read. */
|
|
38
24
|
interface Facts {
|
|
39
25
|
readonly graph: Graph;
|
|
@@ -145,14 +131,10 @@ function topLevelExport(facts: Facts, at: number, symbol: InventorySymbol): bool
|
|
|
145
131
|
return symbol.exported && parent !== OUTSIDE && facts.graph.symbols[parent]?.kind === "file";
|
|
146
132
|
}
|
|
147
133
|
|
|
148
|
-
function wordOf(symbol: InventorySymbol): string {
|
|
149
|
-
return WORDS[symbol.kind] ?? symbol.kind;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
134
|
/** The visibility each narrowing code names, which only the narrowing codes carry. */
|
|
153
135
|
const NARROWER: Readonly<Record<string, "file" | "package">> = {
|
|
154
|
-
[
|
|
155
|
-
[
|
|
136
|
+
[CODE.redundantExportKeyword]: "file",
|
|
137
|
+
[CODE.unnecessaryExport]: "package",
|
|
156
138
|
};
|
|
157
139
|
|
|
158
140
|
function findingOf(symbol: InventorySymbol, code: string, message: string): Finding {
|
|
@@ -184,7 +166,11 @@ function narrowing(
|
|
|
184
166
|
facts: Facts,
|
|
185
167
|
at: number,
|
|
186
168
|
symbol: InventorySymbol,
|
|
187
|
-
world: {
|
|
169
|
+
world: {
|
|
170
|
+
readonly closed: boolean;
|
|
171
|
+
readonly writeOnly: ReadonlySet<string>;
|
|
172
|
+
readonly exposed: ReadonlySet<string> | "every";
|
|
173
|
+
},
|
|
188
174
|
): Finding | undefined {
|
|
189
175
|
const { closed } = world;
|
|
190
176
|
const file = facts.fileOf[at] ?? OUTSIDE;
|
|
@@ -198,7 +184,7 @@ function narrowing(
|
|
|
198
184
|
) {
|
|
199
185
|
return undefined;
|
|
200
186
|
}
|
|
201
|
-
const word =
|
|
187
|
+
const word = WORDS[symbol.kind];
|
|
202
188
|
switch (facts.reach[at]) {
|
|
203
189
|
case "file":
|
|
204
190
|
if (facts.published[file] === true && !closed) {
|
|
@@ -206,16 +192,16 @@ function narrowing(
|
|
|
206
192
|
}
|
|
207
193
|
return findingOf(
|
|
208
194
|
symbol,
|
|
209
|
-
|
|
195
|
+
CODE.redundantExportKeyword,
|
|
210
196
|
`exported ${word} is referenced only inside the file that declares it`,
|
|
211
197
|
);
|
|
212
198
|
case "package":
|
|
213
|
-
if (!closed || !publishes(facts.rootedBy[at])) {
|
|
199
|
+
if (!closed || !publishes(facts.rootedBy[at]) || exposes(world.exposed, symbol)) {
|
|
214
200
|
return undefined;
|
|
215
201
|
}
|
|
216
202
|
return findingOf(
|
|
217
203
|
symbol,
|
|
218
|
-
|
|
204
|
+
CODE.unnecessaryExport,
|
|
219
205
|
`exported ${word} is referenced only inside the package that declares it`,
|
|
220
206
|
);
|
|
221
207
|
default:
|
|
@@ -234,7 +220,7 @@ function unreachable(
|
|
|
234
220
|
facts: Facts,
|
|
235
221
|
at: number,
|
|
236
222
|
symbol: InventorySymbol,
|
|
237
|
-
candidate:
|
|
223
|
+
candidate: Candidate,
|
|
238
224
|
input: EmitterInput,
|
|
239
225
|
): Finding | undefined {
|
|
240
226
|
if (
|
|
@@ -254,13 +240,13 @@ function unreachable(
|
|
|
254
240
|
: "has no reference in the target";
|
|
255
241
|
return findingOf(
|
|
256
242
|
symbol,
|
|
257
|
-
|
|
258
|
-
`exported ${
|
|
243
|
+
CODE.unreachableExport,
|
|
244
|
+
`exported ${WORDS[symbol.kind]} ${found}, and nothing outside can import the file that declares it`,
|
|
259
245
|
);
|
|
260
246
|
}
|
|
261
247
|
|
|
262
248
|
/**
|
|
263
|
-
* The visibility-narrowing
|
|
249
|
+
* The visibility-narrowing kinds over one run: `DS1501`, `DS1101` and `DS1503`. A finding
|
|
264
250
|
* about a symbol a declared cross-language edge names is reported here like any other, and
|
|
265
251
|
* the edge's evaluation then holds it pending, because the edge stands for a reference from
|
|
266
252
|
* outside the file and the package whose use is decided in the other language. A
|
|
@@ -269,13 +255,23 @@ function unreachable(
|
|
|
269
255
|
export function narrowings(input: EmitterInput): readonly Finding[] {
|
|
270
256
|
const facts = factsOf(input.swept.matrix.union);
|
|
271
257
|
const { declared, loaded } = input.boundary.consumers;
|
|
272
|
-
const closed = input.config.
|
|
273
|
-
const world = {
|
|
258
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
259
|
+
const world = {
|
|
260
|
+
closed,
|
|
261
|
+
writeOnly: writeOnlyDeclarations(input),
|
|
262
|
+
exposed: closed
|
|
263
|
+
? exposedTypes(
|
|
264
|
+
facts.graph,
|
|
265
|
+
facts.reach.map((reach) => reach === "outside"),
|
|
266
|
+
input.boundary.carriers,
|
|
267
|
+
)
|
|
268
|
+
: new Set<string>(),
|
|
269
|
+
};
|
|
274
270
|
const candidates = new Map(
|
|
275
271
|
input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]),
|
|
276
272
|
);
|
|
277
273
|
const generated =
|
|
278
|
-
input.config.analysis.
|
|
274
|
+
input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
279
275
|
|
|
280
276
|
const findings: Finding[] = [];
|
|
281
277
|
facts.graph.symbols.forEach((symbol, at) => {
|
|
@@ -316,5 +312,5 @@ export function unreachableExports(input: EmitterInput): ReadonlySet<string> {
|
|
|
316
312
|
return found;
|
|
317
313
|
}
|
|
318
314
|
|
|
319
|
-
/** The findings of the visibility-narrowing
|
|
315
|
+
/** The findings of the visibility-narrowing kinds, `DS1501`, `DS1101` and `DS1503`. */
|
|
320
316
|
export const visibilityNarrowing: Emitter = narrowings;
|
package/src/findings-pass.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* verdict a report carries beside them.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { EXIT_CLEAN, EXIT_FINDINGS, EXIT_PENDING } from "./contract/contract.generated.ts";
|
|
7
8
|
import type { AnalysisInputs } from "./analysis.ts";
|
|
8
|
-
import { fails, type
|
|
9
|
+
import { fails, type ResolvedConfig } from "./config.ts";
|
|
9
10
|
import type { EdgeEvaluation } from "./edges.ts";
|
|
10
11
|
import type { CompletedFinding, Finding } from "./finding.ts";
|
|
11
12
|
import { decidedFindings, type Withheld } from "./findings/emitters.ts";
|
|
@@ -15,15 +16,10 @@ import {
|
|
|
15
16
|
type StaleRecord,
|
|
16
17
|
type StaleSuppression,
|
|
17
18
|
} from "./findings/self-check.ts";
|
|
18
|
-
import {
|
|
19
|
-
isDependencySubject,
|
|
20
|
-
isRowSubject,
|
|
21
|
-
pairs,
|
|
22
|
-
suppressionCounts,
|
|
23
|
-
type Ledger,
|
|
24
|
-
} from "./suppress.ts";
|
|
19
|
+
import { namesKind, pairs, suppressionCounts, type Ledger } from "./suppress.ts";
|
|
25
20
|
import type { Provenance, Recorded } from "./suppress-file.ts";
|
|
26
|
-
import {
|
|
21
|
+
import { bytewise } from "./text/compare.ts";
|
|
22
|
+
import { PRODUCT } from "./product.ts";
|
|
27
23
|
|
|
28
24
|
/** The counts a report's totals carry beside its findings. */
|
|
29
25
|
interface PassTotals {
|
|
@@ -49,25 +45,14 @@ export interface PassResult {
|
|
|
49
45
|
readonly ledger: Ledger;
|
|
50
46
|
}
|
|
51
47
|
|
|
52
|
-
/** The exit code of a report holding at least one pending finding. */
|
|
53
|
-
export const EXIT_PENDING = 4;
|
|
54
|
-
|
|
55
|
-
/** Two strings ordered bytewise. */
|
|
56
|
-
function compare(a: string, b: string): number {
|
|
57
|
-
if (a === b) {
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
return a < b ? -1 : 1;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
48
|
/** The canonical order: path, line, column, code, then the symbol reference. */
|
|
64
49
|
function canonical(a: Finding, b: Finding): number {
|
|
65
50
|
return (
|
|
66
|
-
|
|
51
|
+
bytewise(a.position.path, b.position.path) ||
|
|
67
52
|
a.position.line - b.position.line ||
|
|
68
53
|
a.position.column - b.position.column ||
|
|
69
|
-
|
|
70
|
-
|
|
54
|
+
bytewise(a.code, b.code) ||
|
|
55
|
+
bytewise(a.symbol.ref, b.symbol.ref)
|
|
71
56
|
);
|
|
72
57
|
}
|
|
73
58
|
|
|
@@ -118,7 +103,7 @@ export function findingsPass(inputs: AnalysisInputs): PassResult {
|
|
|
118
103
|
* suppression is stale or a finding's severity reaches the configured failing severity;
|
|
119
104
|
* clean otherwise.
|
|
120
105
|
*/
|
|
121
|
-
export function verdictOf(result: PassResult, config:
|
|
106
|
+
export function verdictOf(result: PassResult, config: ResolvedConfig): number {
|
|
122
107
|
if (result.totals.pending > 0) {
|
|
123
108
|
return EXIT_PENDING;
|
|
124
109
|
}
|
|
@@ -126,7 +111,7 @@ export function verdictOf(result: PassResult, config: Config): number {
|
|
|
126
111
|
return EXIT_FINDINGS;
|
|
127
112
|
}
|
|
128
113
|
const failing = result.findings.some((finding) =>
|
|
129
|
-
fails(finding.severity, config.reporters.
|
|
114
|
+
fails(finding.severity, config.reporters.fail_on),
|
|
130
115
|
);
|
|
131
116
|
return failing ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
132
117
|
}
|
|
@@ -140,15 +125,14 @@ export interface RecordableFinding {
|
|
|
140
125
|
|
|
141
126
|
/**
|
|
142
127
|
* The rows one round of a baseline write records from its findings, in their order. A
|
|
143
|
-
* finding
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* once.
|
|
128
|
+
* finding no baseline row can name is not recorded: a row recording it would be stale on
|
|
129
|
+
* the first read back. Two findings that would record the same row, two parts of one
|
|
130
|
+
* declaration under one code, record it once.
|
|
147
131
|
*/
|
|
148
132
|
export function recordedFindings(findings: readonly RecordableFinding[]): readonly Recorded[] {
|
|
149
133
|
const rows = new Map<string, Recorded>();
|
|
150
134
|
for (const finding of findings) {
|
|
151
|
-
if (
|
|
135
|
+
if (!namesKind(finding.symbol.kind, "baseline")) {
|
|
152
136
|
continue;
|
|
153
137
|
}
|
|
154
138
|
const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
|
|
@@ -164,5 +148,5 @@ export function rowKey(row: Recorded): string {
|
|
|
164
148
|
|
|
165
149
|
/** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
|
|
166
150
|
export function analyzerProvenance(version: string): Provenance {
|
|
167
|
-
return { analyzer:
|
|
151
|
+
return { analyzer: PRODUCT, version };
|
|
168
152
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one read of a document the target holds at its root, which a checkout under analysis
|
|
3
|
+
* controls: the repository configuration, the ignore file, the baseline and the edges
|
|
4
|
+
* document. The read stays inside the target root, takes a regular file alone, and holds no
|
|
5
|
+
* more of it than the Contract's document bound.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CONFIG_MAX_BYTES } from "../contract/config.generated.ts";
|
|
9
|
+
import type { Host } from "../host.ts";
|
|
10
|
+
import { failureExit } from "../failure.ts";
|
|
11
|
+
import { joinPath, relativePath } from "../paths.ts";
|
|
12
|
+
import { message } from "../product.ts";
|
|
13
|
+
import { utf8Bytes } from "../reporters/sha256.ts";
|
|
14
|
+
import { UsageError } from "../usage.ts";
|
|
15
|
+
|
|
16
|
+
function unreadable(label: string, reason: string): Error {
|
|
17
|
+
return failureExit(
|
|
18
|
+
"unreadable-document",
|
|
19
|
+
`${label}: ${reason}: make it a regular file inside the target root that the run can read, or remove it`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Refuses a document past the Contract's bound with the usage code, naming it `label`. */
|
|
24
|
+
export function refuseOverBound(label: string, text: string): void {
|
|
25
|
+
if (utf8Bytes(text).length > CONFIG_MAX_BYTES) {
|
|
26
|
+
throw new UsageError(`${label}: holds more than ${String(CONFIG_MAX_BYTES)} bytes`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The text of the document `name` at the absolute `targetRoot`, undefined where nothing is
|
|
32
|
+
* there. One whose name resolves outside the target root or to anything but a regular file,
|
|
33
|
+
* or whose read fails, is refused by `refuse` naming it `label`, nothing read from it. A
|
|
34
|
+
* document past the bound is read one byte past it and refused as {@link refuseOverBound} does.
|
|
35
|
+
*/
|
|
36
|
+
export function readTargetDocument(
|
|
37
|
+
host: Host,
|
|
38
|
+
targetRoot: string,
|
|
39
|
+
name: string,
|
|
40
|
+
label = name,
|
|
41
|
+
refuse: (label: string, reason: string) => Error = unreadable,
|
|
42
|
+
): string | undefined {
|
|
43
|
+
const attempt = <T>(read: () => T): T => {
|
|
44
|
+
try {
|
|
45
|
+
return read();
|
|
46
|
+
} catch (error: unknown) {
|
|
47
|
+
throw refuse(label, message(error));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const path = joinPath(targetRoot, name);
|
|
51
|
+
const kind = attempt(() => host.kindOf(path));
|
|
52
|
+
if (kind === "absent") {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const real = attempt(() => host.realPath(path));
|
|
56
|
+
if (
|
|
57
|
+
relativePath(
|
|
58
|
+
attempt(() => host.realPath(targetRoot)),
|
|
59
|
+
real,
|
|
60
|
+
) === undefined
|
|
61
|
+
) {
|
|
62
|
+
throw refuse(label, `resolves to ${real}, outside the target root`);
|
|
63
|
+
}
|
|
64
|
+
if (kind !== "file") {
|
|
65
|
+
throw refuse(label, "is not a regular file");
|
|
66
|
+
}
|
|
67
|
+
const text = attempt(() => host.readFile(real, CONFIG_MAX_BYTES + 1));
|
|
68
|
+
refuseOverBound(label, text);
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Host } from "../host.ts";
|
|
2
|
+
import { isRecord } from "../json-document.ts";
|
|
3
|
+
import { joinPath } from "../paths.ts";
|
|
4
|
+
import { message } from "../product.ts";
|
|
5
|
+
|
|
6
|
+
/** The file a package's manifest is read from. */
|
|
7
|
+
export const MANIFEST = "package.json";
|
|
8
|
+
|
|
9
|
+
/** The directory a package manager installs packages into, whose files are no project's own. */
|
|
10
|
+
export const INSTALLED_PACKAGES = "node_modules";
|
|
11
|
+
|
|
12
|
+
/** The manifest members a package declares the packages it depends on under. */
|
|
13
|
+
export const DEPENDENCY_SECTIONS = [
|
|
14
|
+
"dependencies",
|
|
15
|
+
"devDependencies",
|
|
16
|
+
"peerDependencies",
|
|
17
|
+
"optionalDependencies",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
/** Whether one path passes through an installed-packages directory. */
|
|
21
|
+
export function inInstalledPackages(path: string): boolean {
|
|
22
|
+
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** What one JSON file reads as: its text and decoded value, text that does not parse, or a file that cannot be read, with the read's error. */
|
|
26
|
+
type JSONRead =
|
|
27
|
+
| { readonly kind: "read"; readonly text: string; readonly value: unknown }
|
|
28
|
+
| { readonly kind: "malformed" }
|
|
29
|
+
| { readonly kind: "unread"; readonly reason: string };
|
|
30
|
+
|
|
31
|
+
/** One JSON file, read and parsed, as {@link JSONRead} tells its states apart. */
|
|
32
|
+
export function readJSONFile(host: Host, path: string): JSONRead {
|
|
33
|
+
let text: string;
|
|
34
|
+
try {
|
|
35
|
+
text = host.readFile(path);
|
|
36
|
+
} catch (error: unknown) {
|
|
37
|
+
return { kind: "unread", reason: message(error) };
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return { kind: "read", text, value: JSON.parse(text) as unknown };
|
|
41
|
+
} catch {
|
|
42
|
+
return { kind: "malformed" };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
47
|
+
export function readDocument(
|
|
48
|
+
host: Host,
|
|
49
|
+
path: string,
|
|
50
|
+
): { text: string; value: unknown } | undefined {
|
|
51
|
+
const read = readJSONFile(host, path);
|
|
52
|
+
return read.kind === "read" ? { text: read.text, value: read.value } : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
56
|
+
export function readJSON(host: Host, path: string): unknown {
|
|
57
|
+
return readDocument(host, path)?.value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The manifest in one directory, decoded, or undefined where none reads as an object. A
|
|
62
|
+
* manifest this program cannot read declares nothing, the same answer as an absent one:
|
|
63
|
+
* the package manager owns the refusal of a manifest it rejects.
|
|
64
|
+
*/
|
|
65
|
+
export function manifestAt(host: Host, dir: string): Record<string, unknown> | undefined {
|
|
66
|
+
const value = readJSON(host, joinPath(dir, MANIFEST));
|
|
67
|
+
return isRecord(value) ? value : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* What one directory's manifest reads as: none, an object, a file that does not parse as one,
|
|
72
|
+
* or a file that cannot be read, with the read's error. Either of the last two still bounds
|
|
73
|
+
* the package it would declare.
|
|
74
|
+
*/
|
|
75
|
+
export type ManifestRead =
|
|
76
|
+
| { readonly kind: "absent" }
|
|
77
|
+
| { readonly kind: "read"; readonly manifest: Record<string, unknown> }
|
|
78
|
+
| { readonly kind: "malformed" }
|
|
79
|
+
| { readonly kind: "unread"; readonly reason: string };
|
|
80
|
+
|
|
81
|
+
/** The manifest in one directory, as {@link ManifestRead} tells its states apart. */
|
|
82
|
+
export function readManifestAt(host: Host, dir: string): ManifestRead {
|
|
83
|
+
if (!holdsManifest(host, dir)) {
|
|
84
|
+
return { kind: "absent" };
|
|
85
|
+
}
|
|
86
|
+
const read = readJSONFile(host, joinPath(dir, MANIFEST));
|
|
87
|
+
if (read.kind !== "read") {
|
|
88
|
+
return read;
|
|
89
|
+
}
|
|
90
|
+
return isRecord(read.value) ? { kind: "read", manifest: read.value } : { kind: "malformed" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Whether one directory holds a manifest file. */
|
|
94
|
+
export function holdsManifest(host: Host, dir: string): boolean {
|
|
95
|
+
return host.kindOf(joinPath(dir, MANIFEST)) === "file";
|
|
96
|
+
}
|
package/src/fs/tree.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { DirectoryEntry, Host } from "../host.ts";
|
|
2
|
+
import { ancestors, joinPath, resolvePath } from "../paths.ts";
|
|
3
|
+
import { LANGUAGE_ROWS } from "../contract/contract.generated.ts";
|
|
4
|
+
import { LANGUAGE } from "../product.ts";
|
|
5
|
+
import { bytewise } from "../text/compare.ts";
|
|
6
|
+
import { globExpression } from "../text/glob.ts";
|
|
7
|
+
|
|
8
|
+
/** The directories no walk of the tree enters, by the patterns the language's row names. */
|
|
9
|
+
const SKIPPED_DIRECTORIES = LANGUAGE_ROWS[LANGUAGE].skippedDirectories.map(globExpression);
|
|
10
|
+
|
|
11
|
+
function skipped(name: string): boolean {
|
|
12
|
+
return SKIPPED_DIRECTORIES.some((pattern) => pattern.test(name));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A git repository's working tree root, and the `info/exclude` file git reads for it. */
|
|
16
|
+
interface Repository {
|
|
17
|
+
readonly root: string;
|
|
18
|
+
/** Undefined where a `.git` file names no repository directory. */
|
|
19
|
+
readonly exclude: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One file's text without its line ending, or undefined where it cannot be read. */
|
|
23
|
+
function lineOf(host: Host, path: string): string | undefined {
|
|
24
|
+
try {
|
|
25
|
+
return host.readFile(path).replace(/\r?\n$/u, "");
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The repository whose working tree holds `dir`: the nearest directory at or above it with a
|
|
33
|
+
* `.git`. A `.git` file, which a linked worktree or a submodule holds, names the repository's
|
|
34
|
+
* directory on its `gitdir:` line; that directory's `commondir` file, where it has one, names
|
|
35
|
+
* the directory shared with the main worktree, which holds `info/exclude`:
|
|
36
|
+
* https://git-scm.com/docs/gitrepository-layout
|
|
37
|
+
*/
|
|
38
|
+
export function repositoryOf(host: Host, dir: string): Repository | undefined {
|
|
39
|
+
for (const root of ancestors(dir)) {
|
|
40
|
+
const dotGit = joinPath(root, ".git");
|
|
41
|
+
const kind = host.kindOf(dotGit);
|
|
42
|
+
if (kind === "directory") {
|
|
43
|
+
return { root, exclude: joinPath(dotGit, "info/exclude") };
|
|
44
|
+
}
|
|
45
|
+
if (kind === "file") {
|
|
46
|
+
const named = /^gitdir: (.+)$/u.exec(lineOf(host, dotGit) ?? "")?.[1];
|
|
47
|
+
const gitDir = named === undefined ? undefined : resolvePath(root, named);
|
|
48
|
+
const common = gitDir === undefined ? undefined : lineOf(host, joinPath(gitDir, "commondir"));
|
|
49
|
+
const shared =
|
|
50
|
+
gitDir === undefined || common === undefined ? gitDir : resolvePath(gitDir, common);
|
|
51
|
+
return { root, exclude: shared === undefined ? undefined : joinPath(shared, "info/exclude") };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The entries of one directory, none where it cannot be read. */
|
|
58
|
+
export function entriesOf(host: Host, dir: string): readonly DirectoryEntry[] {
|
|
59
|
+
try {
|
|
60
|
+
return host.readDirectory(dir);
|
|
61
|
+
} catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The names of the files directly in `dir` whose name `accepts`, in bytewise order. */
|
|
67
|
+
export function filesIn(
|
|
68
|
+
host: Host,
|
|
69
|
+
dir: string,
|
|
70
|
+
accepts: (name: string) => boolean,
|
|
71
|
+
): readonly string[] {
|
|
72
|
+
return entriesOf(host, dir)
|
|
73
|
+
.filter((entry) => !entry.directory && accepts(entry.name))
|
|
74
|
+
.map((entry) => entry.name)
|
|
75
|
+
.sort(bytewise);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The names of the directories directly in `dir` a walk enters, in bytewise order. */
|
|
79
|
+
export function directoriesIn(host: Host, dir: string): readonly string[] {
|
|
80
|
+
return entriesOf(host, dir)
|
|
81
|
+
.filter((entry) => entry.directory && !skipped(entry.name))
|
|
82
|
+
.map((entry) => entry.name)
|
|
83
|
+
.sort(bytewise);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Every file below `root` whose name `accepts`, by its path relative to `root` in bytewise
|
|
88
|
+
* order, read in no skipped directory.
|
|
89
|
+
*/
|
|
90
|
+
export function filesBelow(
|
|
91
|
+
host: Host,
|
|
92
|
+
root: string,
|
|
93
|
+
accepts: (name: string) => boolean,
|
|
94
|
+
): readonly string[] {
|
|
95
|
+
const found: string[] = [];
|
|
96
|
+
const pending = [""];
|
|
97
|
+
for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
|
|
98
|
+
for (const entry of entriesOf(host, joinPath(root, dir))) {
|
|
99
|
+
const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
100
|
+
if (!entry.directory) {
|
|
101
|
+
if (accepts(entry.name)) {
|
|
102
|
+
found.push(path);
|
|
103
|
+
}
|
|
104
|
+
} else if (!skipped(entry.name)) {
|
|
105
|
+
pending.push(path);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return found.sort(bytewise);
|
|
110
|
+
}
|
package/src/git-ignore.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which files
|
|
2
|
+
* Which files git ignores below a tree's root, read as git reads its ignore files:
|
|
3
3
|
* https://git-scm.com/docs/gitignore#_pattern_format
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { globExpression } from "./glob.ts";
|
|
7
6
|
import type { Host } from "./host.ts";
|
|
8
|
-
import { dirnamePath, joinPath, relativePath } from "./paths.ts";
|
|
7
|
+
import { ancestors, dirnamePath, joinPath, relativePath } from "./paths.ts";
|
|
8
|
+
import { message } from "./product.ts";
|
|
9
|
+
import { gitignoreExpression } from "./text/glob.ts";
|
|
10
|
+
import { repositoryOf } from "./fs/tree.ts";
|
|
9
11
|
|
|
10
|
-
/** One pattern line of
|
|
12
|
+
/** One pattern line of an ignore file. */
|
|
11
13
|
interface Rule {
|
|
12
14
|
readonly negated: boolean;
|
|
13
15
|
/** Whether the pattern ends with `/`, so it matches a directory alone. */
|
|
@@ -16,7 +18,7 @@ interface Rule {
|
|
|
16
18
|
readonly expression: RegExp;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
/** The rules one
|
|
21
|
+
/** The rules one ignore file's text writes, in order. */
|
|
20
22
|
function rulesOf(text: string): readonly Rule[] {
|
|
21
23
|
const rules: Rule[] = [];
|
|
22
24
|
for (const raw of text.split(/\r?\n/u)) {
|
|
@@ -25,7 +27,7 @@ function rulesOf(text: string): readonly Rule[] {
|
|
|
25
27
|
continue;
|
|
26
28
|
}
|
|
27
29
|
const negated = line.startsWith("!");
|
|
28
|
-
line = negated
|
|
30
|
+
line = negated ? line.slice(1) : line;
|
|
29
31
|
const directoryOnly = line.endsWith("/");
|
|
30
32
|
line = line.replace(/\/+$/u, "");
|
|
31
33
|
if (line === "") {
|
|
@@ -38,43 +40,65 @@ function rulesOf(text: string): readonly Rule[] {
|
|
|
38
40
|
rules.push({
|
|
39
41
|
negated,
|
|
40
42
|
directoryOnly,
|
|
41
|
-
expression:
|
|
43
|
+
expression: gitignoreExpression(anchored ? pattern : `**/${pattern}`),
|
|
42
44
|
});
|
|
43
45
|
}
|
|
44
46
|
return rules;
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
/** Told each ignore file that exists and cannot be read, with why. */
|
|
50
|
+
type UnreadIgnoreFile = (path: string, reason: string) => void;
|
|
51
|
+
|
|
52
|
+
function rulesIn(host: Host, path: string, unread: UnreadIgnoreFile): readonly Rule[] {
|
|
53
|
+
try {
|
|
54
|
+
return rulesOf(host.readFile(path));
|
|
55
|
+
} catch (error: unknown) {
|
|
56
|
+
if (host.kindOf(path) !== "absent") {
|
|
57
|
+
unread(path, message(error));
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
47
63
|
/**
|
|
48
|
-
* Whether
|
|
49
|
-
*
|
|
50
|
-
*
|
|
64
|
+
* Whether git ignores one absolute path below `root`: by the repository's `info/exclude`, then
|
|
65
|
+
* the `.gitignore` files from its root down to the path's directory, a later pattern overriding
|
|
66
|
+
* an earlier one, though a file below an ignored directory stays ignored, as git cannot re-include
|
|
67
|
+
* it. A path outside `root` is not ignored. With no repository, the files at and below `root` are
|
|
68
|
+
* read; with one, the directories above `root` give patterns but are not tested. An unreadable
|
|
69
|
+
* ignore file ignores nothing, and `unread` is told it.
|
|
51
70
|
*/
|
|
52
|
-
export function gitIgnored(
|
|
71
|
+
export function gitIgnored(
|
|
72
|
+
host: Host,
|
|
73
|
+
root: string,
|
|
74
|
+
unread: UnreadIgnoreFile = () => undefined,
|
|
75
|
+
): (path: string) => boolean {
|
|
76
|
+
const found = repositoryOf(host, root);
|
|
77
|
+
const repository = found?.root;
|
|
78
|
+
const above =
|
|
79
|
+
repository === undefined ? [] : [...ancestors(dirnamePath(root), repository)].reverse();
|
|
80
|
+
const excluded = found?.exclude === undefined ? [] : rulesIn(host, found.exclude, unread);
|
|
53
81
|
const read = new Map<string, readonly Rule[]>();
|
|
54
82
|
const rulesAt = (dir: string): readonly Rule[] => {
|
|
55
83
|
let rules = read.get(dir);
|
|
56
84
|
if (rules === undefined) {
|
|
57
|
-
|
|
58
|
-
rules = rulesOf(host.readFile(joinPath(dir, ".gitignore")));
|
|
59
|
-
} catch {
|
|
60
|
-
rules = [];
|
|
61
|
-
}
|
|
85
|
+
rules = rulesIn(host, joinPath(dir, ".gitignore"), unread);
|
|
62
86
|
read.set(dir, rules);
|
|
63
87
|
}
|
|
64
88
|
return rules;
|
|
65
89
|
};
|
|
66
|
-
/** Whether the rules of `root` and of each directory down to one path's own decide it ignored. */
|
|
67
90
|
const decided = (path: string, directory: boolean): boolean => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
const sources: [string, readonly Rule[]][] = [
|
|
92
|
+
...(repository === undefined ? [] : [[repository, excluded] as [string, readonly Rule[]]]),
|
|
93
|
+
...[...above, ...[...ancestors(dirnamePath(path), root)].reverse()].map(
|
|
94
|
+
(dir): [string, readonly Rule[]] => [dir, rulesAt(dir)],
|
|
95
|
+
),
|
|
96
|
+
];
|
|
73
97
|
let ignored = false;
|
|
74
|
-
for (const dir of
|
|
75
|
-
const below = relativePath(dir, path);
|
|
76
|
-
for (const rule of
|
|
77
|
-
if ((directory || !rule.directoryOnly) && rule.expression.test(below
|
|
98
|
+
for (const [dir, rules] of sources) {
|
|
99
|
+
const below = relativePath(dir, path) ?? "";
|
|
100
|
+
for (const rule of rules) {
|
|
101
|
+
if ((directory || !rule.directoryOnly) && rule.expression.test(below)) {
|
|
78
102
|
ignored = !rule.negated;
|
|
79
103
|
}
|
|
80
104
|
}
|