Skip to content

Commit 558c930

Browse files
Add cwd setting and support for env, interpreter and fileDirname substitutions (#106)
* Add support for interpreter and env substitution * Add cwd setting * Update README variable substitution - fix showNotification typo * Update variable substitution table in README * Fix missing closing brace * Fix missing blank line for Black formatting
1 parent feed32e commit 558c930

File tree

7 files changed

+61
-17
lines changed

7 files changed

+61
-17
lines changed

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@ There are several settings you can configure to customize the behavior of this e
1919
| `bandit.path` | `[]` | Path or command used by the extension to run Bandit. Accepts an array of strings (each arg separate). <br> Examples: <br> - `"bandit.path": ["~/global_env/bandit"]` <br> - `"bandit.path": ["bandit"]` <br> - `"bandit.path": ["${interpreter}", "-m", "bandit"]` <br> If set to `["bandit"]`, it uses the Bandit available in your `PATH`. Note: Using a custom path may slow down linting. |
2020
| `bandit.interpreter` | `[]` | Python executable or command used to launch Bandit. Accepts an array of strings (each arg separate). If left as `[]`, it uses the selected Python interpreter. |
2121
| `bandit.importStrategy` | `useBundled` | Specifies which Bandit binary to use. `useBundled` uses the version shipped with the extension. `fromEnvironment` uses the Bandit in the current Python environment. If it can't find one, it falls back to the bundled version. Overridden if `bandit.path` is set. |
22-
| `bandit.showNotification` | `off` | Controls when extension notifications appear. Options: `onError`, `onWarning`, `always`, `off`. |
22+
| `bandit.showNotifications` | `off` | Controls when extension notifications appear. Options: `onError`, `onWarning`, `always`, `off`. |
2323

24-
The following variables are supported for substitution in the `bandit.args`, `bandit.cwd`, `bandit.path`, and `bandit.interpreter` settings:
24+
### Variable Substitution
2525

26-
- `${workspaceFolder}`
27-
- `${workspaceFolder:FolderName}`
28-
- `${userHome}`
29-
- `${env:EnvVarName}`
26+
The following variables are supported for substitution in the `bandit.args`, `bandit.cwd`, and `bandit.path` settings:
3027

31-
The `bandit.path` setting also supports the `${interpreter}` variable as one of the entries of the array. This variable is subtituted based on the value of the `bandit.interpreter` setting.
28+
| Variable | Description | Supported In |
29+
|---------------------------------|--------------------------------------------------------------------------------|-----------------------|
30+
| `${workspaceFolder}` | Root directory of the currently active workspace folder | `args`, `cwd`, `path` |
31+
| `${workspaceFolder:FolderName}` | Root directory of a specific workspace folder in a multi-root workspace | `args`, `path` |
32+
| `${userHome}` | Path to the current user's home directory | `args`, `path` |
33+
| `${env:EnvVarName}` | Value of the environment variable `EnvVarName` | `args`, `path` |
34+
| `${interpreter}` | Python executable used by Bandit (workspace interpreter or bandit.interpreter) | `path` |
35+
| `${fileDirname}` | Directory of the file being linted | `cwd` |
3236

3337
## Commands
3438

bundled/tool/lsp_server.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import pathlib
1010
import sys
1111
import traceback
12-
from typing import Any, Optional, Sequence
12+
from typing import Any, Dict, Optional, Sequence
1313

1414

1515
# **********************************************************
@@ -341,6 +341,19 @@ def _get_settings_by_document(document: workspace.Document | None):
341341
# *****************************************************
342342
# Internal execution APIs.
343343
# *****************************************************
344+
def get_cwd(settings: Dict[str, Any], document: Optional[workspace.Document]) -> str:
345+
"""Returns cwd for the given settings and document."""
346+
if settings["cwd"] == "${workspaceFolder}":
347+
return settings["workspaceFS"]
348+
349+
if settings["cwd"] == "${fileDirname}":
350+
if document is not None:
351+
return os.fspath(pathlib.Path(document.path).parent)
352+
return settings["workspaceFS"]
353+
354+
return settings["cwd"]
355+
356+
344357
# pylint: disable=too-many-branches,too-many-statements
345358
def _run_tool_on_document(
346359
document: workspace.Document,
@@ -374,7 +387,7 @@ def _run_tool_on_document(
374387
return None
375388

376389
code_workspace = settings["workspaceFS"]
377-
cwd = settings["cwd"]
390+
cwd = get_cwd(settings, document)
378391

379392
use_path = False
380393
use_rpc = False
@@ -469,7 +482,7 @@ def _run_tool(extra_args: Sequence[str]) -> utils.RunResult:
469482
settings = copy.deepcopy(_get_settings_by_document(None))
470483

471484
code_workspace = settings["workspaceFS"]
472-
cwd = settings["workspaceFS"]
485+
cwd = get_cwd(settings, None)
473486

474487
use_path = False
475488
use_rpc = False

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@
8686
"scope": "resource",
8787
"type": "array"
8888
},
89+
"bandit.cwd": {
90+
"default": "${workspaceFolder}",
91+
"markdownDescription": "%settings.cwd.description%",
92+
"scope": "resource",
93+
"type": "string",
94+
"examples": [
95+
"${workspaceFolder}/src",
96+
"${fileDirname}"
97+
]
98+
},
8999
"bandit.enabled": {
90100
"default": true,
91101
"markdownDescription": "%settings.enabled.description%",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"command.restartServer": "Restart Server",
33
"extension.description": "The official Bandit extension developed by PyCQA",
44
"settings.args.description": "Arguments passed in. Each argument is a separate item in the array.",
5+
"settings.cwd.description": "Working directory for Bandit. Defaults to workspace folder if not set.",
56
"settings.enabled.description": "Enable/disable linting Python files with Bandit.",
67
"settings.path.description": "When set to a path to bandit binary, extension will use that. NOTE: Using this option may slowdown server response time.",
78
"settings.importStrategy.description": "Defines where `bandit` is imported from. This setting may be ignored if `bandit.path` is set.",

src/common/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License.
33

44
import * as fsapi from 'fs-extra';
5-
import { Disposable, env, LogOutputChannel } from 'vscode';
5+
import { Disposable, env, LogOutputChannel, Uri } from 'vscode';
66
import { State } from 'vscode-languageclient';
77
import {
88
LanguageClient,
@@ -27,7 +27,7 @@ async function createServer(
2727
initializationOptions: IInitOptions,
2828
): Promise<LanguageClient> {
2929
const command = settings.interpreter[0];
30-
const cwd = settings.cwd;
30+
const cwd = settings.cwd === '${fileDirname}' ? Uri.parse(settings.workspace).fsPath : settings.cwd;
3131

3232
// Set debugger path needed for debugging python code.
3333
const newEnv = { ...process.env };

src/common/settings.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function getExtensionSettings(namespace: string, includeInterpreter?: boo
1919
return Promise.all(getWorkspaceFolders().map((w) => getWorkspaceSettings(namespace, w, includeInterpreter)));
2020
}
2121

22-
function resolveVariables(value: string[], workspace?: WorkspaceFolder): string[] {
22+
function resolveVariables(value: string[], workspace?: WorkspaceFolder, interpreter?: string[]): string[] {
2323
const substitutions = new Map<string, string>();
2424
const home = process.env.HOME || process.env.USERPROFILE;
2525
if (home) {
@@ -32,6 +32,14 @@ function resolveVariables(value: string[], workspace?: WorkspaceFolder): string[
3232
getWorkspaceFolders().forEach((w) => {
3333
substitutions.set('${workspaceFolder:' + w.name + '}', w.uri.fsPath);
3434
});
35+
for (const [envVar, envValue] of Object.entries(process.env)) {
36+
if (envValue) {
37+
substitutions.set('${env:' + envVar + '}', envValue);
38+
}
39+
}
40+
if (interpreter) {
41+
value = value.flatMap((v) => (v === '${interpreter}' ? interpreter : v));
42+
}
3543

3644
return value.map((s) => {
3745
for (const [key, value] of substitutions) {
@@ -41,6 +49,11 @@ function resolveVariables(value: string[], workspace?: WorkspaceFolder): string[
4149
});
4250
}
4351

52+
function getCwd(config: WorkspaceConfiguration, workspace: WorkspaceFolder): string {
53+
const cwd = config.get<string>('cwd', workspace.uri.fsPath);
54+
return resolveVariables([cwd], workspace)[0];
55+
}
56+
4457
export function getInterpreterFromSetting(namespace: string, scope?: ConfigurationScope) {
4558
const config = getConfiguration(namespace, scope);
4659
return config.get<string[]>('interpreter');
@@ -59,15 +72,16 @@ export async function getWorkspaceSettings(
5972
if (interpreter.length === 0) {
6073
interpreter = (await getInterpreterDetails(workspace.uri)).path ?? [];
6174
}
75+
interpreter = resolveVariables(interpreter, workspace);
6276
}
6377

6478
const workspaceSetting = {
6579
enabled: config.get<boolean>('enabled', true),
66-
cwd: workspace.uri.fsPath,
80+
cwd: getCwd(config, workspace),
6781
workspace: workspace.uri.toString(),
6882
args: resolveVariables(config.get<string[]>(`args`) ?? [], workspace),
69-
path: resolveVariables(config.get<string[]>(`path`) ?? [], workspace),
70-
interpreter: resolveVariables(interpreter, workspace),
83+
path: resolveVariables(config.get<string[]>(`path`) ?? [], workspace, interpreter),
84+
interpreter: interpreter,
7185
importStrategy: config.get<string>(`importStrategy`) ?? 'useBundled',
7286
showNotifications: config.get<string>(`showNotifications`) ?? 'off',
7387
};
@@ -91,7 +105,7 @@ export async function getGlobalSettings(namespace: string, includeInterpreter?:
91105
}
92106

93107
const setting = {
94-
cwd: process.cwd(),
108+
cwd: getGlobalValue<string>(config, 'cwd', process.cwd()),
95109
enabled: getGlobalValue<boolean>(config, 'enabled', true),
96110
workspace: process.cwd(),
97111
args: getGlobalValue<string[]>(config, 'args', []),
@@ -106,6 +120,7 @@ export async function getGlobalSettings(namespace: string, includeInterpreter?:
106120
export function checkIfConfigurationChanged(e: ConfigurationChangeEvent, namespace: string): boolean {
107121
const settings = [
108122
`${namespace}.args`,
123+
`${namespace}.cwd`,
109124
`${namespace}.enabled`,
110125
`${namespace}.path`,
111126
`${namespace}.interpreter`,

src/test/python_tests/lsp_test_client/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ def get_initialization_options():
6969

7070
setting["workspace"] = as_uri(str(PROJECT_ROOT))
7171
setting["interpreter"] = []
72+
setting["cwd"] = str(PROJECT_ROOT)
7273

7374
return {"settings": [setting]}

0 commit comments

Comments
 (0)