Skip to content

Commit 6d80d4a

Browse files
committed
fixes
1 parent cbbc879 commit 6d80d4a

File tree

22 files changed

+1257
-169
lines changed

22 files changed

+1257
-169
lines changed

.vscode/launch.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@
5858
],
5959
"stopOnEntry": false,
6060
"sourceMaps": true,
61-
"outDir": "${workspaceRoot}/out/test",
61+
"xxoutDir": "${workspaceRoot}/out/test",
62+
"outFiles": [
63+
"${workspaceRoot}/out/**/*.js"
64+
],
6265
"preLaunchTask": "npm"
6366
},
6467
{

src/client/common/enumUtils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export class EnumEx {
2+
static getNamesAndValues<T extends number>(e: any) {
3+
return EnumEx.getNames(e).map(n => ({ name: n, value: e[n] as T }));
4+
}
5+
6+
static getNames(e: any) {
7+
return EnumEx.getObjValues(e).filter(v => typeof v === "string") as string[];
8+
}
9+
10+
static getValues<T extends number>(e: any) {
11+
return EnumEx.getObjValues(e).filter(v => typeof v === "number") as T[];
12+
}
13+
14+
private static getObjValues(e: any): (number | string)[] {
15+
return Object.keys(e).map(k => e[k]);
16+
}
17+
}

src/client/common/helpers.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
const tmp = require('tmp');
22

33
export function isNotInstalledError(error: Error): boolean {
4-
return typeof (error) === 'object' && error !== null && ((<any>error).code === 'ENOENT' || (<any>error).code === 127);
4+
const isError = typeof (error) === 'object' && error !== null;
5+
const errorObj = <any>error;
6+
if (!isError) {
7+
return false;
8+
}
9+
const isModuleNoInstalledError = errorObj.code === 1 && error.message.indexOf('No module named') >= 0;
10+
return errorObj.code === 'ENOENT' || errorObj.code === 127 || isModuleNoInstalledError;
511
}
612

713
export interface Deferred<T> {
@@ -29,7 +35,7 @@ class DeferredImpl<T> implements Deferred<T> {
2935
this._resolve.apply(this.scope ? this.scope : this, arguments);
3036
this._resolved = true;
3137
}
32-
reject(reason?: any){
38+
reject(reason?: any) {
3339
this._reject.apply(this.scope ? this.scope : this, arguments);
3440
this._rejected = true;
3541
}

src/client/common/installer.ts

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,31 @@ ProductInstallScripts.set(Product.pylint, ['-m', 'pip', 'install', 'pylint']);
3434
ProductInstallScripts.set(Product.pytest, ['-m', 'pip', 'install', '-U', 'pytest']);
3535
ProductInstallScripts.set(Product.yapf, ['-m', 'pip', 'install', 'yapf']);
3636

37-
export const ProductExecutableAndArgs = new Map<Product, {executable: string, args: string[]}>();
38-
ProductExecutableAndArgs.set(Product.mypy, {executable: 'python', args:['-m', 'mypy']});
39-
ProductExecutableAndArgs.set(Product.nosetest, {executable: 'python', args:['-m', 'nose']});
40-
ProductExecutableAndArgs.set(Product.pylama, {executable: 'python', args:['-m', 'pylama']});
41-
ProductExecutableAndArgs.set(Product.prospector, {executable: 'python', args:['-m', 'prospector']});
42-
ProductExecutableAndArgs.set(Product.pylint, {executable: 'python', args:['-m', 'pylint']});
43-
ProductExecutableAndArgs.set(Product.pytest, {executable: 'python', args:['-m', 'pytest']});
37+
const ProductUninstallScripts = new Map<Product, string[]>();
38+
ProductUninstallScripts.set(Product.autopep8, ['-m', 'pip', 'uninstall', 'autopep8', '--yes']);
39+
ProductUninstallScripts.set(Product.flake8, ['-m', 'pip', 'uninstall', 'flake8', '--yes']);
40+
ProductUninstallScripts.set(Product.mypy, ['-m', 'pip', 'uninstall', 'mypy', '--yes']);
41+
ProductUninstallScripts.set(Product.nosetest, ['-m', 'pip', 'uninstall', 'nose', '--yes']);
42+
ProductUninstallScripts.set(Product.pep8, ['-m', 'pip', 'uninstall', 'pep8', '--yes']);
43+
ProductUninstallScripts.set(Product.pylama, ['-m', 'pip', 'uninstall', 'pylama', '--yes']);
44+
ProductUninstallScripts.set(Product.prospector, ['-m', 'pip', 'uninstall', 'prospector', '--yes']);
45+
ProductUninstallScripts.set(Product.pydocstyle, ['-m', 'pip', 'uninstall', 'pydocstyle', '--yes']);
46+
ProductUninstallScripts.set(Product.pylint, ['-m', 'pip', 'uninstall', 'pylint', '--yes']);
47+
ProductUninstallScripts.set(Product.pytest, ['-m', 'pip', 'uninstall', 'pytest', '--yes']);
48+
ProductUninstallScripts.set(Product.yapf, ['-m', 'pip', 'uninstall', 'yapf', '--yes']);
49+
50+
export const ProductExecutableAndArgs = new Map<Product, { executable: string, args: string[] }>();
51+
ProductExecutableAndArgs.set(Product.mypy, { executable: 'python', args: ['-m', 'mypy'] });
52+
ProductExecutableAndArgs.set(Product.nosetest, { executable: 'python', args: ['-m', 'nose'] });
53+
ProductExecutableAndArgs.set(Product.pylama, { executable: 'python', args: ['-m', 'pylama'] });
54+
ProductExecutableAndArgs.set(Product.prospector, { executable: 'python', args: ['-m', 'prospector'] });
55+
ProductExecutableAndArgs.set(Product.pylint, { executable: 'python', args: ['-m', 'pylint'] });
56+
ProductExecutableAndArgs.set(Product.pytest, { executable: 'python', args: ['-m', 'pytest'] });
57+
ProductExecutableAndArgs.set(Product.autopep8, { executable: 'python', args: ['-m', 'autopep8'] });
58+
ProductExecutableAndArgs.set(Product.pep8, { executable: 'python', args: ['-m', 'pep8'] });
59+
ProductExecutableAndArgs.set(Product.pydocstyle, { executable: 'python', args: ['-m', 'pydocstyle'] });
60+
ProductExecutableAndArgs.set(Product.yapf, { executable: 'python', args: ['-m', 'yapf'] });
61+
ProductExecutableAndArgs.set(Product.flake8, { executable: 'python', args: ['-m', 'flake8'] });
4462

4563
switch (os.platform()) {
4664
case 'win32': {
@@ -72,8 +90,7 @@ ProductNames.set(Product.pylint, 'pylint');
7290
ProductNames.set(Product.pytest, 'py.test');
7391
ProductNames.set(Product.yapf, 'yapf');
7492

75-
const SettingToDisableProduct = new Map<Product, string>();
76-
SettingToDisableProduct.set(Product.autopep8, 'autopep8');
93+
export const SettingToDisableProduct = new Map<Product, string>();
7794
SettingToDisableProduct.set(Product.flake8, 'linting.flake8Enabled');
7895
SettingToDisableProduct.set(Product.mypy, 'linting.mypyEnabled');
7996
SettingToDisableProduct.set(Product.nosetest, 'unitTest.nosetestsEnabled');
@@ -83,7 +100,6 @@ SettingToDisableProduct.set(Product.prospector, 'linting.prospectorEnabled');
83100
SettingToDisableProduct.set(Product.pydocstyle, 'linting.pydocstyleEnabled');
84101
SettingToDisableProduct.set(Product.pylint, 'linting.pylintEnabled');
85102
SettingToDisableProduct.set(Product.pytest, 'unitTest.pyTestEnabled');
86-
SettingToDisableProduct.set(Product.yapf, 'yapf');
87103

88104
export class Installer {
89105
private static terminal: vscode.Terminal;
@@ -117,7 +133,7 @@ export class Installer {
117133
return vscode.window.showErrorMessage(`${productType} ${productName} is not installed`, ...options).then(item => {
118134
switch (item) {
119135
case installOption: {
120-
return this.installProduct(product);
136+
return this.install(product);
121137
}
122138
case disableOption: {
123139
if (Linters.indexOf(product) >= 0) {
@@ -140,7 +156,7 @@ export class Installer {
140156
});
141157
}
142158

143-
installProduct(product: Product): Promise<any> {
159+
install(product: Product): Promise<any> {
144160
if (!this.outputChannel && !Installer.terminal) {
145161
Installer.terminal = vscode.window.createTerminal('Python Installer');
146162
}
@@ -190,9 +206,13 @@ export class Installer {
190206
}
191207
}
192208

193-
isProductInstalled(product: Product): Promise<boolean> {
209+
isInstalled(product: Product): Promise<boolean> {
194210
return isProductInstalled(product);
195211
}
212+
213+
uninstall(product: Product): Promise<any> {
214+
return uninstallproduct(product);
215+
}
196216
}
197217

198218
export function disableLinter(product: Product) {
@@ -206,30 +226,17 @@ export function disableLinter(product: Product) {
206226
}
207227
}
208228

209-
function isTestFrameworkInstalled(product: Product): Promise<boolean> {
210-
const fileToRun = product === Product.pytest ? 'py.test' : 'nosetests';
211-
const def = createDeferred<boolean>();
212-
execPythonFile(fileToRun, ['--version'], vscode.workspace.rootPath, false)
229+
function isProductInstalled(product: Product): Promise<boolean> {
230+
const prodExec = ProductExecutableAndArgs.get(product);
231+
return execPythonFile(prodExec.executable, prodExec.args.concat(['--version']), vscode.workspace.rootPath, false, undefined, undefined, true)
213232
.then(() => {
214-
def.resolve(true);
233+
return true;
215234
}).catch(reason => {
216-
if (isNotInstalledError(reason)) {
217-
def.resolve(false);
218-
}
219-
else {
220-
def.resolve(true);
221-
}
235+
return !isNotInstalledError(reason);
222236
});
223-
return def.promise;
224237
}
225-
function isProductInstalled(product: Product): Promise<boolean> {
226-
switch (product) {
227-
case Product.pytest: {
228-
return isTestFrameworkInstalled(product);
229-
}
230-
case Product.nosetest: {
231-
return isTestFrameworkInstalled(product);
232-
}
233-
}
234-
throw new Error('Not supported');
238+
239+
function uninstallproduct(product: Product): Promise<any> {
240+
const uninstallArgs = ProductUninstallScripts.get(product);
241+
return execPythonFile('python', uninstallArgs, vscode.workspace.rootPath, false, undefined, undefined, true);
235242
}

src/client/common/utils.ts

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,17 @@ export function getPathFromPythonCommand(args: string[]): Promise<string> {
9696
});
9797
}
9898

99-
export function execPythonFile(file: string, args: string[], cwd: string, includeErrorAsResponse: boolean = false, stdOut: (line: string) => void = null, token?: CancellationToken): Promise<string> {
99+
export function execPythonFile(file: string, args: string[], cwd: string, includeErrorAsResponse: boolean = false, stdOut: (line: string) => void = null, token?: CancellationToken, execAsModule: boolean = false): Promise<string> {
100100
// If running the python file, then always revert to execFileInternal
101101
// Cuz python interpreter is always a file and we can and will always run it using child_process.execFile()
102102
if (file === settings.PythonSettings.getInstance().pythonPath) {
103103
if (stdOut) {
104104
return spawnFileInternal(file, args, { cwd }, includeErrorAsResponse, stdOut, token);
105105
}
106+
if (execAsModule) {
107+
return getFullyQualifiedPythonInterpreterPath()
108+
.then(p => execPythonModule(p, args, { cwd: cwd }, includeErrorAsResponse, token));
109+
}
106110
return execFileInternal(file, args, { cwd: cwd }, includeErrorAsResponse, token);
107111
}
108112

@@ -136,47 +140,84 @@ export function execPythonFile(file: string, args: string[], cwd: string, includ
136140
if (stdOut) {
137141
return spawnFileInternal(file, args, { cwd, env: customEnvVariables }, includeErrorAsResponse, stdOut, token);
138142
}
143+
if (execAsModule) {
144+
return getFullyQualifiedPythonInterpreterPath()
145+
.then(p => execPythonModule(p, args, { cwd: cwd }, includeErrorAsResponse, token));
146+
}
139147
return execFileInternal(file, args, { cwd, env: customEnvVariables }, includeErrorAsResponse, token);
140148
});
141149
}
142150

143151
function handleResponse(file: string, includeErrorAsResponse: boolean, error: Error, stdout: string, stderr: string, token?: CancellationToken): Promise<string> {
144-
return new Promise<string>((resolve, reject) => {
145-
if (token && token.isCancellationRequested) {
146-
return;
147-
}
148-
if (isNotInstalledError(error)) {
149-
return reject(error);
150-
}
152+
if (token && token.isCancellationRequested) {
153+
return Promise.resolve(undefined);
154+
}
155+
if (isNotInstalledError(error)) {
156+
return Promise.reject(error);
157+
}
151158

152-
// pylint:
153-
// In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr
154-
// These error messages are useless when using pylint
155-
if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) {
156-
return resolve(stdout + '\n' + stderr);
157-
}
159+
// pylint:
160+
// In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr
161+
// These error messages are useless when using pylint
162+
if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) {
163+
return Promise.resolve(stdout + '\n' + stderr);
164+
}
158165

159-
let hasErrors = (error && error.message.length > 0) || (stderr && stderr.length > 0);
160-
if (hasErrors && (typeof stdout !== 'string' || stdout.length === 0)) {
161-
let errorMsg = (error && error.message) ? error.message : (stderr && stderr.length > 0 ? stderr + '' : '');
162-
console.error('stdout');
163-
console.error(stdout);
164-
console.error('stderr');
165-
console.error(stderr);
166-
console.error('error');
167-
console.error(error);
168-
console.error('Over');
169-
return reject(errorMsg);
170-
}
166+
let hasErrors = (error && error.message.length > 0) || (stderr && stderr.length > 0);
167+
if (hasErrors && (typeof stdout !== 'string' || stdout.length === 0)) {
168+
let errorMsg = (error && error.message) ? error.message : (stderr && stderr.length > 0 ? stderr + '' : '');
169+
return Promise.reject(errorMsg);
170+
}
171+
else {
172+
return Promise.resolve(stdout + '');
173+
}
174+
}
175+
function handlePythonModuleResponse(includeErrorAsResponse: boolean, error: Error, stdout: string, stderr: string, token?: CancellationToken): Promise<string> {
176+
if (token && token.isCancellationRequested) {
177+
return Promise.resolve(undefined);
178+
}
179+
if (isNotInstalledError(error) || !!error) {
180+
return Promise.reject(error);
181+
}
171182

172-
resolve(stdout + '');
183+
// pylint:
184+
// In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr
185+
// These error messages are useless when using pylint
186+
if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) {
187+
return Promise.resolve(stdout + '\n' + stderr);
188+
}
189+
if (!includeErrorAsResponse && stderr.length > 0) {
190+
return Promise.reject(stderr);
191+
}
192+
193+
return Promise.resolve(stdout + '');
194+
}
195+
function execPythonModule(file: string, args: string[], options: child_process.ExecFileOptions, includeErrorAsResponse: boolean, token?: CancellationToken): Promise<string> {
196+
options.maxBuffer = options.maxBuffer ? options.maxBuffer : 1024 * 102400;
197+
return new Promise<string>((resolve, reject) => {
198+
let proc = child_process.execFile(file, args, options, (error, stdout, stderr) => {
199+
handlePythonModuleResponse(includeErrorAsResponse, error, stdout, stderr, token)
200+
.then(resolve)
201+
.catch(reject);
202+
});
203+
if (token && token.onCancellationRequested) {
204+
token.onCancellationRequested(() => {
205+
if (proc) {
206+
proc.kill();
207+
proc = null;
208+
}
209+
});
210+
}
173211
});
174212
}
213+
175214
function execFileInternal(file: string, args: string[], options: child_process.ExecFileOptions, includeErrorAsResponse: boolean, token?: CancellationToken): Promise<string> {
176215
options.maxBuffer = options.maxBuffer ? options.maxBuffer : 1024 * 102400;
177216
return new Promise<string>((resolve, reject) => {
178217
let proc = child_process.execFile(file, args, options, (error, stdout, stderr) => {
179-
handleResponse(file, includeErrorAsResponse, error, stdout, stderr, token).then(resolve, reject);
218+
handleResponse(file, includeErrorAsResponse, error, stdout, stderr, token)
219+
.then(data => resolve(data))
220+
.catch(err => reject(err));
180221
});
181222
if (token && token.onCancellationRequested) {
182223
token.onCancellationRequested(() => {
@@ -202,7 +243,7 @@ function spawnFileInternal(file: string, args: string[], options: child_process.
202243
});
203244
}
204245
proc.on('error', error => {
205-
return reject(error);
246+
reject(error);
206247
});
207248
proc.stdout.setEncoding('utf8');
208249
proc.stderr.setEncoding('utf8');
@@ -243,7 +284,9 @@ function spawnFileInternal(file: string, args: string[], options: child_process.
243284
function execInternal(command: string, args: string[], options: child_process.ExecFileOptions, includeErrorAsResponse: boolean): Promise<string> {
244285
return new Promise<string>((resolve, reject) => {
245286
child_process.exec([command].concat(args).join(' '), options, (error, stdout, stderr) => {
246-
handleResponse(command, includeErrorAsResponse, error, stdout, stderr).then(resolve, reject);
287+
handleResponse(command, includeErrorAsResponse, error, stdout, stderr)
288+
.then(data => resolve(data))
289+
.catch(err => reject(err));
247290
});
248291
});
249292
}
@@ -308,9 +351,7 @@ export function getCustomEnvVars(): any {
308351
}
309352
}
310353
catch (ex) {
311-
console.error('Failed to load env file');
312-
console.error(ex);
313-
return null;
354+
console.error('Failed to load env file', ex);
314355
}
315356
}
316357
return null;

0 commit comments

Comments
 (0)