Skip to content

Commit

Permalink
Ignore the blended associated code
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrunner committed Jan 30, 2025
1 parent 224fc52 commit 76ff6d1
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 19 deletions.
14 changes: 12 additions & 2 deletions prospector/postfilter.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from pathlib import Path
from typing import Optional

from prospector.message import Message
from prospector.suppression import get_suppressions
from prospector.tools.base import ToolBase


def filter_messages(filepaths: list[Path], messages: list[Message]) -> list[Message]:
def filter_messages(
filepaths: list[Path],
messages: list[Message],
tools: Optional[dict[str, ToolBase]] = None,
blending: bool = False,
blend_combos: Optional[list[list[tuple[str, str]]]] = None,
) -> list[Message]:
"""
This method post-processes all messages output by all tools, in order to filter
out any based on the overall output.
Expand All @@ -23,7 +31,9 @@ def filter_messages(filepaths: list[Path], messages: list[Message]) -> list[Mess
This method uses the information about suppressed messages from pylint to
squash the unwanted redundant error from pyflakes and frosted.
"""
paths_to_ignore, lines_to_ignore, messages_to_ignore = get_suppressions(filepaths, messages)
paths_to_ignore, lines_to_ignore, messages_to_ignore = get_suppressions(
filepaths, messages, tools, blending, blend_combos
)

filtered = []
for message in messages:
Expand Down
13 changes: 10 additions & 3 deletions prospector/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from prospector.formatters import FORMATTERS, Formatter
from prospector.message import Location, Message
from prospector.tools import DEPRECATED_TOOL_NAMES
from prospector.tools.base import ToolBase
from prospector.tools.utils import CaptureOutput


Expand All @@ -25,7 +26,9 @@ def __init__(self, config: ProspectorConfig) -> None:
self.summary: Optional[dict[str, Any]] = None
self.messages = config.messages

def process_messages(self, found_files: FileFinder, messages: list[Message]) -> list[Message]:
def process_messages(
self, found_files: FileFinder, messages: list[Message], tools: dict[str, tools.ToolBase]
) -> list[Message]:
if self.config.blending:
messages = blender.blend(messages)

Expand All @@ -37,7 +40,7 @@ def process_messages(self, found_files: FileFinder, messages: list[Message]) ->
updated.append(msg)
messages = updated

return postfilter.filter_messages(found_files.python_modules, messages)
return postfilter.filter_messages(found_files.python_modules, messages, tools, self.config.blending)

def execute(self) -> None:
deprecated_names = self.config.replace_deprecated_tool_names()
Expand Down Expand Up @@ -70,6 +73,8 @@ def execute(self) -> None:
messages.append(message)
warnings.warn(msg, category=DeprecationWarning, stacklevel=0)

running_tools: dict[str, ToolBase] = {}

# Run the tools
for tool in self.config.get_tools(found_files):
for name, cls in tools.TOOLS.items():
Expand All @@ -79,6 +84,8 @@ def execute(self) -> None:
else:
toolname = "Unknown"

running_tools[toolname] = tool

try:
# Tools can output to stdout/stderr in unexpected places, for example,
# pydocstyle emits warnings about __all__ and as pyroma exec's the setup.py
Expand Down Expand Up @@ -116,7 +123,7 @@ def execute(self) -> None:
)
messages.append(message)

messages = self.process_messages(found_files, messages)
messages = self.process_messages(found_files, messages, running_tools)

summary["message_count"] = len(messages)
summary["completed"] = datetime.now()
Expand Down
65 changes: 51 additions & 14 deletions prospector/suppression.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
from typing import Optional

from prospector import encoding
from prospector.blender import BLEND_COMBOS
from prospector.exceptions import FatalProspectorException
from prospector.message import Message
from prospector.tools.base import ToolBase

_FLAKE8_IGNORE_FILE = re.compile(r"flake8[:=]\s*noqa", re.IGNORECASE)
_PEP8_IGNORE_LINE = re.compile(r"#\s*noqa(\s*#.*)?$", re.IGNORECASE)
Expand All @@ -51,6 +53,17 @@ def __init__(
def __str__(self) -> str:
return self.code if self.source is None else f"{self.source}.{self.code}"

def __repr__(self) -> str:
return f"<{type(self).__name__} {self}>"

def __eq__(self, value: object) -> bool:
if not isinstance(value, Ignore):
return False
return self.code == value.code and self.source == value.source

def __hash__(self) -> int:
return hash((self.source, self.code))


def get_noqa_suppressions(file_contents: list[str]) -> tuple[bool, set[int], dict[int, set[Ignore]]]:
"""
Expand Down Expand Up @@ -81,15 +94,6 @@ def get_noqa_suppressions(file_contents: list[str]) -> tuple[bool, set[int], dic
return ignore_whole_file, ignore_lines, messages_to_ignore


_PYLINT_EQUIVALENTS = {
# TODO: blending has this info already?
"unused-import": (
("pyflakes", "FL0001"),
("frosted", "E101"),
)
}


def _parse_pylint_informational(
messages: list[Message],
) -> tuple[set[Optional[Path]], dict[Optional[Path], dict[int, list[str]]]]:
Expand All @@ -113,17 +117,43 @@ def _parse_pylint_informational(
return ignore_files, ignore_messages


def _process_tool_ignores(
tools_ignore: dict[Path, dict[int, set[Ignore]]],
blend_combos_dict: dict[Ignore, set[Ignore]],
messages_to_ignore: dict[Optional[Path], dict[int, set[Ignore]]],
) -> None:
for path, lines_ignore in tools_ignore.items():
for line, ignores in lines_ignore.items():
for ignore in ignores:
if ignore in blend_combos_dict:
messages_to_ignore[path][line].update(blend_combos_dict[ignore])


def get_suppressions(
filepaths: list[Path], messages: list[Message]
filepaths: list[Path],
messages: list[Message],
tools: Optional[dict[str, ToolBase]] = None,
blending: bool = False,
blend_combos: Optional[list[list[tuple[str, str]]]] = None,
) -> tuple[set[Optional[Path]], dict[Path, set[int]], dict[Optional[Path], dict[int, set[Ignore]]]]:
"""
Given every message which was emitted by the tools, and the
list of files to inspect, create a list of files to ignore,
and a map of filepath -> line-number -> codes to ignore
"""
tools = tools or {}
blend_combos = blend_combos or BLEND_COMBOS
blend_combos_dict: dict[Ignore, set[Ignore]] = {}
if blending:
for combo in blend_combos:
ignore_combos = {Ignore(tool, code) for tool, code in combo}
for ignore in ignore_combos:
blend_combos_dict[ignore] = ignore_combos

paths_to_ignore: set[Optional[Path]] = set()
lines_to_ignore: dict[Path, set[int]] = defaultdict(set)
messages_to_ignore: dict[Optional[Path], dict[int, set[Ignore]]] = defaultdict(lambda: defaultdict(set))
tools_ignore: dict[Path, dict[int, set[Ignore]]] = defaultdict(lambda: defaultdict(set))

# First deal with 'noqa' style messages
for filepath in filepaths:
Expand All @@ -141,6 +171,17 @@ def get_suppressions(
for line, codes_ignore in file_messages_to_ignore.items():
messages_to_ignore[filepath][line] |= codes_ignore

if blending:
for line_number, line_content in enumerate(file_contents):
for tool_name, tool in tools.items():
tool_ignores = tool.get_ignored_codes(line_content)
for tool_ignore in tool_ignores:
tools_ignore[filepath][line_number + 1].add(Ignore(tool_name, tool_ignore))

# Ignore the blending messages
if blending:
_process_tool_ignores(tools_ignore, blend_combos_dict, messages_to_ignore)

# Now figure out which messages were suppressed by pylint
pylint_ignore_files, pylint_ignore_messages = _parse_pylint_informational(messages)
paths_to_ignore |= pylint_ignore_files
Expand All @@ -149,9 +190,5 @@ def get_suppressions(
for code in codes:
ignore = Ignore("pylint", code)
messages_to_ignore[pylint_filepath][line_number].add(ignore)
if code in _PYLINT_EQUIVALENTS:
for ignore_source, ignore_code in _PYLINT_EQUIVALENTS[code]:
ignore = Ignore(ignore_source, ignore_code)
messages_to_ignore[pylint_filepath][line_number].add(ignore)

return paths_to_ignore, lines_to_ignore, messages_to_ignore
7 changes: 7 additions & 0 deletions prospector/tools/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,10 @@ def run(self, found_files: FileFinder) -> list[Message]:
standard prospector Message and Location objects.
"""
raise NotImplementedError

def get_ignored_codes(self, line: str) -> list[str]:
"""
Return a list of error codes that the tool will ignore from a line of code.
"""
del line # unused
return []
9 changes: 9 additions & 0 deletions prospector/tools/mypy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from multiprocessing import Process, Queue
from typing import TYPE_CHECKING, Any, Callable, Optional

Expand All @@ -14,6 +15,8 @@
if TYPE_CHECKING:
from prospector.config import ProspectorConfig

_IGNORE_RE = re.compile(r"#\s*type:\s*ignore\[([^#]*[^# ])\](\s*#.*)?$", re.IGNORECASE)


def format_message(message: str) -> Message:
character: Optional[int]
Expand Down Expand Up @@ -105,3 +108,9 @@ def run(self, found_files: FileFinder) -> list[Message]:
report, _ = result[0], result[1:] # noqa

return [format_message(message) for message in report.splitlines()]

def get_ignored_codes(self, line: str) -> list[str]:
match = _IGNORE_RE.search(line)
if match:
return match.group(1).split(",")
return []
8 changes: 8 additions & 0 deletions prospector/tools/pylint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

_UNUSED_WILDCARD_IMPORT_RE = re.compile(r"^Unused import(\(s\))? (.*) from wildcard import")

_IGNORE_RE = re.compile(r"#\s*pylint:\s*disable=([^#]*[^#\s])(?:\s*#.*)?$", re.IGNORECASE)


def _is_in_dir(subpath: Path, path: Path) -> bool:
return subpath.parent == path
Expand Down Expand Up @@ -266,3 +268,9 @@ def run(self, found_files: FileFinder) -> list[Message]:

messages = self._collector.get_messages()
return self.combine(messages)

def get_ignored_codes(self, line: str) -> list[str]:
match = _IGNORE_RE.search(line)
if match:
return match.group(1).split(",")
return []
9 changes: 9 additions & 0 deletions prospector/tools/ruff/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import re
import subprocess # nosec
from typing import TYPE_CHECKING, Any

Expand All @@ -11,6 +12,8 @@
if TYPE_CHECKING:
from prospector.config import ProspectorConfig

_IGNORE_RE = re.compile(r"#\s*noqa:([^#]*[^# ])(?:\s*#.*)?$", re.IGNORECASE)


class RuffTool(ToolBase):
def configure(self, prospector_config: "ProspectorConfig", _: Any) -> None:
Expand Down Expand Up @@ -84,3 +87,9 @@ def run(self, found_files: FileFinder) -> list[Message]:
)
)
return messages

def get_ignored_codes(self, line: str) -> list[str]:
match = _IGNORE_RE.search(line)
if match:
return match.group(1).split(",")
return []

0 comments on commit 76ff6d1

Please sign in to comment.