44from abc import abstractmethod
55from dataclasses import dataclass , field
66from pathlib import Path
7- from typing import TYPE_CHECKING , Any , ClassVar , Type
7+ from typing import TYPE_CHECKING , Any , ClassVar , Sequence , Type
88
99import libcst as cst
10+ from boltons .setutils import IndexedSet
1011from libcst ._position import CodeRange
1112from typing_extensions import Self
1213
1819 from codemodder .context import CodemodExecutionContext
1920
2021
21- @dataclass
22+ @dataclass ( frozen = True )
2223class LineInfo :
2324 line : int
2425 column : int = - 1
2526 snippet : str | None = None
2627
2728
28- @dataclass
29+ @dataclass ( frozen = True )
2930class Location (ABCDataclass ):
3031 file : Path
3132 start : LineInfo
3233 end : LineInfo
3334
3435
36+ @dataclass (frozen = True )
3537class SarifLocation (Location ):
3638 @classmethod
3739 @abstractmethod
3840 def from_sarif (cls , sarif_location ) -> Self :
3941 pass
4042
4143
42- @dataclass
44+ @dataclass ( frozen = True )
4345class LocationWithMessage :
4446 location : Location
4547 message : str
4648
4749
48- @dataclass (kw_only = True )
50+ @dataclass (frozen = True , kw_only = True )
4951class Result (ABCDataclass ):
5052 rule_id : str
51- locations : list [Location ]
52- codeflows : list [ list [Location ]] = field (default_factory = list )
53- related_locations : list [LocationWithMessage ] = field (default_factory = list )
53+ locations : Sequence [Location ]
54+ codeflows : Sequence [ Sequence [Location ]] = field (default_factory = tuple )
55+ related_locations : Sequence [LocationWithMessage ] = field (default_factory = tuple )
5456 finding : Finding | None = None
5557
5658 def match_location (self , pos : CodeRange , node : cst .CSTNode ) -> bool :
@@ -67,13 +69,16 @@ def match_location(self, pos: CodeRange, node: cst.CSTNode) -> bool:
6769 for location in self .locations
6870 )
6971
72+ def __hash__ (self ):
73+ return hash (self .rule_id )
7074
71- @dataclass (kw_only = True )
75+
76+ @dataclass (frozen = True , kw_only = True )
7277class SASTResult (Result ):
7378 finding_id : str
7479
7580
76- @dataclass (kw_only = True )
81+ @dataclass (frozen = True , kw_only = True )
7782class SarifResult (SASTResult , ABCDataclass ):
7883 location_type : ClassVar [Type [SarifLocation ]]
7984
@@ -84,32 +89,40 @@ def from_sarif(
8489 raise NotImplementedError
8590
8691 @classmethod
87- def extract_locations (cls , sarif_result ) -> list [Location ]:
88- return [
89- cls .location_type .from_sarif (location )
90- for location in sarif_result ["locations" ]
91- ]
92+ def extract_locations (cls , sarif_result ) -> Sequence [Location ]:
93+ return tuple (
94+ [
95+ cls .location_type .from_sarif (location )
96+ for location in sarif_result ["locations" ]
97+ ]
98+ )
9299
93100 @classmethod
94- def extract_related_locations (cls , sarif_result ) -> list [LocationWithMessage ]:
95- return [
96- LocationWithMessage (
97- message = rel_location .get ("message" , {}).get ("text" , "" ),
98- location = cls .location_type .from_sarif (rel_location ),
99- )
100- for rel_location in sarif_result .get ("relatedLocations" , [])
101- ]
101+ def extract_related_locations (cls , sarif_result ) -> Sequence [LocationWithMessage ]:
102+ return tuple (
103+ [
104+ LocationWithMessage (
105+ message = rel_location .get ("message" , {}).get ("text" , "" ),
106+ location = cls .location_type .from_sarif (rel_location ),
107+ )
108+ for rel_location in sarif_result .get ("relatedLocations" , [])
109+ ]
110+ )
102111
103112 @classmethod
104- def extract_code_flows (cls , sarif_result ) -> list [ list [Location ]]:
105- return [
113+ def extract_code_flows (cls , sarif_result ) -> Sequence [ Sequence [Location ]]:
114+ return tuple (
106115 [
107- cls .location_type .from_sarif (locations .get ("location" ))
108- for locations in threadflow .get ("locations" , {})
116+ tuple (
117+ [
118+ cls .location_type .from_sarif (locations .get ("location" ))
119+ for locations in threadflow .get ("locations" , {})
120+ ]
121+ )
122+ for codeflow in sarif_result .get ("codeFlows" , {})
123+ for threadflow in codeflow .get ("threadFlows" , {})
109124 ]
110- for codeflow in sarif_result .get ("codeFlows" , {})
111- for threadflow in codeflow .get ("threadFlows" , {})
112- ]
125+ )
113126
114127 @classmethod
115128 def extract_rule_id (cls , result , sarif_run , truncate_rule_id : bool = False ) -> str :
@@ -199,5 +212,7 @@ def list_dict_or(
199212) -> dict [Any , list [Any ]]:
200213 result_dict = {}
201214 for k in other .keys () | dictionary .keys ():
202- result_dict [k ] = dictionary .get (k , []) + other .get (k , [])
215+ result_dict [k ] = list (
216+ IndexedSet (dictionary .get (k , [])) | (IndexedSet (other .get (k , [])))
217+ )
203218 return result_dict
0 commit comments