Skip to content

Commit 6d9f28f

Browse files
committed
Fix variable interpolation in .env files loaded through envFile
1 parent e38cfef commit 6d9f28f

3 files changed

Lines changed: 39 additions & 0 deletions

File tree

‎Extension/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* Reduce CPU usage when resolving headers in large Windows workspaces.
1616

1717
### Bug Fixes
18+
* Fix `${VAR}` interpolation in `.env` files used by `envFile`: references to variables defined earlier in the same file (or in the OS environment) are now resolved. [#13781](https://github.com/microsoft/vscode-cpptools/issues/13781)
1819
* Fix 'Go to Declaration' and 'Go to Definition' returning the current location instead of an available counterpart. [#14509](https://github.com/microsoft/vscode-cpptools/issues/14509)
1920
* Update MIEngine to fix debugger failures with synthetic GDB stack frames. [#14741](https://github.com/microsoft/vscode-cpptools/issues/14741)
2021
* Thanks for the contribution. [@tzcnt](https://github.com/tzcnt) [PR MIEngine#1605](https://github.com/microsoft/MIEngine/pull/1605)

‎Extension/src/Debugger/ParsedEnvironmentFile.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ export class ParsedEnvironmentFile {
5959

6060
value = value.replace(/(^['"]|['"]$)/g, "");
6161

62+
// Resolve ${VAR} references against the variables defined so far in the file
63+
// (and the process environment), following the dotenv interpolation rules.
64+
// A reference to a name we don't know is left as-is instead of being dropped.
65+
value = value.replace(/\$\{([\w\.\-]+)\}/g, (full: string, name: string): string => {
66+
if (env.has(name)) {
67+
return env.get(name);
68+
}
69+
const processValue: string | undefined = process.env[name];
70+
return processValue !== undefined ? processValue : full;
71+
});
72+
6273
env.set(key, value);
6374
} else {
6475
// Blank lines and lines starting with # are no parse errors

‎Extension/test/scenarios/SingleRootProject/tests/ParsedEnvironmentFile.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,31 @@ MyName2=Value2
100100
assertEnvironmentEqual(result.Env, "MyName1", "Value1");
101101
assertEnvironmentEqual(result.Env, "MyName2", "Value2");
102102
});
103+
104+
test("Resolve a reference to a variable defined earlier in the file", () => {
105+
const content: string = `USER=admin
106+
EMAIL=\${USER}@example.org`;
107+
const result: ParsedEnvironmentFile = ParsedEnvironmentFile.CreateFromContent(content, "TestEnvFileName", []);
108+
109+
assert(!result.Warning, `Failed to assert that Warning was empty: ${result.Warning}`);
110+
assertEnvironmentEqual(result.Env, "USER", "admin");
111+
assertEnvironmentEqual(result.Env, "EMAIL", "admin@example.org");
112+
});
113+
114+
test("Resolve a reference from the initial environment", () => {
115+
const initialEnv: Environment[] = [{ name: "USER", value: "admin" }];
116+
const content: string = `EMAIL=\${USER}@example.org`;
117+
const result: ParsedEnvironmentFile = ParsedEnvironmentFile.CreateFromContent(content, "TestEnvFileName", initialEnv);
118+
119+
assert(!result.Warning, `Failed to assert that Warning was empty: ${result.Warning}`);
120+
assertEnvironmentEqual(result.Env, "EMAIL", "admin@example.org");
121+
});
122+
123+
test("Leave an unknown reference untouched", () => {
124+
const content: string = `EMAIL=\${UNKNOWN_USER}@example.org`;
125+
const result: ParsedEnvironmentFile = ParsedEnvironmentFile.CreateFromContent(content, "TestEnvFileName", []);
126+
127+
assert(!result.Warning, `Failed to assert that Warning was empty: ${result.Warning}`);
128+
assertEnvironmentEqual(result.Env, "EMAIL", "${UNKNOWN_USER}@example.org");
129+
});
103130
});

0 commit comments

Comments
 (0)