diff --git a/src/codemodder/codetf/common.py b/src/codemodder/codetf/common.py index 011809c0..fa1086ec 100644 --- a/src/codemodder/codetf/common.py +++ b/src/codemodder/codetf/common.py @@ -1,8 +1,9 @@ from abc import ABCMeta from enum import Enum from pathlib import Path +from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict, model_validator from codemodder.logging import logger @@ -26,3 +27,60 @@ def write_report(self, outfile: Path | str) -> int: return 2 logger.debug("wrote report to %s", outfile) return 0 + + +class Rule(BaseModel): + id: str + name: str + url: Optional[str] = None + + model_config = ConfigDict(frozen=True) + + +class Finding(BaseModel): + id: str + rule: Rule + + model_config = ConfigDict(frozen=True) + + +class Action(CaseInsensitiveEnum): + ADD = "add" + REMOVE = "remove" + + +class PackageResult(CaseInsensitiveEnum): + COMPLETED = "completed" + FAILED = "failed" + SKIPPED = "skipped" + + +class DiffSide(CaseInsensitiveEnum): + LEFT = "left" + RIGHT = "right" + + +class PackageAction(BaseModel): + action: Action + result: PackageResult + package: str + + +class Change(BaseModel): + lineNumber: int + description: Optional[str] + diffSide: DiffSide = DiffSide.RIGHT + properties: Optional[dict] = None + packageActions: Optional[list[PackageAction]] = None + + @model_validator(mode="after") + def validate_lineNumber(self): + if self.lineNumber < 1: + raise ValueError("lineNumber must be greater than 0") + return self + + @model_validator(mode="after") + def validate_description(self): + if self.description is not None and not self.description: + raise ValueError("description must not be empty") + return self diff --git a/src/codemodder/codetf/v2/codetf.py b/src/codemodder/codetf/v2/codetf.py index fefb9f5d..863fe527 100644 --- a/src/codemodder/codetf/v2/codetf.py +++ b/src/codemodder/codetf/v2/codetf.py @@ -11,11 +11,21 @@ from enum import Enum from typing import TYPE_CHECKING, Optional -from pydantic import BaseModel, ConfigDict, model_validator +from pydantic import BaseModel, model_validator from codemodder import __version__ -from ..common import CaseInsensitiveEnum, CodeTFWriter +from ..common import ( + CaseInsensitiveEnum, +) +from ..common import Change as CommonChange +from ..common import ( + CodeTFWriter, +) +from ..common import Finding as CommonFinding +from ..common import ( + Rule, +) if TYPE_CHECKING: from codemodder.context import CodemodExecutionContext @@ -46,10 +56,6 @@ class PackageAction(BaseModel): class Change(BaseModel): lineNumber: int description: Optional[str] - # All of our changes are currently treated as additive, so it makes sense - # for the comments to appear on the RIGHT side of the split diff. Eventually we - # may want to differentiate between LEFT and RIGHT, but for now we'll just - # default to RIGHT. diffSide: DiffSide = DiffSide.RIGHT properties: Optional[dict] = None packageActions: Optional[list[PackageAction]] = None @@ -77,6 +83,15 @@ def with_findings(self, findings: list[Finding] | None) -> Change: fixedFindings=findings, ) + def to_common(self) -> CommonChange: + return CommonChange( + lineNumber=self.lineNumber, + description=self.description, + diffSide=self.diffSide, + properties=self.properties, + packageActions=self.packageActions, + ) + class AIMetadata(BaseModel): provider: Optional[str] = None @@ -138,20 +153,7 @@ def validate_description(self): return self -class Rule(BaseModel): - id: str - name: str - url: Optional[str] = None - - model_config = ConfigDict(frozen=True) - - -class Finding(BaseModel): - id: Optional[str] = None - rule: Rule - - model_config = ConfigDict(frozen=True) - +class Finding(CommonFinding): def to_unfixed_finding( self, *, @@ -170,7 +172,7 @@ def to_unfixed_finding( def with_rule(self, name: str, url: Optional[str]) -> Finding: return Finding( id=self.id, - rule=Rule(id=self.rule.id, name=name, url=url), + rule=Rule(id=self.rule.id, name=name, url=url) if self.rule else None, ) diff --git a/src/codemodder/codetf/v3/codetf.py b/src/codemodder/codetf/v3/codetf.py index 1ed7e50d..255b8cec 100644 --- a/src/codemodder/codetf/v3/codetf.py +++ b/src/codemodder/codetf/v3/codetf.py @@ -5,7 +5,8 @@ from pydantic import BaseModel, model_validator -from ..common import CaseInsensitiveEnum, CodeTFWriter +from ..common import Change, CodeTFWriter, Finding +from ..v2.codetf import Finding as V2Finding class Run(BaseModel): @@ -44,59 +45,6 @@ class FixStatus(BaseModel): details: Optional[str] -class Rule(BaseModel): - id: str - name: str - url: Optional[str] = None - - -class Finding(BaseModel): - id: str - rule: Optional[Rule] = None - - -class Action(CaseInsensitiveEnum): - ADD = "add" - REMOVE = "remove" - - -class PackageResult(CaseInsensitiveEnum): - COMPLETED = "completed" - FAILED = "failed" - SKIPPED = "skipped" - - -class DiffSide(CaseInsensitiveEnum): - LEFT = "left" - RIGHT = "right" - - -class PackageAction(BaseModel): - action: Action - result: PackageResult - package: str - - -class Change(BaseModel): - lineNumber: int - description: Optional[str] - diffSide: DiffSide = DiffSide.RIGHT - properties: Optional[dict] = None - packageActions: Optional[list[PackageAction]] = None - - @model_validator(mode="after") - def validate_lineNumber(self): - if self.lineNumber < 1: - raise ValueError("lineNumber must be greater than 0") - return self - - @model_validator(mode="after") - def validate_description(self): - if self.description is not None and not self.description: - raise ValueError("description must not be empty") - return self - - class ChangeSet(BaseModel): path: str diff: str @@ -158,7 +106,7 @@ class FixQuality(BaseModel): class FixResult(BaseModel): """Result corresponding to a single finding""" - finding: Finding + finding: Finding | V2Finding fixStatus: FixStatus changeSets: list[ChangeSet] fixMetadata: Optional[FixMetadata] = None