2525from dataclasses import dataclass
2626
2727from ...scanossbase import ScanossBase
28+ from ...utils import scanoss_scan_results_utils
2829from ..utils .file_utils import load_json_file
2930from ..utils .markdown_utils import generate_table
3031
@@ -38,10 +39,12 @@ class MatchSummaryItem:
3839 match found during scanning, including file location, license details, and
3940 match quality metrics.
4041 """
42+ file : str
4143 file_url : str
4244 license : str
4345 similarity : str
4446 purl : str
47+ purl_url : str
4548 version : str
4649 lines : str
4750
@@ -92,25 +95,6 @@ def __init__( # noqa: PLR0913
9295 self .line_range_prefix = line_range_prefix
9396 self .output = output
9497
95- @staticmethod
96- def _get_lines (lines : str ) -> list :
97- """
98- Parse line range string into a list of line numbers.
99-
100- Converts SCANOSS line notation (e.g., '10-20,25-30') into a flat list
101- of individual line numbers for processing.
102-
103- :param lines: Comma-separated line ranges in SCANOSS format (e.g., '10-20,25-30')
104- :return: Flat list of all line numbers extracted from the ranges
105- """
106- lineArray = []
107- lines = lines .split (',' )
108- for line in lines :
109- line_parts = line .split ('-' )
110- for part in line_parts :
111- lineArray .append (int (part ))
112- return lineArray
113-
11498
11599 def _get_match_summary_item (self , file_name : str , result : dict ) -> MatchSummaryItem :
116100 """
@@ -126,27 +110,64 @@ def _get_match_summary_item(self, file_name: str, result: dict) -> MatchSummaryI
126110 """
127111 if result .get ('id' ) == "snippet" :
128112 # Snippet match: create URL with line range anchor
129- lines = self . _get_lines (result .get ('lines' ))
113+ lines = scanoss_scan_results_utils . get_lines (result .get ('lines' ))
130114 end_line = lines [len (lines ) - 1 ] if len (lines ) > 1 else lines [0 ]
131115 file_url = f"{ self .line_range_prefix } /{ file_name } #L{ lines [0 ]} -L{ end_line } "
132116 return MatchSummaryItem (
133117 file_url = file_url ,
118+ file = file_name ,
134119 license = result .get ('licenses' )[0 ].get ('name' ),
135120 similarity = result .get ('matched' ),
136121 purl = result .get ('purl' )[0 ],
122+ purl_url = result .get ('url' ),
137123 version = result .get ('version' ),
138124 lines = f"{ lines [0 ]} -{ lines [len (lines ) - 1 ] if len (lines ) > 1 else lines [0 ]} "
139125 )
140126 # File match: create URL without line range
141127 return MatchSummaryItem (
128+ file = file_name ,
142129 file_url = f"{ self .line_range_prefix } /{ file_name } " ,
143130 license = result .get ('licenses' )[0 ].get ('name' ),
144131 similarity = result .get ('matched' ),
145132 purl = result .get ('purl' )[0 ],
133+ purl_url = result .get ('url' ),
146134 version = result .get ('version' ),
147135 lines = "all"
148136 )
149137
138+ def _validate_result (self , file_name : str , result : dict ) -> bool :
139+ """
140+ Validate that a scan result has all required fields.
141+
142+ :param file_name: Name of the file being validated
143+ :param result: The scan result to validate
144+ :return: True if valid, False otherwise
145+ """
146+ validations = [
147+ ('id' , 'No id found' ),
148+ ('lines' , 'No lines found' ),
149+ ('purl' , 'No purl found' ),
150+ ('licenses' , 'No licenses found' ),
151+ ('version' , 'No version found' ),
152+ ('matched' , 'No matched found' ),
153+ ('url' , 'No url found' ),
154+ ]
155+
156+ for field , error_msg in validations :
157+ if not result .get (field ):
158+ self .print_debug (f'ERROR: { error_msg } for file { file_name } ' )
159+ return False
160+
161+ # Additional validation for non-empty lists
162+ if len (result .get ('purl' )) == 0 :
163+ self .print_debug (f'ERROR: No purl found for file { file_name } ' )
164+ return False
165+ if len (result .get ('licenses' )) == 0 :
166+ self .print_debug (f'ERROR: Empty licenses list for file { file_name } ' )
167+ return False
168+
169+ return True
170+
150171 def _get_matches_summary (self ) -> ComponentMatchSummary :
151172 """
152173 Parse SCANOSS scan results and create categorized match summaries.
@@ -162,29 +183,12 @@ def _get_matches_summary(self) -> ComponentMatchSummary:
162183 # Process each file and its results
163184 for file_name , results in scan_results .items ():
164185 for result in results :
165- # Validate required fields - skip invalid results with debug messages
166- if not result .get ('id' ):
167- self .print_debug (f'ERROR: No id found for file { file_name } ' )
168- continue
169- if result .get ('id' ) == "none" : # Skip non-matches
186+ # Skip non-matches
187+ if result .get ('id' ) == "none" :
170188 continue
171- if not result .get ('lines' ):
172- self .print_debug (f'ERROR: No lines found for file { file_name } ' )
173- continue
174- if not result .get ('purl' ):
175- self .print_debug (f'ERROR: No purl found for file { file_name } ' )
176- continue
177- if not len (result .get ('purl' )) > 0 :
178- self .print_debug (f'ERROR: No purl found for file { file_name } ' )
179- continue
180- if not result .get ('licenses' ):
181- self .print_debug (f'ERROR: No licenses found for file { file_name } ' )
182- continue
183- if not result .get ('version' ):
184- self .print_debug (f'ERROR: No version found for file { file_name } ' )
185- continue
186- if not result .get ('matched' ):
187- self .print_debug (f'ERROR: No matched found for file { file_name } ' )
189+
190+ # Validate required fields
191+ if not self ._validate_result (file_name , result ):
188192 continue
189193
190194 # Create summary item and categorize by match type
@@ -207,50 +211,53 @@ def _markdown(self, gitlab_matches_summary: ComponentMatchSummary) -> str:
207211 :param gitlab_matches_summary: Container with categorized file and snippet matches to format
208212 :return: Complete Markdown document with formatted match tables
209213 """
210- # Define table headers
211- headers = ['File' , 'License' , 'Similarity' , 'PURL' , 'Version' , 'Lines' ]
212214
215+ if len (gitlab_matches_summary .files ) == 0 and len (gitlab_matches_summary .snippet ) == 0 :
216+ return ""
217+
218+ # Define table headers
219+ file_match_headers = ['File' , 'License' , 'Similarity' , 'PURL' , 'Version' ]
220+ snippet_match_headers = ['File' , 'License' , 'Similarity' , 'PURL' , 'Version' , 'Lines' ]
213221 # Build file matches table
214222 file_match_rows = []
215- for file in gitlab_matches_summary .files :
223+ for file_match in gitlab_matches_summary .files :
216224 row = [
217- file .file_url ,
218- file .license ,
219- file .similarity ,
220- file .purl ,
221- file .version ,
222- file .lines
225+ f"[{ file_match .file } ]({ file_match .file_url } )" ,
226+ file_match .license ,
227+ file_match .similarity ,
228+ f"[{ file_match .purl } ]({ file_match .purl_url } )" ,
229+ file_match .version ,
223230 ]
224231 file_match_rows .append (row )
225- file_match_table = generate_table (headers , file_match_rows )
232+ file_match_table = generate_table (file_match_headers , file_match_rows )
226233
227234 # Build snippet matches table
228235 snippet_match_rows = []
229- for file in gitlab_matches_summary .snippet :
236+ for snippet_match in gitlab_matches_summary .snippet :
230237 row = [
231- file .file_url ,
232- file .license ,
233- file .similarity ,
234- file .purl ,
235- file .version ,
236- file .lines
238+ f"[ { snippet_match . file } ]( { snippet_match .file_url } )" ,
239+ snippet_match .license ,
240+ snippet_match .similarity ,
241+ f"[ { snippet_match .purl } ]( { snippet_match . purl_url } )" ,
242+ snippet_match .version ,
243+ snippet_match .lines
237244 ]
238245 snippet_match_rows .append (row )
239- snippet_match_table = generate_table (headers , snippet_match_rows )
246+ snippet_match_table = generate_table (snippet_match_headers , snippet_match_rows )
240247
241248 # Assemble complete Markdown document
242249 markdown = ""
243- markdown += "### SCANOSS Matches Summary\n \n "
250+ markdown += "### SCANOSS Match Summary\n \n "
244251
245252 # File matches section (collapsible)
246253 markdown += "<details>\n "
247- markdown += "<summary>File Matches Summary</summary>\n \n "
254+ markdown += "<summary>File Match Summary</summary>\n \n "
248255 markdown += file_match_table
249256 markdown += "\n </details>\n "
250257
251258 # Snippet matches section (collapsible)
252259 markdown += "<details>\n "
253- markdown += "<summary>Snippet Matches Summary</summary>\n \n "
260+ markdown += "<summary>Snippet Match Summary</summary>\n \n "
254261 markdown += snippet_match_table
255262 markdown += "\n </details>\n "
256263
@@ -272,7 +279,9 @@ def run(self):
272279
273280 # Format matches as GitLab-compatible Markdown
274281 matches_md = self ._markdown (matches )
275-
282+ if matches_md == "" :
283+ self .print_stdout ("No matches found." )
284+ return
276285 # Output to file or stdout
277286 self .print_to_file_or_stdout (matches_md , self .output )
278287
0 commit comments