Skip to content
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
44 changes: 38 additions & 6 deletions src/fosslight_source/_parsing_scancode_file_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,12 +446,44 @@ def _dedupe_detected_comment_expressions(expressions: list[str]) -> list[str]:
return kept


def build_detected_comment_from_dropped_matches(dropped_matches: list) -> str:
"""Build ``Detected: a, b OR c`` from matches dropped by SPDX priority."""
displays = [
_display_expression_for_detected_comment(match)
for match in (dropped_matches or [])
def _expression_covered_by_reported_licenses(
expression: str, reported_licenses: list
) -> bool:
"""
True when expression is already represented in License column values.

Matches the whole expression, or every AND/OR token, case-insensitively.
"""
reported = {
(lic or "").strip().lower()
for lic in (reported_licenses or [])
if (lic or "").strip()
}
if not expression or not reported:
return False
expr = expression.strip()
if expr.lower() in reported:
return True
tokens = [
token.strip().lower()
for token in split_spdx_expression(expr)
if token and token.strip()
]
return bool(tokens) and all(token in reported for token in tokens)


def build_detected_comment_from_dropped_matches(
dropped_matches: list, reported_licenses: list | None = None
) -> str:
"""Build ``Detected: a, b OR c`` from matches dropped by SPDX priority."""
displays = []
for match in (dropped_matches or []):
display = _display_expression_for_detected_comment(match)
if not display:
continue
if _expression_covered_by_reported_licenses(display, reported_licenses or []):
continue
displays.append(display)
deduped = _dedupe_detected_comment_expressions(displays)
if not deduped:
return ""
Expand Down Expand Up @@ -840,7 +872,7 @@ def parsing_scancode(
match for match in all_matches if id(match) not in process_ids
]
detected_comment = build_detected_comment_from_dropped_matches(
dropped_matches
dropped_matches, result_item.licenses
)
if detected_comment:
result_item.comment = detected_comment
Expand Down
104 changes: 104 additions & 0 deletions tests/test_parsing_unknown_spdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,110 @@ def test_dedupe_detected_comment_examples(dropped_exprs, expected_comment):
assert build_detected_comment_from_dropped_matches(dropped) == expected_comment


def test_detected_comment_excludes_licenses_already_in_license_column():
from fosslight_source._parsing_scancode_file_item import (
build_detected_comment_from_dropped_matches,
)
dropped = [
{
"license_expression": "apache-2.0",
"license_expression_spdx": "Apache-2.0",
"matched_text": "Licensed under the Apache License, Version 2.0",
},
{
"license_expression": "mit",
"license_expression_spdx": "MIT",
"matched_text": "Permission is hereby granted",
},
]
assert (
build_detected_comment_from_dropped_matches(dropped, ["Apache-2.0"])
== "Detected: MIT"
)
assert build_detected_comment_from_dropped_matches(dropped, ["Apache-2.0", "MIT"]) == ""


def test_detected_comment_uses_final_licenses_after_column_limit():
"""License dropped by column length limit must still be allowed in Detected."""
fillers = [f"AaaLic{i:02d}-{'y' * 80}" for i in range(20)]
spdx_matches = [
{
"license_expression": name.lower(),
"license_expression_spdx": name,
"matched_text": f"SPDX-License-Identifier: {name}",
}
for name in fillers + ["ZebraExtra"]
]
scancode_file_list = [{
"path": "crowded.c",
"type": "file",
"detected_license_expression": " AND ".join(
name.lower() for name in fillers + ["ZebraExtra"]
),
"license_detections": [
{"matches": spdx_matches},
{
"matches": [{
"license_expression": "zebraextra",
"license_expression_spdx": "ZebraExtra",
"matched_text": "This file is under ZebraExtra terms.",
}],
},
],
"copyrights": [],
}]

success, results, _messages, _ = parsing_scancode(scancode_file_list)

assert success is True
assert "ZebraExtra" not in results[0].licenses
assert "Detected: ZebraExtra" in results[0].comment


def test_readme_spdx_apache_does_not_repeat_in_detected_comment():
"""Body apache-2.0 match dropped by SPDX priority must not echo License column."""
scancode_file_list = [{
"path": "README.md",
"type": "file",
"detected_license_expression": "apache-2.0",
"detected_license_expression_spdx": "Apache-2.0",
"license_detections": [
{
"matches": [{
"license_expression": "apache-2.0",
"license_expression_spdx": "Apache-2.0",
"matched_text": "SPDX-License-Identifier: Apache-2.0",
}],
},
{
"matches": [
{
"license_expression": "apache-2.0",
"license_expression_spdx": "Apache-2.0",
"matched_text": (
'Licensed under the Apache License, Version 2.0 '
'(the "License"); you may not use this file except '
"in compliance with the License."
),
},
{
"license_expression": "apache-2.0",
"license_expression_spdx": "Apache-2.0",
"matched_text": "SPDX-License-Identifier: Apache-2.0",
},
],
},
],
"copyrights": [],
}]

success, results, _messages, _ = parsing_scancode(scancode_file_list)

assert success is True
assert results[0].licenses == ["Apache-2.0"]
assert results[0].comment == ""


def test_spdx_priority_detected_comment_uses_spdx_ids():
"""Dropped body matches use license_expression_spdx in Detected comment."""
scancode_file_list = [{
Expand Down
Loading