bot/code_review_bot/tasks/clang_format.py (80 lines of code) (raw):
import rs_parsepatch
import structlog
from code_review_bot import Issue, Level
from code_review_bot.config import settings
from code_review_bot.tasks.base import AnalysisTask
logger = structlog.get_logger(__name__)
ISSUE_MARKDOWN = """
## clang-format
- **Path**: {path}
- **Lines**: from {line}, on {nb_lines} lines
"""
class ClangFormatIssue(Issue):
"""
An issue created by the Clang Format tool
"""
def __init__(self, analyzer, path, lines, revision):
assert isinstance(lines, list)
assert len(lines) > 0, "No lines describing patch"
# Find position of first different line
try:
first_diff = [old != new for old, new, _ in lines].index(True)
except ValueError:
first_diff = 0
lines = lines[first_diff:]
# Get the lines impacted by the patch
old_nb, new_nb, _ = zip(*lines)
line = min(filter(None, old_nb))
nb_lines = max(filter(None, old_nb)) - line + 1
# Build the fix to display on reporters
fix = "\n".join([line.decode("utf-8") for _, nb, line in lines if nb])
super().__init__(
analyzer,
revision,
path,
line,
nb_lines,
check="invalid-styling",
fix=fix,
language="c++",
message="The change does not follow the C/C++ coding style, please reformat",
level=Level.Warning,
)
def validates(self):
"""
Should match one of the allowed paths rules
"""
return settings.is_allowed_path(self.path)
def as_text(self):
"""
Build the text body published on reporters
According to diff mode
"""
if self.patch:
return f"Replace with :\n\n```{self.patch}```"
return "Incorrect coding style [clang-format]"
def as_markdown(self):
"""
Build the Markdown content for debug email
"""
return ISSUE_MARKDOWN.format(
path=self.path, line=self.line, nb_lines=self.nb_lines
)
class ClangFormatTask(AnalysisTask):
"""
Support issues from source-test clang-format tasks by reading the
clang-format json output
"""
artifacts = ["public/code-review/clang-format.diff"]
@property
def display_name(self):
return "clang-format"
def build_help_message(self, files):
files = " ".join(files)
return f"`./mach clang-format -p {files}`"
def parse_issues(self, artifacts, revision):
if "public/code-review/clang-format.diff" not in artifacts:
logger.warn(
"public/code-review/clang-format.diff is missing from artifacts"
)
return []
artifact = artifacts["public/code-review/clang-format.diff"]
if artifact is None:
logger.info("Empty clang-format.diff, no patches to process")
return []
# Use all chunks provided by parsepatch
return [
ClangFormatIssue(
analyzer=self,
path=diff["filename"],
lines=diff["lines"],
revision=revision,
)
for diff in rs_parsepatch.get_diffs(artifact)
]
def build_patches(self, artifacts):
artifact = artifacts.get("public/code-review/clang-format.diff")
if artifact is None:
logger.warn("Missing or empty clang-format.diff")
return []
assert isinstance(artifact, bytes), "clang-format.diff should be bytes"
patch = artifact.decode("utf-8")
if patch.strip() == "":
logger.info("Empty patch in clang-format.diff")
return []
return [patch]