Skip to content

Commit

Permalink
Add Ignore Patterns and Include stdlib
Browse files Browse the repository at this point in the history
  • Loading branch information
karthiknadig committed Oct 19, 2023
1 parent 2479dc2 commit 945abe9
Show file tree
Hide file tree
Showing 20 changed files with 872 additions and 358 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ If you want to disable mypy, you can [disable this extension](https://code.visua
| Settings | Default | Description |
| ----------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| mypy-type-checker.args | `[]` | Custom arguments passed to `mypy`. E.g `"mypy-type-checker.args" = ["--config-file=<file>"]` |
| mypy-type-checker.cwd | `${workspaceFolder}` | This setting specifies the working directory for `mypy`. By default, it uses the root directory of the workspace `${workspaceFolder}`. If you want `mypy` to operate within the directory of the file currently being linted, you can set this to `${fileDirname}`. |
| mypy-type-checker.severity | `{ "error": "Error", "note": "Information" }` | Controls mapping of severity from `mypy` to VS Code severity when displaying in the problems window. You can override specific `mypy` error codes `{ "error": "Error", "note": "Information", "name-defined": "Warning" }` |
| mypy-type-checker.path | `[]` | Setting to provide custom `mypy` executable. This will slow down linting, since we will have to run `mypy` executable every time or file save or open. Example 1: `["~/global_env/mypy"]` Example 2: `["conda", "run", "-n", "lint_env", "python", "-m", "mypy"]` |
| mypy-type-checker.interpreter | `[]` | Path to a Python interpreter to use to run the linter server. When set to `[]`, the interpreter for the workspace is obtained from `ms-python.python` extension. If set to some path, that path takes precedence, and the Python extension is not queried for the interpreter. |
| mypy-type-checker.importStrategy | `useBundled` | Setting to choose where to load `mypy` from. `useBundled` picks mypy bundled with the extension. `fromEnvironment` uses `mypy` available in the environment. |
| mypy-type-checker.showNotifications | `off` | Setting to control when a notification is shown. |
| mypy-type-checker.reportingScope | `file` | (experimental) Setting to control if problems are reported for files open in the editor (`file`) or for the entire workspace (`workspace`). |
| mypy-type-checker.preferDaemon | true | (experimental) Setting to control how to invoke mypy. If true, `dmypy` is preferred over mypy; otherwise, mypy is preferred. Be aware, that the latter may slow down linting since it requires the `mypy` executable to be run whenever a file is saved or opened. Note that this setting will be overridden if `mypy-type-checker.path` is set. |
| mypy-type-checker.preferDaemon | `true` | (experimental) Setting to control how to invoke mypy. If true, `dmypy` is preferred over mypy; otherwise, mypy is preferred. Be aware, that the latter may slow down linting since it requires the `mypy` executable to be run whenever a file is saved or opened. Note that this setting will be overridden if `mypy-type-checker.path` is set. |
| mypy-type-checker.ignorePatterns | `[]` | Glob patterns used to exclude files and directories from being linted. |
| mypy-type-checker.includeStdLib | `false` | Controls whether to perform linting on Python's standard library files or directories. |

## Commands

Expand Down
99 changes: 84 additions & 15 deletions bundled/tool/lsp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pathlib
import re
import sys
import sysconfig
import tempfile
import traceback
import uuid
Expand All @@ -28,6 +29,26 @@ def update_sys_path(path_to_add: str, strategy: str) -> None:
sys.path.append(path_to_add)


# **********************************************************
# Update PATH before running anything.
# **********************************************************
def update_environ_path() -> None:
"""Update PATH environment variable with the 'scripts' directory.
Windows: .venv/Scripts
Linux/MacOS: .venv/bin
"""
scripts = sysconfig.get_path("scripts")
paths_variants = ["Path", "PATH"]

for var_name in paths_variants:
if var_name in os.environ:
paths = os.environ[var_name].split(os.pathsep)
if scripts not in paths:
paths.insert(0, scripts)
os.environ[var_name] = os.pathsep.join(paths)
break


# Ensure that we can import LSP libraries, and other bundled libraries.
BUNDLE_DIR = pathlib.Path(__file__).parent.parent
BUNDLED_LIBS = os.fspath(BUNDLE_DIR / "libs")
Expand All @@ -37,6 +58,7 @@ def update_sys_path(path_to_add: str, strategy: str) -> None:
BUNDLED_LIBS,
os.getenv("LS_IMPORT_STRATEGY", "useBundled"),
)
update_environ_path()

# **********************************************************
# Imports needed for the language server goes below this.
Expand Down Expand Up @@ -90,6 +112,9 @@ class MypyInfo:

def get_mypy_info(settings: Dict[str, Any]) -> MypyInfo:
try:
if len(MYPY_INFO_TABLE) == 1:
# If there is only one workspace, just return the first value.
return list(MYPY_INFO_TABLE.values())[0]
code_workspace = settings["workspaceFS"]
if code_workspace not in MYPY_INFO_TABLE:
# This is text we get from running `mypy --version`
Expand Down Expand Up @@ -117,7 +142,7 @@ def _run_unidentified_tool(
This is supposed to be called only in `get_mypy_info`.
"""
cwd = settings["cwd"]
cwd = get_cwd(settings, None)

if settings["path"]:
argv = settings["path"]
Expand Down Expand Up @@ -158,7 +183,7 @@ def did_close(params: lsp.DidCloseTextDocumentParams) -> None:
settings = _get_settings_by_document(document)
if settings["reportingScope"] == "file":
# Publishing empty diagnostics to clear the entries for this file.
LSP_SERVER.publish_diagnostics(document.uri, [])
_clear_diagnostics(document)


def _is_empty_diagnostics(
Expand All @@ -175,6 +200,10 @@ def _is_empty_diagnostics(
_reported_file_paths = set()


def _clear_diagnostics(document: workspace.Document) -> None:
LSP_SERVER.publish_diagnostics(document.uri, [])


def _linting_helper(document: workspace.Document) -> None:
global _reported_file_paths
try:
Expand All @@ -183,6 +212,35 @@ def _linting_helper(document: workspace.Document) -> None:
# deep copy here to prevent accidentally updating global settings.
settings = copy.deepcopy(_get_settings_by_document(document))

if str(document.uri).startswith("vscode-notebook-cell"):
# We don't support running mypy on notebook cells.
log_warning(f"Skipping notebook cells [Not Supported]: {str(document.uri)}")
_clear_diagnostics(document)
return None

if (
not settings["includeStdLib"]
and settings["reportingScope"] == "file"
and utils.is_stdlib_file(document.path)
):
log_warning(
f"Skipping standard library file (stdlib excluded): {document.path}"
)
log_warning(
"You can include stdlib files by setting `mypy-type-checker.includeStdLib` to true."
)
_clear_diagnostics(document)
return None

if settings["reportingScope"] == "file" and utils.is_match(
settings["ignorePatterns"], document.path
):
log_warning(
f"Skipping file due to `mypy-type-checker.ignorePatterns` match: {document.path}"
)
_clear_diagnostics(document)
return None

version = get_mypy_info(settings).version
if (version.major, version.minor) >= (0, 991) and sys.version_info >= (3, 8):
extra_args += ["--show-error-end"]
Expand Down Expand Up @@ -210,15 +268,15 @@ def _linting_helper(document: workspace.Document) -> None:
if _is_empty_diagnostics(document.path, parse_results):
# Ensure that if nothing is returned for this document, at least
# an empty diagnostic is returned to clear any old errors out.
LSP_SERVER.publish_diagnostics(document.uri, [])
_clear_diagnostics(document)

if reportingScope == "workspace":
for file_path in _reported_file_paths:
if file_path not in parse_results:
uri = uris.from_fs_path(file_path)
LSP_SERVER.publish_diagnostics(uri, [])
else:
LSP_SERVER.publish_diagnostics(document.uri, [])
_clear_diagnostics(document)
except Exception:
LSP_SERVER.show_message_log(
f"Linting failed with error:\r\n{traceback.format_exc()}",
Expand Down Expand Up @@ -447,10 +505,13 @@ def _get_global_defaults():
"note": "Information",
},
),
"ignorePatterns": [],
"importStrategy": GLOBAL_SETTINGS.get("importStrategy", "useBundled"),
"showNotifications": GLOBAL_SETTINGS.get("showNotifications", "off"),
"extraPaths": GLOBAL_SETTINGS.get("extraPaths", []),
"includeStdLib": GLOBAL_SETTINGS.get("includeStdLib", False),
"reportingScope": GLOBAL_SETTINGS.get("reportingScope", "file"),
"preferDaemon": GLOBAL_SETTINGS.get("preferDaemon", True),
}


Expand Down Expand Up @@ -590,27 +651,35 @@ def _get_env_vars(settings: Dict[str, Any]) -> Dict[str, str]:
return new_env


def get_cwd(settings: Dict[str, Any], document: Optional[workspace.Document]) -> str:
"""Returns cwd for the given settings and document."""
if settings["cwd"] == "${workspaceFolder}":
return settings["workspaceFS"]

if settings["cwd"] == "${fileDirname}":
if document is not None:
return os.fspath(pathlib.Path(document.path).parent)
return settings["workspaceFS"]

return settings["cwd"]


def _run_tool_on_document(
document: workspace.Document,
extra_args: Sequence[str] = [],
extra_args: Sequence[str] = None,
) -> utils.RunResult | None:
"""Runs tool on the given document.
if use_stdin is true then contents of the document is passed to the
tool via stdin.
"""
if str(document.uri).startswith("vscode-notebook-cell"):
# We don't support running mypy on notebook cells.
log_to_output("Skipping mypy on notebook cells.")
return None

if utils.is_stdlib_file(document.path):
log_to_output("Skipping mypy on stdlib file: " + document.path)
return None
if extra_args is None:
extra_args = []

# deep copy here to prevent accidentally updating global settings.
settings = copy.deepcopy(_get_settings_by_document(document))
cwd = settings["cwd"]

cwd = get_cwd(settings, document)

if settings["path"]:
argv = settings["path"]
Expand Down Expand Up @@ -646,7 +715,7 @@ def _run_dmypy_command(
log_error(f"dmypy command called in non-daemon context: {command}")
raise ValueError(f"dmypy command called in non-daemon context: {command}")

cwd = settings["cwd"]
cwd = get_cwd(settings, None)

if settings["path"]:
argv = settings["path"]
Expand Down
7 changes: 7 additions & 0 deletions bundled/tool/lsp_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ def is_stdlib_file(file_path: str) -> bool:
return any(normalized_path.startswith(path) for path in _stdlib_paths)


def is_match(patterns: List[str], file_path: str) -> bool:
"""Returns true if the file matches one of the glob patterns."""
if not patterns:
return False
return any(pathlib.Path(file_path).match(pattern) for pattern in patterns)


# pylint: disable-next=too-few-public-methods
class RunResult:
"""Object to hold result from running tool."""
Expand Down
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@
"scope": "resource",
"type": "array"
},
"mypy-type-checker.cwd": {
"default": "${workspaceFolder}",
"description": "%settings.cwd.description%",
"scope": "resource",
"type": "string",
"examples": [
"${workspaceFolder}/src",
"${fileDirname}"
]
},
"mypy-type-checker.path": {
"default": [],
"markdownDescription": "%settings.path.description%",
Expand All @@ -83,6 +93,22 @@
},
"type": "array"
},
"mypy-type-checker.ignorePatterns": {
"default": [],
"description": "%settings.ignorePatterns.description%",
"items": {
"type": "string"
},
"scope": "resource",
"type": "array",
"uniqueItems": true,
"examples": [
[
"**/site-packages/**/*.py",
".vscode/*.py"
]
]
},
"mypy-type-checker.importStrategy": {
"default": "useBundled",
"markdownDescription": "%settings.importStrategy.description%",
Expand All @@ -97,6 +123,12 @@
"scope": "window",
"type": "string"
},
"mypy-type-checker.includeStdLib": {
"default": false,
"description": "%settings.includeStdLib.description%",
"scope": "resource",
"type": "boolean"
},
"mypy-type-checker.interpreter": {
"default": [],
"markdownDescription": "%settings.interpreter.description%",
Expand Down
5 changes: 4 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"extension.description": "Linting support for Python files using `mypy`.",
"command.restartServer": "Restart Server",
"settings.args.description": "Arguments passed in. Each argument is a separate string in the array.",
"settings.cwd.description": "The current working directory used to run `mypy`. To use the directory of the file currently being linted, you can use `${fileDirname}` .",
"settings.severity.description": "Mapping from severity of `mypy` message type to severity shown in problem window.",
"settings.path.description": "When set to a path to `mypy` binary, extension will use that for linting. NOTE: Using this option may slowdown linting.",
"settings.ignorePatterns.description": "Patterns used to exclude files or folders from being linted. Applies to `reportingScope==file` only.",
"settings.importStrategy.description": "Defines where `mypy` is imported from. This setting may be ignored if `mypy-type-checker.path` is set.",
"settings.importStrategy.useBundled.description": "Always use the bundled version of `mypy`.",
"settings.importStrategy.fromEnvironment.description": "Use `mypy` from environment, fallback to bundled version only if `mypy` not available in the environment.",
Expand All @@ -16,5 +18,6 @@
"settings.showNotifications.off.description": "All notifications are turned off, any errors or warning are still available in the logs.",
"settings.showNotifications.onError.description": "Notifications are shown only in the case of an error.",
"settings.showNotifications.onWarning.description": "Notifications are shown for errors and warnings.",
"settings.showNotifications.always.description": "Notifications are show for anything that the server chooses to show."
"settings.showNotifications.always.description": "Notifications are show for anything that the server chooses to show.",
"settings.includeStdLib.description": "Controls whether to filter out files from standard library from being linted. Applies to `reportingScope==file` only."
}
3 changes: 3 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ export const EXTENSION_ROOT_DIR =
export const BUNDLED_PYTHON_SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'bundled');
export const SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, 'tool', `lsp_server.py`);
export const DEBUG_SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, 'tool', `_debug_server.py`);
export const PYTHON_MAJOR = 3;
export const PYTHON_MINOR = 8;
export const PYTHON_VERSION = `${PYTHON_MAJOR}.${PYTHON_MINOR}`;
5 changes: 3 additions & 2 deletions src/common/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { commands, Disposable, Event, EventEmitter, Uri } from 'vscode';
import { traceError, traceLog } from './logging';
import { PythonExtension, ResolvedEnvironment } from '@vscode/python-extension';
import { PYTHON_MAJOR, PYTHON_MINOR, PYTHON_VERSION } from './constants';

export interface IInterpreterDetails {
path?: string[];
Expand Down Expand Up @@ -70,11 +71,11 @@ export async function runPythonExtensionCommand(command: string, ...rest: any[])

export function checkVersion(resolved: ResolvedEnvironment | undefined): boolean {
const version = resolved?.version;
if (version?.major === 3 && version?.minor >= 8) {
if (version?.major === PYTHON_MAJOR && version?.minor >= PYTHON_MINOR) {
return true;
}
traceError(`Python version ${version?.major}.${version?.minor} is not supported.`);
traceError(`Selected python path: ${resolved?.executable.uri?.fsPath}`);
traceError('Supported versions are 3.8 and above.');
traceError(`Supported versions are ${PYTHON_VERSION} and above.`);
return false;
}
Loading

0 comments on commit 945abe9

Please sign in to comment.