bot/code_review_bot/tasks/lint.py (91 lines of code) (raw):
import structlog
from code_review_bot import Issue, Level
from code_review_bot.tasks.base import AnalysisTask
logger = structlog.get_logger(__name__)
ISSUE_MARKDOWN = """
## mozlint - {linter}
- **Path**: {path}
- **Level**: {level}
- **Line**: {line}
- **Disabled check**: {disabled_check}
- **Publishable**: {publishable}
```
{message}
```
"""
class MozLintIssue(Issue):
def __init__(
self,
analyzer,
path,
column,
level,
lineno,
linter,
message,
check,
revision,
**kwargs,
):
# Use analyzer name when check is not provided
# This happens for analyzers who only have one rule
if check is None:
check = analyzer.name
super().__init__(
analyzer,
revision,
path,
line=(
lineno and int(lineno) or 0
), # mozlint sometimes produce strings here
nb_lines=1,
check=check,
column=column,
level=Level(level),
message=message,
)
self.linter = linter
def is_disabled_check(self):
"""
Some checks are disabled:
* Python "bad" quotes
"""
# See https://github.com/mozilla/release-services/issues/777
if self.linter == "flake8" and self.check == "Q000":
return True
return False
def validates(self):
"""
A mozlint issues is publishable when:
* check is not disabled
"""
return not self.is_disabled_check()
def as_text(self):
"""
Build the text content for reporters
"""
message = self.message
if len(message) > 0:
message = message[0].capitalize() + message[1:]
linter = f"{self.linter}: {self.check}" if self.check else self.linter
return f"{self.level.name}: {message} [{linter}]"
def as_markdown(self):
"""
Build the Markdown content for debug email
"""
return ISSUE_MARKDOWN.format(
linter=self.linter,
path=self.path,
level=self.level.value,
line=self.line,
message=self.message,
publishable=self.is_publishable() and "yes" or "no",
disabled_check=self.is_disabled_check() and "yes" or "no",
)
class MozLintTask(AnalysisTask):
"""
Support issues from source-test mozlint tasks by parsing the raw log
"""
artifacts = ["public/code-review/mozlint.json"]
@property
def linter(self):
# Detect linter from task name
if not self.name.startswith("source-test-mozlint-"):
return
return self.name[20:]
@property
def display_name(self):
if self.linter:
return f"{self.linter} (Mozlint)"
if self.name.startswith("source-test-"):
return self.name[12:]
return self.name
def build_help_message(self, files):
return "`./mach lint --warnings --outgoing`"
def parse_issues(self, artifacts, revision):
"""
Parse issues from a log file content
"""
assert isinstance(artifacts, dict)
return [
MozLintIssue(
analyzer=self,
revision=revision,
path=issue.get("relpath", issue["path"]),
column=issue["column"],
level=issue["level"],
lineno=issue["lineno"],
linter=issue["linter"],
message=issue["message"],
check=issue["rule"],
)
for artifact in artifacts.values()
for _, path_issues in artifact.items()
for issue in path_issues
]