Skip to content

Commit 7e19648

Browse files
authored
Bug fix: Exclude dependency components from undeclared components list
* bug:SP-2714 Removes dependency components from undeclared components list * chore:Updates CHANGELOG.md file * chore:Updates version to v1.25.1
1 parent f368217 commit 7e19648

File tree

6 files changed

+119
-114
lines changed

6 files changed

+119
-114
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- Upcoming changes...
1111

12+
## [1.25.1] - 2025-06-12
13+
### Fixed
14+
- Removed dependency components from the undeclared component list in the `inspect` subcommand.
15+
1216
## [1.25.0] - 2025-06-10
1317
### Added
1418
- Add `fh2` hash while fingerprinting mixed line ending files
@@ -530,3 +534,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
530534
[1.23.0]: https://github.com/scanoss/scanoss.py/compare/v1.22.0...v1.23.0
531535
[1.24.0]: https://github.com/scanoss/scanoss.py/compare/v1.23.0...v1.24.0
532536
[1.25.0]: https://github.com/scanoss/scanoss.py/compare/v1.24.0...v1.25.0
537+
[1.25.1]: https://github.com/scanoss/scanoss.py/compare/v1.25.0...v1.25.1

src/scanoss/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222
THE SOFTWARE.
2323
"""
2424

25-
__version__ = '1.25.0'
25+
__version__ = '1.25.1'

src/scanoss/inspection/copyleft.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"""
2424

2525
import json
26-
from typing import Dict, Any
26+
from typing import Any, Dict
27+
2728
from .policy_check import PolicyCheck, PolicyStatus
2829

2930

@@ -33,7 +34,7 @@ class Copyleft(PolicyCheck):
3334
Inspects components for copyleft licenses
3435
"""
3536

36-
def __init__(
37+
def __init__( # noqa: PLR0913
3738
self,
3839
debug: bool = False,
3940
trace: bool = True,
@@ -158,6 +159,30 @@ def _filter_components_with_copyleft_licenses(self, components: list) -> list:
158159
self.print_debug(f'Copyleft components: {filtered_components}')
159160
return filtered_components
160161

162+
def _get_components(self):
163+
"""
164+
Extract and process components from results and their dependencies.
165+
166+
This method performs the following steps:
167+
1. Validates that `self.results` is loaded. Returns `None` if not.
168+
2. Extracts file, snippet, and dependency components into a dictionary.
169+
3. Converts components to a list and processes their licenses.
170+
171+
:return: A list of processed components with license data, or `None` if `self.results` is not set.
172+
"""
173+
if self.results is None:
174+
return None
175+
176+
components: dict = {}
177+
# Extract component and license data from file and dependency results. Both helpers mutate `components`
178+
self._get_components_data(self.results, components)
179+
self._get_dependencies_data(self.results, components)
180+
# Convert to list and process licenses
181+
results_list = list(components.values())
182+
for component in results_list:
183+
component['licenses'] = list(component['licenses'].values())
184+
return results_list
185+
161186
def run(self):
162187
"""
163188
Run the copyleft license inspection process.

src/scanoss/inspection/policy_check.py

Lines changed: 27 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,30 @@ def _jira_markdown(self, components: list) -> Dict[str, Any]:
166166
"""
167167
pass
168168

169+
@abstractmethod
170+
def _get_components(self):
171+
"""
172+
Retrieve and process components from the preloaded results.
173+
174+
This method performs the following steps:
175+
1. Checks if the results have been previously loaded (self.results).
176+
2. Extracts and processes components from the loaded results.
177+
178+
:return: A list of processed components, or None if an error occurred during any step.
179+
180+
Possible reasons for returning None include:
181+
- Results not loaded (self.results is None)
182+
- Failure to extract components from the results
183+
184+
Note:
185+
- This method assumes that the results have been previously loaded and stored in self.results.
186+
- Implementations must extract components (e.g. via `_get_components_data`,
187+
`_get_dependencies_data`, or other helpers).
188+
- If `self.results` is `None`, simply return `None`.
189+
"""
190+
pass
191+
192+
169193
def _append_component(
170194
self, components: Dict[str, Any], new_component: Dict[str, Any], id: str, status: str
171195
) -> Dict[str, Any]:
@@ -223,6 +247,9 @@ def _get_components_data(self, results: Dict[str, Any], components: Dict[str, An
223247
if not component_id:
224248
self.print_debug(f'WARNING: Result missing id. Skipping: {c}')
225249
continue
250+
## Skip dependency
251+
if component_id == ComponentID.DEPENDENCY.value:
252+
continue
226253
status = c.get('status')
227254
if not status:
228255
self.print_debug(f'WARNING: Result missing status. Skipping: {c}')
@@ -280,33 +307,6 @@ def _get_dependencies_data(self, results: Dict[str, Any], components: Dict[str,
280307
# End of result loop
281308
return components
282309

283-
def _get_components_from_results(self, results: Dict[str, Any]) -> list or None:
284-
"""
285-
Process the results dictionary to extract and format component information.
286-
287-
This function iterates through the results dictionary, identifying components from
288-
different sources (files, snippets, and dependencies). It consolidates this information
289-
into a list of unique components, each with its associated licenses and other details.
290-
291-
:param results: A dictionary containing the raw results of a component scan
292-
:return: A list of dictionaries, each representing a unique component with its details
293-
"""
294-
if results is None:
295-
self.print_stderr('ERROR: Results cannot be empty')
296-
return None
297-
298-
components = {}
299-
# Extract file and snippet components
300-
components = self._get_components_data(results, components)
301-
# Extract dependency components
302-
components = self._get_dependencies_data(results, components)
303-
# Convert to list and process licenses
304-
results_list = list(components.values())
305-
for component in results_list:
306-
component['licenses'] = list(component['licenses'].values())
307-
308-
return results_list
309-
310310
def generate_table(self, headers, rows, centered_columns=None):
311311
"""
312312
Generate a Markdown table.
@@ -411,29 +411,6 @@ def _load_input_file(self):
411411
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
412412
return None
413413

414-
def _get_components(self):
415-
"""
416-
Retrieve and process components from the preloaded results.
417-
418-
This method performs the following steps:
419-
1. Checks if the results have been previously loaded (self.results).
420-
2. Extracts and processes components from the loaded results.
421-
422-
:return: A list of processed components, or None if an error occurred during any step.
423-
Possible reasons for returning None include:
424-
- Results not loaded (self.results is None)
425-
- Failure to extract components from the results
426-
427-
Note:
428-
- This method assumes that the results have been previously loaded and stored in self.results.
429-
- If results is None, the method returns None without performing any further operations.
430-
- The actual processing of components is delegated to the _get_components_from_results method.
431-
"""
432-
if self.results is None:
433-
return None
434-
components = self._get_components_from_results(self.results)
435-
return components
436-
437414
#
438415
# End of PolicyCheck Class
439416
#

src/scanoss/inspection/undeclared_component.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"""
2424

2525
import json
26-
from typing import Dict, Any
26+
from typing import Any, Dict
27+
2728
from .policy_check import PolicyCheck, PolicyStatus
2829

2930

@@ -33,7 +34,7 @@ class UndeclaredComponent(PolicyCheck):
3334
Inspects for undeclared components
3435
"""
3536

36-
def __init__(
37+
def __init__( # noqa: PLR0913
3738
self,
3839
debug: bool = False,
3940
trace: bool = True,
@@ -73,7 +74,7 @@ def _get_undeclared_component(self, components: list) -> list or None:
7374
:return: List of undeclared components
7475
"""
7576
if components is None:
76-
self.print_debug(f'WARNING: No components provided!')
77+
self.print_debug('WARNING: No components provided!')
7778
return None
7879
undeclared_components = []
7980
for component in components:
@@ -87,25 +88,35 @@ def _get_jira_summary(self, components: list) -> str:
8788
"""
8889
Get a summary of the undeclared components.
8990
91+
:param components: List of all components
92+
:return: Component summary markdown
93+
"""
94+
95+
"""
96+
Get a summary of the undeclared components.
97+
9098
:param components: List of all components
9199
:return: Component summary markdown
92100
"""
93101
if len(components) > 0:
102+
json_content = json.dumps(self._generate_scanoss_file(components), indent=2)
103+
94104
if self.sbom_format == 'settings':
95-
json_str = (
96-
json.dumps(self._generate_scanoss_file(components), indent=2)
97-
.replace('\n', '\\n')
98-
.replace('"', '\\"')
105+
return (
106+
f'{len(components)} undeclared component(s) were found.\n'
107+
f'Add the following snippet into your `scanoss.json` file\n'
108+
f'{{code:json}}\n'
109+
f'{json_content}\n'
110+
f'{{code}}\n'
99111
)
100-
return f'{len(components)} undeclared component(s) were found.\nAdd the following snippet into your `scanoss.json` file\n{{code:json}}\n{json.dumps(self._generate_scanoss_file(components), indent=2)}\n{{code}}\n'
101112
else:
102-
json_str = (
103-
json.dumps(self._generate_scanoss_file(components), indent=2)
104-
.replace('\n', '\\n')
105-
.replace('"', '\\"')
113+
return (
114+
f'{len(components)} undeclared component(s) were found.\n'
115+
f'Add the following snippet into your `sbom.json` file\n'
116+
f'{{code:json}}\n'
117+
f'{json_content}\n'
118+
f'{{code}}\n'
106119
)
107-
return f'{len(components)} undeclared component(s) were found.\nAdd the following snippet into your `sbom.json` file\n{{code:json}}\n{json.dumps(self._generate_scanoss_file(components), indent=2)}\n{{code}}\n'
108-
109120
return f'{len(components)} undeclared component(s) were found.\\n'
110121

111122
def _get_summary(self, components: list) -> str:
@@ -190,7 +201,7 @@ def _get_unique_components(self, components: list) -> list:
190201
"""
191202
unique_components = {}
192203
if components is None:
193-
self.print_stderr(f'WARNING: No components provided!')
204+
self.print_stderr('WARNING: No components provided!')
194205
return []
195206

196207
for component in components:
@@ -225,6 +236,29 @@ def _generate_sbom_file(self, components: list) -> dict:
225236

226237
return sbom
227238

239+
def _get_components(self):
240+
"""
241+
Extract and process components from file results only.
242+
243+
This method performs the following steps:
244+
1. Validates if `self.results` is loaded. Returns `None` if not loaded.
245+
2. Extracts file and snippet components into a dictionary.
246+
3. Converts the components dictionary into a list of components.
247+
4. Processes the licenses for each component by converting them into a list.
248+
249+
:return: A list of processed components with their licenses, or `None` if `self.results` is not set.
250+
"""
251+
if self.results is None:
252+
return None
253+
components: dict = {}
254+
# Extract file and snippet components
255+
components = self._get_components_data(self.results, components)
256+
# Convert to list and process licenses
257+
results_list = list(components.values())
258+
for component in results_list:
259+
component['licenses'] = list(component['licenses'].values())
260+
return results_list
261+
228262
def run(self):
229263
"""
230264
Run the undeclared component inspection process.

0 commit comments

Comments
 (0)