@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
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated-file class on TypeScript: every declaration in a file that marks itself
|
|
3
|
+
* generated, below a directory an applied convention row names as generated, or minified,
|
|
4
|
+
* is retained, because a generator owns the file and a deletion there is undone at the next
|
|
5
|
+
* generation. The same decision is each `generated` entry of the facts document.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
isIdentifier,
|
|
10
|
+
isImportDeclaration,
|
|
11
|
+
isPropertyAssignment,
|
|
12
|
+
isStringLiteralLikeNode,
|
|
13
|
+
isVariableStatement,
|
|
14
|
+
NodeFlags,
|
|
15
|
+
type Node,
|
|
16
|
+
type SourceFile,
|
|
17
|
+
} from "@typescript/native/ast";
|
|
18
|
+
import { GENERATORS } from "../contract/contract.generated.ts";
|
|
19
|
+
import type { DetectorInput, Evidence } from "./lifecycle.ts";
|
|
20
|
+
import { minifiedBy } from "../javascript-file.ts";
|
|
21
|
+
import type { SourceFiles } from "../source-files.ts";
|
|
22
|
+
import { bytewise } from "../text/compare.ts";
|
|
23
|
+
import { satisfies } from "../version-range.ts";
|
|
24
|
+
|
|
25
|
+
/** The generated-code header, matched against each line of a leading comment. */
|
|
26
|
+
const HEADER_LINE = /^\/\/ Code generated .* DO NOT EDIT\.$/u;
|
|
27
|
+
|
|
28
|
+
/** The documentation tag `@generated` as a word of a comment. */
|
|
29
|
+
const GENERATED_TAG = /(?<![\w@$])@generated(?![\w$-])/u;
|
|
30
|
+
|
|
31
|
+
/** A first comment naming the tool that wrote the file. */
|
|
32
|
+
const GENERATED_BY = /^\/\/ Generated by \S/u;
|
|
33
|
+
|
|
34
|
+
/** The word a generator's notice holds, beside one of {@link KEEP_OUT}. */
|
|
35
|
+
const GENERATED_WORD = /\bgenerated\b/iu;
|
|
36
|
+
|
|
37
|
+
/** The phrases a generator's notice asks a reader not to edit the file by. */
|
|
38
|
+
const KEEP_OUT = /\b(?:do not edit|will be overwritten|should not make any changes)\b/iu;
|
|
39
|
+
|
|
40
|
+
/** What decided a file generated, as the facts document's `evidence` names it. */
|
|
41
|
+
type GeneratedEvidence = "header" | "tag" | "notice" | "convention" | "minified";
|
|
42
|
+
|
|
43
|
+
/** The generator row one generated file matches, with the version and the schema files its output states. */
|
|
44
|
+
interface GeneratorMatch {
|
|
45
|
+
readonly row: string;
|
|
46
|
+
readonly version?: string;
|
|
47
|
+
readonly schemaFiles: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Why one file is generated, and which generator wrote it. */
|
|
51
|
+
export interface GeneratedFact {
|
|
52
|
+
readonly evidence: GeneratedEvidence;
|
|
53
|
+
/** The 1-based line of the comment line that marks the file; 1 for a row or a minified file. */
|
|
54
|
+
readonly line: number;
|
|
55
|
+
/** The marking line's text, the convention row's name, or what decided the file minified. */
|
|
56
|
+
readonly detail: string;
|
|
57
|
+
readonly generator?: GeneratorMatch;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** One line of a leading comment, with its 1-based line in the file. */
|
|
61
|
+
interface CommentLine {
|
|
62
|
+
readonly text: string;
|
|
63
|
+
readonly line: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The comments before a text's first token, a leading hashbang line skipped, each as its lines. */
|
|
67
|
+
function leadingComments(text: string): readonly (readonly CommentLine[])[] {
|
|
68
|
+
const comments: CommentLine[][] = [];
|
|
69
|
+
const lineAt = (offset: number): number =>
|
|
70
|
+
text.slice(0, offset).split(/\r\n|[\n\r\u2028\u2029]/u).length;
|
|
71
|
+
const lines = (from: number, to: number): CommentLine[] => {
|
|
72
|
+
const first = lineAt(from);
|
|
73
|
+
return text
|
|
74
|
+
.slice(from, to)
|
|
75
|
+
.split(/\r\n|[\n\r\u2028\u2029]/u)
|
|
76
|
+
.map((one, index) => ({ text: one, line: first + index }));
|
|
77
|
+
};
|
|
78
|
+
let at = text.startsWith("\uFEFF") ? 1 : 0;
|
|
79
|
+
if (text.startsWith("#!", at)) {
|
|
80
|
+
const end = text.indexOf("\n", at);
|
|
81
|
+
at = end === -1 ? text.length : end + 1;
|
|
82
|
+
}
|
|
83
|
+
for (;;) {
|
|
84
|
+
while (at < text.length && /\s/u.test(text.charAt(at))) {
|
|
85
|
+
at++;
|
|
86
|
+
}
|
|
87
|
+
if (text.startsWith("//", at)) {
|
|
88
|
+
const end = text.slice(at).search(/[\r\n\u2028\u2029]/u);
|
|
89
|
+
const stop = end === -1 ? text.length : at + end;
|
|
90
|
+
comments.push(lines(at, stop));
|
|
91
|
+
at = stop;
|
|
92
|
+
} else if (text.startsWith("/*", at)) {
|
|
93
|
+
const end = text.indexOf("*/", at + 2);
|
|
94
|
+
if (end === -1) {
|
|
95
|
+
return comments;
|
|
96
|
+
}
|
|
97
|
+
comments.push(lines(at, end + 2));
|
|
98
|
+
at = end + 2;
|
|
99
|
+
} else {
|
|
100
|
+
return comments;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The mark one file's leading comments make it generated by, or undefined where they mark nothing. */
|
|
106
|
+
export function generatedMark(text: string): Omit<GeneratedFact, "generator"> | undefined {
|
|
107
|
+
const lines = leadingComments(text).flat();
|
|
108
|
+
const header = lines.find((one) => HEADER_LINE.test(one.text));
|
|
109
|
+
if (header !== undefined) {
|
|
110
|
+
return { evidence: "header", line: header.line, detail: header.text };
|
|
111
|
+
}
|
|
112
|
+
const tag = lines.find((one) => GENERATED_TAG.test(one.text));
|
|
113
|
+
if (tag !== undefined) {
|
|
114
|
+
return { evidence: "tag", line: tag.line, detail: tag.text };
|
|
115
|
+
}
|
|
116
|
+
const [first] = lines;
|
|
117
|
+
if (first !== undefined && GENERATED_BY.test(first.text)) {
|
|
118
|
+
return { evidence: "notice", line: first.line, detail: first.text };
|
|
119
|
+
}
|
|
120
|
+
const notice = lines.map((one) => one.text).join("\n");
|
|
121
|
+
if (GENERATED_WORD.test(notice) && KEEP_OUT.test(notice)) {
|
|
122
|
+
const marked = lines.find((one) => GENERATED_WORD.test(one.text) || KEEP_OUT.test(one.text));
|
|
123
|
+
return marked === undefined
|
|
124
|
+
? undefined
|
|
125
|
+
: { evidence: "notice", line: marked.line, detail: marked.text };
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** What one form's first group captures, for each line the form matches. */
|
|
131
|
+
function after(lines: readonly CommentLine[], form: RegExp): readonly string[] {
|
|
132
|
+
return lines.flatMap((one) => form.exec(one.text)?.[1] ?? []);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Where each plugin's output states its version and its schema files, as `references-ts.36`
|
|
137
|
+
* states: a plugin missing here states no version, and one with no schema form names none.
|
|
138
|
+
*/
|
|
139
|
+
const STATEMENTS: Readonly<Record<string, { version?: RegExp; schemaFiles?: RegExp }>> = {
|
|
140
|
+
"protoc-gen-es": {
|
|
141
|
+
version: /^\/\/ @generated by protoc-gen-es (\S+)/u,
|
|
142
|
+
schemaFiles: /^\/\/ @generated from file (\S+)/u,
|
|
143
|
+
},
|
|
144
|
+
"protoc-gen-connect-es": {
|
|
145
|
+
version: /^\/\/ @generated by protoc-gen-connect-es (\S+)/u,
|
|
146
|
+
schemaFiles: /^\/\/ @generated from file (\S+)/u,
|
|
147
|
+
},
|
|
148
|
+
"protobuf-ts": {
|
|
149
|
+
version: /^\/\/ @generated by protobuf-ts (\S+)/u,
|
|
150
|
+
schemaFiles: /^\/\/ @generated from protobuf file "([^"]+)"/u,
|
|
151
|
+
},
|
|
152
|
+
"ts-proto": {
|
|
153
|
+
version: /^\/\/\s+protoc-gen-ts_proto\s+(\S+)$/u,
|
|
154
|
+
schemaFiles: /^\/\/ source: (.+)$/u,
|
|
155
|
+
},
|
|
156
|
+
"protoc-gen-grpc-web": {
|
|
157
|
+
version: /^\/\/\s+protoc-gen-grpc-web\s+(\S+)$/u,
|
|
158
|
+
schemaFiles: /^\/\/ source: (.+)$/u,
|
|
159
|
+
},
|
|
160
|
+
"protoc-gen-js": { schemaFiles: /^\/\/ source: (.+)$/u },
|
|
161
|
+
"ts-protoc-gen": { schemaFiles: /^\/\/ file: (.+)$/u },
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** A ts-proto service path literal: `/<package>.<service>/<method>`. */
|
|
165
|
+
const SERVICE_PATH = /^\/(?:\w+\.)+\w+\/\w+$/u;
|
|
166
|
+
|
|
167
|
+
/** Whether one ts-proto file writes a service path, as `references-ts.36` reads one. */
|
|
168
|
+
function writesServicePath(file: SourceFile): boolean {
|
|
169
|
+
const named = file.statements.some(
|
|
170
|
+
(statement) =>
|
|
171
|
+
isVariableStatement(statement) &&
|
|
172
|
+
(statement.declarationList.flags & NodeFlags.Const) !== 0 &&
|
|
173
|
+
statement.declarationList.declarations.some(
|
|
174
|
+
(one) =>
|
|
175
|
+
isIdentifier(one.name) &&
|
|
176
|
+
one.name.text.endsWith("ServiceName") &&
|
|
177
|
+
one.initializer !== undefined &&
|
|
178
|
+
isStringLiteralLikeNode(one.initializer),
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
let found = named;
|
|
182
|
+
const visit = (node: Node): void => {
|
|
183
|
+
if (found) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (
|
|
187
|
+
isPropertyAssignment(node) &&
|
|
188
|
+
isIdentifier(node.name) &&
|
|
189
|
+
node.name.text === "path" &&
|
|
190
|
+
isStringLiteralLikeNode(node.initializer) &&
|
|
191
|
+
SERVICE_PATH.test(node.initializer.text)
|
|
192
|
+
) {
|
|
193
|
+
found = true;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
node.forEachChild(visit);
|
|
197
|
+
};
|
|
198
|
+
file.forEachChild(visit);
|
|
199
|
+
return found;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Whether one file imports a module by its specifier. */
|
|
203
|
+
function imports(file: SourceFile, module: string): boolean {
|
|
204
|
+
return file.statements.some(
|
|
205
|
+
(statement) =>
|
|
206
|
+
isImportDeclaration(statement) &&
|
|
207
|
+
isStringLiteralLikeNode(statement.moduleSpecifier) &&
|
|
208
|
+
statement.moduleSpecifier.text === module,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** The option sets the rows of `references-ts.36` name, by row; every other row's is met by any output. */
|
|
213
|
+
const OPTION_SETS: Readonly<Record<string, (file: SourceFile) => boolean>> = {
|
|
214
|
+
"ts-proto-2-output-schema": (file) => imports(file, "ts-proto-descriptors"),
|
|
215
|
+
"ts-proto-2": writesServicePath,
|
|
216
|
+
"ts-proto-2-types": (file) => !writesServicePath(file),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/** The first generator row one file matches, in `generators.json`'s order, as `bindings.3` reads it. */
|
|
220
|
+
export function generatorOf(file: SourceFile): GeneratorMatch | undefined {
|
|
221
|
+
const lines = leadingComments(file.text).flat();
|
|
222
|
+
for (const row of GENERATORS) {
|
|
223
|
+
const header = new RegExp(row.header, "u");
|
|
224
|
+
if (!lines.some((one) => header.test(one.text))) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const statements = STATEMENTS[row.plugin] ?? {};
|
|
228
|
+
const [version] = statements.version === undefined ? [] : after(lines, statements.version);
|
|
229
|
+
if (
|
|
230
|
+
"versions" in row &&
|
|
231
|
+
(version === undefined ||
|
|
232
|
+
!satisfies(
|
|
233
|
+
version.replace(/^v/u, ""),
|
|
234
|
+
`>=${row.versions.min.replace(/^v/u, "")} <=${row.versions.max.replace(/^v/u, "")}`,
|
|
235
|
+
))
|
|
236
|
+
) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (OPTION_SETS[row.id]?.(file) === false) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const schemaFiles =
|
|
243
|
+
statements.schemaFiles === undefined
|
|
244
|
+
? []
|
|
245
|
+
: [
|
|
246
|
+
...new Set(
|
|
247
|
+
after(lines, statements.schemaFiles).filter((one) => one.endsWith(".proto")),
|
|
248
|
+
),
|
|
249
|
+
].sort(bytewise);
|
|
250
|
+
return { row: row.id, ...(version === undefined ? {} : { version }), schemaFiles };
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Every own file of one project that is generated, by its path below the target root, with
|
|
257
|
+
* what decided it: its leading comments' mark, else the row naming its directory as
|
|
258
|
+
* generated, else its minified name or text; and the generator row it matches.
|
|
259
|
+
*/
|
|
260
|
+
export function generatedFilesOf(
|
|
261
|
+
files: SourceFiles,
|
|
262
|
+
byRow: ReadonlyMap<string, string>,
|
|
263
|
+
): ReadonlyMap<string, GeneratedFact> {
|
|
264
|
+
const found = new Map<string, GeneratedFact>();
|
|
265
|
+
for (const [path, file] of files.byPath) {
|
|
266
|
+
const row = byRow.get(path);
|
|
267
|
+
const minified = row === undefined ? minifiedBy(file.fileName, file.text) : undefined;
|
|
268
|
+
const fact =
|
|
269
|
+
generatedMark(file.text) ??
|
|
270
|
+
(row === undefined
|
|
271
|
+
? minified === undefined
|
|
272
|
+
? undefined
|
|
273
|
+
: { evidence: "minified" as const, line: 1, detail: minified }
|
|
274
|
+
: { evidence: "convention" as const, line: 1, detail: row });
|
|
275
|
+
if (fact === undefined) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const generator = generatorOf(file);
|
|
279
|
+
found.set(path, generator === undefined ? fact : { ...fact, generator });
|
|
280
|
+
}
|
|
281
|
+
return found;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** The detail a retention in a generated file names, from what decided the file. */
|
|
285
|
+
function retentionDetail(fact: GeneratedFact): string {
|
|
286
|
+
switch (fact.evidence) {
|
|
287
|
+
case "header":
|
|
288
|
+
return "generated by the header line its leading comments hold";
|
|
289
|
+
case "tag":
|
|
290
|
+
return "generated by the @generated tag its leading comments hold";
|
|
291
|
+
case "notice":
|
|
292
|
+
return "generated by the notice its leading comments hold";
|
|
293
|
+
case "convention":
|
|
294
|
+
return `generated below a directory the convention row ${fact.detail} names`;
|
|
295
|
+
case "minified":
|
|
296
|
+
return "generated as a minified JavaScript file";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The generated-file detector, recording each file's first line. */
|
|
301
|
+
export function generatedFile<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
|
|
302
|
+
const generated = input.generated;
|
|
303
|
+
if (generated === undefined || generated.size === 0) {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
return input.held.symbols.flatMap((symbol): Evidence[] => {
|
|
307
|
+
const fact = generated.get(symbol.position.path);
|
|
308
|
+
return symbol.kind === "file" || fact === undefined
|
|
309
|
+
? []
|
|
310
|
+
: [
|
|
311
|
+
{
|
|
312
|
+
id: symbol.id,
|
|
313
|
+
detail: retentionDetail(fact),
|
|
314
|
+
site: { path: symbol.position.path, line: 1, column: 1 },
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
});
|
|
318
|
+
}
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
isClassDeclaration,
|
|
12
|
-
isClassExpression,
|
|
13
11
|
isConstructorDeclaration,
|
|
14
12
|
isGetAccessorDeclaration,
|
|
15
13
|
isPropertyDeclaration,
|
|
@@ -18,19 +16,20 @@ import {
|
|
|
18
16
|
type Decorator,
|
|
19
17
|
type Node,
|
|
20
18
|
} from "@typescript/native/ast";
|
|
21
|
-
import { aliasChains } from "
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
19
|
+
import { aliasChains } from "../alias-chain.ts";
|
|
20
|
+
import { nameOf } from "../index/values.ts";
|
|
21
|
+
import { classesPassed, decoratorsOf, spelledDecorator } from "./calls.ts";
|
|
22
|
+
import { classMembers, type ClassMember } from "../class-members.ts";
|
|
24
23
|
import {
|
|
25
24
|
entriesAt,
|
|
26
25
|
namesADeclaration,
|
|
27
26
|
resolveEntries,
|
|
28
27
|
spelledEntry,
|
|
29
28
|
type ResolvedEntry,
|
|
30
|
-
} from "
|
|
31
|
-
import type { DetectorInput, Evidence } from "./
|
|
32
|
-
import { isParameterProperty
|
|
33
|
-
import { renderPosition } from "
|
|
29
|
+
} from "../roots/configured-declarations.ts";
|
|
30
|
+
import type { DetectorInput, Evidence } from "./lifecycle.ts";
|
|
31
|
+
import { isParameterProperty } from "../inventory.ts";
|
|
32
|
+
import { renderPosition } from "../position.ts";
|
|
34
33
|
|
|
35
34
|
/** One class a container constructs, by the registration that says so. */
|
|
36
35
|
interface Registration {
|
|
@@ -52,32 +51,36 @@ function isInjected(node: Node): boolean {
|
|
|
52
51
|
|
|
53
52
|
/** The classes of the target a call whose callee an entry names passes, with that call's entry. */
|
|
54
53
|
function registrations<Brand>(input: DetectorInput<Brand>): readonly Registration[] {
|
|
55
|
-
const { project, held } = input;
|
|
56
|
-
const resolved = resolveEntries(project, held, input.ts.
|
|
54
|
+
const { project, held, sites } = input;
|
|
55
|
+
const resolved = resolveEntries(project, held, input.ts.injection_registrations);
|
|
57
56
|
if (!resolved.some(namesADeclaration)) {
|
|
58
57
|
return [];
|
|
59
58
|
}
|
|
60
59
|
const chains = aliasChains(project, held);
|
|
61
|
-
const calls =
|
|
60
|
+
const calls = sites.calls.map((one) => one.call);
|
|
61
|
+
const invoked = (call: CallExpression): Node | undefined =>
|
|
62
|
+
nameOf(sites.invocations.of(call).callee);
|
|
62
63
|
const named = entriesAt(
|
|
63
64
|
project,
|
|
64
65
|
chains,
|
|
65
|
-
calls.flatMap((call) =>
|
|
66
|
+
calls.flatMap((call) => invoked(call) ?? []),
|
|
66
67
|
resolved,
|
|
67
68
|
);
|
|
68
69
|
const matched = new Map<CallExpression, ResolvedEntry>();
|
|
69
70
|
for (const call of calls) {
|
|
70
|
-
const name =
|
|
71
|
+
const name = invoked(call);
|
|
71
72
|
const [entry] = name === undefined ? [] : (named.get(name) ?? []);
|
|
72
73
|
if (entry !== undefined) {
|
|
73
74
|
matched.set(call, entry);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
return [...classesPassed(project, held, chains, [...matched.keys()])].flatMap(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
return [...classesPassed(project, held, chains, sites, [...matched.keys()])].flatMap(
|
|
78
|
+
([call, uses]) => {
|
|
79
|
+
const entry = matched.get(call);
|
|
80
|
+
const detail = entry === undefined ? "" : `registered by ${spelledEntry(entry.entry, held)}`;
|
|
81
|
+
return uses.map((use) => ({ id: use.id, site: use.site, detail }));
|
|
82
|
+
},
|
|
83
|
+
);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
/**
|
|
@@ -87,22 +90,14 @@ function registrations<Brand>(input: DetectorInput<Brand>): readonly Registratio
|
|
|
87
90
|
function constructedForParameters<Brand>(
|
|
88
91
|
input: DetectorInput<Brand>,
|
|
89
92
|
): ReadonlyMap<string, Decorator> {
|
|
90
|
-
const { project, held } = input;
|
|
91
93
|
const found = new Map<string, Decorator>();
|
|
92
|
-
for (const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (id !== undefined && asks !== undefined) {
|
|
100
|
-
found.set(id, asks);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
node.forEachChild(visit);
|
|
104
|
-
};
|
|
105
|
-
file.forEachChild(visit);
|
|
94
|
+
for (const { id, node } of input.sites.classes) {
|
|
95
|
+
const [asks] = node.members.flatMap((member) =>
|
|
96
|
+
isConstructorDeclaration(member) ? member.parameters.flatMap(decoratorsOf) : [],
|
|
97
|
+
);
|
|
98
|
+
if (asks !== undefined) {
|
|
99
|
+
found.set(id, asks);
|
|
100
|
+
}
|
|
106
101
|
}
|
|
107
102
|
return found;
|
|
108
103
|
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The classes a program installs as globals: a class value written into a property of
|
|
3
|
+
* the global object, which code outside the analysis that names the global constructs.
|
|
4
|
+
* The checker finds each write: the receiver's type is the global object's, the callee is
|
|
5
|
+
* the standard library's `Object.defineProperty` or a test runner's stub, and the
|
|
6
|
+
* property is the one of the key's name that `typeof globalThis` declares.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
isIdentifier,
|
|
11
|
+
isNoSubstitutionTemplateLiteral,
|
|
12
|
+
isObjectLiteralExpression,
|
|
13
|
+
isPropertyAssignment,
|
|
14
|
+
isShorthandPropertyAssignment,
|
|
15
|
+
isStringLiteral,
|
|
16
|
+
type Expression,
|
|
17
|
+
type Node,
|
|
18
|
+
} from "@typescript/native/ast";
|
|
19
|
+
import {
|
|
20
|
+
SignatureKind,
|
|
21
|
+
SymbolFlags,
|
|
22
|
+
type Symbol as TSSymbol,
|
|
23
|
+
type Type,
|
|
24
|
+
} from "@typescript/native/sync";
|
|
25
|
+
import type { AliasChains } from "../alias-chain.ts";
|
|
26
|
+
import { typesAt } from "../class-members.ts";
|
|
27
|
+
import { packageOfPath } from "../dependencies.ts";
|
|
28
|
+
import type { Inventory } from "../inventory.ts";
|
|
29
|
+
import { must } from "../query.ts";
|
|
30
|
+
import type { ProjectView } from "../session.ts";
|
|
31
|
+
import { parenthesesRemoved, unwrapped } from "../unwrap.ts";
|
|
32
|
+
import { spansOf } from "./argument-places.ts";
|
|
33
|
+
import { classesCarried } from "./calls.ts";
|
|
34
|
+
import type { ProjectSites } from "./sites.ts";
|
|
35
|
+
|
|
36
|
+
/** A call that writes a value into a property of an object, by the places of its arguments. */
|
|
37
|
+
interface Writer {
|
|
38
|
+
/** The object's place, none where the object is the global one. */
|
|
39
|
+
readonly object: number | undefined;
|
|
40
|
+
readonly key: number;
|
|
41
|
+
readonly value: number;
|
|
42
|
+
/** Whether the value is the `value` member of a property descriptor. */
|
|
43
|
+
readonly described: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The test runners' stubs of a global, by method name and the packages that declare it:
|
|
48
|
+
* `vi.stubGlobal` of vitest, and `jest.replaceProperty`, which `@jest/environment` types
|
|
49
|
+
* for `@jest/globals` and `@types/jest` declares for the `jest` global.
|
|
50
|
+
*/
|
|
51
|
+
const STUBS: ReadonlyMap<string, Writer & { readonly packages: ReadonlySet<string> }> = new Map([
|
|
52
|
+
[
|
|
53
|
+
"stubGlobal",
|
|
54
|
+
{ packages: new Set(["vitest"]), object: undefined, key: 0, value: 1, described: false },
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
"replaceProperty",
|
|
58
|
+
{
|
|
59
|
+
packages: new Set(["@jest/environment", "@jest/globals", "@types/jest"]),
|
|
60
|
+
object: 0,
|
|
61
|
+
key: 1,
|
|
62
|
+
value: 2,
|
|
63
|
+
described: false,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/** `Object.defineProperty(object, key, descriptor)`. */
|
|
69
|
+
const DEFINE: Writer = { object: 0, key: 1, value: 2, described: true };
|
|
70
|
+
|
|
71
|
+
/** One write of a value into a property a written key names. */
|
|
72
|
+
interface Write {
|
|
73
|
+
readonly site: Node;
|
|
74
|
+
/** The object written to, none where it is the global one. */
|
|
75
|
+
readonly receiver: Expression | undefined;
|
|
76
|
+
readonly key: string;
|
|
77
|
+
readonly value: Expression;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** One class a write installs as a global, and the instance types the global constructs. */
|
|
81
|
+
interface Installed {
|
|
82
|
+
readonly id: string;
|
|
83
|
+
readonly site: Node;
|
|
84
|
+
readonly global: string;
|
|
85
|
+
readonly instances: readonly Type[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The text of a key written as a string literal. */
|
|
89
|
+
function keyText(node: Expression | undefined): string | undefined {
|
|
90
|
+
const key = node === undefined ? undefined : parenthesesRemoved(node);
|
|
91
|
+
return key !== undefined && (isStringLiteral(key) || isNoSubstitutionTemplateLiteral(key))
|
|
92
|
+
? key.text
|
|
93
|
+
: undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Whether a property's name is `value`. */
|
|
97
|
+
function named(name: Node): boolean {
|
|
98
|
+
return (isIdentifier(name) || isStringLiteral(name)) && name.text === "value";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The value a property descriptor written as an object literal gives. */
|
|
102
|
+
function describedValue(node: Expression | undefined): Expression | undefined {
|
|
103
|
+
const descriptor = node === undefined ? undefined : parenthesesRemoved(node);
|
|
104
|
+
if (descriptor === undefined || !isObjectLiteralExpression(descriptor)) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
for (const property of descriptor.properties) {
|
|
108
|
+
if (isPropertyAssignment(property) && named(property.name)) {
|
|
109
|
+
return property.initializer;
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
isShorthandPropertyAssignment(property) &&
|
|
113
|
+
isIdentifier(property.name) &&
|
|
114
|
+
named(property.name)
|
|
115
|
+
) {
|
|
116
|
+
return property.name;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** How a call's callee writes a property: the standard library's `Object.defineProperty` or a runner's stub. */
|
|
123
|
+
function writerOf<Brand>(project: ProjectView<Brand>, method: TSSymbol): Writer | undefined {
|
|
124
|
+
if (method.name === "defineProperty") {
|
|
125
|
+
const standard = method.declarations.some(
|
|
126
|
+
(handle) => project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true,
|
|
127
|
+
);
|
|
128
|
+
return standard && must(project.queries.parentOf(method))?.name === "ObjectConstructor"
|
|
129
|
+
? DEFINE
|
|
130
|
+
: undefined;
|
|
131
|
+
}
|
|
132
|
+
const stub = STUBS.get(method.name);
|
|
133
|
+
return stub !== undefined &&
|
|
134
|
+
method.declarations.some((handle) => stub.packages.has(packageOfPath(handle.path) ?? ""))
|
|
135
|
+
? stub
|
|
136
|
+
: undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Each write of one project's own files that may install a value as a global, read from syntax and the callee. */
|
|
140
|
+
function writesOf<Brand>(project: ProjectView<Brand>, sites: ProjectSites): readonly Write[] {
|
|
141
|
+
const found: Write[] = sites.stores.map((store) => ({
|
|
142
|
+
site: store.store,
|
|
143
|
+
receiver: store.receiver,
|
|
144
|
+
key: store.key,
|
|
145
|
+
value: store.value,
|
|
146
|
+
}));
|
|
147
|
+
for (const { call } of sites.calls) {
|
|
148
|
+
const method = sites.invocations.callee(call);
|
|
149
|
+
const writer = method === undefined ? undefined : writerOf(project, method);
|
|
150
|
+
if (writer === undefined) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const invoked = sites.invocations.of(call);
|
|
154
|
+
// With no types asked, a spread has no known length, so only a place no spread
|
|
155
|
+
// comes at or before is read.
|
|
156
|
+
const spans = spansOf(project, invoked.arguments, invoked.listed, []);
|
|
157
|
+
const at = (place: number): Expression | undefined => {
|
|
158
|
+
const span = spans.find((one) => !one.open && one.first === place);
|
|
159
|
+
return span === undefined ? undefined : invoked.arguments[span.argument];
|
|
160
|
+
};
|
|
161
|
+
const key = keyText(at(writer.key));
|
|
162
|
+
const receiver = writer.object === undefined ? undefined : at(writer.object);
|
|
163
|
+
const value = writer.described ? describedValue(at(writer.value)) : at(writer.value);
|
|
164
|
+
if (
|
|
165
|
+
key !== undefined &&
|
|
166
|
+
value !== undefined &&
|
|
167
|
+
(writer.object === undefined || receiver !== undefined)
|
|
168
|
+
) {
|
|
169
|
+
found.push({ site: call, receiver, key, value });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return found;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The classes one project installs as globals. A write installs one where its object is
|
|
177
|
+
* the global object, as the checker types it (`window` and `self` are intersections that
|
|
178
|
+
* hold `typeof globalThis`, and an object asserted to `any` is read inside the
|
|
179
|
+
* assertion), and where the global object declares a property of the key's name with a
|
|
180
|
+
* construct signature, whose instances code outside the analysis makes.
|
|
181
|
+
*/
|
|
182
|
+
export function installedGlobals<Brand>(
|
|
183
|
+
project: ProjectView<Brand>,
|
|
184
|
+
held: Inventory,
|
|
185
|
+
chains: AliasChains,
|
|
186
|
+
sites: ProjectSites,
|
|
187
|
+
): readonly Installed[] {
|
|
188
|
+
const { queries } = project;
|
|
189
|
+
const writes = writesOf(project, sites);
|
|
190
|
+
if (writes.length === 0) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const global = must(queries.resolveName("globalThis", SymbolFlags.Value, undefined, false));
|
|
194
|
+
const globalType = global === undefined ? undefined : must(queries.typeOfSymbol(global));
|
|
195
|
+
if (global === undefined || globalType === undefined) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const properties = new Map(
|
|
199
|
+
must(queries.propertiesOf(globalType)).map((property) => [property.name, property]),
|
|
200
|
+
);
|
|
201
|
+
const keyed = writes.filter((write) => properties.has(write.key));
|
|
202
|
+
const objects = keyed.flatMap((write) =>
|
|
203
|
+
write.receiver === undefined ? [] : [unwrapped(write.receiver)],
|
|
204
|
+
);
|
|
205
|
+
const objectTypes = new Map(
|
|
206
|
+
typesAt(project, objects).map((type, at) => [objects[at], type] as const),
|
|
207
|
+
);
|
|
208
|
+
const globals = new Map<number, boolean>();
|
|
209
|
+
const isGlobal = (type: Type | undefined): boolean => {
|
|
210
|
+
if (type === undefined) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
let known = globals.get(type.id);
|
|
214
|
+
if (known === undefined) {
|
|
215
|
+
const parts =
|
|
216
|
+
type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : [type];
|
|
217
|
+
known = parts.some((part) => must(queries.symbolOfType(part))?.id === global.id);
|
|
218
|
+
globals.set(type.id, known);
|
|
219
|
+
}
|
|
220
|
+
return known;
|
|
221
|
+
};
|
|
222
|
+
const installs = keyed.filter(
|
|
223
|
+
(write) => write.receiver === undefined || isGlobal(objectTypes.get(unwrapped(write.receiver))),
|
|
224
|
+
);
|
|
225
|
+
if (installs.length === 0) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
const instances = new Map<string, readonly Type[]>();
|
|
229
|
+
const instancesOf = (key: string): readonly Type[] => {
|
|
230
|
+
let made = instances.get(key);
|
|
231
|
+
if (made === undefined) {
|
|
232
|
+
const property = properties.get(key);
|
|
233
|
+
const type = property === undefined ? undefined : must(queries.typeOfSymbol(property));
|
|
234
|
+
made = (
|
|
235
|
+
type === undefined ? [] : must(queries.signaturesOf(type, SignatureKind.Construct))
|
|
236
|
+
).flatMap((signature) => must(queries.returnTypeOf(signature)) ?? []);
|
|
237
|
+
instances.set(key, made);
|
|
238
|
+
}
|
|
239
|
+
return made;
|
|
240
|
+
};
|
|
241
|
+
const carried = classesCarried(
|
|
242
|
+
project,
|
|
243
|
+
held,
|
|
244
|
+
chains,
|
|
245
|
+
sites,
|
|
246
|
+
installs.map((write) => [write.value]),
|
|
247
|
+
);
|
|
248
|
+
return installs.flatMap((write, at) => {
|
|
249
|
+
const uses = carried[at] ?? [];
|
|
250
|
+
const made = uses.length === 0 ? [] : instancesOf(write.key);
|
|
251
|
+
return made.length === 0
|
|
252
|
+
? []
|
|
253
|
+
: uses.map((use) => ({ id: use.id, site: write.site, global: write.key, instances: made }));
|
|
254
|
+
});
|
|
255
|
+
}
|