Skip to content

Commit 70f4241

Browse files
committed
refactor resolvingWorkspaceVars and implement deprecation telemetry tracker
1 parent 61b9069 commit 70f4241

File tree

7 files changed

+76
-31
lines changed

7 files changed

+76
-31
lines changed

src/extension/debugger/configuration/providers/pyramidLaunch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { EventName } from '../../../telemetry/constants';
1313
import { DebuggerTypeName } from '../../../constants';
1414
import { LaunchRequestArguments } from '../../../types';
1515
import { DebugConfigurationState, DebugConfigurationType } from '../../types';
16-
import { resolveVariables } from '../utils/common';
16+
import { resolveWorkspaceVariables } from '../utils/common';
1717

1818
const workspaceFolderToken = '${workspaceFolder}';
1919

@@ -73,7 +73,7 @@ export async function validateIniPath(
7373
if (!selected || selected.trim().length === 0) {
7474
return error;
7575
}
76-
const resolvedPath = resolveVariables(selected, undefined, folder);
76+
const resolvedPath = resolveWorkspaceVariables(selected, undefined, folder);
7777
if (resolvedPath) {
7878
if (selected !== defaultValue && !fs.pathExists(resolvedPath)) {
7979
return error;

src/extension/debugger/configuration/resolvers/base.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getWorkspaceFolders, getWorkspaceFolder as getVSCodeWorkspaceFolder } f
1212
import { AttachRequestArguments, DebugOptions, LaunchRequestArguments, PathMapping } from '../../../types';
1313
import { PythonPathSource } from '../../types';
1414
import { IDebugConfigurationResolver } from '../types';
15-
import { resolveVariables } from '../utils/common';
15+
import { resolveWorkspaceVariables } from '../utils/common';
1616
import { getProgram } from './helper';
1717
import { getSettingsPythonPath, getInterpreterDetails } from '../../../common/python';
1818
import { getOSType, OSType } from '../../../common/platform';
@@ -96,7 +96,7 @@ export abstract class BaseConfigurationResolver<T extends DebugConfiguration>
9696
return;
9797
}
9898
if (debugConfiguration.envFile && (workspaceFolder || debugConfiguration.cwd)) {
99-
debugConfiguration.envFile = resolveVariables(
99+
debugConfiguration.envFile = resolveWorkspaceVariables(
100100
debugConfiguration.envFile,
101101
(workspaceFolder ? workspaceFolder.fsPath : undefined) || debugConfiguration.cwd,
102102
undefined,
@@ -118,7 +118,7 @@ export abstract class BaseConfigurationResolver<T extends DebugConfiguration>
118118
: await getSettingsPythonPath(workspaceFolder);
119119
debugConfiguration.pythonPath = interpreterPath ? interpreterPath[0] : interpreterPath;
120120
} else {
121-
debugConfiguration.pythonPath = resolveVariables(
121+
debugConfiguration.pythonPath = resolveWorkspaceVariables(
122122
debugConfiguration.pythonPath ? debugConfiguration.pythonPath : undefined,
123123
workspaceFolder?.fsPath,
124124
undefined,
@@ -137,7 +137,7 @@ export abstract class BaseConfigurationResolver<T extends DebugConfiguration>
137137
debugConfiguration.python = debugConfiguration.pythonPath;
138138
} else {
139139
this.pythonPathSource = PythonPathSource.launchJson;
140-
debugConfiguration.python = resolveVariables(
140+
debugConfiguration.python = resolveWorkspaceVariables(
141141
debugConfiguration.python ?? debugConfiguration.pythonPath,
142142
workspaceFolder?.fsPath,
143143
undefined,
@@ -194,7 +194,7 @@ export abstract class BaseConfigurationResolver<T extends DebugConfiguration>
194194
} else {
195195
// Expand ${workspaceFolder} variable first if necessary.
196196
pathMappings = pathMappings.map(({ localRoot: mappedLocalRoot, remoteRoot }) => {
197-
const resolvedLocalRoot = resolveVariables(mappedLocalRoot, defaultLocalRoot, undefined);
197+
const resolvedLocalRoot = resolveWorkspaceVariables(mappedLocalRoot, defaultLocalRoot, undefined);
198198
return {
199199
localRoot: resolvedLocalRoot || '',
200200
// TODO: Apply to remoteRoot too?

src/extension/debugger/configuration/resolvers/launch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getOSType, OSType } from '../../../common/platform';
88
import { getEnvFile } from '../../../common/settings';
99
import { DebuggerTypeName } from '../../../constants';
1010
import { DebugOptions, DebugPurpose, LaunchRequestArguments } from '../../../types';
11-
import { resolveVariables } from '../utils/common';
11+
import { resolveWorkspaceVariables } from '../utils/common';
1212
import { BaseConfigurationResolver } from './base';
1313
import { getDebugEnvironmentVariables, getProgram } from './helper';
1414
import { getConfiguration } from '../../../common/vscodeapi';
@@ -83,7 +83,7 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver<Launc
8383
debugConfiguration.cwd = workspaceFolder.fsPath;
8484
}
8585
if (typeof debugConfiguration.envFile !== 'string' && workspaceFolder) {
86-
debugConfiguration.envFile = resolveVariables(
86+
debugConfiguration.envFile = resolveWorkspaceVariables(
8787
getEnvFile('python', workspaceFolder),
8888
workspaceFolder.fsPath,
8989
undefined,

src/extension/debugger/configuration/utils/common.ts

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,63 @@
88

99
import { Uri, WorkspaceFolder } from 'vscode';
1010
import { getWorkspaceFolder } from '../../../common/vscodeapi';
11+
import { sendTelemetryEvent } from '../../../telemetry';
12+
import { EventName } from '../../../telemetry/constants';
1113

1214
/**
1315
* @returns whether the provided parameter is a JavaScript String or not.
1416
*/
1517
function isString(str: any): str is string {
16-
if (typeof str === 'string' || str instanceof String) {
17-
return true;
18-
}
19-
20-
return false;
18+
return typeof str === 'string' || str instanceof String;
2119
}
2220

23-
export function resolveVariables(
21+
/**
22+
* Resolves VS Code variable placeholders in a string value.
23+
*
24+
* Specifically handles:
25+
* - `${workspaceFolder}` - replaced with the workspace folder path
26+
* - `${env.VAR}` or `${env:VAR}` - replaced with empty string
27+
* - Unknown variables - left as-is in the original `${variable}` format
28+
*
29+
* @param value The string containing variable placeholders to resolve
30+
* @param rootFolder Fallback folder path to use if no workspace folder is available
31+
* @param folder The workspace folder context for variable resolution
32+
* @returns The string with variables resolved, or undefined if input was undefined
33+
*/
34+
export function resolveWorkspaceVariables(
2435
value: string | undefined,
2536
rootFolder: string | Uri | undefined,
2637
folder: WorkspaceFolder | undefined,
2738
): string | undefined {
28-
if (value) {
29-
const workspaceFolder = folder ? getWorkspaceFolder(folder.uri) : undefined;
30-
const variablesObject: { [key: string]: any } = {};
31-
variablesObject.workspaceFolder = workspaceFolder ? workspaceFolder.uri.fsPath : rootFolder;
32-
33-
const regexp = /\$\{(.*?)\}/g;
34-
return value.replace(regexp, (match: string, name: string) => {
35-
const newValue = variablesObject[name];
36-
if (isString(newValue)) {
37-
return newValue;
38-
}
39-
return match && (match.indexOf('env.') > 0 || match.indexOf('env:') > 0) ? '' : match;
40-
});
39+
if (!value) {
40+
return value;
4141
}
42-
return value;
42+
43+
// opt for folder with fallback to rootFolder
44+
const workspaceFolder = folder ? getWorkspaceFolder(folder.uri) : undefined;
45+
const workspaceFolderPath = workspaceFolder ? workspaceFolder.uri.fsPath : rootFolder;
46+
47+
// Replace all ${variable} patterns
48+
return value.replace(/\$\{([^}]+)\}/g, (match: string, variableName: string) => {
49+
// Handle workspaceFolder variable
50+
if (variableName === 'workspaceFolder' && isString(workspaceFolderPath)) {
51+
// Track usage of this potentially deprecated code path
52+
sendTelemetryEvent(EventName.DEPRECATED_CODE_PATH_USAGE, undefined, {
53+
codePath: 'workspaceFolder_substitution',
54+
});
55+
return workspaceFolderPath;
56+
}
57+
58+
// Replace environment variables with empty string
59+
if (variableName.startsWith('env.') || variableName.startsWith('env:')) {
60+
// Track usage of this potentially deprecated code path
61+
sendTelemetryEvent(EventName.DEPRECATED_CODE_PATH_USAGE, undefined, {
62+
codePath: 'env_variable_substitution',
63+
});
64+
return '';
65+
}
66+
67+
// Unknown variables are left unchanged
68+
return match;
69+
});
4370
}

src/extension/telemetry/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ export enum EventName {
2424
USE_REPORT_ISSUE_COMMAND = 'USE_REPORT_ISSUE_COMMAND',
2525
DEBUGGER_PYTHON_37_DEPRECATED = 'DEBUGGER_PYTHON_37_DEPRECATED',
2626
DEBUGGER_SHOW_PYTHON_INLINE_VALUES = 'DEBUGGER_SHOW_PYTHON_INLINE_VALUES',
27+
DEPRECATED_CODE_PATH_USAGE = 'DEPRECATED_CODE_PATH_USAGE',
2728
}

src/extension/telemetry/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,4 +691,21 @@ export interface IEventNamePropertyMapping {
691691
"DEBUGGER_SHOW_PYTHON_INLINE_VALUES" : { "owner": "eleanorjboyd" }
692692
*/
693693
[EventName.DEBUGGER_SHOW_PYTHON_INLINE_VALUES]: never | undefined;
694+
/**
695+
* Telemetry event sent when potentially deprecated code paths are executed.
696+
*/
697+
/* __GDPR__
698+
"deprecated_code_path_usage" : {
699+
"codepath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
700+
}
701+
*/
702+
[EventName.DEPRECATED_CODE_PATH_USAGE]: {
703+
/**
704+
* Identifier for the specific deprecated code path that was executed.
705+
* Examples: 'workspaceFolder_substitution', 'env_variable_substitution'
706+
*
707+
* @type {string}
708+
*/
709+
codePath: string;
710+
};
694711
}

src/test/unittest/configuration/providers/pyramidLaunch.unit.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { Uri } from 'vscode';
1212
import { DebugConfigStrings } from '../../../../extension/common/utils/localize';
1313
import { MultiStepInput } from '../../../../extension/common/multiStepInput';
1414
import { DebuggerTypeName } from '../../../../extension/constants';
15-
import { resolveVariables } from '../../../../extension/debugger/configuration/utils/common';
1615
import * as pyramidLaunch from '../../../../extension/debugger/configuration/providers/pyramidLaunch';
1716
import { DebugConfigurationState } from '../../../../extension/debugger/types';
1817
import * as vscodeapi from '../../../../extension/common/vscodeapi';
18+
import { resolveWorkspaceVariables } from '../../../../extension/debugger/configuration/utils/common';
1919

2020
suite('Debugging - Configuration Provider Pyramid', () => {
2121
let input: MultiStepInput<DebugConfigurationState>;
@@ -52,7 +52,7 @@ suite('Debugging - Configuration Provider Pyramid', () => {
5252
test('Resolve variables (with resource)', async () => {
5353
const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 };
5454
workspaceStub.returns(folder);
55-
const resolvedPath = resolveVariables('${workspaceFolder}/one.py', undefined, folder);
55+
const resolvedPath = resolveWorkspaceVariables('${workspaceFolder}/one.py', undefined, folder);
5656

5757
expect(resolvedPath).to.be.equal(`${folder.uri.fsPath}/one.py`);
5858
});

0 commit comments

Comments
 (0)