Skip to content

Commit 4758b35

Browse files
authored
Fix linting under conda run (#18390)
* Fix linting under conda run. * Fix tests * Fix for 2.7
1 parent f7ab5b5 commit 4758b35

23 files changed

Lines changed: 142 additions & 64 deletions

news/2 Fixes/18364.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure linting works under `conda run` (work-around for https://github.com/conda/conda/issues/10972).

pythonFiles/linter.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import subprocess
2+
import sys
3+
4+
5+
linter_settings = {
6+
"pylint": {
7+
"args": ["--reports=n", "--output-format=json"],
8+
},
9+
"flake8": {
10+
"args": ["--format", "%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s"],
11+
},
12+
"bandit": {
13+
"args": [
14+
"-f",
15+
"custom",
16+
"--msg-template",
17+
"{line},{col},{severity},{test_id}:{msg}",
18+
"-n",
19+
"-1",
20+
],
21+
},
22+
"mypy": {"args": []},
23+
"prospector": {
24+
"args": ["--absolute-paths", "--output-format=json"],
25+
},
26+
"pycodestyle": {
27+
"args": ["--format", "%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s"],
28+
},
29+
"pydocstyle": {
30+
"args": [],
31+
},
32+
"pylama": {"args": ["--format=parsable"]},
33+
}
34+
35+
36+
def main():
37+
invoke = sys.argv[1]
38+
if invoke == "-m":
39+
linter = sys.argv[2]
40+
args = (
41+
[sys.executable, "-m", linter]
42+
+ linter_settings[linter]["args"]
43+
+ sys.argv[3:]
44+
)
45+
else:
46+
linter = sys.argv[2]
47+
args = [sys.argv[3]] + linter_settings[linter]["args"] + sys.argv[4:]
48+
49+
if hasattr(subprocess, "run"):
50+
subprocess.run(args, encoding="utf-8", stdout=sys.stdout, stderr=sys.stderr)
51+
else:
52+
subprocess.call(args, stdout=sys.stdout, stderr=sys.stderr)
53+
54+
55+
if __name__ == "__main__":
56+
main()

src/client/common/process/internal/scripts/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,10 @@ export function tensorboardLauncher(args: string[]): string[] {
144144
const script = path.join(SCRIPTS_DIR, 'tensorboard_launcher.py');
145145
return [script, ...args];
146146
}
147+
148+
// linter.py
149+
150+
export function linterScript(): string {
151+
const script = path.join(SCRIPTS_DIR, 'linter.py');
152+
return script;
153+
}

src/client/common/process/pythonExecutionFactory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,5 +191,6 @@ function createPythonService(
191191
execModuleObservable: (m, a, o) => procs.execModuleObservable(m, a, o),
192192
exec: (a, o) => procs.exec(a, o),
193193
execModule: (m, a, o) => procs.execModule(m, a, o),
194+
execForLinter: (m, a, o) => procs.execForLinter(m, a, o),
194195
};
195196
}

src/client/common/process/pythonProcess.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ class PythonProcessService {
6464

6565
return result;
6666
}
67+
68+
public async execForLinter(
69+
moduleName: string,
70+
args: string[],
71+
options: SpawnOptions,
72+
): Promise<ExecutionResult<string>> {
73+
const opts: SpawnOptions = { ...options };
74+
const executable = this.deps.getExecutionInfo(args);
75+
const result = await this.deps.exec(executable.command, executable.args, opts);
76+
77+
// If a module is not installed we'll have something in stderr.
78+
if (moduleName && ErrorUtils.outputHasModuleNotInstalledError(moduleName, result.stderr)) {
79+
const isInstalled = await this.deps.isModuleInstalled(moduleName);
80+
if (!isInstalled) {
81+
throw new ModuleNotInstalledError(moduleName);
82+
}
83+
}
84+
85+
return result;
86+
}
6787
}
6888

6989
export function createPythonProcessService(

src/client/common/process/pythonToolService.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,23 @@ export class PythonToolExecutionService implements IPythonToolExecutionService {
5757
return processService.exec(executionInfo.execPath!, executionInfo.args, { ...options });
5858
}
5959
}
60+
61+
public async execForLinter(
62+
executionInfo: ExecutionInfo,
63+
options: SpawnOptions,
64+
resource: Uri,
65+
): Promise<ExecutionResult<string>> {
66+
if (options.env) {
67+
throw new Error('Environment variables are not supported');
68+
}
69+
const pythonExecutionService = await this.serviceContainer
70+
.get<IPythonExecutionFactory>(IPythonExecutionFactory)
71+
.create({ resource });
72+
73+
if (executionInfo.execPath) {
74+
return pythonExecutionService.exec(executionInfo.args, options);
75+
}
76+
77+
return pythonExecutionService.execForLinter(executionInfo.moduleName!, executionInfo.args, options);
78+
}
6079
}

src/client/common/process/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export interface IPythonExecutionService {
100100

101101
exec(args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
102102
execModule(moduleName: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
103+
execForLinter(moduleName: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
103104
}
104105

105106
export class StdErrError extends Error {
@@ -117,4 +118,5 @@ export interface IPythonToolExecutionService {
117118
resource: Uri,
118119
): Promise<ObservableExecutionResult<string>>;
119120
exec(executionInfo: ExecutionInfo, options: SpawnOptions, resource: Uri): Promise<ExecutionResult<string>>;
121+
execForLinter(executionInfo: ExecutionInfo, options: SpawnOptions, resource: Uri): Promise<ExecutionResult<string>>;
120122
}

src/client/linters/bandit.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,7 @@ export class Bandit extends BaseLinter {
2626

2727
protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise<ILintMessage[]> {
2828
// View all errors in bandit <= 1.5.1 (https://github.com/PyCQA/bandit/issues/371)
29-
const messages = await this.run(
30-
[
31-
'-f',
32-
'custom',
33-
'--msg-template',
34-
'{line},{col},{severity},{test_id}:{msg}',
35-
'-n',
36-
'-1',
37-
document.uri.fsPath,
38-
],
39-
document,
40-
cancellation,
41-
BANDIT_REGEX,
42-
);
29+
const messages = await this.run([document.uri.fsPath], document, cancellation, BANDIT_REGEX);
4330

4431
messages.forEach((msg) => {
4532
msg.severity = severityMapping[msg.type];

src/client/linters/baseLinter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export abstract class BaseLinter implements ILinter {
165165
IPythonToolExecutionService,
166166
);
167167
try {
168-
const result = await pythonToolsExecutionService.exec(
168+
const result = await pythonToolsExecutionService.execForLinter(
169169
executionInfo,
170170
{ cwd, token: cancellation, mergeStdOutErr: false },
171171
document.uri,

src/client/linters/flake8.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ export class Flake8 extends BaseLinter {
1313
}
1414

1515
protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise<ILintMessage[]> {
16-
const messages = await this.run(
17-
['--format= %(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath],
18-
document,
19-
cancellation,
20-
);
16+
const messages = await this.run([document.uri.fsPath], document, cancellation);
2117
messages.forEach((msg) => {
2218
msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.flake8CategorySeverity);
2319
// flake8 uses 0th line for some file-wide problems

0 commit comments

Comments
 (0)