Skip to content

Commit

Permalink
New way to define SonarCodemod and filter_by_result behavior change
Browse files Browse the repository at this point in the history
  • Loading branch information
andrecsilva committed Jan 29, 2024
1 parent ce5785a commit 9afb1ed
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 54 deletions.
4 changes: 2 additions & 2 deletions integration_tests/test_sonar_numpy_nan_equality.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from core_codemods.numpy_nan_equality import NumpyNanEqualityTransformer
from core_codemods.sonar.sonar_numpy_nan_equality import (
SonarNumpyNanEquality,
SonarNumpyNanEqualityTransformer,
)
from integration_tests.base_test import (
BaseIntegrationTest,
Expand Down Expand Up @@ -34,5 +34,5 @@ class TestNumpyNanEquality(BaseIntegrationTest):
# fmt: on

expected_line_change = "4"
change_description = SonarNumpyNanEqualityTransformer.change_description
change_description = NumpyNanEqualityTransformer.change_description
num_changed_files = 1
14 changes: 9 additions & 5 deletions src/codemodder/codemods/base_codemod.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def _apply(
# It seems like semgrep doesn't like our fully-specified id format
self.detector.apply(self.name, context, files_to_analyze)
if self.detector
else ResultSet()
else None
)

process_file = functools.partial(
Expand Down Expand Up @@ -180,14 +180,18 @@ def _process_file(
self,
filename: Path,
context: CodemodExecutionContext,
results: ResultSet,
results: ResultSet | None,
rules: list[str],
):
line_exclude = file_line_patterns(filename, context.path_exclude)
line_include = file_line_patterns(filename, context.path_include)
findings_for_rule = []
for rule in rules:
findings_for_rule.extend(results.results_for_rule_and_file(rule, filename))
findings_for_rule = None
if results is not None:
findings_for_rule = []
for rule in rules:
findings_for_rule.extend(
results.results_for_rule_and_file(rule, filename)
)

file_context = FileContext(
context.directory,
Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/codemods/base_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def apply(
self,
context: CodemodExecutionContext,
file_context: FileContext,
results: list[Result],
results: list[Result] | None,
) -> ChangeSet | None:
"""
Apply the pipeline to the given file context
Expand Down
11 changes: 5 additions & 6 deletions src/codemodder/codemods/base_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

# TODO: this should just be part of BaseTransformer and BaseVisitor?
class UtilsMixin:
results: list[Result]
results: list[Result] | None

def __init__(self, results: list[Result]):
def __init__(self, results: list[Result] | None):
self.results = results

def filter_by_result(self, pos_to_match):
if self.results is None:
return True
return any(
location.match(pos_to_match)
for result in self.results
Expand All @@ -31,9 +33,6 @@ def filter_by_path_includes_or_excludes(self, pos_to_match):
return True

def node_is_selected(self, node) -> bool:
if not self.results:
return False

pos_to_match = self.node_position(node)
return self.filter_by_result(
pos_to_match
Expand All @@ -50,7 +49,7 @@ def lineno_for_node(self, node):
class BaseTransformer(VisitorBasedCodemodCommand, UtilsMixin):
METADATA_DEPENDENCIES: Tuple[Any, ...] = (PositionProvider,)

def __init__(self, context, results: list[Result]):
def __init__(self, context, results: list[Result] | None):
super().__init__(context)
UtilsMixin.__init__(self, results)

Expand Down
6 changes: 3 additions & 3 deletions src/codemodder/codemods/libcst_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class LibcstResultTransformer(
def __init__(
self,
context: CodemodContext,
results: list[Result],
results: list[Result] | None,
file_context: FileContext,
_transformer: bool = False,
):
Expand All @@ -56,7 +56,7 @@ def __init__(

@classmethod
def transform(
cls, module: cst.Module, results: list[Result], file_context: FileContext
cls, module: cst.Module, results: list[Result] | None, file_context: FileContext
) -> cst.Module:
wrapper = cst.MetadataWrapper(module)
codemod = cls(
Expand Down Expand Up @@ -260,7 +260,7 @@ def apply(
self,
context: CodemodExecutionContext,
file_context: FileContext,
results: list[Result],
results: list[Result] | None,
) -> ChangeSet | None:
file_path = file_context.file_path

Expand Down
31 changes: 30 additions & 1 deletion src/codemodder/codemods/sonar.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
from pathlib import Path
from codemodder.codemods.base_codemod import Metadata, Reference
from codemodder.codemods.base_detector import BaseDetector
from codemodder.codemods.base_transformer import BaseTransformerPipeline
from codemodder.context import CodemodExecutionContext
from codemodder.result import ResultSet
from codemodder.sonar_results import SonarResultSet
from core_codemods.api.core_codemod import SASTCodemod
from core_codemods.api.core_codemod import CoreCodemod, SASTCodemod


class SonarCodemod(SASTCodemod):
@property
def origin(self):
return "sonar"

@classmethod
def from_core_codemod(
cls,
name: str,
other: CoreCodemod,
rules: list[str],
transformer: BaseTransformerPipeline | None = None,
new_references: list[Reference] | None = None,
): # pylint: disable=too-many-arguments
return SonarCodemod(
metadata=Metadata(
name=name,
summary="Sonar: " + other.summary,
review_guidance=other._metadata.review_guidance, # pylint: disable=protected-access
references=(
other.references
if not new_references
else other.references + new_references
),
description=f"This codemod acts upon the following Sonar rules: {str(rules)[1:-1]}.\n\n"
+ other.description,
),
transformer=transformer if transformer else other.transformer,
detector=SonarDetector(),
requested_rules=rules,
)


class SonarDetector(BaseDetector):
_lazy_cache = None
Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/file_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class FileContext: # pylint: disable=too-many-instance-attributes
file_path: Path
line_exclude: list[int] = field(default_factory=list)
line_include: list[int] = field(default_factory=list)
findings: list[Result] = field(default_factory=list)
findings: list[Result] | None = field(default_factory=list)
dependencies: set[Dependency] = field(default_factory=set)
codemod_changes: list[Change] = field(default_factory=list)
results: list[ChangeSet] = field(default_factory=list)
Expand Down
2 changes: 1 addition & 1 deletion src/core_codemods/numpy_nan_equality.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def _is_np_nan_eq(self, left: cst.BaseExpression, target: cst.ComparisonTarget):
def leave_Comparison(
self, original_node: cst.Comparison, updated_node: cst.Comparison
) -> cst.BaseExpression:
if self.filter_by_path_includes_or_excludes(self.node_position(original_node)):
if self.node_is_selected(original_node):
match original_node:
case cst.Comparison(comparisons=[cst.ComparisonTarget() as target]):
maybe_nan_eq = self._is_np_nan_eq(original_node.left, target)
Expand Down
41 changes: 7 additions & 34 deletions src/core_codemods/sonar/sonar_numpy_nan_equality.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,15 @@
import libcst as cst
from codemodder.codemods.base_codemod import Reference
from codemodder.codemods.libcst_transformer import (
LibcstTransformerPipeline,
)
from codemodder.codemods.sonar import SonarDetector
from codemodder.codemods.sonar import SonarCodemod

from core_codemods.api import Metadata
from core_codemods.numpy_nan_equality import (
NumpyNanEquality,
NumpyNanEqualityTransformer,
)


class SonarNumpyNanEqualityTransformer(NumpyNanEqualityTransformer):
def leave_Comparison(
self, original_node: cst.Comparison, updated_node: cst.Comparison
) -> cst.BaseExpression:
if self.filter_by_result(self.node_position(original_node)):
return super().leave_Comparison(original_node, updated_node)
return updated_node


rules = ["python:S6725"]

SonarNumpyNanEquality = SonarCodemod(
metadata=Metadata(
name="numpy-nan-equality-S6725",
summary="Sonar: " + NumpyNanEquality.summary,
review_guidance=NumpyNanEquality._metadata.review_guidance, # pylint: disable=protected-access
references=NumpyNanEquality.references
+ [
Reference(url="https://rules.sonarsource.com/python/type/Bug/RSPEC-6725/"),
],
description=f"This codemod acts upon the following Sonar rules: {str(rules)[1:-1]}.\n\n"
+ NumpyNanEquality.description,
),
transformer=LibcstTransformerPipeline(SonarNumpyNanEqualityTransformer),
detector=SonarDetector(),
requested_rules=rules,
SonarNumpyNanEquality = SonarCodemod.from_core_codemod(
name="numpy-nan-equality-S6725",
other=NumpyNanEquality,
rules=["python:S6725"],
new_references=[
Reference(url="https://rules.sonarsource.com/python/type/Bug/RSPEC-6725/"),
],
)

0 comments on commit 9afb1ed

Please sign in to comment.