Skip to content

Commit ca0eac9

Browse files
committed
Fix validate() to report errors for invalid SPDX expressions with trailing operators
The validate() method failed silently when encountering expressions with trailing operators (e.g. 'GPL-3.0-or-later AND'). The root cause was that the exception handler accessed e.token_string on ExpressionError, but only ExpressionParseError (a subclass) has that attribute. Fix by catching ExpressionParseError and ExpressionError separately. Fixes #114 Signed-off-by: Saquib Saifee <saquibsaifee2@gmail.com>
1 parent a3c00c0 commit ca0eac9

3 files changed

Lines changed: 51 additions & 1 deletion

File tree

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Changelog
22
=========
33

4+
v30.4.5 - 2026-03-03
5+
--------------------
6+
7+
This is a minor bugfix release:
8+
9+
- Fix validate() to properly report errors for invalid expressions with
10+
trailing operators (e.g. "GPL-3.0-or-later AND").
11+
412
v30.4.4 - 2025-01-10
513
--------------------
614

src/license_expression/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,10 +785,13 @@ def validate(self, expression, strict=True, **kwargs):
785785
# Check `expression` type and syntax
786786
try:
787787
parsed_expression = self.parse(expression, strict=strict)
788-
except ExpressionError as e:
788+
except ExpressionParseError as e:
789789
expression_info.errors.append(str(e))
790790
expression_info.invalid_symbols.append(e.token_string)
791791
return expression_info
792+
except ExpressionError as e:
793+
expression_info.errors.append(str(e))
794+
return expression_info
792795

793796
# Check `expression` keys (validate)
794797
try:

tests/test_license_expression.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2472,6 +2472,45 @@ def test_validation_invalid_license_exception_strict_false(self):
24722472
assert result.errors == []
24732473
assert result.invalid_symbols == []
24742474

2475+
def test_validate_trailing_and_operator(self):
2476+
result = self.licensing.validate("GPL-2.0-or-later AND")
2477+
assert result.original_expression == "GPL-2.0-or-later AND"
2478+
assert not result.normalized_expression
2479+
assert len(result.errors) == 1
2480+
assert "AND" in result.errors[0]
2481+
2482+
def test_validate_trailing_or_operator(self):
2483+
result = self.licensing.validate("GPL-2.0-or-later OR")
2484+
assert result.original_expression == "GPL-2.0-or-later OR"
2485+
assert not result.normalized_expression
2486+
assert len(result.errors) == 1
2487+
assert "OR" in result.errors[0]
2488+
2489+
def test_validate_trailing_with_operator(self):
2490+
result = self.licensing.validate("GPL-2.0-or-later WITH")
2491+
assert result.original_expression == "GPL-2.0-or-later WITH"
2492+
assert not result.normalized_expression
2493+
assert len(result.errors) == 1
2494+
2495+
def test_validate_multiple_trailing_operators(self):
2496+
result = self.licensing.validate("GPL-2.0-or-later AND MIT OR")
2497+
assert result.original_expression == "GPL-2.0-or-later AND MIT OR"
2498+
assert not result.normalized_expression
2499+
assert len(result.errors) == 1
2500+
assert "OR" in result.errors[0]
2501+
2502+
def test_validate_leading_operator(self):
2503+
result = self.licensing.validate("AND MIT")
2504+
assert result.original_expression == "AND MIT"
2505+
assert not result.normalized_expression
2506+
assert len(result.errors) == 1
2507+
2508+
def test_validate_only_operators(self):
2509+
result = self.licensing.validate("AND OR")
2510+
assert result.original_expression == "AND OR"
2511+
assert not result.normalized_expression
2512+
assert len(result.errors) == 1
2513+
24752514

24762515
class UtilTest(TestCase):
24772516
test_data_dir = join(dirname(__file__), "data")

0 commit comments

Comments
 (0)