/** * Which environment variables a shell command actually DEPENDS ON — the * parser-backed answer to "would this command run the same program here?". * * 🔴 WHY A PARSER AND NOT A REGEX, measured. The caller * (`experimental_verifyPluginGuards`) refuses to run a hook whose command names * a variable nothing has set, because running it would measure a different * program than the harness runs. Deciding that with `/\$\{?NAME\}?/` gets two * ordinary shapes wrong, in the direction that costs a measurement: * * | command | the shell | a raw regex | * | -------------------------------- | -------------------- | ------------- | * | `GUARD=hooks/g.sh; "$GUARD"` | sets it, then expands| "unset GUARD" | * | `echo '$NOT_A_VAR'` | no expansion at all | "unset …" | * * Both are self-contained commands reported as unresolvable, so a real guard * goes unmeasured for a reason that is not true of it. This is the * `parse-structured-input-with-a-real-parser` rule applied to the same shell * grammar `core/bash-effects.ts` already parses: an ASSIGNMENT and a SINGLE- * QUOTED literal are nodes, so once the command is an AST the two mistakes above * are not expressible. * * 🔴 AND THE PARSER REMOVED TWO WAYS TO BE WRONG WHILE ADDING A THIRD, in the * worse direction. Subtracting every assigned name GLOBALLY excused a read the * assignment never reached, so the sweep ran a differently-configured program * and gave it a score. Measured against `/bin/sh` with the name exported first: * * ``` * export X=ambient; echo "$X"; X=1 → ambient (read comes FIRST) * export FOO=ambient; FOO=1 sh -c "echo $FOO" → ambient (prefix assign does * not reach its own * command's words) * (G=inner); printf '[%s]' "$G" → [] (subshell-scoped) * G=dominates; printf '[%s]' "$G" → dominates (this one persists) * ``` * * So the rule is DOMINANCE, not membership, and CONTROL FLOW, not source order: * an assignment excuses a read only when it is an unconditional top-level * statement (see {@link persistingAssigns}) AND sits before that read. A prefix, * subshell, function-body, backgrounded, conditional or pipelined assignment * excuses nothing at all. Where dominance is not provable, the read stands. * * It does NOT reach into `bash-effects.ts` for the parse: that module's `sh` * handle and node types are private to it, and its types model EFFECTS * (redirections, wrapper heads, flag tables) rather than expansions. The shared * thing is the dependency, not the code — both `require("mvdan-sh")`. * * CONSERVATIVE, ON PURPOSE, IN ONE DIRECTION. Over-reporting a dependency costs * a hook its measurement (the caller says so and names the variable); * under-reporting one lets a differently-configured program be measured and * scored. So where the parser cannot decide, this reports MORE: * * - a parse failure falls back to the regex scan and says `parsed: false`; * - `${FOO:-default}` and `${FOO:?msg}` count as reads even though the first * always resolves — reading the expansion operator is a further step, and its * only effect would be to measure more hooks; * - a `for f in …` loop variable is a read (nothing binds it in the AST the way * an `Assign` does). * * `$1` / `$@` / `$?` are never reads: they are positional and special * parameters, not environment the caller could set. */ /** What a command reads from its environment. */ export interface ShellVarReads { /** Names it expands and does not itself assign, first-seen order. */ readonly reads: readonly string[]; /** * Whether the shell parser accepted the command. `false` means `reads` came * from the regex fallback and may name a variable the command sets itself. */ readonly parsed: boolean; } export declare function shellVarReads(command: string): ShellVarReads; //# sourceMappingURL=shell-vars.d.ts.map