diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 80c86fc..e3f5030 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +v30.4.5 - 2026-03-03 +-------------------- + +This is a minor bugfix release: + +- Fix validate() to properly report errors for invalid expressions with + trailing operators (e.g. "GPL-3.0-or-later AND"). + v30.4.4 - 2025-01-10 -------------------- diff --git a/src/license_expression/__init__.py b/src/license_expression/__init__.py index dc1ab31..712e2df 100644 --- a/src/license_expression/__init__.py +++ b/src/license_expression/__init__.py @@ -785,10 +785,13 @@ def validate(self, expression, strict=True, **kwargs): # Check `expression` type and syntax try: parsed_expression = self.parse(expression, strict=strict) - except ExpressionError as e: + except ExpressionParseError as e: expression_info.errors.append(str(e)) expression_info.invalid_symbols.append(e.token_string) return expression_info + except ExpressionError as e: + expression_info.errors.append(str(e)) + return expression_info # Check `expression` keys (validate) try: diff --git a/tests/test_license_expression.py b/tests/test_license_expression.py index 193fafd..e43e057 100644 --- a/tests/test_license_expression.py +++ b/tests/test_license_expression.py @@ -2472,6 +2472,45 @@ def test_validation_invalid_license_exception_strict_false(self): assert result.errors == [] assert result.invalid_symbols == [] + def test_validate_trailing_and_operator(self): + result = self.licensing.validate("GPL-2.0-or-later AND") + assert result.original_expression == "GPL-2.0-or-later AND" + assert not result.normalized_expression + assert len(result.errors) == 1 + assert "AND" in result.errors[0] + + def test_validate_trailing_or_operator(self): + result = self.licensing.validate("GPL-2.0-or-later OR") + assert result.original_expression == "GPL-2.0-or-later OR" + assert not result.normalized_expression + assert len(result.errors) == 1 + assert "OR" in result.errors[0] + + def test_validate_trailing_with_operator(self): + result = self.licensing.validate("GPL-2.0-or-later WITH") + assert result.original_expression == "GPL-2.0-or-later WITH" + assert not result.normalized_expression + assert len(result.errors) == 1 + + def test_validate_multiple_trailing_operators(self): + result = self.licensing.validate("GPL-2.0-or-later AND MIT OR") + assert result.original_expression == "GPL-2.0-or-later AND MIT OR" + assert not result.normalized_expression + assert len(result.errors) == 1 + assert "OR" in result.errors[0] + + def test_validate_leading_operator(self): + result = self.licensing.validate("AND MIT") + assert result.original_expression == "AND MIT" + assert not result.normalized_expression + assert len(result.errors) == 1 + + def test_validate_only_operators(self): + result = self.licensing.validate("AND OR") + assert result.original_expression == "AND OR" + assert not result.normalized_expression + assert len(result.errors) == 1 + class UtilTest(TestCase): test_data_dir = join(dirname(__file__), "data")