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

New way to define a SonarCodemod and filter_by_result behavior change #226

Merged
merged 1 commit into from
Jan 29, 2024
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
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR but I think we should just reconfigure or disable this pylint rule globally.

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/"),
],
)
Loading