diff --git a/src/spec-common/variableSubstitution.ts b/src/spec-common/variableSubstitution.ts index c1dd969f9..f7d2c67ad 100644 --- a/src/spec-common/variableSubstitution.ts +++ b/src/spec-common/variableSubstitution.ts @@ -37,7 +37,7 @@ export function containerSubstitute(platform: NodeJS.Platform, return substitute0(replaceContainerEnv.bind(undefined, isWindows, configFile, normalizeEnv(isWindows, containerEnv)), value); } -type Replace = (match: string, variable: string, argument: string | undefined) => string; +type Replace = (match: string, variable: string, args: string[]) => string; function substitute0(replace: Replace, value: any): any { if (typeof value === 'string') { @@ -75,21 +75,21 @@ function resolveString(replace: Replace, value: string): string { function evaluateSingleVariable(replace: Replace, match: string, variable: string): string { // try to separate variable arguments from variable name - let argument: string | undefined; + let args: string[] = []; const parts = variable.split(':'); if (parts.length > 1) { variable = parts[0]; - argument = parts[1]; + args = parts.slice(1); } - return replace(match, variable, argument); + return replace(match, variable, args); } -function replaceWithContext(isWindows: boolean, context: SubstitutionContext, match: string, variable: string, argument: string | undefined) { +function replaceWithContext(isWindows: boolean, context: SubstitutionContext, match: string, variable: string, args: string[]) { switch (variable) { case 'env': case 'localEnv': - return lookupValue(isWindows, context.env, argument, match, context.configFile); + return lookupValue(isWindows, context.env, args, match, context.configFile); case 'localWorkspaceFolder': return context.localWorkspaceFolder !== undefined ? context.localWorkspaceFolder : match; @@ -108,25 +108,32 @@ function replaceWithContext(isWindows: boolean, context: SubstitutionContext, ma } } -function replaceContainerEnv(isWindows: boolean, configFile: URI | undefined, containerEnvObj: NodeJS.ProcessEnv, match: string, variable: string, argument: string | undefined) { +function replaceContainerEnv(isWindows: boolean, configFile: URI | undefined, containerEnvObj: NodeJS.ProcessEnv, match: string, variable: string, args: string[]) { switch (variable) { case 'containerEnv': - return lookupValue(isWindows, containerEnvObj, argument, match, configFile); + return lookupValue(isWindows, containerEnvObj, args, match, configFile); default: return match; } } -function lookupValue(isWindows: boolean, envObj: NodeJS.ProcessEnv, argument: string | undefined, match: string, configFile: URI | undefined) { - if (argument) { +function lookupValue(isWindows: boolean, envObj: NodeJS.ProcessEnv, args: string[], match: string, configFile: URI | undefined) { + if (args.length > 0) { + let envVariableName = args[0]; if (isWindows) { - argument = argument.toLowerCase(); + envVariableName = envVariableName.toLowerCase(); } - const env = envObj[argument]; + const env = envObj[envVariableName]; if (typeof env === 'string') { return env; } + + if (args.length > 1) { + const defaultValue = args[1]; + return defaultValue; + } + // For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436 return ''; } diff --git a/src/test/variableSubstitution.test.ts b/src/test/variableSubstitution.test.ts new file mode 100644 index 000000000..218bcc4d0 --- /dev/null +++ b/src/test/variableSubstitution.test.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; + +import { substitute } from '../spec-common/variableSubstitution'; +import { URI } from 'vscode-uri'; + +describe('Variable substitution', function () { + + it(`environment variables`, async () => { + const raw = { + foo: 'bar${env:baz}bar' + }; + const result = substitute({ + platform: 'linux', + localWorkspaceFolder: '/foo/bar', + containerWorkspaceFolder: '/baz/blue', + configFile: URI.file('/foo/bar/baz.json'), + env: { + baz: 'somevalue' + }, + }, raw); + assert.strictEqual(result.foo, 'barsomevaluebar'); + }); + + it(`localWorkspaceFolder`, async () => { + const raw = { + foo: 'bar${localWorkspaceFolder}bar' + }; + const result = substitute({ + platform: 'linux', + localWorkspaceFolder: '/foo/bar', + containerWorkspaceFolder: '/baz/blue', + configFile: URI.file('/foo/bar/baz.json'), + env: { + baz: 'somevalue' + }, + }, raw); + assert.strictEqual(result.foo, 'bar/foo/barbar'); + }); + + it(`containerWorkspaceFolder`, async () => { + const raw = { + foo: 'bar${containerWorkspaceFolder}bar' + }; + const result = substitute({ + platform: 'linux', + localWorkspaceFolder: '/foo/bar', + containerWorkspaceFolder: '/baz/blue', + configFile: URI.file('/foo/bar/baz.json'), + env: { + baz: 'somevalue' + }, + }, raw); + assert.strictEqual(result.foo, 'bar/baz/bluebar'); + }); + + it(`localWorkspaceFolderBasename and containerWorkspaceFolder`, async () => { + const raw = { + foo: 'bar${containerWorkspaceFolder}bar' + }; + const result = substitute({ + platform: 'linux', + localWorkspaceFolder: '/foo/red', + containerWorkspaceFolder: '/baz/${localWorkspaceFolderBasename}', + configFile: URI.file('/foo/bar/baz.json'), + env: { + baz: 'somevalue' + }, + }, raw); + assert.strictEqual(result.foo, 'bar/baz/redbar'); + }); + + it(`environment variables with default value if they do not exist`, async () => { + const raw = { + foo: 'bar${localEnv:baz:default}bar' + }; + const result = substitute({ + platform: 'linux', + localWorkspaceFolder: '/foo/bar', + containerWorkspaceFolder: '/baz/blue', + configFile: URI.file('/foo/bar/baz.json'), + env: { + }, + }, raw); + assert.strictEqual(result.foo, 'bardefaultbar'); + }); + + it(`environment variables without default value if they do not exist`, async () => { + const raw = { + foo: 'bar${localEnv:baz}bar' + }; + const result = substitute({ + platform: 'linux', + localWorkspaceFolder: '/foo/bar', + containerWorkspaceFolder: '/baz/blue', + configFile: URI.file('/foo/bar/baz.json'), + env: { + }, + }, raw); + assert.strictEqual(result.foo, 'barbar'); + }); + + it(`environment variables with default value if they do not exist`, async () => { + const raw = { + foo: 'bar${localEnv:baz:default}bar' + }; + const result = substitute({ + platform: 'linux', + localWorkspaceFolder: '/foo/bar', + containerWorkspaceFolder: '/baz/blue', + configFile: URI.file('/foo/bar/baz.json'), + env: { + baz: 'somevalue' + }, + }, raw); + assert.strictEqual(result.foo, 'barsomevaluebar'); + }); + + it(`environment variables without default value if they do not exist`, async () => { + const raw = { + foo: 'bar${localEnv:baz:default:a:b:c}bar' + }; + const result = substitute({ + platform: 'linux', + localWorkspaceFolder: '/foo/bar', + containerWorkspaceFolder: '/baz/blue', + configFile: URI.file('/foo/bar/baz.json'), + env: { + }, + }, raw); + assert.strictEqual(result.foo, 'bardefaultbar'); + }); +});