@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,1729 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The serialization-contract class: the data members and `toJSON` of every type a value's
|
|
3
|
+
* static type reaches, where the value reaches `JSON.stringify` or a call `ts.serializers`
|
|
4
|
+
* names, and `toString` too where it reaches an erased parameter of a function outside the
|
|
5
|
+
* analysis, or an erased property of a type the program does not declare. A function of
|
|
6
|
+
* the project handing such a parameter to a destination is one, to a fixpoint, and a
|
|
7
|
+
* function invoked through `call`, `apply` or `bind` is the callee.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
isArrowFunction,
|
|
12
|
+
isAsExpression,
|
|
13
|
+
isAwaitExpression,
|
|
14
|
+
isCallExpression,
|
|
15
|
+
isClassDeclaration,
|
|
16
|
+
isClassExpression,
|
|
17
|
+
isElementAccessExpression,
|
|
18
|
+
isExpressionStatement,
|
|
19
|
+
isFunctionDeclaration,
|
|
20
|
+
isFunctionExpression,
|
|
21
|
+
isFunctionLikeDeclaration,
|
|
22
|
+
isGetAccessorDeclaration,
|
|
23
|
+
isIdentifier,
|
|
24
|
+
isInterfaceDeclaration,
|
|
25
|
+
isMethodDeclaration,
|
|
26
|
+
isNonNullExpression,
|
|
27
|
+
isNoSubstitutionTemplateLiteral,
|
|
28
|
+
isNumericLiteral,
|
|
29
|
+
isObjectLiteralExpression,
|
|
30
|
+
isParenthesizedExpression,
|
|
31
|
+
isForInStatement,
|
|
32
|
+
isPropertyAccessExpression,
|
|
33
|
+
isPropertyDeclaration,
|
|
34
|
+
isReturnStatement,
|
|
35
|
+
isSatisfiesExpression,
|
|
36
|
+
isSetAccessorDeclaration,
|
|
37
|
+
isSourceFile,
|
|
38
|
+
isSpreadAssignment,
|
|
39
|
+
isSpreadElement,
|
|
40
|
+
isStringLiteral,
|
|
41
|
+
isTypeAssertion,
|
|
42
|
+
isTypeLiteralNode,
|
|
43
|
+
isVariableDeclaration,
|
|
44
|
+
NodeFlags,
|
|
45
|
+
SyntaxKind,
|
|
46
|
+
type CallExpression,
|
|
47
|
+
type Expression,
|
|
48
|
+
type Node,
|
|
49
|
+
type SourceFile,
|
|
50
|
+
type SpreadAssignment,
|
|
51
|
+
} from "@typescript/native/ast";
|
|
52
|
+
import {
|
|
53
|
+
SignatureKind,
|
|
54
|
+
SymbolFlags,
|
|
55
|
+
TypeFlags,
|
|
56
|
+
type NodeHandle,
|
|
57
|
+
type Signature,
|
|
58
|
+
type Symbol as TSSymbol,
|
|
59
|
+
type Type,
|
|
60
|
+
} from "@typescript/native/sync";
|
|
61
|
+
import { aliasChains, type AliasChains } from "../alias-chain.ts";
|
|
62
|
+
import { carriedValues, valuesOf } from "../index/values.ts";
|
|
63
|
+
import { parenthesesRemoved } from "../unwrap.ts";
|
|
64
|
+
import {
|
|
65
|
+
fills,
|
|
66
|
+
knownElements,
|
|
67
|
+
layoutOf,
|
|
68
|
+
placesIn,
|
|
69
|
+
spansOf,
|
|
70
|
+
type Layout,
|
|
71
|
+
type Placed,
|
|
72
|
+
type Span,
|
|
73
|
+
} from "./argument-places.ts";
|
|
74
|
+
import type { Invocation } from "./calls.ts";
|
|
75
|
+
import { classMembers, memberComponent, typesAt } from "../class-members.ts";
|
|
76
|
+
import type { DeclarationEntry } from "../config.ts";
|
|
77
|
+
import {
|
|
78
|
+
names,
|
|
79
|
+
namesADeclaration,
|
|
80
|
+
resolveEntries,
|
|
81
|
+
spelledEntry,
|
|
82
|
+
} from "../roots/configured-declarations.ts";
|
|
83
|
+
import type { DetectorInput, Evidence } from "./lifecycle.ts";
|
|
84
|
+
import type { Inventory } from "../inventory.ts";
|
|
85
|
+
import { renderPosition, type Position } from "../position.ts";
|
|
86
|
+
import { must, Unanswerable } from "../query.ts";
|
|
87
|
+
import type { ProjectView } from "../session.ts";
|
|
88
|
+
import { erasedStores, type ErasedStores } from "./erased-stores.ts";
|
|
89
|
+
import type { CallSite, ParameterSite, ProjectSites } from "./sites.ts";
|
|
90
|
+
import {
|
|
91
|
+
checkedTypes,
|
|
92
|
+
CONVERSION_MEMBERS,
|
|
93
|
+
CONVERSION_NAMES,
|
|
94
|
+
ERASED,
|
|
95
|
+
valueReach,
|
|
96
|
+
type Reached,
|
|
97
|
+
} from "./value-reach.ts";
|
|
98
|
+
|
|
99
|
+
/** The serializer every run reads, whatever the configuration names. */
|
|
100
|
+
const JSON_STRINGIFY: DeclarationEntry = { global: "JSON.stringify" };
|
|
101
|
+
|
|
102
|
+
/** The method a serializer calls on a value that carries one, wherever the value goes. */
|
|
103
|
+
const SERIALIZED_CONVERSION = "toJSON";
|
|
104
|
+
|
|
105
|
+
/** The standard library's promise types, whose value is their one type argument. */
|
|
106
|
+
const PROMISES: ReadonlySet<string> = new Set(["Promise", "PromiseLike"]);
|
|
107
|
+
|
|
108
|
+
/** How deep a callee's result is searched for a type parameter: `Promise<Box<T>>` is two levels. */
|
|
109
|
+
const MENTION_DEPTH = 3;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* What a destination holds back of a class: its properties, or its properties and its
|
|
113
|
+
* two conversion methods. The second holds back everything the first does.
|
|
114
|
+
*/
|
|
115
|
+
type Reach = "properties" | "conversions";
|
|
116
|
+
|
|
117
|
+
/** One place a function hands a parameter of its own to the argument of a call. */
|
|
118
|
+
interface Forwarded {
|
|
119
|
+
readonly parameter: ParameterSite;
|
|
120
|
+
readonly site: CallSite;
|
|
121
|
+
readonly argument: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A destination at one argument: what it holds back, and the callee a record names. */
|
|
125
|
+
interface Destination {
|
|
126
|
+
readonly reach: Reach;
|
|
127
|
+
readonly callee: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The wider of two reaches, where either may be absent. */
|
|
131
|
+
function wider(a: Reach | undefined, b: Reach | undefined): Reach | undefined {
|
|
132
|
+
return a === "conversions" || b === "conversions" ? "conversions" : (a ?? b);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Whether an argument is written in a form that holds no class instance, which saves a type. */
|
|
136
|
+
function holdsNoInstance(argument: Expression): boolean {
|
|
137
|
+
return (
|
|
138
|
+
isArrowFunction(argument) ||
|
|
139
|
+
isFunctionExpression(argument) ||
|
|
140
|
+
isStringLiteral(argument) ||
|
|
141
|
+
isNumericLiteral(argument) ||
|
|
142
|
+
isNoSubstitutionTemplateLiteral(argument) ||
|
|
143
|
+
argument.kind === SyntaxKind.TemplateExpression
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The calls of `Object` that read every own property of their argument by name. */
|
|
148
|
+
const ENUMERATIONS: ReadonlySet<string> = new Set([
|
|
149
|
+
"keys",
|
|
150
|
+
"entries",
|
|
151
|
+
"values",
|
|
152
|
+
"getOwnPropertyNames",
|
|
153
|
+
"assign",
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Whether a parameter type keeps no member name of the value it is given beyond the
|
|
158
|
+
* properties it names: `unknown`, `any`, or an object type of a string index signature,
|
|
159
|
+
* under which every other member of the value is held.
|
|
160
|
+
*/
|
|
161
|
+
function keepsNoName<Brand>(project: ProjectView<Brand>, type: Type): boolean {
|
|
162
|
+
if ((type.flags & ERASED) !== 0) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
if ((type.flags & TypeFlags.Object) === 0) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
return must(project.queries.indexInfosOf(type)).some(
|
|
169
|
+
(info) => (info.keyType.flags & TypeFlags.String) !== 0,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The parameters that keep no member name of the value they are given, as `keepsNoName`
|
|
175
|
+
* reads it, and a rest parameter whose number index holds a type that keeps none, an
|
|
176
|
+
* element of an array, a tuple, an array-like interface or a union or an intersection of
|
|
177
|
+
* them, each by its symbol. A rest a type parameter types is left to its calls' flows.
|
|
178
|
+
*/
|
|
179
|
+
function erasedParameters<Brand>(
|
|
180
|
+
project: ProjectView<Brand>,
|
|
181
|
+
parameters: readonly ParameterSite[],
|
|
182
|
+
): ReadonlyMap<number, ParameterSite> {
|
|
183
|
+
const types = typesAt(
|
|
184
|
+
project,
|
|
185
|
+
parameters.map((parameter) => parameter.name),
|
|
186
|
+
);
|
|
187
|
+
const erased = parameters.filter((parameter, index) => {
|
|
188
|
+
const type = types[index];
|
|
189
|
+
if (type === undefined) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
if (!parameter.rest) {
|
|
193
|
+
return keepsNoName(project, type);
|
|
194
|
+
}
|
|
195
|
+
const element =
|
|
196
|
+
(type.flags & TypeFlags.Instantiable) === 0
|
|
197
|
+
? must(project.queries.numberIndexOf(type))
|
|
198
|
+
: undefined;
|
|
199
|
+
return (
|
|
200
|
+
element !== undefined &&
|
|
201
|
+
must(project.queries.constituents(element)).some((part) => keepsNoName(project, part))
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
const bySymbol = new Map<number, ParameterSite>();
|
|
205
|
+
project
|
|
206
|
+
.symbolsAt(erased.map((parameter) => project.handle(parameter.name)))
|
|
207
|
+
.map(must)
|
|
208
|
+
.forEach((symbol, index) => {
|
|
209
|
+
const parameter = erased[index];
|
|
210
|
+
if (symbol !== undefined && parameter !== undefined) {
|
|
211
|
+
bySymbol.set(symbol.id, parameter);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return bySymbol;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The type any place of a signature's rest may take, the number index of the rest's type;
|
|
219
|
+
* none where it has no rest or the type no number index.
|
|
220
|
+
*/
|
|
221
|
+
function restElementOf<Brand>(project: ProjectView<Brand>, signature: Signature): Type | undefined {
|
|
222
|
+
const rest = must(project.queries.restTypeOf(signature));
|
|
223
|
+
return rest === undefined ? undefined : must(project.queries.numberIndexOf(rest));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Whether one node is a function, whose body runs only where something calls it. */
|
|
227
|
+
function isFunctionNode(node: Node): boolean {
|
|
228
|
+
return (
|
|
229
|
+
isFunctionDeclaration(node) ||
|
|
230
|
+
isFunctionExpression(node) ||
|
|
231
|
+
isArrowFunction(node) ||
|
|
232
|
+
isMethodDeclaration(node) ||
|
|
233
|
+
isGetAccessorDeclaration(node) ||
|
|
234
|
+
isSetAccessorDeclaration(node)
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The name a function nested in another is called by: its own, or the variable it initializes. */
|
|
239
|
+
function nestedName(fn: Node): string | undefined {
|
|
240
|
+
if (isFunctionDeclaration(fn)) {
|
|
241
|
+
return fn.name?.text;
|
|
242
|
+
}
|
|
243
|
+
let at = fn.parent;
|
|
244
|
+
while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
|
|
245
|
+
at = at.parent;
|
|
246
|
+
}
|
|
247
|
+
return isVariableDeclaration(at) && isIdentifier(at.name) ? at.name.text : undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** The names one function's body writes as identifiers, other than the names its declarations bind. */
|
|
251
|
+
function namesWritten(fn: Node): ReadonlySet<string> {
|
|
252
|
+
const found = new Set<string>();
|
|
253
|
+
const visit = (node: Node): void => {
|
|
254
|
+
const declares =
|
|
255
|
+
(isFunctionDeclaration(node.parent) || isVariableDeclaration(node.parent)) &&
|
|
256
|
+
node.parent.name === node;
|
|
257
|
+
if (isIdentifier(node) && !declares) {
|
|
258
|
+
found.add(node.text);
|
|
259
|
+
}
|
|
260
|
+
node.forEachChild(visit);
|
|
261
|
+
};
|
|
262
|
+
fn.forEachChild(visit);
|
|
263
|
+
return found;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The erased parameters their own function enumerates: one an `Object.keys`, `entries`,
|
|
268
|
+
* `values`, `getOwnPropertyNames` or `assign` call takes, `Object` the standard library's,
|
|
269
|
+
* or a `for…in` statement walks, which reads every property of the value by its name. A
|
|
270
|
+
* nested function is read as the function's own only where its name is written in it, or
|
|
271
|
+
* it has none, a callback or a function called where it is written.
|
|
272
|
+
*/
|
|
273
|
+
function enumeratedParameters<Brand>(
|
|
274
|
+
project: ProjectView<Brand>,
|
|
275
|
+
erased: ReadonlyMap<number, ParameterSite>,
|
|
276
|
+
): readonly ParameterSite[] {
|
|
277
|
+
const asked: {
|
|
278
|
+
readonly node: Node;
|
|
279
|
+
readonly parameter: ParameterSite;
|
|
280
|
+
readonly object: Node | undefined;
|
|
281
|
+
}[] = [];
|
|
282
|
+
for (const parameter of erased.values()) {
|
|
283
|
+
const fn = parameter.name.parent.parent;
|
|
284
|
+
const spelled = parameter.name.getText();
|
|
285
|
+
const written = namesWritten(fn);
|
|
286
|
+
const visit = (node: Node): void => {
|
|
287
|
+
if (isFunctionNode(node)) {
|
|
288
|
+
const name = nestedName(node);
|
|
289
|
+
if (name !== undefined && !written.has(name)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const read: Expression[] = [];
|
|
294
|
+
let object: Node | undefined;
|
|
295
|
+
if (isCallExpression(node)) {
|
|
296
|
+
const callee = node.expression;
|
|
297
|
+
if (
|
|
298
|
+
isPropertyAccessExpression(callee) &&
|
|
299
|
+
isIdentifier(callee.expression) &&
|
|
300
|
+
callee.expression.text === "Object" &&
|
|
301
|
+
ENUMERATIONS.has(callee.name.text)
|
|
302
|
+
) {
|
|
303
|
+
object = callee.expression;
|
|
304
|
+
// `Object.assign` reads its sources, every argument after the target.
|
|
305
|
+
read.push(...node.arguments.slice(...(callee.name.text === "assign" ? [1] : [0, 1])));
|
|
306
|
+
}
|
|
307
|
+
} else if (isForInStatement(node)) {
|
|
308
|
+
read.push(node.expression);
|
|
309
|
+
}
|
|
310
|
+
for (const value of read.flatMap(valuesOf)) {
|
|
311
|
+
if (isIdentifier(value) && value.text === spelled) {
|
|
312
|
+
asked.push({ node: value, parameter, object });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
node.forEachChild(visit);
|
|
316
|
+
};
|
|
317
|
+
fn.forEachChild(visit);
|
|
318
|
+
}
|
|
319
|
+
const symbols = project.symbolsAt(asked.map((one) => project.handle(one.node))).map(must);
|
|
320
|
+
const objects = asked.flatMap((one) => (one.object === undefined ? [] : [one.object]));
|
|
321
|
+
const own = project.ownPaths();
|
|
322
|
+
const standard = new Set(
|
|
323
|
+
project
|
|
324
|
+
.symbolsAt(objects.map((node) => project.handle(node)))
|
|
325
|
+
.map(must)
|
|
326
|
+
.flatMap((symbol, at) =>
|
|
327
|
+
symbol?.declarations.every((handle) => !own.has(handle.path)) === true ? [objects[at]] : [],
|
|
328
|
+
),
|
|
329
|
+
);
|
|
330
|
+
return [
|
|
331
|
+
...new Set(
|
|
332
|
+
asked.flatMap((one, at) => {
|
|
333
|
+
const symbol = symbols[at];
|
|
334
|
+
return symbol !== undefined &&
|
|
335
|
+
erased.get(symbol.id) === one.parameter &&
|
|
336
|
+
(one.object === undefined || standard.has(one.object))
|
|
337
|
+
? [one.parameter]
|
|
338
|
+
: [];
|
|
339
|
+
}),
|
|
340
|
+
),
|
|
341
|
+
];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Each place a call's argument passes one of the erased parameters it can see. */
|
|
345
|
+
function handedOn<Brand>(
|
|
346
|
+
project: ProjectView<Brand>,
|
|
347
|
+
sites: ProjectSites,
|
|
348
|
+
erased: ReadonlyMap<number, ParameterSite>,
|
|
349
|
+
): readonly Forwarded[] {
|
|
350
|
+
const spellings = new Set([...erased.values()].map((parameter) => parameter.name.getText()));
|
|
351
|
+
const candidates = sites.calls.flatMap((site) =>
|
|
352
|
+
sites.invocations
|
|
353
|
+
.of(site.call)
|
|
354
|
+
.arguments.flatMap((argument, index) =>
|
|
355
|
+
carriedValues(argument).flatMap((value) =>
|
|
356
|
+
value.name !== undefined && spellings.has(value.name.getText())
|
|
357
|
+
? [{ site, argument: index, value: { ...value, name: value.name } }]
|
|
358
|
+
: [],
|
|
359
|
+
),
|
|
360
|
+
),
|
|
361
|
+
);
|
|
362
|
+
const plain = candidates.filter((one) => !one.value.shorthand);
|
|
363
|
+
const resolved = project.symbolsAt(plain.map((one) => project.handle(one.value.name))).map(must);
|
|
364
|
+
const symbolOf = new Map(plain.map((one, index) => [one, resolved[index]]));
|
|
365
|
+
return candidates.flatMap((one) => {
|
|
366
|
+
const symbol = one.value.shorthand
|
|
367
|
+
? must(project.shorthandValueAt(project.handle(one.value.name)))
|
|
368
|
+
: symbolOf.get(one);
|
|
369
|
+
const parameter = symbol === undefined ? undefined : erased.get(symbol.id);
|
|
370
|
+
return parameter === undefined ? [] : [{ parameter, site: one.site, argument: one.argument }];
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The symbols of the interfaces and object types code of the analysis implements: those a
|
|
376
|
+
* class's `implements` and `extends` clauses name, those an object literal is typed as by
|
|
377
|
+
* a variable or property annotation or a `satisfies` or `as` expression, and every type
|
|
378
|
+
* each of those extends. Read at the first call, which asks the checker.
|
|
379
|
+
*/
|
|
380
|
+
function implementedOwners<Brand>(
|
|
381
|
+
project: ProjectView<Brand>,
|
|
382
|
+
inAnalysis: (path: string) => boolean,
|
|
383
|
+
): () => ReadonlySet<number> {
|
|
384
|
+
let owners: Set<number> | undefined;
|
|
385
|
+
return () => {
|
|
386
|
+
if (owners !== undefined) {
|
|
387
|
+
return owners;
|
|
388
|
+
}
|
|
389
|
+
const found = new Set<number>();
|
|
390
|
+
const queries = project.queries;
|
|
391
|
+
const own = project.ownPaths();
|
|
392
|
+
const files = [
|
|
393
|
+
...project.ownSourceFiles(),
|
|
394
|
+
...project.program
|
|
395
|
+
.getSourceFileNames()
|
|
396
|
+
.filter((name) => !own.has(name) && inAnalysis(name))
|
|
397
|
+
.flatMap((name) => project.program.getSourceFile(name) ?? []),
|
|
398
|
+
];
|
|
399
|
+
const naming: Node[] = [];
|
|
400
|
+
const visit = (node: Node): void => {
|
|
401
|
+
if (isClassDeclaration(node) || isClassExpression(node)) {
|
|
402
|
+
naming.push(...(node.heritageClauses ?? []).flatMap((clause) => [...clause.types]));
|
|
403
|
+
} else if (
|
|
404
|
+
(isVariableDeclaration(node) || isPropertyDeclaration(node)) &&
|
|
405
|
+
node.type !== undefined &&
|
|
406
|
+
node.initializer !== undefined &&
|
|
407
|
+
isObjectLiteralExpression(parenthesesRemoved(node.initializer))
|
|
408
|
+
) {
|
|
409
|
+
naming.push(node.type);
|
|
410
|
+
} else if (
|
|
411
|
+
(isSatisfiesExpression(node) || isAsExpression(node)) &&
|
|
412
|
+
isObjectLiteralExpression(parenthesesRemoved(node.expression))
|
|
413
|
+
) {
|
|
414
|
+
naming.push(node.type);
|
|
415
|
+
}
|
|
416
|
+
node.forEachChild(visit);
|
|
417
|
+
};
|
|
418
|
+
for (const file of files) {
|
|
419
|
+
file.forEachChild(visit);
|
|
420
|
+
}
|
|
421
|
+
const pending = typesAt(project, naming).filter((type) => type !== undefined);
|
|
422
|
+
const seen = new Set<number>();
|
|
423
|
+
for (let type = pending.pop(); type !== undefined; type = pending.pop()) {
|
|
424
|
+
for (const part of must(queries.constituents(type))) {
|
|
425
|
+
const target = must(queries.targetOf(part));
|
|
426
|
+
if (!seen.has(target.id)) {
|
|
427
|
+
seen.add(target.id);
|
|
428
|
+
const symbol = must(queries.symbolOfType(target));
|
|
429
|
+
if (symbol !== undefined) {
|
|
430
|
+
found.add(symbol.id);
|
|
431
|
+
}
|
|
432
|
+
pending.push(...must(queries.baseTypes(target)));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
owners = found;
|
|
437
|
+
return owners;
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** The destination of each call's arguments, with the project's forwarding functions settled. */
|
|
442
|
+
interface Destinations {
|
|
443
|
+
/** The function one call invokes and the arguments its parameters bind. */
|
|
444
|
+
readonly invocation: (call: CallExpression) => Invocation;
|
|
445
|
+
/** The signatures each call binds. */
|
|
446
|
+
readonly bindings: Bindings;
|
|
447
|
+
/**
|
|
448
|
+
* The destination one argument of one call is, if it is one; with `element`, the
|
|
449
|
+
* destination of that element of a spread of known length, at the one place it fills.
|
|
450
|
+
*/
|
|
451
|
+
at(call: CallExpression, argument: number, element?: number): Destination | undefined;
|
|
452
|
+
/** Whether a call can be a destination at any argument, read without a round trip. */
|
|
453
|
+
mayReceive(call: CallExpression): boolean;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* The destinations of one project's calls. A call is one at every argument where its
|
|
458
|
+
* callee is a serializer an entry names; at the arguments its resolved signature types
|
|
459
|
+
* `unknown` or `any` where its callee is declared outside the analysis or only ambiently
|
|
460
|
+
* with no implementation in it; and, where its callee is a function of the project, at each
|
|
461
|
+
* parameter the function enumerates or hands on to a destination, holding back the widest
|
|
462
|
+
* of what those hold back, settled by widening until no parameter moves.
|
|
463
|
+
*/
|
|
464
|
+
function destinations<Brand>(input: DetectorInput<Brand>, chains: AliasChains): Destinations {
|
|
465
|
+
const { project, held, sites } = input;
|
|
466
|
+
const queries = project.queries;
|
|
467
|
+
const own = project.ownPaths();
|
|
468
|
+
const named = resolveEntries(project, held, [JSON_STRINGIFY, ...input.ts.serializers]).filter(
|
|
469
|
+
namesADeclaration,
|
|
470
|
+
);
|
|
471
|
+
const { of: invocation, callee: calleeOf } = sites.invocations;
|
|
472
|
+
const erased = erasedParameters(project, sites.parameters);
|
|
473
|
+
const byFunction = new Map<string, ParameterSite[]>();
|
|
474
|
+
for (const parameter of erased.values()) {
|
|
475
|
+
byFunction.set(parameter.fn, [...(byFunction.get(parameter.fn) ?? []), parameter]);
|
|
476
|
+
}
|
|
477
|
+
const inAnalysis = (path: string): boolean =>
|
|
478
|
+
own.has(path) || input.consumers.some((root) => path === root || path.startsWith(`${root}/`));
|
|
479
|
+
const implemented = implementedOwners(project, inAnalysis);
|
|
480
|
+
/**
|
|
481
|
+
* Whether every one of `declarations`, all of the callee's by default, is outside the
|
|
482
|
+
* analysis or ambient. An ambient declaration has no body, so the code that runs is
|
|
483
|
+
* outside the analysis, unless it is a member of an interface or an object type a class
|
|
484
|
+
* or a typed object literal of the analysis implements; `settled` false skips that
|
|
485
|
+
* question, which asks the checker.
|
|
486
|
+
*/
|
|
487
|
+
const outside = (
|
|
488
|
+
target: TSSymbol,
|
|
489
|
+
settled: boolean,
|
|
490
|
+
declarations: readonly NodeHandle[] = target.declarations,
|
|
491
|
+
): boolean =>
|
|
492
|
+
declarations.length > 0 &&
|
|
493
|
+
declarations.every((handle) => {
|
|
494
|
+
if (!inAnalysis(handle.path)) {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
const node = project.declarationAt(handle)?.node;
|
|
498
|
+
if (node === undefined || (node.flags & NodeFlags.Ambient) === 0) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
if (!settled || !(isInterfaceDeclaration(node.parent) || isTypeLiteralNode(node.parent))) {
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
const owner = must(queries.parentOf(target));
|
|
505
|
+
return owner === undefined || !implemented().has(owner.id);
|
|
506
|
+
});
|
|
507
|
+
/**
|
|
508
|
+
* Whether some one declaration of a callee is outside the analysis or ambient, without
|
|
509
|
+
* asking the checker. A project's declaration merged into a library's symbol leaves the
|
|
510
|
+
* library's beside it, so a call can still resolve to the library's.
|
|
511
|
+
*/
|
|
512
|
+
const mayBeOutside = (target: TSSymbol): boolean =>
|
|
513
|
+
target.declarations.some((handle) => outside(target, false, [handle]));
|
|
514
|
+
const bindings = callBindings(project, invocation);
|
|
515
|
+
/**
|
|
516
|
+
* The declarations of a callee that the declaration of a direct call's resolved
|
|
517
|
+
* signature lies in, or all of them where it lies in none: a declaration merged into
|
|
518
|
+
* the callee's symbol that the call does not resolve to does not decide where it runs.
|
|
519
|
+
*/
|
|
520
|
+
const calledDeclarations = (call: CallExpression, target: TSSymbol): readonly NodeHandle[] => {
|
|
521
|
+
const declaration = invocation(call).indirect
|
|
522
|
+
? undefined
|
|
523
|
+
: bindings.of(call)[0]?.signature.declaration;
|
|
524
|
+
const signature =
|
|
525
|
+
declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
|
|
526
|
+
if (declaration === undefined || signature === undefined) {
|
|
527
|
+
return target.declarations;
|
|
528
|
+
}
|
|
529
|
+
const containing = target.declarations.filter((handle) => {
|
|
530
|
+
const node =
|
|
531
|
+
handle.path === declaration.path ? project.declarationAt(handle)?.node : undefined;
|
|
532
|
+
return node !== undefined && node.pos <= signature.pos && signature.end <= node.end;
|
|
533
|
+
});
|
|
534
|
+
return containing.length === 0 ? target.declarations : containing;
|
|
535
|
+
};
|
|
536
|
+
/** Whether a type names a type parameter, through constituents, type arguments and call signatures. */
|
|
537
|
+
const mentions = (type: Type, parameter: Type, depth: number): boolean => {
|
|
538
|
+
// A conditional type an alias names, `Awaited<T>` among them, carries its arguments
|
|
539
|
+
// only on the alias the client's type object reports.
|
|
540
|
+
const aliased = (type as { readonly aliasTypeArguments?: readonly number[] })
|
|
541
|
+
.aliasTypeArguments;
|
|
542
|
+
if (type.id === parameter.id || aliased?.includes(parameter.id) === true) {
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
if (depth >= MENTION_DEPTH) {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
const inner = [
|
|
549
|
+
...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
|
|
550
|
+
...must(queries.typeArguments(type)),
|
|
551
|
+
...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
|
|
552
|
+
...signature.parameters.map((_parameter, at) =>
|
|
553
|
+
must(queries.typeAtPosition(signature, at)),
|
|
554
|
+
),
|
|
555
|
+
...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
|
|
556
|
+
]),
|
|
557
|
+
];
|
|
558
|
+
return inner.some((one) => mentions(one, parameter, depth + 1));
|
|
559
|
+
};
|
|
560
|
+
const erasedPlaces = new Map<CallExpression, ReadonlySet<string>>();
|
|
561
|
+
/**
|
|
562
|
+
* Whether an argument of a call is erased: where `element` is given, that element of a
|
|
563
|
+
* span of known length at the one place it fills, and otherwise at any place the
|
|
564
|
+
* argument fills, in any signature the call binds. An argument no signature the call
|
|
565
|
+
* binds has a place for says nothing of what it keeps, so it counts as erased.
|
|
566
|
+
*/
|
|
567
|
+
const erasedAt = (call: CallExpression, argument: number, element?: number): boolean => {
|
|
568
|
+
let erased = erasedPlaces.get(call);
|
|
569
|
+
if (erased === undefined) {
|
|
570
|
+
// A type parameter or an indexed access over one keeps nothing of the value where its
|
|
571
|
+
// base constraint is erased or absent, unless the result names the type parameter:
|
|
572
|
+
// the instantiated result is then the value's own type, handed back. A mapped type
|
|
573
|
+
// over type parameters keeps nothing where any of theirs is erased, whatever the
|
|
574
|
+
// result names, because the checker infers through it a copy without the class's
|
|
575
|
+
// non-public members, and which keys the mapping keeps is not read.
|
|
576
|
+
const erasedConstraint = (generic: Type): boolean => {
|
|
577
|
+
const constraint = must(queries.baseConstraintOf(generic));
|
|
578
|
+
return constraint === undefined || (constraint.flags & ERASED) !== 0;
|
|
579
|
+
};
|
|
580
|
+
const keepsNothing = (part: Type, result: Type | undefined): boolean => {
|
|
581
|
+
const mapped = must(queries.mappedParametersOf(part));
|
|
582
|
+
if (mapped.length > 0) {
|
|
583
|
+
return mapped.some(erasedConstraint);
|
|
584
|
+
}
|
|
585
|
+
if ((part.flags & TypeFlags.Instantiable) === 0) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
const free = part.isTypeParameter() ? part : must(queries.indexedAccessOf(part))?.object;
|
|
589
|
+
if (free?.isTypeParameter() === true && result !== undefined && mentions(result, free, 0)) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
return erasedConstraint(part);
|
|
593
|
+
};
|
|
594
|
+
// At a rest placement the declaration's type is its rest's element, the number index,
|
|
595
|
+
// which the checker reads through the base constraint of a rest a type parameter
|
|
596
|
+
// types, so an element erased there is one the declaration leaves generic.
|
|
597
|
+
const unconstrained = (declared: Signature | undefined, placed: Placed): boolean => {
|
|
598
|
+
if (declared === undefined) {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
const result = must(queries.returnTypeOf(declared));
|
|
602
|
+
if (!placed.rest) {
|
|
603
|
+
const parameter = must(queries.parameterType(declared, placed.place));
|
|
604
|
+
return (parameter === undefined ? [] : must(queries.constituents(parameter))).some(
|
|
605
|
+
(part) => keepsNothing(part, result),
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
const rest = must(queries.restTypeOf(declared));
|
|
609
|
+
const element = restElementOf(project, declared);
|
|
610
|
+
if (rest?.isTypeParameter() === true && result !== undefined && mentions(result, rest, 0)) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
return (
|
|
614
|
+
element === undefined ||
|
|
615
|
+
must(queries.constituents(element)).some(
|
|
616
|
+
(part) => (part.flags & ERASED) !== 0 || keepsNothing(part, result),
|
|
617
|
+
)
|
|
618
|
+
);
|
|
619
|
+
};
|
|
620
|
+
const found = new Set<string>();
|
|
621
|
+
const placedAt = new Set<string>();
|
|
622
|
+
const unplaced = new Map<string, number>();
|
|
623
|
+
for (const one of bindings.of(call)) {
|
|
624
|
+
const asked = new Set<string>();
|
|
625
|
+
for (const placed of one.placed()) {
|
|
626
|
+
const { argument: at, element: given, place, uncovered, rest } = placed;
|
|
627
|
+
const key = `${String(at)}:${String(given)}`;
|
|
628
|
+
if (uncovered) {
|
|
629
|
+
unplaced.set(key, at);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
placedAt.add(key);
|
|
633
|
+
const where = `${key}:${rest ? "rest" : String(place)}`;
|
|
634
|
+
if (found.has(key) || asked.has(where)) {
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
asked.add(where);
|
|
638
|
+
// A signature that is its own declaration, as one an indirect call binds, has its
|
|
639
|
+
// rest's element judged as the declaration's alone.
|
|
640
|
+
const own = rest && one.signature === one.declared;
|
|
641
|
+
const parameter = own
|
|
642
|
+
? undefined
|
|
643
|
+
: rest
|
|
644
|
+
? restElementOf(project, one.signature)
|
|
645
|
+
: must(queries.parameterType(one.signature, place));
|
|
646
|
+
if (
|
|
647
|
+
(rest && !own && parameter === undefined) ||
|
|
648
|
+
(parameter !== undefined && (parameter.flags & ERASED) !== 0) ||
|
|
649
|
+
unconstrained(one.declared, placed)
|
|
650
|
+
) {
|
|
651
|
+
found.add(key);
|
|
652
|
+
found.add(String(at));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (const [key, at] of unplaced) {
|
|
657
|
+
if (!placedAt.has(key)) {
|
|
658
|
+
found.add(key);
|
|
659
|
+
found.add(String(at));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
erased = found;
|
|
663
|
+
erasedPlaces.set(call, erased);
|
|
664
|
+
}
|
|
665
|
+
const span = bindings.spans(call).find((one) => one.argument === argument);
|
|
666
|
+
return element === undefined || span?.open !== false
|
|
667
|
+
? erased.has(String(argument))
|
|
668
|
+
: erased.has(`${String(argument)}:${String(element)}`);
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const forwards = new Map<string, Reach>();
|
|
672
|
+
const keyOf = (parameter: ParameterSite): string =>
|
|
673
|
+
`${parameter.fn}\u0000${String(parameter.index)}`;
|
|
674
|
+
const at = (
|
|
675
|
+
call: CallExpression,
|
|
676
|
+
argument: number,
|
|
677
|
+
element?: number,
|
|
678
|
+
): Destination | undefined => {
|
|
679
|
+
const target = calleeOf(call);
|
|
680
|
+
if (target === undefined) {
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
const entry = named.find((one) => names(one, target, chains));
|
|
684
|
+
if (entry !== undefined) {
|
|
685
|
+
return { reach: "properties", callee: spelledEntry(entry.entry, held) };
|
|
686
|
+
}
|
|
687
|
+
if (outside(target, true, calledDeclarations(call, target))) {
|
|
688
|
+
return erasedAt(call, argument, element)
|
|
689
|
+
? {
|
|
690
|
+
reach: "conversions",
|
|
691
|
+
callee: invocation(call).callee.getText().replace(/\s+/gu, ""),
|
|
692
|
+
}
|
|
693
|
+
: undefined;
|
|
694
|
+
}
|
|
695
|
+
const spans = bindings.spans(call).filter((span) => span.argument === argument);
|
|
696
|
+
for (const fn of chains.declarationsOf(target)) {
|
|
697
|
+
const reach = (byFunction.get(fn) ?? [])
|
|
698
|
+
.filter((one) => spans.some((span) => fills(span, one.index, one.rest, element)))
|
|
699
|
+
.reduce<Reach | undefined>(
|
|
700
|
+
(widest, one) => wider(widest, forwards.get(keyOf(one))),
|
|
701
|
+
undefined,
|
|
702
|
+
);
|
|
703
|
+
if (reach !== undefined) {
|
|
704
|
+
return { reach, callee: held.symbols.find((symbol) => symbol.id === fn)?.name ?? fn };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return undefined;
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
for (const parameter of enumeratedParameters(project, erased)) {
|
|
711
|
+
forwards.set(keyOf(parameter), "properties");
|
|
712
|
+
}
|
|
713
|
+
const forwarded = handedOn(project, sites, erased);
|
|
714
|
+
for (let moved = true; moved;) {
|
|
715
|
+
moved = false;
|
|
716
|
+
for (const one of forwarded) {
|
|
717
|
+
const key = keyOf(one.parameter);
|
|
718
|
+
const before = forwards.get(key);
|
|
719
|
+
const after = wider(before, at(one.site.call, one.argument)?.reach);
|
|
720
|
+
if (after !== undefined && after !== before) {
|
|
721
|
+
forwards.set(key, after);
|
|
722
|
+
moved = true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const mayReceive = (call: CallExpression): boolean => {
|
|
728
|
+
const target = calleeOf(call);
|
|
729
|
+
if (target === undefined) {
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
if (named.some((one) => names(one, target, chains)) || mayBeOutside(target)) {
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
return chains
|
|
736
|
+
.declarationsOf(target)
|
|
737
|
+
.some((fn) => (byFunction.get(fn) ?? []).some((parameter) => forwards.has(keyOf(parameter))));
|
|
738
|
+
};
|
|
739
|
+
return { invocation, bindings, at, mayReceive };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/** One value that carries classes and object types of the target into a destination. */
|
|
743
|
+
interface Flow {
|
|
744
|
+
readonly site: Node;
|
|
745
|
+
readonly classes: ReadonlySet<string>;
|
|
746
|
+
/** The members of the interfaces and object types the value carries. */
|
|
747
|
+
readonly members: readonly TSSymbol[];
|
|
748
|
+
readonly to: Destination;
|
|
749
|
+
/** What a retention names the destination by. */
|
|
750
|
+
readonly detail: string;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** What two reaches carry together. */
|
|
754
|
+
function joined(a: Reached, b: Reached): Reached {
|
|
755
|
+
return { classes: new Set([...a.classes, ...b.classes]), members: [...a.members, ...b.members] };
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/** Each argument of a call that can receive one whose value carries a class of the target. */
|
|
759
|
+
function flowsOf<Brand>(
|
|
760
|
+
project: ProjectView<Brand>,
|
|
761
|
+
held: Inventory,
|
|
762
|
+
sites: readonly CallSite[],
|
|
763
|
+
to: Destinations,
|
|
764
|
+
stores: ErasedStores,
|
|
765
|
+
): readonly Flow[] {
|
|
766
|
+
const reach = valueReach(project, held, stores.inside);
|
|
767
|
+
const arguments_ = sites.flatMap((site) =>
|
|
768
|
+
to.mayReceive(site.call)
|
|
769
|
+
? to
|
|
770
|
+
.invocation(site.call)
|
|
771
|
+
.arguments.flatMap((argument, index) =>
|
|
772
|
+
valuesOf(argument).flatMap((value) =>
|
|
773
|
+
holdsNoInstance(value) ? [] : [{ site, argument: index, value }],
|
|
774
|
+
),
|
|
775
|
+
)
|
|
776
|
+
: [],
|
|
777
|
+
);
|
|
778
|
+
const asked = arguments_.filter((one) => one.value.kind !== SyntaxKind.ThisKeyword);
|
|
779
|
+
const types = typesAt(
|
|
780
|
+
project,
|
|
781
|
+
asked.map((one) => one.value),
|
|
782
|
+
);
|
|
783
|
+
const typeOf = new Map<Expression, Type | undefined>(
|
|
784
|
+
asked.map((one, index) => [one.value, types[index]]),
|
|
785
|
+
);
|
|
786
|
+
const checked = checkedTypes(
|
|
787
|
+
project,
|
|
788
|
+
arguments_.map((one) => one.value),
|
|
789
|
+
);
|
|
790
|
+
/**
|
|
791
|
+
* The types one argument's value carries, each with the element of the argument's span
|
|
792
|
+
* it fills. A spread's value is its elements, which a container other than an array does
|
|
793
|
+
* not walk to, so the span's element types join. A spread of known length gives each
|
|
794
|
+
* element at its own place, as does any type read for it that is a tuple of that length,
|
|
795
|
+
* and every other type fills every place the argument does.
|
|
796
|
+
*/
|
|
797
|
+
const partsOf = (
|
|
798
|
+
one: (typeof arguments_)[number],
|
|
799
|
+
types: readonly Type[],
|
|
800
|
+
): readonly { readonly element: number | undefined; readonly types: readonly Type[] }[] => {
|
|
801
|
+
const span = to.bindings.spans(one.site.call).find((at) => at.argument === one.argument);
|
|
802
|
+
const given = span?.spread === true ? span.given.filter((type) => type !== undefined) : [];
|
|
803
|
+
if (span === undefined || !span.spread || span.open) {
|
|
804
|
+
return [{ element: undefined, types: [...types, ...given] }];
|
|
805
|
+
}
|
|
806
|
+
const whole: Type[] = [];
|
|
807
|
+
const elements = span.given.map((type) => (type === undefined ? [] : [type]));
|
|
808
|
+
for (const type of types) {
|
|
809
|
+
const known = knownElements(project, type, elements.length);
|
|
810
|
+
if (known === undefined) {
|
|
811
|
+
whole.push(type);
|
|
812
|
+
} else {
|
|
813
|
+
known.forEach((part, element) => {
|
|
814
|
+
if (part !== undefined) {
|
|
815
|
+
elements[element]?.push(part);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return [
|
|
821
|
+
{ element: undefined, types: whole },
|
|
822
|
+
...elements.map((parts, element) => ({ element, types: parts })),
|
|
823
|
+
];
|
|
824
|
+
};
|
|
825
|
+
const passed = arguments_.flatMap((one, index) => {
|
|
826
|
+
const type = typeOf.get(one.value);
|
|
827
|
+
const self: Reached = {
|
|
828
|
+
classes: new Set(
|
|
829
|
+
one.value.kind === SyntaxKind.ThisKeyword && one.site.self !== undefined
|
|
830
|
+
? [one.site.self]
|
|
831
|
+
: [],
|
|
832
|
+
),
|
|
833
|
+
members: [],
|
|
834
|
+
};
|
|
835
|
+
const types = [
|
|
836
|
+
...(type === undefined ? [] : [type]),
|
|
837
|
+
...(checked[index] ?? []),
|
|
838
|
+
...(stores.ofVariable(one.value) ?? []),
|
|
839
|
+
];
|
|
840
|
+
return partsOf(one, types).flatMap(({ element, types: parts }) => {
|
|
841
|
+
const reached = parts.reduce<Reached>(
|
|
842
|
+
(all, part) => joined(all, reach.reachedFrom(part)),
|
|
843
|
+
element === undefined ? self : { classes: new Set<string>(), members: [] },
|
|
844
|
+
);
|
|
845
|
+
const carries = reached.classes.size > 0 || reached.members.length > 0;
|
|
846
|
+
const destination = carries ? to.at(one.site.call, one.argument, element) : undefined;
|
|
847
|
+
return destination === undefined
|
|
848
|
+
? []
|
|
849
|
+
: [
|
|
850
|
+
{
|
|
851
|
+
site: one.value,
|
|
852
|
+
classes: reached.classes,
|
|
853
|
+
members: reached.members,
|
|
854
|
+
to: destination,
|
|
855
|
+
detail: `passed to ${destination.callee}`,
|
|
856
|
+
},
|
|
857
|
+
];
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
const generic = typeParameterFlows(
|
|
861
|
+
project,
|
|
862
|
+
reach,
|
|
863
|
+
sites,
|
|
864
|
+
to.bindings,
|
|
865
|
+
arguments_.flatMap((one) => {
|
|
866
|
+
const type = typeOf.get(one.value);
|
|
867
|
+
return partsOf(one, type === undefined ? [] : [type]).flatMap(({ element, types }) => {
|
|
868
|
+
const parameters = types.flatMap((part) => typeParametersOf(project, part, 0));
|
|
869
|
+
const destination =
|
|
870
|
+
parameters.length === 0 ? undefined : to.at(one.site.call, one.argument, element);
|
|
871
|
+
return destination === undefined
|
|
872
|
+
? []
|
|
873
|
+
: parameters.map((parameter) => ({ parameter, to: destination }));
|
|
874
|
+
});
|
|
875
|
+
}),
|
|
876
|
+
);
|
|
877
|
+
const left = stores.outside.flatMap((store) => {
|
|
878
|
+
const reached = reach.reachedFrom(store.type);
|
|
879
|
+
return reached.classes.size === 0 && reached.members.length === 0
|
|
880
|
+
? []
|
|
881
|
+
: [
|
|
882
|
+
{
|
|
883
|
+
site: store.value,
|
|
884
|
+
classes: reached.classes,
|
|
885
|
+
members: reached.members,
|
|
886
|
+
to: { reach: "conversions" as const, callee: store.property },
|
|
887
|
+
detail: `stored in the property ${store.property} of a type the program does not declare`,
|
|
888
|
+
},
|
|
889
|
+
];
|
|
890
|
+
});
|
|
891
|
+
return [...passed, ...generic, ...left];
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* The type parameters one type names, through constituents, type arguments and the type
|
|
896
|
+
* parameters a mapped type is over. A class's polymorphic `this` type is no parameter a
|
|
897
|
+
* call binds: the reach walks it as its class.
|
|
898
|
+
*/
|
|
899
|
+
function typeParametersOf<Brand>(project: ProjectView<Brand>, type: Type, depth: number): Type[] {
|
|
900
|
+
if (type.isTypeParameter()) {
|
|
901
|
+
return type.isThisType === true ? [] : [type];
|
|
902
|
+
}
|
|
903
|
+
if (depth >= MENTION_DEPTH) {
|
|
904
|
+
return [];
|
|
905
|
+
}
|
|
906
|
+
const { queries } = project;
|
|
907
|
+
return [
|
|
908
|
+
...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
|
|
909
|
+
...must(queries.typeArguments(type)),
|
|
910
|
+
...must(queries.mappedParametersOf(type)),
|
|
911
|
+
].flatMap((one) => typeParametersOf(project, one, depth + 1));
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* The type parameters one type names, by identifier: through constituents, type arguments,
|
|
916
|
+
* an alias's arguments and the parameters and results of its call signatures, so a callback
|
|
917
|
+
* parameter's result names the parameter its callee's result does.
|
|
918
|
+
*/
|
|
919
|
+
function namedTypeParameters<Brand>(
|
|
920
|
+
project: ProjectView<Brand>,
|
|
921
|
+
type: Type,
|
|
922
|
+
depth: number,
|
|
923
|
+
): readonly number[] {
|
|
924
|
+
const { queries } = project;
|
|
925
|
+
const aliased = (type as { readonly aliasTypeArguments?: readonly number[] }).aliasTypeArguments;
|
|
926
|
+
if (type.isTypeParameter()) {
|
|
927
|
+
return [type.id];
|
|
928
|
+
}
|
|
929
|
+
if (depth >= MENTION_DEPTH) {
|
|
930
|
+
return aliased ?? [];
|
|
931
|
+
}
|
|
932
|
+
return [
|
|
933
|
+
...(aliased ?? []),
|
|
934
|
+
...[
|
|
935
|
+
...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
|
|
936
|
+
...must(queries.typeArguments(type)),
|
|
937
|
+
...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
|
|
938
|
+
...signature.parameters.flatMap((_parameter, at) => [
|
|
939
|
+
must(queries.typeAtPosition(signature, at)),
|
|
940
|
+
]),
|
|
941
|
+
...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
|
|
942
|
+
]),
|
|
943
|
+
].flatMap((one) => namedTypeParameters(project, one, depth + 1)),
|
|
944
|
+
];
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* The types an instantiated type puts where a declared type names `parameter`: matched
|
|
949
|
+
* through type arguments, and in a union every constituent the declared union does not
|
|
950
|
+
* itself name.
|
|
951
|
+
*/
|
|
952
|
+
function bound<Brand>(
|
|
953
|
+
project: ProjectView<Brand>,
|
|
954
|
+
declared: Type,
|
|
955
|
+
instantiated: Type,
|
|
956
|
+
parameter: Type,
|
|
957
|
+
depth: number,
|
|
958
|
+
): Type[] {
|
|
959
|
+
if (declared.id === parameter.id) {
|
|
960
|
+
return [instantiated];
|
|
961
|
+
}
|
|
962
|
+
if (depth >= MENTION_DEPTH) {
|
|
963
|
+
return [];
|
|
964
|
+
}
|
|
965
|
+
const { queries } = project;
|
|
966
|
+
// A place of a rest the parameter types is declared as an indexed access over it, and
|
|
967
|
+
// what the call passes there is one element of the type it binds.
|
|
968
|
+
if (must(queries.indexedAccessOf(declared))?.object.id === parameter.id) {
|
|
969
|
+
return [instantiated];
|
|
970
|
+
}
|
|
971
|
+
if (declared.isUnionType()) {
|
|
972
|
+
const parts = must(queries.constituents(declared));
|
|
973
|
+
if (parts.some((part) => part.id === parameter.id)) {
|
|
974
|
+
const kept = new Set(parts.map((part) => part.id));
|
|
975
|
+
const given = instantiated.isUnionType()
|
|
976
|
+
? must(queries.constituents(instantiated))
|
|
977
|
+
: [instantiated];
|
|
978
|
+
return given.filter((part) => !kept.has(part.id));
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const declaredArguments = must(queries.typeArguments(declared));
|
|
982
|
+
const givenArguments = must(queries.typeArguments(instantiated));
|
|
983
|
+
return declaredArguments.length !== givenArguments.length
|
|
984
|
+
? []
|
|
985
|
+
: declaredArguments.flatMap((one, at) => {
|
|
986
|
+
const given = givenArguments[at];
|
|
987
|
+
return given === undefined ? [] : bound(project, one, given, parameter, depth + 1);
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/** A declared type at a place and the type a call puts there. */
|
|
992
|
+
interface Placing {
|
|
993
|
+
readonly from: Type;
|
|
994
|
+
readonly into: Type;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* One signature a call binds its arguments to: the one the checker resolves for a direct
|
|
999
|
+
* call, and each call signature of the value the method is read from for one through
|
|
1000
|
+
* `call`, `apply` or `bind`, whose resolved signature is the method's.
|
|
1001
|
+
*/
|
|
1002
|
+
interface Binding {
|
|
1003
|
+
readonly call: CallExpression;
|
|
1004
|
+
/** The signature whose parameter types the arguments meet. */
|
|
1005
|
+
readonly signature: Signature;
|
|
1006
|
+
/** The generic signature the call instantiates: the resolved one's, or the callee's own. */
|
|
1007
|
+
readonly declared: Signature | undefined;
|
|
1008
|
+
/** The parameter place each argument binds in `signature`. */
|
|
1009
|
+
readonly placed: () => readonly Placed[];
|
|
1010
|
+
/**
|
|
1011
|
+
* The declared and the instantiated type at each place an argument binds, and an open
|
|
1012
|
+
* rest's element type.
|
|
1013
|
+
*/
|
|
1014
|
+
readonly places: () => readonly Placing[];
|
|
1015
|
+
/** The type arguments the call writes for the declared signature, if it writes them. */
|
|
1016
|
+
readonly written: readonly Node[] | undefined;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/** The signatures each call binds, each call read once. */
|
|
1020
|
+
interface Bindings {
|
|
1021
|
+
of(call: CallExpression): readonly Binding[];
|
|
1022
|
+
/** Those of every call, asked of the checker in batches. */
|
|
1023
|
+
all(calls: readonly CallExpression[]): readonly Binding[];
|
|
1024
|
+
/** The places each argument of one call fills, read as `argument-places.ts` reads them. */
|
|
1025
|
+
spans(call: CallExpression): readonly Span[];
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/** One value read once, at its first use. */
|
|
1029
|
+
function once<T>(read: () => T): () => T {
|
|
1030
|
+
let held: { readonly value: T } | undefined;
|
|
1031
|
+
return () => (held ??= { value: read() }).value;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* The signatures a project's calls bind, and the place each argument binds in each. A
|
|
1036
|
+
* direct call binds the signature the checker resolves, its places taking the types the
|
|
1037
|
+
* checker instantiates there. Through `call`, `apply` or `bind` a call binds every call
|
|
1038
|
+
* signature of the function, a place taking the type of the argument that fills it: the
|
|
1039
|
+
* checker instantiates those methods from the arguments, not from the function's own
|
|
1040
|
+
* parameters, so no instantiation of them says what the function keeps.
|
|
1041
|
+
*/
|
|
1042
|
+
function callBindings<Brand>(
|
|
1043
|
+
project: ProjectView<Brand>,
|
|
1044
|
+
invocation: (call: CallExpression) => Invocation,
|
|
1045
|
+
): Bindings {
|
|
1046
|
+
const { queries } = project;
|
|
1047
|
+
const known = new Map<CallExpression, readonly Binding[]>();
|
|
1048
|
+
const passed = new Map<CallExpression, readonly (Type | undefined)[]>();
|
|
1049
|
+
const spanned = new Map<CallExpression, readonly Span[]>();
|
|
1050
|
+
const layouts = new Map<number, Layout>();
|
|
1051
|
+
const valueOf = (argument: Expression): Expression =>
|
|
1052
|
+
isSpreadElement(argument) ? argument.expression : argument;
|
|
1053
|
+
// An indirect call's places take its arguments' types, and a spread's type says how
|
|
1054
|
+
// many places it fills; a direct call with neither reads no argument's type.
|
|
1055
|
+
const typed = (call: CallExpression): boolean => {
|
|
1056
|
+
const invoked = invocation(call);
|
|
1057
|
+
return invoked.indirect || invoked.arguments.some(isSpreadElement);
|
|
1058
|
+
};
|
|
1059
|
+
const typesPassed = (call: CallExpression): readonly (Type | undefined)[] => {
|
|
1060
|
+
let types = passed.get(call);
|
|
1061
|
+
if (types === undefined) {
|
|
1062
|
+
types = typesAt(project, invocation(call).arguments.map(valueOf));
|
|
1063
|
+
passed.set(call, types);
|
|
1064
|
+
}
|
|
1065
|
+
return types;
|
|
1066
|
+
};
|
|
1067
|
+
const spans = (call: CallExpression): readonly Span[] => {
|
|
1068
|
+
let found = spanned.get(call);
|
|
1069
|
+
if (found === undefined) {
|
|
1070
|
+
const invoked = invocation(call);
|
|
1071
|
+
found = spansOf(
|
|
1072
|
+
project,
|
|
1073
|
+
invoked.arguments,
|
|
1074
|
+
invoked.listed,
|
|
1075
|
+
typed(call) ? typesPassed(call) : [],
|
|
1076
|
+
);
|
|
1077
|
+
spanned.set(call, found);
|
|
1078
|
+
}
|
|
1079
|
+
return found;
|
|
1080
|
+
};
|
|
1081
|
+
const layout = (signature: Signature): Layout => {
|
|
1082
|
+
let found = layouts.get(signature.id);
|
|
1083
|
+
if (found === undefined) {
|
|
1084
|
+
found = layoutOf(project, signature);
|
|
1085
|
+
layouts.set(signature.id, found);
|
|
1086
|
+
}
|
|
1087
|
+
return found;
|
|
1088
|
+
};
|
|
1089
|
+
const distinct = (pairs: readonly Placing[]): readonly Placing[] => [
|
|
1090
|
+
...new Map(
|
|
1091
|
+
pairs.map((pair) => [`${String(pair.from.id)}:${String(pair.into.id)}`, pair]),
|
|
1092
|
+
).values(),
|
|
1093
|
+
];
|
|
1094
|
+
// The checker infers a type parameter a mapped type is over by reversing the mapping,
|
|
1095
|
+
// into a copy that names no class, so at a place declared as one the value passed is
|
|
1096
|
+
// what each of its type parameters carries.
|
|
1097
|
+
const mappedFrom = (from: Type, value: Type | undefined): readonly Placing[] =>
|
|
1098
|
+
value === undefined
|
|
1099
|
+
? []
|
|
1100
|
+
: must(queries.constituents(from)).flatMap((part) =>
|
|
1101
|
+
must(queries.mappedParametersOf(part)).map((source) => ({ from: source, into: value })),
|
|
1102
|
+
);
|
|
1103
|
+
const direct = (call: CallExpression, resolved: Signature | undefined): readonly Binding[] => {
|
|
1104
|
+
if (resolved === undefined) {
|
|
1105
|
+
return [];
|
|
1106
|
+
}
|
|
1107
|
+
const declared = must(queries.genericOf(resolved));
|
|
1108
|
+
const placed = once(() => placesIn(spans(call), layout(resolved)));
|
|
1109
|
+
const places = () => {
|
|
1110
|
+
if (declared === undefined) {
|
|
1111
|
+
return [];
|
|
1112
|
+
}
|
|
1113
|
+
return distinct(
|
|
1114
|
+
placed().flatMap((one) => {
|
|
1115
|
+
const from = one.uncovered ? undefined : must(queries.parameterType(declared, one.place));
|
|
1116
|
+
if (from === undefined) {
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
const into = must(queries.parameterType(resolved, one.place));
|
|
1120
|
+
const element = one.rest ? restElementOf(project, declared) : undefined;
|
|
1121
|
+
const elements = one.rest ? restElementOf(project, resolved) : undefined;
|
|
1122
|
+
return [
|
|
1123
|
+
...(into === undefined ? [] : [{ from, into }]),
|
|
1124
|
+
...(element === undefined || elements === undefined
|
|
1125
|
+
? []
|
|
1126
|
+
: [{ from: element, into: elements }]),
|
|
1127
|
+
...mappedFrom(from, one.given ?? typesPassed(call)[one.argument]),
|
|
1128
|
+
];
|
|
1129
|
+
}),
|
|
1130
|
+
);
|
|
1131
|
+
};
|
|
1132
|
+
return [{ call, signature: resolved, declared, placed, places, written: call.typeArguments }];
|
|
1133
|
+
};
|
|
1134
|
+
const indirect = (call: CallExpression, callee: Type | undefined): readonly Binding[] =>
|
|
1135
|
+
(callee === undefined ? [] : must(queries.signaturesOf(callee, SignatureKind.Call))).map(
|
|
1136
|
+
(signature) => {
|
|
1137
|
+
const placed = once(() => placesIn(spans(call), layout(signature)));
|
|
1138
|
+
return {
|
|
1139
|
+
call,
|
|
1140
|
+
signature,
|
|
1141
|
+
declared: signature,
|
|
1142
|
+
placed,
|
|
1143
|
+
places: () =>
|
|
1144
|
+
distinct(
|
|
1145
|
+
placed().flatMap(({ place, given, uncovered, rest }) => {
|
|
1146
|
+
const from = uncovered ? undefined : must(queries.parameterType(signature, place));
|
|
1147
|
+
const element = rest ? restElementOf(project, signature) : undefined;
|
|
1148
|
+
return from === undefined || given === undefined
|
|
1149
|
+
? []
|
|
1150
|
+
: [
|
|
1151
|
+
{ from, into: given },
|
|
1152
|
+
...(element === undefined ? [] : [{ from: element, into: given }]),
|
|
1153
|
+
...mappedFrom(from, given),
|
|
1154
|
+
];
|
|
1155
|
+
}),
|
|
1156
|
+
),
|
|
1157
|
+
written: undefined,
|
|
1158
|
+
};
|
|
1159
|
+
},
|
|
1160
|
+
);
|
|
1161
|
+
const of = (call: CallExpression): readonly Binding[] => {
|
|
1162
|
+
let bound = known.get(call);
|
|
1163
|
+
if (bound === undefined) {
|
|
1164
|
+
const invoked = invocation(call);
|
|
1165
|
+
bound = invoked.indirect
|
|
1166
|
+
? indirect(call, must(queries.typeAt(invoked.callee)))
|
|
1167
|
+
: direct(call, must(queries.resolvedSignature(call)));
|
|
1168
|
+
known.set(call, bound);
|
|
1169
|
+
}
|
|
1170
|
+
return bound;
|
|
1171
|
+
};
|
|
1172
|
+
const all = (calls: readonly CallExpression[]): readonly Binding[] => {
|
|
1173
|
+
const asked = calls.filter((call) => !known.has(call));
|
|
1174
|
+
const plain = asked.filter((call) => !invocation(call).indirect);
|
|
1175
|
+
queries
|
|
1176
|
+
.resolvedSignatures(plain)
|
|
1177
|
+
.map(must)
|
|
1178
|
+
.forEach((resolved, at) => {
|
|
1179
|
+
const call = plain[at];
|
|
1180
|
+
if (call !== undefined) {
|
|
1181
|
+
known.set(call, direct(call, resolved));
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
const through = asked.filter((call) => invocation(call).indirect);
|
|
1185
|
+
const callees = typesAt(
|
|
1186
|
+
project,
|
|
1187
|
+
through.map((call) => invocation(call).callee),
|
|
1188
|
+
);
|
|
1189
|
+
const typing = asked.filter((call) => typed(call) && !passed.has(call));
|
|
1190
|
+
const values = typesAt(
|
|
1191
|
+
project,
|
|
1192
|
+
typing.flatMap((call) => invocation(call).arguments.map(valueOf)),
|
|
1193
|
+
);
|
|
1194
|
+
let offset = 0;
|
|
1195
|
+
for (const call of typing) {
|
|
1196
|
+
const count = invocation(call).arguments.length;
|
|
1197
|
+
passed.set(call, values.slice(offset, offset + count));
|
|
1198
|
+
offset += count;
|
|
1199
|
+
}
|
|
1200
|
+
through.forEach((call, at) => {
|
|
1201
|
+
known.set(call, indirect(call, callees[at]));
|
|
1202
|
+
});
|
|
1203
|
+
return calls.flatMap(of);
|
|
1204
|
+
};
|
|
1205
|
+
return { of, all, spans };
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* The flows a type parameter of a function of the project carries where a value its type
|
|
1210
|
+
* names reaches a destination: each call of the function passes its type argument there,
|
|
1211
|
+
* written or inferred from the arguments, and a type argument that is a type parameter
|
|
1212
|
+
* of the calling function is followed on to that function's calls, to a fixpoint.
|
|
1213
|
+
*/
|
|
1214
|
+
function typeParameterFlows<Brand>(
|
|
1215
|
+
project: ProjectView<Brand>,
|
|
1216
|
+
reach: ReturnType<typeof valueReach>,
|
|
1217
|
+
sites: readonly CallSite[],
|
|
1218
|
+
bindings: Bindings,
|
|
1219
|
+
pending: { readonly parameter: Type; readonly to: Destination }[],
|
|
1220
|
+
): readonly Flow[] {
|
|
1221
|
+
const { queries } = project;
|
|
1222
|
+
let calls: ReadonlyMap<Node, readonly Binding[]> | undefined;
|
|
1223
|
+
const byDeclaration = (): ReadonlyMap<Node, readonly Binding[]> => {
|
|
1224
|
+
const found = new Map<Node, Binding[]>();
|
|
1225
|
+
for (const one of bindings.all(sites.map((site) => site.call))) {
|
|
1226
|
+
const handle = one.declared?.declaration;
|
|
1227
|
+
const node = handle === undefined ? undefined : project.declarationAt(handle)?.node;
|
|
1228
|
+
if (node !== undefined) {
|
|
1229
|
+
found.set(node, [...(found.get(node) ?? []), one]);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return found;
|
|
1233
|
+
};
|
|
1234
|
+
const found: Flow[] = [];
|
|
1235
|
+
// One type parameter reaches each of its destinations, each holding back its own.
|
|
1236
|
+
const seen = new Set<string>();
|
|
1237
|
+
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
|
1238
|
+
const { parameter, to } = next;
|
|
1239
|
+
const key = `${String(parameter.id)}\u0000${to.reach}\u0000${to.callee}`;
|
|
1240
|
+
if (seen.has(key)) {
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
seen.add(key);
|
|
1244
|
+
const symbol = must(queries.symbolOfType(parameter));
|
|
1245
|
+
const [handle] = symbol?.declarations ?? [];
|
|
1246
|
+
const declaration = handle === undefined ? undefined : project.declarationAt(handle)?.node;
|
|
1247
|
+
const fn = declaration?.parent;
|
|
1248
|
+
if (symbol === undefined || fn === undefined || !isFunctionLikeDeclaration(fn)) {
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const index = (fn.typeParameters ?? []).findIndex((one) => one === declaration);
|
|
1252
|
+
if (index < 0) {
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
calls ??= byDeclaration();
|
|
1256
|
+
for (const one of calls.get(fn) ?? []) {
|
|
1257
|
+
const written = one.written?.[index];
|
|
1258
|
+
const given =
|
|
1259
|
+
written === undefined
|
|
1260
|
+
? one.places().flatMap(({ from, into }) => bound(project, from, into, parameter, 0))
|
|
1261
|
+
: typesAt(project, [written]).filter((type) => type !== undefined);
|
|
1262
|
+
for (const type of given) {
|
|
1263
|
+
const reached = reach.reachedFrom(type);
|
|
1264
|
+
if (reached.classes.size > 0 || reached.members.length > 0) {
|
|
1265
|
+
found.push({
|
|
1266
|
+
site: one.call,
|
|
1267
|
+
classes: reached.classes,
|
|
1268
|
+
members: reached.members,
|
|
1269
|
+
to,
|
|
1270
|
+
detail: `passed to ${to.callee} through the type parameter ${symbol.name}`,
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
pending.push(...typeParametersOf(project, type, 0).map((one) => ({ parameter: one, to })));
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return found;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* The serialization-contract detector: each data member and `toJSON` of each class,
|
|
1282
|
+
* interface and object type a value that reaches a destination carries, and `toString`
|
|
1283
|
+
* as well where the destination is outside the analysis, recorded at the argument with
|
|
1284
|
+
* the callee named.
|
|
1285
|
+
*/
|
|
1286
|
+
export function serializationContract<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
|
|
1287
|
+
const { project, held, sites, targetRoot } = input;
|
|
1288
|
+
const chains = aliasChains(project, held);
|
|
1289
|
+
const flows = flowsOf(
|
|
1290
|
+
project,
|
|
1291
|
+
held,
|
|
1292
|
+
sites.calls,
|
|
1293
|
+
destinations(input, chains),
|
|
1294
|
+
erasedStores(project),
|
|
1295
|
+
);
|
|
1296
|
+
if (flows.length === 0) {
|
|
1297
|
+
return [];
|
|
1298
|
+
}
|
|
1299
|
+
const members = classMembers(
|
|
1300
|
+
project,
|
|
1301
|
+
held,
|
|
1302
|
+
new Set(flows.flatMap((flow) => [...flow.classes])),
|
|
1303
|
+
"instance",
|
|
1304
|
+
);
|
|
1305
|
+
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
1306
|
+
// A destination holds back a data member and `toJSON`, and `toString` as well where
|
|
1307
|
+
// the value left the analysis.
|
|
1308
|
+
const holdsBack = (flags: SymbolFlags, name: string, to: Destination): boolean =>
|
|
1309
|
+
(flags & SymbolFlags.Property) !== 0 ||
|
|
1310
|
+
((flags & CONVERSION_MEMBERS) !== 0 &&
|
|
1311
|
+
(name === SERIALIZED_CONVERSION ||
|
|
1312
|
+
(to.reach === "conversions" && CONVERSION_NAMES.has(name))));
|
|
1313
|
+
// The receiver reads only what the program wrote, so a data member holds while written.
|
|
1314
|
+
const evidence = (id: string, flags: SymbolFlags, detail: string, site: Position): Evidence =>
|
|
1315
|
+
(flags & SymbolFlags.Property) !== 0 && !sites.initialized.has(id)
|
|
1316
|
+
? { id, detail, site, whileWritten: true }
|
|
1317
|
+
: { id, detail, site };
|
|
1318
|
+
const found: Evidence[] = [];
|
|
1319
|
+
for (const flow of flows) {
|
|
1320
|
+
const site = renderPosition(flow.site.getSourceFile(), targetRoot, flow.site.getStart());
|
|
1321
|
+
const { detail } = flow;
|
|
1322
|
+
for (const member of [...flow.classes].flatMap((id) => members.get(id) ?? [])) {
|
|
1323
|
+
const symbol = byId.get(member.id);
|
|
1324
|
+
const name = symbol === undefined ? "" : (memberComponent(symbol, byId) ?? "");
|
|
1325
|
+
if (holdsBack(member.flags, name, flow.to)) {
|
|
1326
|
+
found.push(evidence(member.id, member.flags, detail, site));
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
for (const member of flow.members) {
|
|
1330
|
+
if (holdsBack(member.flags, member.name, flow.to)) {
|
|
1331
|
+
for (const id of chains.declarationsOf(member)) {
|
|
1332
|
+
found.push(evidence(id, member.flags, detail, site));
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return found;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/** The data members the decodes and copies of a project's production code fill. */
|
|
1341
|
+
export interface Decoded {
|
|
1342
|
+
/**
|
|
1343
|
+
* Those a generic declared outside the analysis fills, which it may read, and those of
|
|
1344
|
+
* a value another call or a property types `any` or `unknown`, whose maker it cannot see.
|
|
1345
|
+
*/
|
|
1346
|
+
readonly filled: readonly string[];
|
|
1347
|
+
/** Those a `JSON.parse` call of the standard library fills. */
|
|
1348
|
+
readonly parsed: readonly string[];
|
|
1349
|
+
/** Those a spread or `Object.assign` copies from a value whose type has a member of the name. */
|
|
1350
|
+
readonly copied: readonly string[];
|
|
1351
|
+
/** Every data member of the project, where the checker left a question unanswered. */
|
|
1352
|
+
readonly unanswered: readonly string[];
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* The data members the decodes and copies of `files` fill, as {@link Decoded} names them.
|
|
1357
|
+
* A generic declared outside the analysis, or only ambiently, or one that reads a value
|
|
1358
|
+
* {@link readTypes} finds untyped as its own type parameter, fills the type that a type
|
|
1359
|
+
* parameter of its own no parameter names binds, where the awaited result carries that
|
|
1360
|
+
* type through its data members rather than only naming it, as a store's methods do: no
|
|
1361
|
+
* argument hands the value over, so it fills it where the analysis cannot see, and may
|
|
1362
|
+
* read it. Where the checker leaves a question unanswered, every data member is `unanswered`.
|
|
1363
|
+
*/
|
|
1364
|
+
export function decodedMembers<Brand>(
|
|
1365
|
+
project: ProjectView<Brand>,
|
|
1366
|
+
held: Inventory,
|
|
1367
|
+
files: readonly SourceFile[],
|
|
1368
|
+
consumers: readonly string[],
|
|
1369
|
+
): Decoded {
|
|
1370
|
+
try {
|
|
1371
|
+
return { ...decodedOf(project, held, files, consumers), unanswered: [] };
|
|
1372
|
+
} catch (error: unknown) {
|
|
1373
|
+
if (!(error instanceof Unanswerable)) {
|
|
1374
|
+
throw error;
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
filled: [],
|
|
1378
|
+
parsed: [],
|
|
1379
|
+
copied: [],
|
|
1380
|
+
unanswered: held.symbols
|
|
1381
|
+
.filter((symbol) => symbol.kind === "class-member" || symbol.kind === "type-member")
|
|
1382
|
+
.map((symbol) => symbol.id),
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
function decodedOf<Brand>(
|
|
1388
|
+
project: ProjectView<Brand>,
|
|
1389
|
+
held: Inventory,
|
|
1390
|
+
files: readonly SourceFile[],
|
|
1391
|
+
consumers: readonly string[],
|
|
1392
|
+
): Omit<Decoded, "unanswered"> {
|
|
1393
|
+
const { queries } = project;
|
|
1394
|
+
const own = project.ownPaths();
|
|
1395
|
+
const inAnalysis = (path: string): boolean =>
|
|
1396
|
+
own.has(path) || consumers.some((root) => path === root || path.startsWith(`${root}/`));
|
|
1397
|
+
const calls: CallExpression[] = [];
|
|
1398
|
+
const untyped: Expression[] = [];
|
|
1399
|
+
const spreads: SpreadAssignment[] = [];
|
|
1400
|
+
const noteRead = (node: Expression): void => {
|
|
1401
|
+
const at = readAs(node);
|
|
1402
|
+
if (at.tops.length > 0 || at.asserted.length > 0) {
|
|
1403
|
+
untyped.push(node);
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
const visit = (node: Node): void => {
|
|
1407
|
+
if (isCallExpression(node)) {
|
|
1408
|
+
calls.push(node);
|
|
1409
|
+
noteRead(node);
|
|
1410
|
+
} else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) {
|
|
1411
|
+
noteRead(node);
|
|
1412
|
+
} else if (isSpreadAssignment(node)) {
|
|
1413
|
+
spreads.push(node);
|
|
1414
|
+
}
|
|
1415
|
+
node.forEachChild(visit);
|
|
1416
|
+
};
|
|
1417
|
+
for (const file of files) {
|
|
1418
|
+
file.forEachChild(visit);
|
|
1419
|
+
}
|
|
1420
|
+
const read = readTypes(project, untyped);
|
|
1421
|
+
// A generic of the analysis that reads an untyped value as its own type parameter is a
|
|
1422
|
+
// client of what made the value, and fills what that parameter binds as an outside one does.
|
|
1423
|
+
const clients = new Set(read.made.flatMap((type) => (type.isTypeParameter() ? [type.id] : [])));
|
|
1424
|
+
const filled: { readonly carried: Type; readonly types: readonly Type[] }[] = [];
|
|
1425
|
+
const resolved = queries.resolvedSignatures(calls).map(must);
|
|
1426
|
+
for (const signature of resolved) {
|
|
1427
|
+
const declared =
|
|
1428
|
+
signature?.target === undefined ? undefined : must(queries.genericOf(signature));
|
|
1429
|
+
const declaration = declared?.declaration;
|
|
1430
|
+
const generic = declared?.typeParameters ?? [];
|
|
1431
|
+
if (signature === undefined || declared === undefined || generic.length === 0) {
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
const node = declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
|
|
1435
|
+
if (
|
|
1436
|
+
declaration === undefined ||
|
|
1437
|
+
(inAnalysis(declaration.path) &&
|
|
1438
|
+
(node === undefined || (node.flags & NodeFlags.Ambient) === 0) &&
|
|
1439
|
+
!generic.some((id) => clients.has(id)))
|
|
1440
|
+
) {
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
const result = must(queries.returnTypeOf(declared));
|
|
1444
|
+
const given = must(queries.returnTypeOf(signature));
|
|
1445
|
+
if (result === undefined || given === undefined) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
const passed = new Set(
|
|
1449
|
+
declared.parameters.flatMap((_parameter, at) => {
|
|
1450
|
+
const type = must(queries.parameterType(declared, at));
|
|
1451
|
+
return type === undefined ? [] : namedTypeParameters(project, type, 0);
|
|
1452
|
+
}),
|
|
1453
|
+
);
|
|
1454
|
+
const types = typeParametersOf(project, result, 0)
|
|
1455
|
+
.filter((parameter) => generic.includes(parameter.id) && !passed.has(parameter.id))
|
|
1456
|
+
.flatMap((parameter) => bound(project, result, given, parameter, 0));
|
|
1457
|
+
if (types.length > 0) {
|
|
1458
|
+
filled.push({ carried: awaited(project, given), types });
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
const copies = copiedNames(project, spreads, calls);
|
|
1462
|
+
if (
|
|
1463
|
+
filled.length === 0 &&
|
|
1464
|
+
read.parsed.length === 0 &&
|
|
1465
|
+
read.made.length === 0 &&
|
|
1466
|
+
copies.length === 0
|
|
1467
|
+
) {
|
|
1468
|
+
return { filled: [], parsed: [], copied: [] };
|
|
1469
|
+
}
|
|
1470
|
+
const reach = valueReach(project, held);
|
|
1471
|
+
const chains = aliasChains(project, held);
|
|
1472
|
+
const members = (type: Type): readonly string[] => {
|
|
1473
|
+
const reached = reach.reachedFrom(type);
|
|
1474
|
+
const ofClasses = classMembers(project, held, reached.classes, "instance");
|
|
1475
|
+
return [
|
|
1476
|
+
...[...reached.classes].flatMap((id) =>
|
|
1477
|
+
(ofClasses.get(id) ?? []).flatMap((member) =>
|
|
1478
|
+
(member.flags & SymbolFlags.Property) !== 0 ? [member.id] : [],
|
|
1479
|
+
),
|
|
1480
|
+
),
|
|
1481
|
+
...reached.members.flatMap((member) =>
|
|
1482
|
+
(member.flags & SymbolFlags.Property) !== 0 ? chains.declarationsOf(member) : [],
|
|
1483
|
+
),
|
|
1484
|
+
];
|
|
1485
|
+
};
|
|
1486
|
+
return {
|
|
1487
|
+
filled: [
|
|
1488
|
+
...new Set([
|
|
1489
|
+
...filled.flatMap(({ carried, types }) => {
|
|
1490
|
+
const carries = new Set(members(carried));
|
|
1491
|
+
return types.flatMap((type) => members(type).filter((id) => carries.has(id)));
|
|
1492
|
+
}),
|
|
1493
|
+
...read.made.flatMap(members),
|
|
1494
|
+
]),
|
|
1495
|
+
],
|
|
1496
|
+
parsed: [...new Set(read.parsed.flatMap(members))],
|
|
1497
|
+
copied: [
|
|
1498
|
+
...new Set(
|
|
1499
|
+
copies.flatMap(({ into, names }) =>
|
|
1500
|
+
must(queries.constituents(into)).flatMap((part) =>
|
|
1501
|
+
must(queries.propertiesOf(part)).flatMap((property) =>
|
|
1502
|
+
(property.flags & SymbolFlags.Property) !== 0 && names.has(property.name)
|
|
1503
|
+
? chains.declarationsOf(property)
|
|
1504
|
+
: [],
|
|
1505
|
+
),
|
|
1506
|
+
),
|
|
1507
|
+
),
|
|
1508
|
+
),
|
|
1509
|
+
],
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/** Where a value is read as a type: the assertions around it, and the positions it is written in. */
|
|
1514
|
+
interface ReadAs {
|
|
1515
|
+
/** The type nodes of the `as` and angle-bracket assertions around the value. */
|
|
1516
|
+
readonly asserted: readonly Node[];
|
|
1517
|
+
/** The outermost expressions whose contextual types the value is read as. */
|
|
1518
|
+
readonly tops: readonly Expression[];
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/**
|
|
1522
|
+
* Where one value is read as a type: each `as` and angle-bracket assertion around it,
|
|
1523
|
+
* through parentheses, `await` and `!`, and the outermost expression unless a statement
|
|
1524
|
+
* of its own or the object or callee of a further access holds it. A `satisfies` check
|
|
1525
|
+
* is stepped through and types nothing, its expression's type being its operand's. A
|
|
1526
|
+
* callback's result is read as well where the call taking the callback is, which may
|
|
1527
|
+
* hand it on inside its result, as `map` does.
|
|
1528
|
+
*/
|
|
1529
|
+
function readAs(value: Expression, depth = 0): ReadAs {
|
|
1530
|
+
const asserted: Node[] = [];
|
|
1531
|
+
let at: Expression = value;
|
|
1532
|
+
for (;;) {
|
|
1533
|
+
const parent = at.parent;
|
|
1534
|
+
if (isAsExpression(parent) || isTypeAssertion(parent)) {
|
|
1535
|
+
asserted.push(parent.type);
|
|
1536
|
+
} else if (
|
|
1537
|
+
!isParenthesizedExpression(parent) &&
|
|
1538
|
+
!isAwaitExpression(parent) &&
|
|
1539
|
+
!isNonNullExpression(parent) &&
|
|
1540
|
+
!isSatisfiesExpression(parent)
|
|
1541
|
+
) {
|
|
1542
|
+
break;
|
|
1543
|
+
}
|
|
1544
|
+
at = parent;
|
|
1545
|
+
}
|
|
1546
|
+
const parent = at.parent;
|
|
1547
|
+
const unused =
|
|
1548
|
+
isExpressionStatement(parent) ||
|
|
1549
|
+
((isPropertyAccessExpression(parent) ||
|
|
1550
|
+
isElementAccessExpression(parent) ||
|
|
1551
|
+
isCallExpression(parent)) &&
|
|
1552
|
+
parent.expression === at);
|
|
1553
|
+
const call = depth < MENTION_DEPTH ? callTaking(at) : undefined;
|
|
1554
|
+
const outer = call === undefined ? undefined : readAs(call, depth + 1);
|
|
1555
|
+
return {
|
|
1556
|
+
asserted: [...asserted, ...(outer?.asserted ?? [])],
|
|
1557
|
+
tops: [...(unused ? [] : [at]), ...(outer?.tops ?? [])],
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/** The call one value is returned to as a callback's result, an argument of that call. */
|
|
1562
|
+
function callTaking(value: Expression): CallExpression | undefined {
|
|
1563
|
+
const parent = value.parent;
|
|
1564
|
+
let callback: Node | undefined;
|
|
1565
|
+
if (isArrowFunction(parent) && parent.body === value) {
|
|
1566
|
+
callback = parent;
|
|
1567
|
+
} else if (isReturnStatement(parent)) {
|
|
1568
|
+
let at: Node = parent.parent;
|
|
1569
|
+
while (!isFunctionLikeDeclaration(at) && !isSourceFile(at)) {
|
|
1570
|
+
at = at.parent;
|
|
1571
|
+
}
|
|
1572
|
+
callback = isArrowFunction(at) || isFunctionExpression(at) ? at : undefined;
|
|
1573
|
+
}
|
|
1574
|
+
let at = callback?.parent;
|
|
1575
|
+
while (at !== undefined && isParenthesizedExpression(at)) {
|
|
1576
|
+
at = at.parent;
|
|
1577
|
+
}
|
|
1578
|
+
return at !== undefined && isCallExpression(at) && at.expression !== callback ? at : undefined;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/** The global the standard library declares one name of `JSON` or `Object` as. */
|
|
1582
|
+
function standardGlobal(callee: Expression, global: string, name: string): Node | undefined {
|
|
1583
|
+
return isPropertyAccessExpression(callee) &&
|
|
1584
|
+
isIdentifier(callee.expression) &&
|
|
1585
|
+
callee.expression.text === global &&
|
|
1586
|
+
callee.name.text === name
|
|
1587
|
+
? callee.expression
|
|
1588
|
+
: undefined;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* The types each value of `values` typed `any` or `unknown` is read as ({@link readAs}):
|
|
1593
|
+
* `parsed` for a `JSON.parse` call of the standard library, `made` for any other call or
|
|
1594
|
+
* property, whose maker the analysis cannot see. `JSON` must be the standard library's
|
|
1595
|
+
* global.
|
|
1596
|
+
*/
|
|
1597
|
+
function readTypes<Brand>(
|
|
1598
|
+
project: ProjectView<Brand>,
|
|
1599
|
+
values: readonly Expression[],
|
|
1600
|
+
): { readonly parsed: readonly Type[]; readonly made: readonly Type[] } {
|
|
1601
|
+
if (values.length === 0) {
|
|
1602
|
+
return { parsed: [], made: [] };
|
|
1603
|
+
}
|
|
1604
|
+
const own = project.ownPaths();
|
|
1605
|
+
const parses = values.map((value) =>
|
|
1606
|
+
isCallExpression(value) ? standardGlobal(value.expression, "JSON", "parse") : undefined,
|
|
1607
|
+
);
|
|
1608
|
+
const globals = parses.flatMap((global) => (global === undefined ? [] : [global]));
|
|
1609
|
+
const standard = new Set(
|
|
1610
|
+
project
|
|
1611
|
+
.symbolsAt(globals.map((global) => project.handle(global)))
|
|
1612
|
+
.map(must)
|
|
1613
|
+
.flatMap((symbol, at) =>
|
|
1614
|
+
symbol?.declarations.every((handle) => !own.has(handle.path)) === true ? [globals[at]] : [],
|
|
1615
|
+
),
|
|
1616
|
+
);
|
|
1617
|
+
// An `await` around the value reads the value it resolves to.
|
|
1618
|
+
const valueTypes = typesAt(
|
|
1619
|
+
project,
|
|
1620
|
+
values.map((value) => (isAwaitExpression(value.parent) ? value.parent : value)),
|
|
1621
|
+
);
|
|
1622
|
+
const sites = values.flatMap((value, at) => {
|
|
1623
|
+
const parse = parses[at];
|
|
1624
|
+
const parsed = parse !== undefined && standard.has(parse);
|
|
1625
|
+
const type = valueTypes[at];
|
|
1626
|
+
const untyped = type !== undefined && (type.flags & ERASED) !== 0;
|
|
1627
|
+
return parsed || untyped ? [{ ...readAs(value), parsed }] : [];
|
|
1628
|
+
});
|
|
1629
|
+
// Each type node and each outermost expression, with whether a `JSON.parse` call is read as it.
|
|
1630
|
+
const typed = sites.flatMap((site) =>
|
|
1631
|
+
site.asserted.map((node) => ({ node, parsed: site.parsed })),
|
|
1632
|
+
);
|
|
1633
|
+
const placed = sites.flatMap((site) => site.tops.map((node) => ({ node, parsed: site.parsed })));
|
|
1634
|
+
const read = [
|
|
1635
|
+
...typesAt(
|
|
1636
|
+
project,
|
|
1637
|
+
typed.map((one) => one.node),
|
|
1638
|
+
).map((type, at) => ({ type, parsed: typed[at]?.parsed })),
|
|
1639
|
+
...project.queries
|
|
1640
|
+
.contextualTypes(placed.map((one) => one.node))
|
|
1641
|
+
.map(must)
|
|
1642
|
+
.map((type, at) => ({ type, parsed: placed[at]?.parsed })),
|
|
1643
|
+
];
|
|
1644
|
+
const typesWhere = (parsed: boolean): Type[] =>
|
|
1645
|
+
read.flatMap((one) => (one.type !== undefined && one.parsed === parsed ? [one.type] : []));
|
|
1646
|
+
return { parsed: typesWhere(true), made: typesWhere(false) };
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
/**
|
|
1650
|
+
* The copies the spreads and calls of `files` make: for each spread in an object literal,
|
|
1651
|
+
* the type the literal is read as, and for each `Object.assign` call of the standard
|
|
1652
|
+
* library, its first argument's type, with the member names of the copied values' types.
|
|
1653
|
+
*/
|
|
1654
|
+
function copiedNames<Brand>(
|
|
1655
|
+
project: ProjectView<Brand>,
|
|
1656
|
+
spreads: readonly SpreadAssignment[],
|
|
1657
|
+
calls: readonly CallExpression[],
|
|
1658
|
+
): readonly { readonly into: Type; readonly names: ReadonlySet<string> }[] {
|
|
1659
|
+
const own = project.ownPaths();
|
|
1660
|
+
const assigns = calls.flatMap((call) => {
|
|
1661
|
+
const global = standardGlobal(call.expression, "Object", "assign");
|
|
1662
|
+
const [target, ...sources] = call.arguments;
|
|
1663
|
+
return global === undefined || target === undefined || sources.length === 0
|
|
1664
|
+
? []
|
|
1665
|
+
: [{ global, target, sources: sources.filter((source) => !isSpreadElement(source)) }];
|
|
1666
|
+
});
|
|
1667
|
+
const standard = project
|
|
1668
|
+
.symbolsAt(assigns.map((one) => project.handle(one.global)))
|
|
1669
|
+
.map(must)
|
|
1670
|
+
.map((symbol) => symbol?.declarations.every((handle) => !own.has(handle.path)) === true);
|
|
1671
|
+
const assigned = assigns.filter((_one, at) => standard[at] === true);
|
|
1672
|
+
const literals = spreads.flatMap((spread) =>
|
|
1673
|
+
isObjectLiteralExpression(spread.parent)
|
|
1674
|
+
? [{ literal: spread.parent, source: spread.expression }]
|
|
1675
|
+
: [],
|
|
1676
|
+
);
|
|
1677
|
+
const contexts = project.queries.contextualTypes(literals.map((one) => one.literal)).map(must);
|
|
1678
|
+
const targets = typesAt(
|
|
1679
|
+
project,
|
|
1680
|
+
assigned.map((one) => one.target),
|
|
1681
|
+
);
|
|
1682
|
+
const spreadTypes = typesAt(
|
|
1683
|
+
project,
|
|
1684
|
+
literals.map((one) => one.source),
|
|
1685
|
+
);
|
|
1686
|
+
const sourceTypes = assigned.map((one) => typesAt(project, one.sources));
|
|
1687
|
+
const namesOf = (types: readonly (Type | undefined)[]): ReadonlySet<string> =>
|
|
1688
|
+
new Set(
|
|
1689
|
+
types.flatMap((type) =>
|
|
1690
|
+
type === undefined
|
|
1691
|
+
? []
|
|
1692
|
+
: must(project.queries.propertiesOf(type)).map((property) => property.name),
|
|
1693
|
+
),
|
|
1694
|
+
);
|
|
1695
|
+
const found: { readonly into: Type; readonly names: ReadonlySet<string> }[] = [];
|
|
1696
|
+
const add = (into: Type | undefined, names: ReadonlySet<string>): void => {
|
|
1697
|
+
if (into !== undefined && names.size > 0) {
|
|
1698
|
+
found.push({ into, names });
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
contexts.forEach((into, at) => {
|
|
1702
|
+
add(into, namesOf([spreadTypes[at]]));
|
|
1703
|
+
});
|
|
1704
|
+
targets.forEach((into, at) => {
|
|
1705
|
+
add(into, namesOf(sourceTypes[at] ?? []));
|
|
1706
|
+
});
|
|
1707
|
+
return found;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/** The value a promise of the standard library resolves to, through nested promises. */
|
|
1711
|
+
function awaited<Brand>(project: ProjectView<Brand>, type: Type): Type {
|
|
1712
|
+
const { queries } = project;
|
|
1713
|
+
let at = type;
|
|
1714
|
+
for (let depth = 0; depth < MENTION_DEPTH; depth += 1) {
|
|
1715
|
+
const symbol = must(queries.symbolOfType(at));
|
|
1716
|
+
const [inner] = must(queries.typeArguments(at));
|
|
1717
|
+
const standard =
|
|
1718
|
+
symbol !== undefined &&
|
|
1719
|
+
PROMISES.has(symbol.name) &&
|
|
1720
|
+
symbol.declarations.every(
|
|
1721
|
+
(handle) => project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true,
|
|
1722
|
+
);
|
|
1723
|
+
if (!standard || inner === undefined) {
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
at = inner;
|
|
1727
|
+
}
|
|
1728
|
+
return at;
|
|
1729
|
+
}
|