Skip to content

Commit c04de20

Browse files
author
Kartik Raj
committed
Add diagnostic if we're unable to determine the exact cause
1 parent b7a1429 commit c04de20

File tree

1 file changed

+81
-63
lines changed

1 file changed

+81
-63
lines changed

src/client/application/diagnostics/checks/pythonInterpreter.ts

+81-63
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ const messages = {
4848
[DiagnosticCodes.IncompletePathVarDiagnostic]: l10n.t(
4949
'We detected an issue with "Path" environment variable that breaks features such as IntelliSense, linting and debugging. Please edit it to make sure it contains the "SystemRoot" subdirectories.',
5050
),
51+
[DiagnosticCodes.DefaultShellErrorDiagnostic]: l10n.t(
52+
'We detected an issue with your default shell that breaks features such as IntelliSense, linting and debugging. Try resetting "Comspec" and "Path" environment variables to fix it.',
53+
),
5154
};
5255

5356
export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
@@ -75,7 +78,10 @@ export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
7578

7679
export class DefaultShellDiagnostic extends BaseDiagnostic {
7780
constructor(
78-
code: DiagnosticCodes.InvalidComspecDiagnostic | DiagnosticCodes.IncompletePathVarDiagnostic,
81+
code:
82+
| DiagnosticCodes.InvalidComspecDiagnostic
83+
| DiagnosticCodes.IncompletePathVarDiagnostic
84+
| DiagnosticCodes.DefaultShellErrorDiagnostic,
7985
resource: Resource,
8086
scope = DiagnosticScope.Global,
8187
) {
@@ -170,6 +176,69 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
170176
return false;
171177
}
172178

179+
private async diagnoseDefaultShell(resource: Resource): Promise<IDiagnostic[]> {
180+
if (getOSType() !== OSType.Windows) {
181+
return [];
182+
}
183+
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
184+
const currentInterpreter = await interpreterService.getActiveInterpreter(resource);
185+
if (currentInterpreter) {
186+
return [];
187+
}
188+
try {
189+
await this.shellExecPython();
190+
} catch (ex) {
191+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
192+
if ((ex as any).errno === -4058) {
193+
// ENOENT (-4058) error is thrown by Node when the default shell is invalid.
194+
if (await this.isComspecInvalid()) {
195+
traceError('ComSpec is set to an invalid value', getEnvironmentVariable('ComSpec'));
196+
return [new DefaultShellDiagnostic(DiagnosticCodes.InvalidComspecDiagnostic, resource)];
197+
}
198+
if (this.isPathVarIncomplete()) {
199+
traceError('PATH env var appears to be incomplete', process.env.Path, process.env.PATH);
200+
return [new DefaultShellDiagnostic(DiagnosticCodes.IncompletePathVarDiagnostic, resource)];
201+
}
202+
return [new DefaultShellDiagnostic(DiagnosticCodes.DefaultShellErrorDiagnostic, resource)];
203+
}
204+
}
205+
return [];
206+
}
207+
208+
private async isComspecInvalid() {
209+
const comSpec = getEnvironmentVariable('ComSpec') ?? '';
210+
const fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
211+
return fs.fileExists(comSpec).then((exists) => !exists);
212+
}
213+
214+
// eslint-disable-next-line class-methods-use-this
215+
private isPathVarIncomplete() {
216+
const envVars = getSearchPathEnvVarNames();
217+
const systemRoot = getEnvironmentVariable('SystemRoot') ?? 'C:\\WINDOWS';
218+
for (const envVar of envVars) {
219+
const value = getEnvironmentVariable(envVar);
220+
if (value?.includes(systemRoot)) {
221+
return false;
222+
}
223+
}
224+
return true;
225+
}
226+
227+
@cache(-1, true)
228+
// eslint-disable-next-line class-methods-use-this
229+
private async shellExecPython() {
230+
const configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
231+
const { pythonPath } = configurationService.getSettings();
232+
const [args] = getExecutable();
233+
const argv = [pythonPath, ...args];
234+
// Concat these together to make a set of quoted strings
235+
const quoted = argv.reduce(
236+
(p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`),
237+
'',
238+
);
239+
return shellExec(quoted, { timeout: 15000 });
240+
}
241+
173242
@cache(1000, true) // This is to handle throttling of multiple events.
174243
protected async onHandle(diagnostics: IDiagnostic[]): Promise<void> {
175244
if (diagnostics.length === 0) {
@@ -215,6 +284,17 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
215284
},
216285
];
217286
}
287+
if (diagnostic.code === DiagnosticCodes.DefaultShellErrorDiagnostic) {
288+
return [
289+
{
290+
prompt: Common.seeInstructions,
291+
command: commandFactory.createCommand(diagnostic, {
292+
type: 'launch',
293+
options: 'https://aka.ms/AAk744c',
294+
}),
295+
},
296+
];
297+
}
218298
const prompts = [
219299
{
220300
prompt: Common.selectPythonInterpreter,
@@ -235,68 +315,6 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
235315
}
236316
return prompts;
237317
}
238-
239-
private async diagnoseDefaultShell(resource: Resource): Promise<IDiagnostic[]> {
240-
if (getOSType() !== OSType.Windows) {
241-
return [];
242-
}
243-
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
244-
const currentInterpreter = await interpreterService.getActiveInterpreter(resource);
245-
if (currentInterpreter) {
246-
return [];
247-
}
248-
try {
249-
await this.shellExecPython();
250-
} catch (ex) {
251-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
252-
if ((ex as any).errno === -4058) {
253-
// ENOENT (-4058) error is thrown by Node when the default shell is invalid.
254-
if (await this.isComspecInvalid()) {
255-
traceError('ComSpec is set to an invalid value', getEnvironmentVariable('ComSpec'));
256-
return [new DefaultShellDiagnostic(DiagnosticCodes.InvalidComspecDiagnostic, resource)];
257-
}
258-
if (this.isPathVarIncomplete()) {
259-
traceError('PATH env var appears to be incomplete', process.env.Path, process.env.PATH);
260-
return [new DefaultShellDiagnostic(DiagnosticCodes.IncompletePathVarDiagnostic, resource)];
261-
}
262-
}
263-
}
264-
return [];
265-
}
266-
267-
private async isComspecInvalid() {
268-
const comSpec = getEnvironmentVariable('ComSpec') ?? '';
269-
const fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
270-
return fs.fileExists(comSpec).then((exists) => !exists);
271-
}
272-
273-
// eslint-disable-next-line class-methods-use-this
274-
private isPathVarIncomplete() {
275-
const envVars = getSearchPathEnvVarNames();
276-
const systemRoot = getEnvironmentVariable('SystemRoot') ?? 'C:\\WINDOWS';
277-
for (const envVar of envVars) {
278-
const value = getEnvironmentVariable(envVar);
279-
if (value?.includes(systemRoot)) {
280-
return false;
281-
}
282-
}
283-
return true;
284-
}
285-
286-
@cache(-1, true)
287-
// eslint-disable-next-line class-methods-use-this
288-
private async shellExecPython() {
289-
const configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
290-
const { pythonPath } = configurationService.getSettings();
291-
const [args] = getExecutable();
292-
const argv = [pythonPath, ...args];
293-
// Concat these together to make a set of quoted strings
294-
const quoted = argv.reduce(
295-
(p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`),
296-
'',
297-
);
298-
return shellExec(quoted, { timeout: 15000 });
299-
}
300318
}
301319

302320
function getOnCloseHandler(diagnostic: IDiagnostic): IDiagnosticMessageOnCloseHandler | undefined {

0 commit comments

Comments
 (0)