Skip to content

Commit 2d30dd7

Browse files
refactor(license): improve license expression normalization (#8257)
Signed-off-by: knqyf263 <knqyf263@gmail.com> Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
1 parent c002327 commit 2d30dd7

File tree

7 files changed

+270
-194
lines changed

7 files changed

+270
-194
lines changed

pkg/licensing/expression/expression.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var (
1111
ErrInvalidExpression = xerrors.New("invalid expression error")
1212
)
1313

14-
type NormalizeFunc func(license string) SimpleExpr
14+
type NormalizeFunc func(license Expression) Expression
1515

1616
func parse(license string) (Expression, error) {
1717
l := NewLexer(strings.NewReader(license))
@@ -24,42 +24,48 @@ func parse(license string) (Expression, error) {
2424
return l.result, nil
2525
}
2626

27-
func Normalize(license string, fn ...NormalizeFunc) (string, error) {
27+
func Normalize(license string, funcs ...NormalizeFunc) (string, error) {
2828
expr, err := parse(license)
2929
if err != nil {
3030
return "", xerrors.Errorf("license (%s) parse error: %w", license, err)
3131
}
32-
expr = normalize(expr, fn...)
32+
for _, fn := range funcs {
33+
expr = normalize(expr, fn)
34+
}
3335

3436
return expr.String(), nil
3537
}
3638

37-
func normalize(expr Expression, fn ...NormalizeFunc) Expression {
38-
switch e := expr.(type) {
39+
func normalize(expr Expression, fn NormalizeFunc) Expression {
40+
// Apply normalization function first
41+
normalized := fn(expr)
42+
43+
switch e := normalized.(type) {
3944
case SimpleExpr:
40-
for _, f := range fn {
41-
normalized := f(e.License)
42-
e.License = normalized.License
43-
e.HasPlus = e.HasPlus || normalized.HasPlus
44-
}
45-
return e
45+
// No further normalization for SimpleExpr
4646
case CompoundExpr:
47-
e.left = normalize(e.left, fn...)
48-
e.right = normalize(e.right, fn...)
47+
// Only recursively process if the result is a CompoundExpr
48+
e.left = normalize(e.left, fn)
49+
e.right = normalize(e.right, fn)
4950
e.conjunction.literal = strings.ToUpper(e.conjunction.literal) // e.g. "and" => "AND"
5051
return e
5152
}
5253

53-
return expr
54+
return normalized
5455
}
5556

5657
// NormalizeForSPDX replaces ' ' to '-' in license-id.
5758
// SPDX license MUST NOT have white space between a license-id.
5859
// There MUST be white space on either side of the operator "WITH".
5960
// ref: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions
60-
func NormalizeForSPDX(s string) SimpleExpr {
61+
func NormalizeForSPDX(expr Expression) Expression {
62+
e, ok := expr.(SimpleExpr)
63+
if !ok {
64+
return expr // do not normalize compound expressions
65+
}
66+
6167
var b strings.Builder
62-
for _, c := range s {
68+
for _, c := range e.License {
6369
switch {
6470
// spec: idstring = 1*(ALPHA / DIGIT / "-" / "." )
6571
case isAlphabet(c) || unicode.IsNumber(c) || c == '-' || c == '.':
@@ -72,7 +78,7 @@ func NormalizeForSPDX(s string) SimpleExpr {
7278
_, _ = b.WriteRune('-')
7379
}
7480
}
75-
return SimpleExpr{License: b.String(), HasPlus: false}
81+
return SimpleExpr{License: b.String(), HasPlus: e.HasPlus}
7682
}
7783

7884
func isAlphabet(r rune) bool {

pkg/licensing/expression/expression_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestNormalize(t *testing.T) {
3737
{
3838
name: "upper",
3939
license: "LGPL-2.1-only OR MIT",
40-
fn: func(license string) SimpleExpr { return SimpleExpr{strings.ToUpper(license), false} },
40+
fn: func(license Expression) Expression { return SimpleExpr{strings.ToUpper(license.String()), false} },
4141
want: "LGPL-2.1-ONLY OR MIT",
4242
},
4343
}

pkg/licensing/expression/types.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import (
55
"slices"
66
)
77

8+
var (
9+
TokenIdent = Token{token: IDENT, literal: "IDENT"}
10+
TokenAnd = Token{token: AND, literal: "AND"}
11+
TokenOR = Token{token: OR, literal: "OR"}
12+
TokenWith = Token{token: WITH, literal: "WITH"}
13+
)
14+
815
type Expression interface {
916
String() string
1017
}
@@ -41,6 +48,14 @@ type CompoundExpr struct {
4148
right Expression
4249
}
4350

51+
func NewCompoundExpr(left Expression, conjunction Token, right Expression) CompoundExpr {
52+
return CompoundExpr{left: left, conjunction: conjunction, right: right}
53+
}
54+
55+
func (c CompoundExpr) Conjunction() Token {
56+
return c.conjunction
57+
}
58+
4459
func (c CompoundExpr) String() string {
4560
left := c.left.String()
4661
if l, ok := c.left.(CompoundExpr); ok {

pkg/licensing/normalize.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -674,17 +674,38 @@ func standardizeKeyAndSuffix(name string) expr.SimpleExpr {
674674
}
675675

676676
func Normalize(name string) string {
677-
return NormalizeLicense(name).String()
677+
return NormalizeLicense(expr.SimpleExpr{License: name}).String()
678678
}
679679

680-
func NormalizeLicense(name string) expr.SimpleExpr {
680+
func NormalizeLicense(exp expr.Expression) expr.Expression {
681+
switch e := exp.(type) {
682+
case expr.SimpleExpr:
683+
return normalizeSimpleExpr(e)
684+
case expr.CompoundExpr:
685+
return normalizeCompoundExpr(e)
686+
}
687+
return exp
688+
}
689+
690+
func normalizeSimpleExpr(e expr.SimpleExpr) expr.Expression {
681691
// Always trim leading and trailing spaces, even if we don't find this license in `mapping`.
682-
name = strings.TrimSpace(name)
692+
name := strings.TrimSpace(e.License)
683693
normalized := standardizeKeyAndSuffix(name)
694+
if found, ok := mapping[normalized.License]; ok {
695+
return expr.SimpleExpr{License: found.License, HasPlus: e.HasPlus || found.HasPlus || normalized.HasPlus}
696+
}
697+
return expr.SimpleExpr{License: name, HasPlus: e.HasPlus}
698+
}
699+
700+
func normalizeCompoundExpr(e expr.CompoundExpr) expr.Expression {
701+
if e.Conjunction() != expr.TokenWith {
702+
return e // Do not normalize compound expressions other than "WITH"
703+
}
704+
normalized := standardizeKeyAndSuffix(e.String())
684705
if found, ok := mapping[normalized.License]; ok {
685706
return expr.SimpleExpr{License: found.License, HasPlus: found.HasPlus || normalized.HasPlus}
686707
}
687-
return expr.SimpleExpr{License: name, HasPlus: false}
708+
return e // Do not normalize compound expressions that are not found in `mapping`
688709
}
689710

690711
func SplitLicenses(str string) []string {

0 commit comments

Comments
 (0)