Skip to content
Draft
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
2 changes: 2 additions & 0 deletions diff_cover/diff_quality_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
pydocstyle_driver,
pyflakes_driver,
ruff_check_driver,
ruff_format_driver,
shellcheck_driver,
)

Expand All @@ -70,6 +71,7 @@
"pyflakes": pyflakes_driver,
"pylint": PylintDriver(),
"ruff.check": ruff_check_driver,
"ruff.format": ruff_format_driver,
"flake8": flake8_driver,
"jshint": jshint_driver,
"eslint": EslintDriver(),
Expand Down
11 changes: 8 additions & 3 deletions diff_cover/report_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from diff_cover.snippets import Snippet
from diff_cover.util import to_unix_path
from diff_cover.violationsreporters.base import Violation


class DiffViolations:
Expand All @@ -19,9 +20,10 @@
"""

def __init__(self, violations, measured_lines, diff_lines):
self.lines = {violation.line for violation in violations}.intersection(
diff_lines
)
_lines = {violation.line for violation in violations}
self.lines = _lines.intersection(diff_lines)
if Violation.ALL_LINES in _lines:
self.lines.add(Violation.ALL_LINES)

Check warning on line 26 in diff_cover/report_generator.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 26 missing coverage

self.violations = {
violation for violation in violations if violation.line in self.lines
Expand Down Expand Up @@ -99,6 +101,9 @@
if diff_violations is None:
return None

if Violation.ALL_LINES in diff_violations.lines:
return 0

Check warning on line 105 in diff_cover/report_generator.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 105 missing coverage

# Protect against a divide by zero
num_measured = len(diff_violations.measured_lines)
if num_measured > 0:
Expand Down
29 changes: 29 additions & 0 deletions diff_cover/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,32 @@ def to_unescaped_filename(filename: str) -> str:
i += 1

return "".join(result)


def merge_ranges(nums):
"""
Merge a list of numbers into a list of ranges.
Given a list of numbers, merge consecutive numbers
into ranges of strings e.g. [1, 2, 3] -> ["1-3"]
"""
if not nums:
return []
nums = sorted(set(nums))
ranges = []
start = prev = nums[0]

def add_range(start_, end_):
"""Helper function to add a range to the list."""
if start_ == end_:
ranges.append(str(start_))
else:
ranges.append(f"{start_}-{end_}")

for n in nums[1:]:
if n == prev + 1:
prev = n
continue
add_range(start, prev)
start = prev = n
add_range(start, prev)
return ranges
51 changes: 43 additions & 8 deletions diff_cover/violationsreporters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,41 @@
import sys
from abc import ABC, abstractmethod
from collections import defaultdict, namedtuple
from functools import lru_cache

from diff_cover.command_runner import execute, run_command_for_code
from diff_cover.util import to_unix_path

Violation = namedtuple("Violation", "line, message")

class Violation(namedtuple("_", "line, message")):
ALL_LINES = -1


class SourceFile:
__slots__ = ("path", "violations")

def __init__(self, path):
self.path = path
self.violations = []

Check warning on line 22 in diff_cover/violationsreporters/base.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 21-22 missing coverage

def add_violation(self, violation):
self.violations.add(violation)

Check warning on line 25 in diff_cover/violationsreporters/base.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 25 missing coverage

@property
@lru_cache(maxsize=1)
def content(self):
with open(self.path, "r", encoding="utf-8") as f:
return f.read()

Check warning on line 31 in diff_cover/violationsreporters/base.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 30-31 missing coverage

@property
@lru_cache(maxsize=1)
def size(self):
return len(self.content)

Check warning on line 36 in diff_cover/violationsreporters/base.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 36 missing coverage

@property
@lru_cache(maxsize=1)
def lines(self):
return set(self.content.splitlines())

Check warning on line 41 in diff_cover/violationsreporters/base.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 41 missing coverage


class QualityReporterError(Exception):
Expand Down Expand Up @@ -228,20 +258,25 @@
violations_dict = defaultdict(list)
for report in reports:
if self.expression.flags & re.MULTILINE:
matches = (match for match in re.finditer(self.expression, report))
matches = re.finditer(self.expression, report)
else:
matches = (
self.expression.match(line.rstrip()) for line in report.split("\n")
)
for match in matches:
if match is not None:
src, line_number, message = match.groups()
# Transform src to a relative path, if it isn't already
src = to_unix_path(os.path.relpath(src))
violation = Violation(int(line_number), message.rstrip())
violations_dict[src].append(violation)
if match is None:
continue
src, violation = self._get_violation(match)
src = to_unix_path(os.path.relpath(src))
violations_dict[src].append(violation)
return violations_dict

def _get_violation(self, match):
src, line_number, message = match.groups()
# Transform src to a relative path, if it isn't already
src = os.path.relpath(src)
return src, Violation(int(line_number), message)

def installed(self):
"""
Method checks if the provided tool is installed.
Expand Down
28 changes: 27 additions & 1 deletion diff_cover/violationsreporters/violations_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,10 @@
# This is an unreported line.
# We add it with the previous line hit score
line_nodes.append(
{_hits: last_hit_number, _number: line_number}
{
_hits: last_hit_number,
_number: line_number,
}
)

# First case, need to define violations initially
Expand Down Expand Up @@ -571,6 +574,29 @@
exit_codes=[0, 1],
)


class RuffFormatDriver(RegexBasedDriver):
def _get_violation(self, match):
src = match.groups()[0]

Check warning on line 580 in diff_cover/violationsreporters/violations_reporter.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 580 missing coverage
# Transform src to a relative path, if it isn't already
src = os.path.relpath(src)
return src, Violation(Violation.ALL_LINES, "Needs reformat")

Check warning on line 583 in diff_cover/violationsreporters/violations_reporter.py

View workflow job for this annotation

GitHub Actions / coverage

Missing Coverage

Line 582-583 missing coverage


ruff_format_driver = RuffFormatDriver(
name="ruff.format",
supported_extensions=["py"],
command=["ruff", "format", "--check"],
# Match lines of the form:
# Would reformat: path/to/file.py
# Would reformat: path/to/file2.py
expression=r"^Would reformat: ([^:]+)$",
command_to_check_install=["ruff", "--version"],
# ruff exit code is 1 if there are violations
# https://docs.astral.sh/ruff/linter/#exit-codes
exit_codes=[0, 1],
)

"""
Report Flake8 violations.
"""
Expand Down
18 changes: 18 additions & 0 deletions tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,21 @@ def test_open_file_encoding_binary(tmp_path):
with util.open_file(tmp_path / "some_file.txt", "br", encoding="utf-16") as f:
assert not hasattr(f, "encoding")
assert f.read() == b"cafe naive resume"


@pytest.mark.parametrize(
(
"nums",
"expected",
),
(
([], []),
([1, 2, 3], ["1-3"]),
([1, 2, 1, 2, 2, 3, 3], ["1-3"]),
([1, 2, 3, 5, 6, 7], ["1-3", "5-7"]),
([1, 3, 6, 8], ["1", "3", "6", "8"]),
(range(1, 101), ["1-100"]),
),
)
def test_merge_ranges(nums, expected):
assert util.merge_ranges(nums) == expected
Loading