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
11 changes: 9 additions & 2 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ func ParseStrict(expression string) (Expression, error) {
// validates identifier syntax, operators, modifiers, and grouping. Known
// identifiers are returned in their canonical form; unknown identifiers are
// preserved as written.
// For ScanCode compatibility, LicenseRef-* exception identifiers are also
// accepted after WITH.
//
// Use ParseStrict when identifiers must also be present in the bundled SPDX
// list.
Expand Down Expand Up @@ -618,7 +620,8 @@ func (p *parser) parseWith() (Expression, error) {
return nil, err
}

if p.current.typ != tokenLicense {
if p.current.typ != tokenLicense &&
(!p.allowUnknownIdentifiers || p.current.typ != tokenLicenseRef) {
return nil, fmt.Errorf("%w: expected exception after WITH", ErrMissingOperand)
}

Expand Down Expand Up @@ -726,7 +729,11 @@ func (p *parser) resolveExceptionIdentifier(identifier string) (string, error) {
if exception := lookupException(identifier); exception != "" {
return exception, nil
}
if p.allowUnknownIdentifiers && validIdentifier(identifier) {
valid := validIdentifier(identifier)
if hasPrefixFold(identifier, "LicenseRef-") {
valid = validLicenseRefString(identifier)
}
if p.allowUnknownIdentifiers && valid {
return identifier, nil
}
return "", fmt.Errorf("%w: %s", ErrInvalidException, identifier)
Expand Down
77 changes: 77 additions & 0 deletions parse_syntax_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ func TestParseSyntaxAllowsUnknownIdentifiers(t *testing.T) {
want: "GPL-2.0-only WITH Future-exception-9.9",
err: ErrInvalidException,
},
{
name: "ScanCode exception reference",
input: "GPL-2.0-only WITH LicenseRef-scancode-cygwin-exception-2.0",
want: "GPL-2.0-only WITH LicenseRef-scancode-cygwin-exception-2.0",
err: ErrMissingOperand,
},
{
name: "custom exception reference",
input: "gpl-2.0-ONLY WITH licenseref-Custom.1-2",
want: "GPL-2.0-only WITH licenseref-Custom.1-2",
err: ErrMissingOperand,
},
{
name: "exception reference with plus modifier",
input: "GPL-2.0+ WITH LicenseRef-custom",
want: "GPL-2.0+ WITH LicenseRef-custom",
err: ErrMissingOperand,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand All @@ -55,6 +73,57 @@ func TestParseSyntaxAllowsUnknownIdentifiers(t *testing.T) {
}
}

func TestParseSyntaxExceptionReferenceRewrite(t *testing.T) {
t.Parallel()

exception := "LicenseRef-scancode-cygwin-exception-2.0"
input := "MIT OR GPL-2.0-only WITH " + exception + " AND Apache-2.0"
expression, err := ParseSyntax(input)
if err != nil {
t.Fatal(err)
}
want := "MIT OR (GPL-2.0-only WITH " + exception + " AND Apache-2.0)"
if got := expression.String(); got != want {
t.Errorf("String() = %q, want %q", got, want)
}
wantAST := &OrExpression{
Left: &License{ID: "MIT"},
Right: &AndExpression{
Left: &License{ID: "GPL-2.0-only", Exception: exception},
Right: &License{ID: "Apache-2.0"},
},
}
if !reflect.DeepEqual(expression, wantAST) {
t.Errorf("AST = %#v, want %#v", expression, wantAST)
}

var identifiers []string
rewritten := RewriteIdentifiers(expression, func(id string) string {
identifiers = append(identifiers, id)
if id == exception {
return "custom-exception"
}
return id
})
wantIdentifiers := []string{"MIT", "GPL-2.0-only", exception, "Apache-2.0"}
if !reflect.DeepEqual(identifiers, wantIdentifiers) {
t.Errorf("identifiers = %q, want %q", identifiers, wantIdentifiers)
}
wantRewritten := "MIT OR (GPL-2.0-only WITH custom-exception AND Apache-2.0)"
if rewritten != wantRewritten {
t.Errorf("RewriteIdentifiers() = %q, want %q", rewritten, wantRewritten)
}
if got := expression.String(); got != want {
t.Errorf("String() after rewrite = %q, want %q", got, want)
}
if _, err := Parse(input); err == nil {
t.Error("Parse() accepted an unknown exception reference")
}
if Valid(input) {
t.Error("Valid() accepted an unknown exception reference")
}
}

func TestParseSyntaxMatchesStrictAST(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -114,6 +183,14 @@ func TestParseSyntaxRejectsMalformedExpressions(t *testing.T) {
"MIT WITH -Future-exception",
"MIT WITH Future-exception-",
"MIT WITH .",
"MIT WITH LicenseRef-",
"MIT WITH LicenseRef--custom",
"MIT WITH LicenseRef-.custom",
"MIT WITH LicenseRef-custom-",
"MIT WITH LicenseRef-custom.",
"MIT WITH LicenseRef-invalid/value",
"MIT WITH LicenseRef-custom+",
"MIT WITH LicenseRef-custom WITH LicenseRef-other",
"LicenseRef-",
"LicenseRef--",
"LicenseRef-.",
Expand Down
Loading