Skip to content
Open
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: 3 additions & 0 deletions doc/whatsnew/fragments/10880.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
``add_message`` now accepts optional ``module`` and ``filepath`` keyword arguments to override the reported message location. These parameters are mutually exclusive with ``node``; passing both raises ``TypeError``.

Refs #10880
44 changes: 42 additions & 2 deletions pylint/checkers/base_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from collections.abc import Iterable, Sequence
from inspect import cleandoc
from tokenize import TokenInfo
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, overload

from astroid import nodes

Expand Down Expand Up @@ -139,6 +139,35 @@ def get_full_documentation(
result += "\n"
return result

@overload
def add_message(
self,
msgid: str,
line: int | None = None,
node: nodes.NodeNG = ...,
args: Any = None,
confidence: Confidence | None = None,
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
) -> None: ...

@overload
def add_message( # pylint: disable=too-many-arguments
self,
msgid: str,
line: int | None = None,
node: None = None,
args: Any = None,
confidence: Confidence | None = None,
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
module: str | None = None,
filepath: str | None = None,
) -> None: ...

# pylint: disable-next=too-many-arguments
def add_message(
self,
msgid: str,
Expand All @@ -149,9 +178,20 @@ def add_message(
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
module: str | None = None,
filepath: str | None = None,
) -> None:
self.linter.add_message(
msgid, line, node, args, confidence, col_offset, end_lineno, end_col_offset
msgid,
line=line,
node=node,
args=args,
confidence=confidence,
col_offset=col_offset,
end_lineno=end_lineno,
end_col_offset=end_col_offset,
module=module,
filepath=filepath,
)

def check_consistency(self) -> None:
Expand Down
58 changes: 53 additions & 5 deletions pylint/lint/pylinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pathlib import Path
from re import Pattern
from types import ModuleType
from typing import Any, Protocol
from typing import Any, Protocol, overload

import astroid
import astroid.builder
Expand Down Expand Up @@ -1219,6 +1219,7 @@ def _report_evaluation(self, verbose: bool = False) -> int | None:
self.reporter.display_reports(sect)
return note

# pylint: disable-next=too-many-arguments
def _add_one_message(
self,
message_definition: MessageDefinition,
Expand All @@ -1229,6 +1230,8 @@ def _add_one_message(
col_offset: int | None,
end_lineno: int | None,
end_col_offset: int | None,
module: str | None = None,
filepath: str | None = None,
) -> None:
"""After various checks have passed a single Message is
passed to the reporter and added to stats.
Expand Down Expand Up @@ -1282,10 +1285,11 @@ def _add_one_message(
msg %= args
# get module and object
if node is None:
module, obj = self.current_name, ""
abspath = self.current_file
_module = module if module is not None else self.current_name
obj = ""
abspath = filepath if filepath is not None else self.current_file
else:
module, obj = utils.get_module_and_frameid(node)
_module, obj = utils.get_module_and_frameid(node)
abspath = node.root().file
if abspath is not None:
path = abspath.replace(self.reporter.path_strip_prefix, "", 1)
Expand All @@ -1299,7 +1303,7 @@ def _add_one_message(
MessageLocationTuple(
abspath or "",
path,
module or "",
_module or "",
obj,
line or 1,
col_offset or 0,
Expand All @@ -1311,6 +1315,35 @@ def _add_one_message(
)
)

@overload
def add_message(
self,
msgid: str,
line: int | None = None,
node: nodes.NodeNG = ...,
args: Any | None = None,
confidence: interfaces.Confidence | None = None,
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
) -> None: ...

@overload
def add_message( # pylint: disable=too-many-arguments
self,
msgid: str,
line: int | None = None,
node: None = None,
args: Any | None = None,
confidence: interfaces.Confidence | None = None,
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
module: str | None = None,
filepath: str | None = None,
) -> None: ...

# pylint: disable-next=too-many-arguments
def add_message(
self,
msgid: str,
Expand All @@ -1321,6 +1354,8 @@ def add_message(
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
module: str | None = None,
filepath: str | None = None,
) -> None:
"""Adds a message given by ID or name.

Expand All @@ -1329,7 +1364,18 @@ def add_message(
AST checkers must provide the node argument (but may optionally
provide line if the line number is different), raw and token checkers
must provide the line argument.

The ``module`` and ``filepath`` parameters allow overriding the module
name and file path reported in the message location. They cannot be
combined with ``node``; pass either ``node`` **or**
``module``/``filepath``, not both.
"""
if node is not None and (module is not None or filepath is not None):
raise TypeError(
"add_message() does not accept both 'node' and "
"'module'/'filepath'. Pass either 'node' to derive location "
"from the AST, or 'module'/'filepath' to set them explicitly."
)
if confidence is None:
confidence = interfaces.UNDEFINED
message_definitions = self.msgs_store.get_message_definitions(msgid)
Expand All @@ -1343,6 +1389,8 @@ def add_message(
col_offset,
end_lineno,
end_col_offset,
module=module,
filepath=filepath,
)

def add_ignored_message(
Expand Down
33 changes: 32 additions & 1 deletion pylint/testutils/unittest_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from __future__ import annotations

from typing import Any, Literal
from typing import Any, Literal, overload

from astroid import nodes

Expand All @@ -28,7 +28,36 @@ def release_messages(self) -> list[MessageTest]:
finally:
self._messages = []

@overload
def add_message(
self,
msgid: str,
line: int | None = None,
node: nodes.NodeNG = ...,
args: Any = None,
confidence: Confidence | None = None,
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
) -> None: ...

@overload
def add_message( # pylint: disable=too-many-arguments
self,
msgid: str,
line: int | None = None,
node: None = None,
args: Any = None,
confidence: Confidence | None = None,
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
module: str | None = None,
filepath: str | None = None,
) -> None: ...

# pylint: disable-next=too-many-arguments
def add_message( # pylint: disable=unused-argument
self,
msgid: str,
line: int | None = None,
Expand All @@ -39,6 +68,8 @@ def add_message(
col_offset: int | None = None,
end_lineno: int | None = None,
end_col_offset: int | None = None,
module: str | None = None,
filepath: str | None = None,
) -> None:
"""Add a MessageTest to the _messages attribute of the linter class."""
# If confidence is None we set it to UNDEFINED as well in PyLinter
Expand Down
35 changes: 35 additions & 0 deletions tests/lint/unittest_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,41 @@ def test_addmessage(linter: PyLinter) -> None:
)


def test_addmessage_module_and_filepath_override(linter: PyLinter) -> None:
"""Module and filepath override current_name/current_file when node is None."""
linter.set_reporter(testutils.GenericTestReporter())
linter.open()
linter.set_current_module("current_module")
linter.add_message(
"C0301",
line=1,
args=(1, 2),
module="overridden_module",
filepath="/fake/path.py",
)
assert len(linter.reporter.messages) == 1
msg = linter.reporter.messages[0]
assert msg.location.module == "overridden_module"
assert msg.location.abspath == "/fake/path.py"
assert msg.location.path == "/fake/path.py"


def test_addmessage_node_with_module_filepath_raises(linter: PyLinter) -> None:
"""Passing both node and module/filepath raises TypeError."""
linter.set_reporter(testutils.GenericTestReporter())
linter.open()
linter.set_current_module("current_module")
module_node = astroid.parse("x = 1")
node = module_node.body[0]
with pytest.raises(TypeError, match="does not accept both"):
linter.add_message(
"C0321",
node=node,
module="overridden_module",
filepath="/fake/path.py",
)


def test_addmessage_invalid(linter: PyLinter) -> None:
linter.set_reporter(testutils.GenericTestReporter())
linter.open()
Expand Down