Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SARIF-formatter severity levels #3824

Merged
merged 7 commits into from
Oct 10, 2023
Merged
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
42 changes: 37 additions & 5 deletions src/ansiblelint/formatters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

if TYPE_CHECKING:
from ansiblelint.errors import MatchError
from ansiblelint.rules import BaseRule # type: ignore[attr-defined]

T = TypeVar("T", bound="BaseFormatter") # type: ignore[type-arg]

Expand Down Expand Up @@ -264,7 +265,7 @@ def _to_sarif_rule(self, match: MatchError) -> dict[str, Any]:
"text": str(match.message),
},
"defaultConfiguration": {
"level": self._to_sarif_level(match),
"level": self.get_sarif_rule_severity_level(match.rule),
},
"help": {
"text": str(match.rule.description),
Expand All @@ -285,7 +286,7 @@ def _to_sarif_result(self, match: MatchError) -> dict[str, Any]:

result: dict[str, Any] = {
"ruleId": match.tag,
"level": match.level,
"level": self.get_sarif_result_severity_level(match),
"message": {
"text": str(match.details)
if str(match.details)
Expand All @@ -312,6 +313,37 @@ def _to_sarif_result(self, match: MatchError) -> dict[str, Any]:
return result

@staticmethod
def _to_sarif_level(match: MatchError) -> str:
# sarif accepts only 4 levels: error, warning, note, none
return match.level
def get_sarif_rule_severity_level(rule: BaseRule) -> str:
"""General SARIF severity level for a rule.

Note: Can differ from an actual result/match severity.
Possible values: "none", "note", "warning", "error"

see: https://github.com/oasis-tcs/sarif-spec/blob/123e95847b13fbdd4cbe2120fa5e33355d4a042b/Schemata/sarif-schema-2.1.0.json#L1934-L1939
"""
if rule.severity in ["VERY_HIGH", "HIGH"]:
return "error"

if rule.severity in ["MEDIUM", "LOW", "VERY_LOW"]:
return "warning"

if rule.severity == "INFO":
return "note"

return "none"

@staticmethod
def get_sarif_result_severity_level(match: MatchError) -> str:
"""SARIF severity level for an actual result/match.

Possible values: "none", "note", "warning", "error"

see: https://github.com/oasis-tcs/sarif-spec/blob/123e95847b13fbdd4cbe2120fa5e33355d4a042b/Schemata/sarif-schema-2.1.0.json#L2066-L2071
"""
if not match.level:
return "none"

if match.level in ["warning", "error"]:
return match.level

return "note"
91 changes: 57 additions & 34 deletions test/test_formatter_sarif.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,61 @@
class TestSarifFormatter:
"""Unit test for SarifFormatter."""

rule = AnsibleLintRule()
rule1 = AnsibleLintRule()
rule2 = AnsibleLintRule()
matches: list[MatchError] = []
formatter: SarifFormatter | None = None
collection = RulesCollection()
collection.register(rule1)
collection.register(rule2)

def setup_class(self) -> None:
"""Set up few MatchError objects."""
self.rule = AnsibleLintRule()
self.rule.id = "TCF0001"
self.rule.severity = "VERY_HIGH"
self.rule.description = "This is the rule description."
self.rule.link = "https://rules/help#TCF0001"
self.rule.tags = ["tag1", "tag2"]
self.collection.register(self.rule)

self.matches = []
self.matches.append(
MatchError(
message="message",
lineno=1,
column=10,
details="details",
lintable=Lintable("filename.yml", content=""),
rule=self.rule,
tag="yaml[test]",
),
)
self.matches.append(
MatchError(
message="message",
lineno=2,
details="",
lintable=Lintable("filename.yml", content=""),
rule=self.rule,
tag="yaml[test]",
),
self.rule1.id = "TCF0001"
self.rule1.severity = "VERY_HIGH"
self.rule1.description = "This is the rule description."
self.rule1.link = "https://rules/help#TCF0001"
self.rule1.tags = ["tag1", "tag2"]

self.rule2.id = "TCF0002"
self.rule2.severity = "MEDIUM"
self.rule2.link = "https://rules/help#TCF0002"
self.rule2.tags = ["tag3", "tag4"]

self.matches.extend(
[
MatchError(
message="message1",
lineno=1,
column=10,
details="details1",
lintable=Lintable("filename1.yml", content=""),
rule=self.rule1,
tag="yaml[test1]",
ignored=False,
),
MatchError(
message="message2",
lineno=2,
details="",
lintable=Lintable("filename2.yml", content=""),
rule=self.rule1,
tag="yaml[test2]",
ignored=True,
),
MatchError(
message="message3",
lineno=666,
column=667,
details="details3",
lintable=Lintable("filename3.yml", content=""),
rule=self.rule2,
tag="yaml[test3]",
ignored=False,
),
],
)

self.formatter = SarifFormatter(pathlib.Path.cwd(), display_relative_path=True)

def test_format_list(self) -> None:
Expand All @@ -81,7 +99,7 @@ def test_sarif_format(self) -> None:
"""Test if the return SARIF object contains the expected results."""
assert isinstance(self.formatter, SarifFormatter)
sarif = json.loads(self.formatter.format_result(self.matches))
assert len(sarif["runs"][0]["results"]) == 2
assert len(sarif["runs"][0]["results"]) == 3
for result in sarif["runs"][0]["results"]:
# Ensure all reported entries have a level
assert "level" in result
Expand All @@ -98,16 +116,18 @@ def test_validate_sarif_schema(self) -> None:
assert driver["name"] == SarifFormatter.TOOL_NAME
assert driver["informationUri"] == SarifFormatter.TOOL_URL
rules = driver["rules"]
assert len(rules) == 1
assert len(rules) == 3
assert rules[0]["id"] == self.matches[0].tag
assert rules[0]["name"] == self.matches[0].tag
assert rules[0]["shortDescription"]["text"] == self.matches[0].message
assert rules[0]["defaultConfiguration"]["level"] == "error"
assert rules[0]["defaultConfiguration"][
"level"
] == SarifFormatter.get_sarif_rule_severity_level(self.matches[0].rule)
assert rules[0]["help"]["text"] == self.matches[0].rule.description
assert rules[0]["properties"]["tags"] == self.matches[0].rule.tags
assert rules[0]["helpUri"] == self.matches[0].rule.url
results = sarif["runs"][0]["results"]
assert len(results) == 2
assert len(results) == 3
for i, result in enumerate(results):
assert result["ruleId"] == self.matches[i].tag
assert (
Expand All @@ -134,6 +154,9 @@ def test_validate_sarif_schema(self) -> None:
"startColumn"
not in result["locations"][0]["physicalLocation"]["region"]
)
assert result["level"] == SarifFormatter.get_sarif_result_severity_level(
self.matches[i],
)
assert sarif["runs"][0]["originalUriBaseIds"][SarifFormatter.BASE_URI_ID]["uri"]
assert results[0]["message"]["text"] == self.matches[0].details
assert results[1]["message"]["text"] == self.matches[1].message
Expand Down