Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ There are several settings you can configure to customize the behavior of this e
<tr>
<td>mypy-type-checker.reportingScope</td>
<td><code>file</code></td>
<td>(experimental) Controls the scope of Mypy's problem reporting. If set to <code>file</code>, Mypy will limit its problem reporting to the files currently open in the editor. If set to <code>workspace</code>, Mypy will extend its problem reporting to include all files within the workspace.</td>
<td>(experimental) Controls the scope of Mypy's problem reporting. If set to <code>file</code>, Mypy will limit its problem reporting to the files currently open in the editor. If set to <code>workspace</code>, Mypy will extend its problem reporting to include all files within the workspace.
If set to <code>>custom</code>, Mypy will only report problems for files specified in the [Mypy configuration](https://mypy.readthedocs.io/en/stable/config_file.html#confval-files)</td>
</tr>
<tr>
<td>mypy-type-checker.preferDaemon</td>
Expand Down
9 changes: 6 additions & 3 deletions bundled/tool/lsp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def _linting_helper(document: workspace.Document) -> None:
# documents; this is fine/correct, we just need to account for it).
if reportingScope == "file" and is_file_same_as_document:
LSP_SERVER.publish_diagnostics(document.uri, diagnostics)
elif reportingScope == "workspace":
elif reportingScope in ("workspace", "custom"):
_reported_file_paths.add(file_path)
uri = uris.from_fs_path(file_path)
LSP_SERVER.publish_diagnostics(uri, diagnostics)
Expand All @@ -260,7 +260,7 @@ def _linting_helper(document: workspace.Document) -> None:
# an empty diagnostic is returned to clear any old errors out.
_clear_diagnostics(document)

if reportingScope == "workspace":
if reportingScope == ("workspace", "custom"):
for file_path in _reported_file_paths:
if file_path not in parse_results:
uri = uris.from_fs_path(file_path)
Expand Down Expand Up @@ -713,8 +713,11 @@ def _run_tool_on_document(
# correct capitalization to avoid https://github.com/python/mypy/issues/18590#issuecomment-2630249041
argv += [str(pathlib.Path(document.path).resolve())]
cwd = str(pathlib.Path(cwd).resolve())
else:
elif settings["reportingScope"] == "workspace":
argv += [cwd]
elif settings["reportingScope"] == "custom":
# Let mypy use files defined by the configuration
pass

log_to_output(" ".join(argv))
log_to_output(f"CWD Server: {cwd}")
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,22 @@
"markdownDescription": "%settings.daemonStatusFile.description%",
"scope": "resource",
"type": "string",
"tags": ["experimental"]
"tags": [
"experimental"
]
},
"mypy-type-checker.reportingScope": {
"default": "file",
"markdownDescription": "%settings.reportingScope.description%",
"enum": [
"file",
"workspace"
"workspace",
"custom"
],
"markdownEnumDescriptions": [
"%settings.reportingScope.file.description%",
"%settings.reportingScope.workspace.description%"
"%settings.reportingScope.workspace.description%",
"%settings.reportingScope.custom.description%"
],
"scope": "resource",
"type": "string",
Expand Down
3 changes: 2 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
"settings.interpreter.description": "Path to a Python executable or a command that will be used to launch the Mypy server and any subprocess. Accepts an array of a single or multiple strings. When set to `[]`, the extension will use the path to the selected Python interpreter. If passing a command, each argument should be provided as a separate string in the array.",
"settings.preferDaemon.description": "Whether the Mypy daemon (`dmypy`) will take precedence over `mypy`for type checking. <br> Note: if `mypy-type-checker.reportingScope`is set to `workspace`, enabling the Mypy daemon will offer a faster type checking experience. This setting will be overridden if `mypy-type-checker.path`is set.",
"settings.daemonStatusFile.description": "Path to the status file used by the Mypy daemon (`dmypy`).",
"settings.reportingScope.description": "Controls the scope of Mypy's problem reporting. If set to `file`, Mypy will limit its problem reporting to the files currently open in the editor. If set to `workspace`, Mypy will extend its problem reporting to include all files within the workspace. ",
"settings.reportingScope.description": "Controls the scope of Mypy's problem reporting. If set to `file`, Mypy will limit its problem reporting to the files currently open in the editor. If set to `workspace`, Mypy will extend its problem reporting to include all files within the workspace. If set to `custom`, Mypy will only report problems for files specified in the Mypy configuration",
"settings.reportingScope.file.description": "Problems are reported for the files open in the editor only.",
"settings.reportingScope.workspace.description": "Problems are reported for all files within the workspace.",
"settings.reportingScope.custom.description": "Problems are reported for files specified in the mypy configuration.",
"settings.showNotifications.description": "Controls when notifications are shown by this extension.",
"settings.showNotifications.off.description": "Never display a notification. Any errors or warning are still available in the logs.",
"settings.showNotifications.onError.description": "Show notifications for errors.",
Expand Down
2 changes: 2 additions & 0 deletions src/test/python_tests/test_data/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.mypy]
files = ["sample1/sample.py"]
128 changes: 125 additions & 3 deletions src/test/python_tests/test_linting.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"""
Test for linting over LSP.
"""
from threading import Event
from threading import Event, Semaphore
from typing import List

import pytest
from hamcrest import assert_that, greater_than, is_
from hamcrest import assert_that, contains_inanyorder, greater_than, is_

from .lsp_test_client import constants, defaults, session, utils

Expand Down Expand Up @@ -513,6 +513,128 @@ def __eq__(self, other: object) -> bool:
],
},
]
assert_that(actual, contains_inanyorder(*expected))


def test_custom_reporting_scope():
"""Test reports are generated for files defined in the mypy configuration.

We have pyproject.toml in root folder, which sets only sample1/sample.py
to be checked. We open sample2.py, but expect diagnostics only for sample.py.
"""
TEST_FILE2_PATH = constants.TEST_DATA / "sample1" / "sample2.py"
TEST_FILE2_URI = utils.as_uri(str(TEST_FILE2_PATH))
contents = TEST_FILE2_PATH.read_text(encoding="utf-8")

actual = []
with session.LspSession() as ls_session:
default_init = defaults.vscode_initialize_defaults()
default_init["rootPath"] = str(constants.TEST_DATA)
default_init["rootUri"]: utils.as_uri(str(constants.TEST_DATA))
default_init["workspaceFolders"][0]["uri"] = utils.as_uri(
str(constants.TEST_DATA)
)
init_options = default_init["initializationOptions"]
init_options["settings"][0]["reportingScope"] = "custom"
init_options["settings"][0]["workspace"] = utils.as_uri(
str(constants.TEST_DATA)
)
init_options["settings"][0]["cwd"] = str(constants.TEST_DATA)
ls_session.initialize(default_init)

diagnostics = Semaphore(0)

def _handler(params):
params["uri"] = utils.normalizecase(params["uri"])
actual.append(params)
diagnostics.release()

def _log_handler(params):
print(params)

ls_session.set_notification_callback(session.WINDOW_LOG_MESSAGE, _log_handler)
ls_session.set_notification_callback(session.PUBLISH_DIAGNOSTICS, _handler)

ls_session.notify_did_open(
{
"textDocument": {
"uri": TEST_FILE2_URI,
"languageId": "python",
"version": 1,
"text": contents,
}
}
)

# Wait for the first diagnostic to come
assert diagnostics.acquire(timeout=TIMEOUT) is True
# After receiving a first diagnostic, we don't expect any more.
# Using a shorter timeout here, to avoid running test for too long.
# We know mypy has already started reporting diagnostics, so if there
# are any more, they should come pretty quick.
assert diagnostics.acquire(timeout=3) is False

expected = [
{
"uri": TEST_FILE_URI,
"diagnostics": [
{
"range": {
"start": {"line": 2, "character": 6},
"end": {
"line": 2,
"character": 7,
},
},
"message": 'Name "x" is not defined',
"severity": 1,
"code": "name-defined",
"codeDescription": {
"href": "https://mypy.readthedocs.io/en/latest/_refs.html#code-name-defined"
},
"source": "Mypy",
},
{
"range": {
"start": {"line": 6, "character": 21},
"end": {
"line": 6,
"character": 33,
},
},
"message": 'Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object"',
"severity": 1,
"code": "override",
"codeDescription": {
"href": "https://mypy.readthedocs.io/en/latest/_refs.html#code-override"
},
"source": "Mypy",
},
{
"range": {
"start": {"line": 6, "character": 21},
"end": {
"line": 6,
"character": 33,
},
},
"message": """This violates the Liskov substitution principle
See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
It is recommended for "__eq__" to work with arbitrary objects, for example:
def __eq__(self, other: object) -> bool:
if not isinstance(other, Foo):
return NotImplemented
return <logic to compare two Foo instances>""",
"severity": 3,
"code": "note",
"codeDescription": {
"href": "https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides"
},
"source": "Mypy",
},
],
}
]
assert_that(actual, is_(expected))


Expand Down Expand Up @@ -557,7 +679,7 @@ def _log_handler(params):
"uri": TEST_FILE3_URI,
"diagnostics": [],
}
assert_that(actual, is_(expected))
assert_that(actual, contains_inanyorder(*expected))


def test_file_with_no_errors_generates_empty_diagnostics_workspace_mode():
Expand Down