@cplieger/deadset-ts 8.0.0-dev.2 → 8.0.0-dev.20
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 +2 -7
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +269 -432
- 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/calls.js +279 -43
- 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 +1 -1
- 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 +1369 -0
- package/dist/src/contract/report.generated.js +866 -0
- package/dist/src/dependencies.js +164 -121
- 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/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +1 -63
- package/dist/src/exempt.js +21 -66
- 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/framework-lifecycle.js +46 -19
- 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/generated-file.js +174 -28
- 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.js +2 -2
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/injection-container.js +2 -2
- package/dist/src/interface-satisfaction.js +1 -1
- 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 +47 -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/serialization-contract.js +601 -22
- 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/template-field.js +9 -19
- 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 +7 -5
- 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 +2 -9
- package/src/amd.ts +130 -0
- package/src/analysis.ts +424 -650
- package/src/augmentations.ts +93 -4
- package/src/automatic-types.ts +12 -30
- package/src/bindings.ts +460 -0
- package/src/calls.ts +338 -46
- 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 +1 -1
- 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 +1452 -0
- package/src/contract/report.generated.ts +1241 -0
- package/src/dependencies.ts +222 -127
- 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/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +1 -75
- package/src/exempt.ts +70 -98
- 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/framework-lifecycle.ts +47 -19
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +96 -0
- package/src/fs/tree.ts +110 -0
- package/src/generated-file.ts +242 -35
- 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.ts +2 -2
- package/src/indexed-access.ts +570 -527
- package/src/injection-container.ts +2 -2
- package/src/interface-satisfaction.ts +2 -2
- 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 +92 -0
- 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} +27 -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/serialization-contract.ts +769 -23
- 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/template-field.ts +15 -25
- 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 +7 -5
- 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/conventions.js +0 -937
- package/dist/src/discover.js +0 -468
- package/dist/src/entry-points.js +0 -529
- 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/setup-failure.js +0 -23
- package/dist/src/sweep.js +0 -224
- package/dist/src/test-joins.js +0 -101
- package/dist/src/version.js +0 -7
- package/dist/src/workflow-steps.js +0 -185
- package/src/base-url.ts +0 -24
- package/src/discover.ts +0 -653
- package/src/entry-points.ts +0 -687
- 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/setup-failure.ts +0 -39
- package/src/sweep.ts +0 -347
- package/src/test-joins.ts +0 -120
- package/src/version.ts +0 -7
- package/src/workflow-steps.ts +0 -216
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words layer 3 of the command reader reads: each word of a command string as the vendored
|
|
3
|
+
* shell parser reads it, and each token of a value of a command file that is no command, each
|
|
4
|
+
* with whether it may write the file a command runs. `text/named-files.ts` reads the files
|
|
5
|
+
* they name. Comments and keys are never words.
|
|
6
|
+
*/
|
|
7
|
+
import { forEachChild, parse, } from "../vendor/unbash/parser.js";
|
|
8
|
+
import { expressionsAsParts, heldTextsOf, parseLine, PART, wordOf } from "./shell.js";
|
|
9
|
+
import { fileOperands, outputOf } from "./tools.js";
|
|
10
|
+
/** A `uses:` value that references a repository's action, never a local path (https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstepsuses). */
|
|
11
|
+
const ACTION_REFERENCE = /^[\w-][\w.-]*\/[\w.-]+(?:\/[\w.-]+)*@[\w./-]+$/u;
|
|
12
|
+
/** The word of a command whose runtime runs a file no word of it writes, as another program hands it the path. */
|
|
13
|
+
const UNWRITTEN = { text: PART, runs: true };
|
|
14
|
+
const NO_STDIN = { content: [], piped: [], unknown: false };
|
|
15
|
+
const UNKNOWN_STDIN = { ...NO_STDIN, unknown: true };
|
|
16
|
+
/** Whether `redirect` reads into descriptor 0, the default of `<`, `<>`, `<&`, `<<` and `<<<`. */
|
|
17
|
+
function readsInput(redirect) {
|
|
18
|
+
const { descriptor } = redirect;
|
|
19
|
+
return ((redirect.type !== "Redirect" || ["<", "<>", "<&"].includes(redirect.operator)) &&
|
|
20
|
+
(descriptor === undefined || (descriptor.type === "FileDescriptor" && descriptor.value === 0)));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The standard input `redirects` give a command, the last that reads descriptor 0 deciding as
|
|
24
|
+
* the shell applies them in order, or `inherited` where none does. A `<&` duplicates a
|
|
25
|
+
* descriptor the line does not show.
|
|
26
|
+
*/
|
|
27
|
+
function stdinOf(redirects, inherited) {
|
|
28
|
+
const last = redirects.findLast(readsInput);
|
|
29
|
+
if (last === undefined) {
|
|
30
|
+
return inherited;
|
|
31
|
+
}
|
|
32
|
+
switch (last.type) {
|
|
33
|
+
case "HereDoc":
|
|
34
|
+
return { ...NO_STDIN, content: [last.body.text] };
|
|
35
|
+
case "HereString":
|
|
36
|
+
return { ...NO_STDIN, content: last.target === undefined ? [] : heldTextsOf(last.target) };
|
|
37
|
+
default:
|
|
38
|
+
return last.operator === "<&" || last.target === undefined
|
|
39
|
+
? UNKNOWN_STDIN
|
|
40
|
+
: { ...NO_STDIN, file: wordOf(last.target) };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** The words of a simple command, its program word first. */
|
|
44
|
+
function wordsOf(node) {
|
|
45
|
+
return node.type === "Command" && node.name !== undefined
|
|
46
|
+
? [node.name, ...node.suffix.filter((part) => part.type === "Word")]
|
|
47
|
+
: [];
|
|
48
|
+
}
|
|
49
|
+
/** What the pipeline member `member`, whose own standard input `inputs` holds, writes to the next, as {@link outputOf} reads it. */
|
|
50
|
+
function pipedFrom(member, inputs) {
|
|
51
|
+
const words = wordsOf(member);
|
|
52
|
+
const output = words.length === 0 ? undefined : outputOf(words.map(wordOf));
|
|
53
|
+
if (output === undefined) {
|
|
54
|
+
return UNKNOWN_STDIN;
|
|
55
|
+
}
|
|
56
|
+
const own = output.input ? (inputs.get(member) ?? NO_STDIN) : NO_STDIN;
|
|
57
|
+
const textsAt = (at) => {
|
|
58
|
+
const word = words[at];
|
|
59
|
+
return word === undefined ? [] : heldTextsOf(word);
|
|
60
|
+
};
|
|
61
|
+
const lines = [
|
|
62
|
+
...output.at.flatMap(textsAt),
|
|
63
|
+
...output.joined.map(([from, to]) => words
|
|
64
|
+
.slice(from, to)
|
|
65
|
+
.flatMap((_, at) => textsAt(from + at))
|
|
66
|
+
.join(" ")),
|
|
67
|
+
...output.texts,
|
|
68
|
+
];
|
|
69
|
+
return { ...own, piped: [...own.piped, ...lines] };
|
|
70
|
+
}
|
|
71
|
+
/** How many times a quoted text, a here-document's body or a `-c` value is read as a command line in turn. */
|
|
72
|
+
const DEPTH = 4;
|
|
73
|
+
/** A `\\` a path may hold: none that ends a line, nor one before a backquote, which it escapes. */
|
|
74
|
+
const BACKSLASH_PATH = /\\(?![\r\n`])/gu;
|
|
75
|
+
/** Text a shell may read as more than one word: a blank or an operator. */
|
|
76
|
+
const LINE_SYNTAX = /[\s;&|<>()]/u;
|
|
77
|
+
/** A shell parameter's name, or a special parameter, after its `$`. */
|
|
78
|
+
const PARAMETER = /[A-Za-z_]\w*|[0-9@*#?!$-]/uy;
|
|
79
|
+
/** The place after the brackets that open at `at`, nested ones included, or the text's end. */
|
|
80
|
+
function closing(text, at, open, close) {
|
|
81
|
+
let depth = 0;
|
|
82
|
+
for (let next = at; next < text.length; next += 1) {
|
|
83
|
+
depth += text.charAt(next) === open ? 1 : text.charAt(next) === close ? -1 : 0;
|
|
84
|
+
if (depth === 0) {
|
|
85
|
+
return next + 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return text.length;
|
|
89
|
+
}
|
|
90
|
+
/** The text inside each outermost command substitution `text`, a line that does not parse, writes. */
|
|
91
|
+
function substitutions(text) {
|
|
92
|
+
const found = [];
|
|
93
|
+
for (let at = text.indexOf("$("); at >= 0; at = text.indexOf("$(", at + 1)) {
|
|
94
|
+
const end = closing(text, at + 1, "(", ")");
|
|
95
|
+
found.push(text.slice(at + 2, text.charAt(end - 1) === ")" ? end - 1 : end));
|
|
96
|
+
at = end - 1;
|
|
97
|
+
}
|
|
98
|
+
return found;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* `text`, a line that does not parse, with each expansion it writes as a part: a parameter
|
|
102
|
+
* expansion, a command substitution or an arithmetic expansion to its matching bracket, and
|
|
103
|
+
* a backquoted command to its closing backquote, each to the text's end where none closes it.
|
|
104
|
+
*/
|
|
105
|
+
function expansionsAsParts(text) {
|
|
106
|
+
let read = "";
|
|
107
|
+
for (let at = 0; at < text.length; at += 1) {
|
|
108
|
+
const char = text.charAt(at);
|
|
109
|
+
const next = text.charAt(at + 1);
|
|
110
|
+
let end = at;
|
|
111
|
+
if (char === "`") {
|
|
112
|
+
const close = text.indexOf("`", at + 1);
|
|
113
|
+
end = close < 0 ? text.length : close + 1;
|
|
114
|
+
}
|
|
115
|
+
else if (char === "$" && (next === "{" || next === "(")) {
|
|
116
|
+
end = closing(text, at + 1, next, next === "{" ? "}" : ")");
|
|
117
|
+
}
|
|
118
|
+
else if (char === "$") {
|
|
119
|
+
PARAMETER.lastIndex = at + 1;
|
|
120
|
+
end = at + 1 + (PARAMETER.exec(text)?.[0].length ?? -1);
|
|
121
|
+
}
|
|
122
|
+
if (end > at) {
|
|
123
|
+
read += PART;
|
|
124
|
+
at = end - 1;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
read += char;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return read;
|
|
131
|
+
}
|
|
132
|
+
/** A character a shell reads as syntax, a glob or a quote. */
|
|
133
|
+
const METACHARACTER = /[|&;<>()"'`*?[\]{}~\\]/gu;
|
|
134
|
+
/** An operator, which ends a shell word wherever it stands outside quotes. */
|
|
135
|
+
const OPERATOR = /[;&|<>()]+/u;
|
|
136
|
+
/**
|
|
137
|
+
* The texts one token stands for as one shell word, an assignment's value for an assignment,
|
|
138
|
+
* or `undefined` where it is not one.
|
|
139
|
+
*/
|
|
140
|
+
function tokenTexts(token) {
|
|
141
|
+
const script = parse(token);
|
|
142
|
+
const [statement, ...more] = script.commands;
|
|
143
|
+
const command = statement?.command;
|
|
144
|
+
if ((script.errors ?? []).length > 0 || more.length > 0 || command?.type !== "Command") {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const [assignment, ...others] = command.prefix;
|
|
148
|
+
if (command.name === undefined) {
|
|
149
|
+
return others.length === 0 &&
|
|
150
|
+
command.suffix.length === 0 &&
|
|
151
|
+
assignment?.type === "Assignment" &&
|
|
152
|
+
assignment.value.type === "Word"
|
|
153
|
+
? heldTextsOf(assignment.value)
|
|
154
|
+
: undefined;
|
|
155
|
+
}
|
|
156
|
+
return command.prefix.length === 0 && command.suffix.length === 0
|
|
157
|
+
? heldTextsOf(command.name)
|
|
158
|
+
: undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The texts one blank-separated token of a line that does not parse stands for: each
|
|
162
|
+
* expansion in it a part, then each piece of it between operators as the one word it is,
|
|
163
|
+
* else with its quotes removed, else with each other character a shell reads as syntax a part.
|
|
164
|
+
*/
|
|
165
|
+
function tokenPieces(token) {
|
|
166
|
+
return expansionsAsParts(token)
|
|
167
|
+
.split(OPERATOR)
|
|
168
|
+
.filter((piece) => piece !== "")
|
|
169
|
+
.flatMap((piece) => tokenTexts(piece) ??
|
|
170
|
+
tokenTexts(piece.replaceAll(/["']/gu, "")) ?? [piece.replaceAll(METACHARACTER, PART)]);
|
|
171
|
+
}
|
|
172
|
+
/** A token that ends a simple command, as a `;`, `&`, `|` or parenthesis it ends with does, and one that starts the next. */
|
|
173
|
+
const ENDS_COMMAND = /[;&|()]$/u;
|
|
174
|
+
const STARTS_COMMAND = /^[;&|()]/u;
|
|
175
|
+
/** The reserved words that may stand before a simple command's program word in a line. */
|
|
176
|
+
const RESERVED = new Set(["if", "then", "else", "elif", "fi", "do", "done", "while", "until"].concat([
|
|
177
|
+
"case",
|
|
178
|
+
"esac",
|
|
179
|
+
"{",
|
|
180
|
+
"}",
|
|
181
|
+
"!",
|
|
182
|
+
"time",
|
|
183
|
+
]));
|
|
184
|
+
/** A token that redirects: its descriptor, its operator, and its target where the token writes it. */
|
|
185
|
+
const REDIRECT = /^(\d*)(&>>|&>|<<<|<<-|<<|<>|<&|>&|>>|>\||<|>)(.*)$/su;
|
|
186
|
+
/**
|
|
187
|
+
* The standard input one redirect token of a line that does not parse gives: a file for `<`
|
|
188
|
+
* or `<>`, written as `target` writes it, and for a here-document, a here-string or a `<&`,
|
|
189
|
+
* text this reading does not take apart.
|
|
190
|
+
*/
|
|
191
|
+
function tokenStdin(operator, target) {
|
|
192
|
+
if (operator !== "<" && operator !== "<>") {
|
|
193
|
+
return UNKNOWN_STDIN;
|
|
194
|
+
}
|
|
195
|
+
const text = target.replace(/[;&|)]+$/u, "").replaceAll(/["']/gu, "");
|
|
196
|
+
return {
|
|
197
|
+
...NO_STDIN,
|
|
198
|
+
file: { value: text.includes(PART) ? undefined : text, text, written: text },
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* {@link fileOperands} of `words`, the tokens `tokens` of a line read without its structure,
|
|
203
|
+
* one simple command from each operator token to the next, past the reserved words it starts
|
|
204
|
+
* with, its redirect tokens read as {@link tokenStdin} reads them, each first word `program`'s.
|
|
205
|
+
* A command after a `|` reads what no row reads, and any other `stdin`, the line's own; a
|
|
206
|
+
* reader of what no word writes there runs a file no word writes.
|
|
207
|
+
*/
|
|
208
|
+
function commandsOf(tokens, words, program, stdin) {
|
|
209
|
+
const places = new Set();
|
|
210
|
+
const lines = new Map();
|
|
211
|
+
const replaced = new Map();
|
|
212
|
+
const joined = [];
|
|
213
|
+
const texts = [];
|
|
214
|
+
let unwritten = false;
|
|
215
|
+
let input = false;
|
|
216
|
+
let stdinRead = false;
|
|
217
|
+
for (let from = 0; from < words.length;) {
|
|
218
|
+
let to = from;
|
|
219
|
+
while (to < words.length && !(to > from && STARTS_COMMAND.test(tokens[to] ?? ""))) {
|
|
220
|
+
to += 1;
|
|
221
|
+
if (ENDS_COMMAND.test(tokens[to - 1] ?? "")) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const piped = /(?<!\|)\|$/u.test(tokens[from - 1] ?? "") || /^\|(?!\|)/u.test(tokens[from] ?? "");
|
|
226
|
+
let fed = piped ? UNKNOWN_STDIN : stdin;
|
|
227
|
+
const kept = [];
|
|
228
|
+
let start = from;
|
|
229
|
+
while (start < to && RESERVED.has(tokens[start] ?? "")) {
|
|
230
|
+
start += 1;
|
|
231
|
+
}
|
|
232
|
+
for (let at = start; at < to; at += 1) {
|
|
233
|
+
const [, descriptor, operator = "", rest = ""] = REDIRECT.exec(tokens[at] ?? "") ?? [];
|
|
234
|
+
if (descriptor === undefined) {
|
|
235
|
+
kept.push(at);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const target = rest === "" && at + 1 < to ? (tokens[(at += 1)] ?? "") : rest;
|
|
239
|
+
if ((descriptor === "" || descriptor === "0") && operator.startsWith("<")) {
|
|
240
|
+
fed = tokenStdin(operator, target);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const one = fileOperands(kept.map((at) => words[at] ?? { value: undefined, text: "" }), program, fed.file);
|
|
244
|
+
const place = (at) => kept[at] ?? to;
|
|
245
|
+
for (const at of one.places) {
|
|
246
|
+
places.add(place(at));
|
|
247
|
+
}
|
|
248
|
+
for (const [at, prefix] of one.lines) {
|
|
249
|
+
lines.set(place(at), prefix);
|
|
250
|
+
}
|
|
251
|
+
for (const [at, pattern] of one.replaced) {
|
|
252
|
+
replaced.set(place(at), pattern);
|
|
253
|
+
}
|
|
254
|
+
joined.push(...one.joined.map(([start, end]) => [place(start), place(end)]));
|
|
255
|
+
texts.push(...one.texts);
|
|
256
|
+
unwritten ||=
|
|
257
|
+
one.unwritten ||
|
|
258
|
+
((one.input || one.stdin) && (fed.unknown || fed.content.length > 0 || fed.piped.length > 0));
|
|
259
|
+
input ||= one.input;
|
|
260
|
+
stdinRead ||= one.stdin;
|
|
261
|
+
from = to;
|
|
262
|
+
}
|
|
263
|
+
return { places, unwritten, lines, input, stdin: stdinRead, replaced, joined, texts };
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The words of one line read without its structure: the whole line, each blank-separated
|
|
267
|
+
* token as {@link tokenPieces} reads it, as written, so the words inside an expansion count,
|
|
268
|
+
* and with each expansion a part, so a blank inside one splits nothing, and the text of each
|
|
269
|
+
* command substitution as a command line. A token may write the file a command runs at the
|
|
270
|
+
* places {@link fileOperands} gives, `program` saying whether the first token is a program word.
|
|
271
|
+
*/
|
|
272
|
+
function tokenWords(line, program, depth, into, stdin = NO_STDIN) {
|
|
273
|
+
const read = expressionsAsParts(line);
|
|
274
|
+
into.push({ text: expansionsAsParts(read), runs: false });
|
|
275
|
+
// Past the depth a command line a program runs is read no more.
|
|
276
|
+
const asLine = (text, quoted) => {
|
|
277
|
+
if (depth > 0) {
|
|
278
|
+
lineWords(text, depth - 1, quoted, into);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
into.push(UNWRITTEN);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
for (const inner of substitutions(read)) {
|
|
285
|
+
asLine(inner, true);
|
|
286
|
+
}
|
|
287
|
+
const blanks = (text) => text.split(/\s+/u).filter((token) => token !== "");
|
|
288
|
+
for (const token of blanks(read)) {
|
|
289
|
+
for (const text of tokenPieces(token)) {
|
|
290
|
+
into.push({ text, runs: false });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const tokens = blanks(expansionsAsParts(read));
|
|
294
|
+
const texts = tokens.map((token) => tokenTexts(token) ?? tokenPieces(token));
|
|
295
|
+
const operands = commandsOf(tokens, texts.map(([only, ...more], at) => ({
|
|
296
|
+
value: more.length === 0 && only !== undefined && !only.includes(PART) ? only : undefined,
|
|
297
|
+
text: tokens[at] ?? "",
|
|
298
|
+
...(more.length === 0 && only !== undefined ? { written: only } : {}),
|
|
299
|
+
})), program, stdin);
|
|
300
|
+
const replacedAt = (at) => (texts[at] ?? []).map((text) => {
|
|
301
|
+
const replaced = operands.replaced.get(at);
|
|
302
|
+
return replaced === undefined ? text : text.replace(replaced, PART);
|
|
303
|
+
});
|
|
304
|
+
texts.forEach((_, at) => {
|
|
305
|
+
for (const read of replacedAt(at)) {
|
|
306
|
+
const prefix = operands.lines.get(at);
|
|
307
|
+
into.push({ text: read, runs: operands.places.has(at) });
|
|
308
|
+
if (prefix !== undefined) {
|
|
309
|
+
asLine(read.slice(read.startsWith(prefix) ? prefix.length : 0), false);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
for (const [from, to] of operands.joined) {
|
|
314
|
+
asLine(texts
|
|
315
|
+
.slice(from, to)
|
|
316
|
+
.flatMap((_, at) => replacedAt(from + at))
|
|
317
|
+
.join(" "), false);
|
|
318
|
+
}
|
|
319
|
+
for (const text of operands.texts) {
|
|
320
|
+
asLine(text, false);
|
|
321
|
+
}
|
|
322
|
+
if (operands.unwritten) {
|
|
323
|
+
into.push(UNWRITTEN);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* The standard input of each simple command below `node`: its own, as {@link stdinOf} reads
|
|
328
|
+
* it, that of the nearest compound command around it, or `inherited`, what the command that
|
|
329
|
+
* hands the line on reads; each later member of a pipeline reads what the one before writes.
|
|
330
|
+
*/
|
|
331
|
+
function inputsOf(node, inherited, into) {
|
|
332
|
+
const own = node.type === "Command" || node.type === "Redirected"
|
|
333
|
+
? stdinOf(node.redirects, inherited)
|
|
334
|
+
: inherited;
|
|
335
|
+
if (node.type === "Command") {
|
|
336
|
+
into.set(node, own);
|
|
337
|
+
}
|
|
338
|
+
if (node.type === "Pipeline") {
|
|
339
|
+
let carried = own;
|
|
340
|
+
for (const member of node.commands) {
|
|
341
|
+
inputsOf(member, carried, into);
|
|
342
|
+
carried = pipedFrom(member, into);
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
forEachChild(node, (child) => {
|
|
347
|
+
inputsOf(child, own, into);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/** The simple commands of `script` the command reader's first layer reads: each member of each pipeline of each and-or list. */
|
|
351
|
+
function topCommands(script) {
|
|
352
|
+
return new Set(script.commands.flatMap(({ command }) => (command.type === "AndOr" ? command.commands : [command]).flatMap((one) => one.type === "Pipeline" ? one.commands : [one])));
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Each word one parsed command line writes, anywhere in its structure, into `into`. A quoted
|
|
356
|
+
* text or a here-document's body may be a command line a program it is handed to runs, so it
|
|
357
|
+
* is read as one in turn. Where {@link fileOperands} says a command hands a word, or words
|
|
358
|
+
* joined by blanks, to a shell, the shell reads them whole with the command's standard input,
|
|
359
|
+
* and where it reads its standard input, that input as {@link Stdin} reads it: a here-document
|
|
360
|
+
* is a command line only for a shell, and code for a runtime. `stdin` is what the line's own
|
|
361
|
+
* standard input carries, and `tests` whether the line is a manifest's script.
|
|
362
|
+
*/
|
|
363
|
+
function scriptWords(script, depth, quoted, into, stdin, tests) {
|
|
364
|
+
const runs = new Set();
|
|
365
|
+
const lines = new Map();
|
|
366
|
+
const replaced = new Map();
|
|
367
|
+
const handed = new Map();
|
|
368
|
+
const inputs = new Map();
|
|
369
|
+
inputsOf(script, stdin, inputs);
|
|
370
|
+
const top = tests ? topCommands(script) : new Set();
|
|
371
|
+
const asLine = (text, how, fed = NO_STDIN) => {
|
|
372
|
+
if (depth > 0 && (how === "line" || (how === "text" && LINE_SYNTAX.test(text)))) {
|
|
373
|
+
lineWords(text, depth - 1, how === "text", into, fed);
|
|
374
|
+
}
|
|
375
|
+
else if (how === "line") {
|
|
376
|
+
// Past the depth a command line a program runs is read no more.
|
|
377
|
+
into.push(UNWRITTEN);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
const visit = (node, how) => {
|
|
381
|
+
let inner = how;
|
|
382
|
+
switch (node.type) {
|
|
383
|
+
case "Command": {
|
|
384
|
+
const fed = inputs.get(node) ?? NO_STDIN;
|
|
385
|
+
const words = wordsOf(node);
|
|
386
|
+
const operands = words.length === 0
|
|
387
|
+
? undefined
|
|
388
|
+
: fileOperands(words.map(wordOf), !quoted, fed.file, top.has(node));
|
|
389
|
+
words.forEach((word, at) => {
|
|
390
|
+
const pattern = operands?.replaced.get(at);
|
|
391
|
+
if (operands?.places.has(at) === true) {
|
|
392
|
+
runs.add(word);
|
|
393
|
+
}
|
|
394
|
+
const prefix = operands?.lines.get(at);
|
|
395
|
+
if (prefix !== undefined) {
|
|
396
|
+
lines.set(word, prefix);
|
|
397
|
+
handed.set(word, fed);
|
|
398
|
+
}
|
|
399
|
+
if (pattern !== undefined) {
|
|
400
|
+
replaced.set(word, pattern);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
const reads = operands?.input === true || operands?.stdin === true;
|
|
404
|
+
if (operands?.unwritten === true || (reads && fed.unknown)) {
|
|
405
|
+
into.push(UNWRITTEN);
|
|
406
|
+
}
|
|
407
|
+
for (const text of reads ? fed.piped : []) {
|
|
408
|
+
asLine(text, "line");
|
|
409
|
+
}
|
|
410
|
+
for (const text of operands?.input === true ? fed.content : []) {
|
|
411
|
+
asLine(text, "line");
|
|
412
|
+
}
|
|
413
|
+
for (const text of operands?.texts ?? []) {
|
|
414
|
+
asLine(text, "line", fed);
|
|
415
|
+
}
|
|
416
|
+
for (const [from, to] of operands?.joined ?? []) {
|
|
417
|
+
const texts = words.slice(from, to).flatMap((word, at) => {
|
|
418
|
+
const pattern = operands?.replaced.get(from + at);
|
|
419
|
+
return heldTextsOf(word).map((one) => pattern === undefined ? one : one.replace(pattern, PART));
|
|
420
|
+
});
|
|
421
|
+
asLine(texts.join(" "), "line", fed);
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
case "Word": {
|
|
426
|
+
const pattern = replaced.get(node);
|
|
427
|
+
const prefix = lines.get(node);
|
|
428
|
+
for (const one of heldTextsOf(node)) {
|
|
429
|
+
const text = pattern === undefined ? one : one.replace(pattern, PART);
|
|
430
|
+
into.push({ text, runs: runs.has(node) });
|
|
431
|
+
if (prefix !== undefined) {
|
|
432
|
+
asLine(text.slice(text.startsWith(prefix) ? prefix.length : 0), "line", handed.get(node));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
inner = lines.has(node) ? "read" : how;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
case "SingleQuoted":
|
|
439
|
+
case "AnsiCQuoted":
|
|
440
|
+
asLine(node.value, how);
|
|
441
|
+
break;
|
|
442
|
+
case "DoubleQuoted":
|
|
443
|
+
asLine(node.text.slice(1, -1), how);
|
|
444
|
+
break;
|
|
445
|
+
case "HereDocBody":
|
|
446
|
+
asLine(node.text, how);
|
|
447
|
+
break;
|
|
448
|
+
default:
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
forEachChild(node, (child) => {
|
|
452
|
+
visit(child, inner);
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
visit(script, "text");
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Each word of one command line, read twice: as POSIX reads it, and with each
|
|
459
|
+
* {@link BACKSLASH_PATH} read as `/`, as PowerShell and cmd read a path. A line that does not
|
|
460
|
+
* parse is read line by line, and a line of it that does not parse either by {@link tokenWords};
|
|
461
|
+
* `stdin` and `tests` are what {@link scriptWords} takes.
|
|
462
|
+
*/
|
|
463
|
+
function lineWords(source, depth, quoted, into, stdin = NO_STDIN, tests = false) {
|
|
464
|
+
for (const reading of new Set([source, source.replaceAll(BACKSLASH_PATH, "/")])) {
|
|
465
|
+
const script = parseLine(reading);
|
|
466
|
+
if ((script.errors ?? []).length === 0) {
|
|
467
|
+
scriptWords(script, depth, quoted, into, stdin, tests && reading === source);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
for (const line of reading.split(/\r?\n/u)) {
|
|
471
|
+
const one = parseLine(line);
|
|
472
|
+
if ((one.errors ?? []).length === 0) {
|
|
473
|
+
scriptWords(one, depth, quoted, into, stdin, false);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
tokenWords(line, !quoted, depth, into, stdin);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* The words of `texts`: each command string's, and each line of each other value's, read by
|
|
483
|
+
* {@link tokenWords} as a quoted text is, with no program word, but for a `uses:` value that
|
|
484
|
+
* references another repository's action.
|
|
485
|
+
*/
|
|
486
|
+
export function heldWords(texts) {
|
|
487
|
+
const found = [];
|
|
488
|
+
for (const { text, shell, key, tested } of texts) {
|
|
489
|
+
if (shell) {
|
|
490
|
+
lineWords(text, DEPTH, false, found, NO_STDIN, tested === true);
|
|
491
|
+
}
|
|
492
|
+
else if (key !== "uses" || !ACTION_REFERENCE.test(text.trim())) {
|
|
493
|
+
for (const line of text.split(/\r?\n/u)) {
|
|
494
|
+
tokenWords(line, false, DEPTH, found);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return found;
|
|
499
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one reader of every command string the analysis reads. Layer 3, the holding rule
|
|
3
|
+
* `roots/root-set.ts` applies through `commands/held.ts` and `text/named-files.ts`, reads every
|
|
4
|
+
* word of the string whatever the other two layers make of it; layer 1 parses the string and
|
|
5
|
+
* layer 2 reads each simple command a tool's reader understands, and they only add the files
|
|
6
|
+
* and modules it runs.
|
|
7
|
+
*/
|
|
8
|
+
import { joinPath, resolvePath } from "../paths.js";
|
|
9
|
+
import { shellLine } from "./shell.js";
|
|
10
|
+
import { configurationsOf, preloadTarget, readBuiltin, handedOn, testRun, } from "./tools.js";
|
|
11
|
+
import { APPENDED_EXTENSIONS } from "../extensions.js";
|
|
12
|
+
/**
|
|
13
|
+
* The outcomes of one pipeline run in `dir`: those of the builtin that is its one member, as
|
|
14
|
+
* `readBuiltin` gives them, a directory the analysis does not know after a part layer 1 does
|
|
15
|
+
* not read, and either status in `dir` otherwise, a pipeline of several members included,
|
|
16
|
+
* whose members each run in a subshell.
|
|
17
|
+
*/
|
|
18
|
+
function outcomesOf(pipeline, dir) {
|
|
19
|
+
const [only] = pipeline.alone ? pipeline.commands : [];
|
|
20
|
+
const outcomes = pipeline.opaque
|
|
21
|
+
? [
|
|
22
|
+
{ dir: undefined, ok: true },
|
|
23
|
+
{ dir: undefined, ok: false },
|
|
24
|
+
]
|
|
25
|
+
: ((only === undefined ? undefined : readBuiltin(only, dir)) ?? [
|
|
26
|
+
{ dir, ok: true },
|
|
27
|
+
{ dir, ok: false },
|
|
28
|
+
]);
|
|
29
|
+
return outcomes.map((one) => ({ dir: one.dir, ok: one.ok !== pipeline.negated }));
|
|
30
|
+
}
|
|
31
|
+
/** The one directory every outcome is in, or `undefined` where they differ or none is known. */
|
|
32
|
+
function oneDirectory(outcomes) {
|
|
33
|
+
const dirs = new Set(outcomes.map((one) => one.dir));
|
|
34
|
+
return dirs.size === 1 ? [...dirs][0] : undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* What `text`, run in `dir`, `undefined` where not known, runs. A pipeline is read where every
|
|
38
|
+
* way its and-or list reaches it leaves the shell in one known directory, none after an `exit`;
|
|
39
|
+
* a background list moves and ends no later one. The words and configuration files of every
|
|
40
|
+
* simple command of the string's own line are read in `dir` as well, wherever they run.
|
|
41
|
+
*/
|
|
42
|
+
export function readCommands(text, dir) {
|
|
43
|
+
const runs = [];
|
|
44
|
+
const commands = [];
|
|
45
|
+
const configurations = [];
|
|
46
|
+
const tests = [];
|
|
47
|
+
const readTests = (words, at) => {
|
|
48
|
+
const run = testRun(words);
|
|
49
|
+
if (run !== undefined) {
|
|
50
|
+
tests.push({ dir: at, ...run });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const readCommand = (words, at) => {
|
|
54
|
+
commands.push({ dir: at, words: words.map((word) => word.value) });
|
|
55
|
+
const [handed, reading] = handedOn(words);
|
|
56
|
+
readTests(handed, at);
|
|
57
|
+
if (reading?.kind === "line") {
|
|
58
|
+
readLine(reading.text, at);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
configurations.push(...configurationsOf(handed).map((written) => ({ dir: at, written })));
|
|
62
|
+
if (reading !== undefined && (reading.entry !== undefined || reading.preloads.length > 0)) {
|
|
63
|
+
runs.push({
|
|
64
|
+
dir: at,
|
|
65
|
+
entry: reading.entry,
|
|
66
|
+
preloads: reading.preloads,
|
|
67
|
+
runtime: reading.runtime,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const readLine = (source, start) => {
|
|
72
|
+
let at = start;
|
|
73
|
+
for (const list of shellLine(source)) {
|
|
74
|
+
let outcomes = [{ dir: at, ok: true }];
|
|
75
|
+
for (const pipeline of list.pipelines) {
|
|
76
|
+
const { operator } = pipeline;
|
|
77
|
+
const reached = outcomes.filter((one) => operator === undefined || one.ok === (operator === "&&"));
|
|
78
|
+
const where = reached.length === 0 ? undefined : oneDirectory(reached);
|
|
79
|
+
if (where !== undefined) {
|
|
80
|
+
pipeline.commands.forEach((words) => {
|
|
81
|
+
readCommand(words, where);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const next = reached.flatMap((one) => outcomesOf(pipeline, one.dir));
|
|
85
|
+
const passed = outcomes.filter((one) => !reached.includes(one));
|
|
86
|
+
outcomes = [
|
|
87
|
+
...new Map([...passed, ...next].map((one) => [`${String(one.ok)}\u0000${one.dir ?? ""}`, one])).values(),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
if (!list.background) {
|
|
91
|
+
at = oneDirectory(outcomes);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
readLine(text, dir);
|
|
96
|
+
if (dir !== undefined) {
|
|
97
|
+
for (const words of shellLine(text).flatMap((list) => list.pipelines.flatMap((pipeline) => pipeline.commands))) {
|
|
98
|
+
commands.push({ dir, words: words.map((word) => word.value) });
|
|
99
|
+
configurations.push(...configurationsOf(words).map((written) => ({ dir, written })));
|
|
100
|
+
readTests(handedOn(words)[0], dir);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const unique = (all) => [
|
|
104
|
+
...new Map(all.map((one) => [JSON.stringify(one), one])).values(),
|
|
105
|
+
];
|
|
106
|
+
return {
|
|
107
|
+
runs,
|
|
108
|
+
commands: unique(commands),
|
|
109
|
+
configurations: unique(configurations),
|
|
110
|
+
tests: unique(tests),
|
|
111
|
+
held: text,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The files `run` loads: its entry, and each preload written as a path. Each may be at its
|
|
116
|
+
* path, with each extension tried, and as a directory's `index` file.
|
|
117
|
+
*/
|
|
118
|
+
export function loadsOf(run) {
|
|
119
|
+
const written = [
|
|
120
|
+
...(run.entry === undefined
|
|
121
|
+
? []
|
|
122
|
+
: [{ written: run.entry, path: resolvePath(run.dir, run.entry) }]),
|
|
123
|
+
...run.preloads.flatMap((preload) => {
|
|
124
|
+
const target = preloadTarget(preload, run.dir);
|
|
125
|
+
return target !== undefined && "path" in target
|
|
126
|
+
? [{ written: preload.specifier, path: target.path }]
|
|
127
|
+
: [];
|
|
128
|
+
}),
|
|
129
|
+
];
|
|
130
|
+
return written.map(({ written: one, path }) => ({
|
|
131
|
+
written: one,
|
|
132
|
+
paths: [
|
|
133
|
+
path,
|
|
134
|
+
...APPENDED_EXTENSIONS.map((extension) => `${path}${extension}`),
|
|
135
|
+
...APPENDED_EXTENSIONS.map((extension) => joinPath(path, `index${extension}`)),
|
|
136
|
+
],
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
/** The module specifiers `run` preloads that name a package rather than a path. */
|
|
140
|
+
export function preloadedPackages(run) {
|
|
141
|
+
return run.preloads.flatMap((preload) => {
|
|
142
|
+
const target = preloadTarget(preload, run.dir);
|
|
143
|
+
return target !== undefined && "package" in target ? [target.package] : [];
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Every path `reading` names: each static word of its commands read against the directory the
|
|
148
|
+
* command runs in, a word holding `*` aside, which a tool reads as a pattern, and every path a
|
|
149
|
+
* file a read command runs or preloads may be at.
|
|
150
|
+
*/
|
|
151
|
+
export function pathsNamed(reading) {
|
|
152
|
+
const named = [
|
|
153
|
+
...reading.commands.flatMap(({ dir, words }) => words.flatMap((word) => word === undefined || word.includes("*")
|
|
154
|
+
? []
|
|
155
|
+
: [{ path: resolvePath(dir, word), written: word }])),
|
|
156
|
+
...reading.runs
|
|
157
|
+
.flatMap(loadsOf)
|
|
158
|
+
.flatMap(({ written, paths }) => paths.map((path) => ({ path, written }))),
|
|
159
|
+
];
|
|
160
|
+
return [...new Map(named.map((one) => [`${one.path}\u0000${one.written}`, one])).values()];
|
|
161
|
+
}
|