/** * One-hop environment-variable helper binding (change: follow-env-name-through-helper). * * The env extractor reads a variable's NAME out of a literal at the read site: * `os.Getenv("PORT")`, `process.env.PORT`. A very common idiom defeats that — * the read is inside a one-line wrapper and the name is passed in: * * ```go * mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR") // ← the name lives here * … * func mustMapEnv(target *string, envKey string) { v := os.Getenv(envKey); … } // ← the read lives here * ``` * * The wrapper's own `os.Getenv(envKey)` names no variable, so before this module * the whole call site was invisible: `analyze_env_impact` under-reported the read * sites with nothing said about the gap. * * This module follows EXACTLY ONE HOP, and only where the binding is provable from * literal text — a wrong read site sends an agent to the wrong file and inflates the * blast radius, so a refusal is always preferred to a guess. Every condition below is * required: * * 1. the wrapper is a FREE function declared at the top level of the SAME FILE as * the call site (never a method, never a cross-module same-named helper); * 2. its parameter flows DIRECTLY into the env read — the key expression is exactly * the parameter identifier, with no concatenation, reassignment, rebinding, or * shadowing anywhere in the body; * 3. exactly one parameter position feeds an env read; * 4. the call site passes a plain string literal of env-name shape at that position. * * Anything else — a variable argument, a template string, a concatenation, a parameter * with a default, a wrapper that wraps another wrapper (two hops), a shadowed parameter * — stays UNFOLLOWED and is counted in {@link EnvHelperScan.unfollowed} so the caller can * disclose the remaining gap with evidence instead of a generic caveat. * * No general dataflow, no new grammar, no new persisted artifact: one bounded text pass * over the same per-language read patterns the extractor already uses. */ /** * Call sites followed per wrapper. A helper called from 10,000 sites would otherwise * dominate the payload; the cap is disclosed via {@link EnvHelperScan.callSiteCapHit}. */ export declare const MAX_CALL_SITES_PER_WRAPPER = 64; /** A read site recovered at the CALL SITE, with the wrapper disclosed as the mechanism. */ export interface EnvHelperBinding { /** Variable name, read from the string literal at the call site. */ name: string; /** Repo-relative file of the CALL SITE (where the variable is named). */ file: string; /** 1-based line of the call site. */ line: number; /** Per-site fallback verdict, taken from the wrapper's read form. */ required: boolean; /** The helper the name flows through — the mechanism, always disclosed. */ via: { helper: string; file: string; line: number; }; } /** An env read whose key is not a literal and which could NOT be followed. */ export interface UnfollowedEnvRead { file: string; line: number; /** The key expression as written, truncated — e.g. `envKey`, `prefix + name`. */ keyExpression: string; /** Why it was not followed, in a few plain words. */ reason: string; } export interface EnvHelperScan { bindings: EnvHelperBinding[]; unfollowed: UnfollowedEnvRead[]; /** True when a per-wrapper call-site cap stopped the follow short. */ callSiteCapHit: boolean; } /** JS/TS: a read has a site-local fallback when `??`/`||` immediately follows. */ export declare function tsSiteRequired(source: string, afterIdx: number): boolean; /** * A call-form read (`os.environ.get('X'…)`, `os.getenv('X'…)`, `ENV.fetch('X'…)`) * is required only when it has NO default. `afterIdx` is just past the matched * `…('X'` — a comma before the close means a positional default follows → soft. */ export declare function callHasNoDefaultArg(source: string, afterIdx: number): boolean; /** * Ruby `ENV.fetch('X'…)`: required only with no default. A default can be a second * positional arg (`ENV.fetch('X', d)`) OR a block (`ENV.fetch('X') { d }` / * `ENV.fetch('X') do … end`) — both suppress the KeyError, so neither is a hard break. */ export declare function rubyFetchRequired(source: string, afterIdx: number): boolean; /** * Follow an env-var name ONE HOP from a literal at a call site into the helper that * reads it, for one file. Returns the bound read sites (attributed to the call site, * with the helper disclosed) plus every indirect read it refused to follow and why. * * Deterministic, bounded, text-only. Never throws on hostile input. */ export declare function scanEnvHelperBindings(source: string, relPath: string, ext: string): EnvHelperScan; //# sourceMappingURL=env-helper-binding.d.ts.map