bot/code_review_bot/tasks/clang_tidy_external.py (104 lines of code) (raw):
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import re
import structlog
from code_review_bot import Level, Reliability
from code_review_bot.tasks.clang_tidy import ClangTidyIssue, ClangTidyTask
logger = structlog.get_logger(__name__)
ISSUE_MARKDOWN = """
#### Private Static Analysis {level}
- **Message**: {message}
- **Location**: {location}
- **Clang check**: {check}
- **in an expanded Macro**: {expanded_macro}
{notes}"""
ISSUE_NOTE_MARKDOWN = """
- **Note**: {message}
- **Location**: {location}
```
{body}
```
"""
ERROR_MARKDOWN = """
**Message**: ```{message}```
**Location**: {location}
"""
CLANG_MACRO_DETECTION = re.compile(r"^expanded from macro")
BUILD_HELP_MSG = """For private static analysis, please see [our private docs in Mana](https://mana.mozilla.org/wiki/pages/viewpage.action?pageId=130909687), if you cannot access this resource, ask your reviewer to help you resolve the issue."""
class ExternalTidyIssue(ClangTidyIssue):
"""
An issue reported by source-test-clang-external
"""
def is_build_error(self):
return False
def as_error(self):
assert self.is_build_error(), "ExternalTidyIssue is not a build error."
return ERROR_MARKDOWN.format(
message=self.message, location=f"{self.path}:{self.line}"
)
def is_expanded_macro(self):
"""
Is the issue only found in an expanded macro ?
"""
if not self.notes:
return False
# Only consider first note
note = self.notes[0]
return CLANG_MACRO_DETECTION.match(note.message) is not None
def as_text(self):
"""
Build the text body published on reporters
"""
message = self.message
if len(message) > 0:
message = message[0].capitalize() + message[1:]
body = f"{self.level.name}: {message} [external-tidy: {self.check}]"
# Always add body as it's been cleaned up
if self.reason:
body += f"\n{self.reason}"
return body
def as_markdown_for_phab(self):
# skip not in patch or not publishable
if not self.revision.contains(self) or not self.is_publishable():
return ""
return ISSUE_MARKDOWN.format(
level=self.level.value,
message=self.message,
location=f"{self.path}:{self.line}:{self.column}",
check=self.check,
expanded_macro="yes" if self.is_expanded_macro() else "no",
notes="\n".join(
[
ISSUE_NOTE_MARKDOWN.format(
message=n.message,
location=f"{n.path}:{n.line}:{n.column}",
body=n.body,
)
for n in self.notes
]
),
)
def as_markdown(self):
return ISSUE_MARKDOWN.format(
level=self.level.value,
message=self.message,
location=f"{self.path}:{self.line}:{self.column}",
reason=self.reason,
check=self.check,
in_patch="yes" if self.revision.contains(self) else "no",
publishable_check="yes" if self.has_publishable_check() else "no",
publishable="yes" if self.is_publishable() else "no",
expanded_macro="yes" if self.is_expanded_macro() else "no",
reliability=self.reliability.value,
notes="\n".join(
[
ISSUE_NOTE_MARKDOWN.format(
message=n.message,
location=f"{n.path}:{n.line}:{n.column}",
body=n.body,
)
for n in self.notes
]
),
)
class ExternalTidyTask(ClangTidyTask):
"""
Support issues from source-test clang-external tasks
"""
# Note this is currently in fact using the same file name as the
# normal clang tidy check, but this does NOT pose a problem
# as artifact names are separated into individual folders per task id.
artifacts = ["public/code-review/clang-tidy.json"]
@property
def display_name(self):
return "private static analysis"
def build_help_message(self, files):
return BUILD_HELP_MSG
def parse_issues(self, artifacts, revision):
issues = [
ExternalTidyIssue(
analyzer=self,
revision=revision,
path=path,
line=warning["line"],
column=warning["column"],
check=warning["flag"],
level=Level(warning.get("type", "warning")),
message=warning["message"],
reliability=Reliability(warning["reliability"])
if "reliability" in warning
else Reliability.Unknown,
reason=warning.get("reason"),
publish=warning.get("publish")
and warning["flag"].startswith("mozilla-civet-"),
)
for artifact in artifacts.values()
for path, items in artifact["files"].items()
for warning in items["warnings"]
]
return issues