bot/code_review_bot/tasks/default.py (53 lines of code) (raw):
import structlog
from code_review_bot import Issue, Level, taskcluster
from code_review_bot.tasks.base import AnalysisTask
logger = structlog.get_logger(__name__)
ISSUE_MARKDOWN = """
## issue {analyzer}
- **Path**: {path}
- **Level**: {level}
- **Check**: {check}
- **Line**: {line}
- **Publishable**: {publishable}
```
{message}
```
"""
class DefaultIssue(Issue):
def validates(self):
"""
Default issues are valid as long as they match the format
"""
return True
def as_text(self):
"""
Build the text content for reporters
"""
return f"{self.level.name}: {self.message} [{self.check}]"
def as_markdown(self):
"""
Build the Markdown content for debug email
"""
return ISSUE_MARKDOWN.format(
analyzer=self.analyzer.name,
path=self.path,
check=self.check,
level=self.level.value,
line=self.line,
message=self.message,
publishable=self.is_publishable() and "yes" or "no",
)
class DefaultTask(AnalysisTask):
"""
Support issues using the code review format
https://github.com/mozilla/code-review/blob/master/docs/analysis_format.md
"""
artifacts = ["public/code-review/issues.json"]
def parse_issues(self, artifacts, revision):
"""
Parse issues from a log file content
"""
assert isinstance(artifacts, dict)
def default_check(issue):
# Use analyzer name when check is not provided
# This happens for analyzers who only have one rule
# This logic could become the standard once most analyzers
# use that format
check = issue.get("check")
if check:
return check
return issue.get("analyzer", self.name)
return [
DefaultIssue(
analyzer=self,
revision=revision,
path=issue["path"],
line=issue["line"],
column=issue["column"],
nb_lines=issue.get("nb_lines", 1),
level=Level(issue["level"]),
check=default_check(issue),
message=issue["message"],
)
for artifact in artifacts.values()
for _, path_issues in artifact.items()
for issue in path_issues
]
@staticmethod
def matches(task_id):
"""
Check if the default task can work on a task
* Lookup the available latest artifacts
* Check if any artifact matches the official default path
"""
queue = taskcluster.get_service("queue")
result = queue.listLatestArtifacts(task_id)
if "artifacts" not in result:
return False
names = set(artifact["name"] for artifact in result["artifacts"])
return len(names.intersection(DefaultTask.artifacts)) > 0