mirror of
https://github.com/aquasecurity/trivy.git
synced 2026-01-31 05:43:14 +08:00
fix: use canonical SPDX license IDs from embeded licenses.json (#10053)
This commit is contained in:
@@ -264,6 +264,7 @@ license:
|
||||
- Artistic-1.0
|
||||
- Artistic-2.0
|
||||
- BSL-1.0
|
||||
- BSD-1-Clause
|
||||
- BSD-2-Clause-FreeBSD
|
||||
- BSD-2-Clause-NetBSD
|
||||
- BSD-2-Clause
|
||||
|
||||
@@ -11,7 +11,6 @@ var StandardizeKeyAndSuffix = standardizeKeyAndSuffix
|
||||
var NormalizeLicense = normalizeLicense
|
||||
|
||||
// Mapping exports mapping for testing.
|
||||
|
||||
func Mapping() map[string]expression.SimpleExpr {
|
||||
return mapping
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ const (
|
||||
Artistic10 = "Artistic-1.0"
|
||||
Artistic20 = "Artistic-2.0"
|
||||
BCL = "BCL"
|
||||
Beerware = "Beerware"
|
||||
BSD1Clause = "BSD-1-Clause"
|
||||
BSD2ClauseFreeBSD = "BSD-2-Clause-FreeBSD"
|
||||
BSD2ClauseNetBSD = "BSD-2-Clause-NetBSD"
|
||||
BSD2Clause = "BSD-2-Clause"
|
||||
@@ -85,7 +85,6 @@ const (
|
||||
CommonsClause = "Commons-Clause"
|
||||
CPAL10 = "CPAL-1.0"
|
||||
CPL10 = "CPL-1.0"
|
||||
EGenix = "eGenix"
|
||||
EPL10 = "EPL-1.0"
|
||||
EPL20 = "EPL-2.0"
|
||||
EUPL10 = "EUPL-1.0"
|
||||
@@ -121,13 +120,11 @@ const (
|
||||
LGPL20 = "LGPL-2.0"
|
||||
LGPL21 = "LGPL-2.1"
|
||||
LGPL30 = "LGPL-3.0"
|
||||
LGPLLR = "LGPLLR"
|
||||
Libpng = "Libpng"
|
||||
Lil10 = "Lil-1.0"
|
||||
LinuxOpenIB = "Linux-OpenIB"
|
||||
LPL102 = "LPL-1.02"
|
||||
LPL10 = "LPL-1.0"
|
||||
LPPL13c = "LPPL-1.3c"
|
||||
MIT = "MIT"
|
||||
MPL10 = "MPL-1.0"
|
||||
MPL11 = "MPL-1.1"
|
||||
@@ -155,7 +152,6 @@ const (
|
||||
SGIB10 = "SGI-B-1.0"
|
||||
SGIB11 = "SGI-B-1.1"
|
||||
SGIB20 = "SGI-B-2.0"
|
||||
SISSL12 = "SISSL-1.2"
|
||||
SISSL = "SISSL"
|
||||
Sleepycat = "Sleepycat"
|
||||
UnicodeTOU = "Unicode-TOU"
|
||||
@@ -306,6 +302,7 @@ var (
|
||||
Artistic10,
|
||||
Artistic20,
|
||||
BSL10,
|
||||
BSD1Clause,
|
||||
BSD2ClauseFreeBSD,
|
||||
BSD2ClauseNetBSD,
|
||||
BSD2Clause,
|
||||
@@ -420,6 +417,13 @@ func ValidateSPDXLicense(license string) bool {
|
||||
return spdxLicenses.Contains(license)
|
||||
}
|
||||
|
||||
// SPDXLicenseID returns the canonical (properly cased) SPDX license ID.
|
||||
// Returns empty string and false if the license is not in the SPDX list
|
||||
func SPDXLicenseID(license string) (string, bool) {
|
||||
initSpdxLicenses()
|
||||
return spdxLicenses.Find(license)
|
||||
}
|
||||
|
||||
// ValidateSPDXException returns true if SPDX exception list contain exceptionID
|
||||
func ValidateSPDXException(exception string) bool {
|
||||
initSpdxExceptions()
|
||||
|
||||
@@ -46,30 +46,14 @@ var mapping = map[string]expr.SimpleExpr{
|
||||
"ZOPE": licence(expr.ZPL21, false),
|
||||
|
||||
// Non-ambiguous simple mappings
|
||||
"0BSD": licence(expr.ZeroBSD, false),
|
||||
"AFL-1.1": licence(expr.AFL11, false),
|
||||
"AFL-1.2": licence(expr.AFL12, false),
|
||||
"AFL-2": licence(expr.AFL20, false),
|
||||
"AFL-2.0": licence(expr.AFL20, false),
|
||||
"AFL-2.1": licence(expr.AFL21, false),
|
||||
"AFL-3.0": licence(expr.AFL30, false),
|
||||
"AGPL-1.0": licence(expr.AGPL10, false),
|
||||
"AGPL-3.0": licence(expr.AGPL30, false),
|
||||
"APACHE-1": licence(expr.Apache10, false),
|
||||
"APACHE-1.0": licence(expr.Apache10, false),
|
||||
"APACHE-1.1": licence(expr.Apache11, false),
|
||||
"APACHE-2": licence(expr.Apache20, false),
|
||||
"APACHE-2.0": licence(expr.Apache20, false),
|
||||
"APL-2": licence(expr.Apache20, false),
|
||||
"APL-2.0": licence(expr.Apache20, false),
|
||||
"APSL-1.0": licence(expr.APSL10, false),
|
||||
"APSL-1.1": licence(expr.APSL11, false),
|
||||
"APSL-1.2": licence(expr.APSL12, false),
|
||||
"APSL-2.0": licence(expr.APSL20, false),
|
||||
"ARTISTIC-1.0": licence(expr.Artistic10, false),
|
||||
"ARTISTIC-1.0-CL-8": licence(expr.Artistic10cl8, false),
|
||||
"ARTISTIC-1.0-PERL": licence(expr.Artistic10Perl, false),
|
||||
"ARTISTIC-2.0": licence(expr.Artistic20, false),
|
||||
"ASF-1": licence(expr.Apache10, false),
|
||||
"ASF-1.0": licence(expr.Apache10, false),
|
||||
"ASF-1.1": licence(expr.Apache11, false),
|
||||
@@ -81,79 +65,28 @@ var mapping = map[string]expr.SimpleExpr{
|
||||
"ASL-2": licence(expr.Apache20, false),
|
||||
"ASL-2.0": licence(expr.Apache20, false),
|
||||
"BCL": licence(expr.BCL, false),
|
||||
"BEERWARE": licence(expr.Beerware, false),
|
||||
"BOOST": licence(expr.BSL10, false),
|
||||
"BOOST-1.0": licence(expr.BSL10, false),
|
||||
"BOUNCY": licence(expr.MIT, false),
|
||||
"BSD-1": licence(expr.BSD1Clause, false),
|
||||
"BSD-2": licence(expr.BSD2Clause, false),
|
||||
"BSD-2-CLAUSE": licence(expr.BSD2Clause, false),
|
||||
"BSD-2-CLAUSE-FREEBSD": licence(expr.BSD2ClauseFreeBSD, false),
|
||||
"BSD-2-CLAUSE-NETBSD": licence(expr.BSD2ClauseNetBSD, false),
|
||||
"BSD-3": licence(expr.BSD3Clause, false),
|
||||
"BSD-3-CLAUSE": licence(expr.BSD3Clause, false),
|
||||
"BSD-3-CLAUSE-ATTRIBUTION": licence(expr.BSD3ClauseAttribution, false),
|
||||
"BSD-3-CLAUSE-CLEAR": licence(expr.BSD3ClauseClear, false),
|
||||
"BSD-3-CLAUSE-LBNL": licence(expr.BSD3ClauseLBNL, false),
|
||||
"BSD-4": licence(expr.BSD4Clause, false),
|
||||
"BSD-4-CLAUSE": licence(expr.BSD4Clause, false),
|
||||
"BSD-4-CLAUSE-UC": licence(expr.BSD4ClauseUC, false),
|
||||
"BSD-PROTECTION": licence(expr.BSDProtection, false),
|
||||
"BSL": licence(expr.BSL10, false),
|
||||
"BSL-1.0": licence(expr.BSL10, false),
|
||||
"CC-BY-1.0": licence(expr.CCBY10, false),
|
||||
"CC-BY-2.0": licence(expr.CCBY20, false),
|
||||
"CC-BY-2.5": licence(expr.CCBY25, false),
|
||||
"CC-BY-3.0": licence(expr.CCBY30, false),
|
||||
"CC-BY-4.0": licence(expr.CCBY40, false),
|
||||
"CC-BY-NC-1.0": licence(expr.CCBYNC10, false),
|
||||
"CC-BY-NC-2.0": licence(expr.CCBYNC20, false),
|
||||
"CC-BY-NC-2.5": licence(expr.CCBYNC25, false),
|
||||
"CC-BY-NC-3.0": licence(expr.CCBYNC30, false),
|
||||
"CC-BY-NC-4.0": licence(expr.CCBYNC40, false),
|
||||
"CC-BY-NC-ND-1.0": licence(expr.CCBYNCND10, false),
|
||||
"CC-BY-NC-ND-2.0": licence(expr.CCBYNCND20, false),
|
||||
"CC-BY-NC-ND-2.5": licence(expr.CCBYNCND25, false),
|
||||
"CC-BY-NC-ND-3.0": licence(expr.CCBYNCND30, false),
|
||||
"CC-BY-NC-ND-4.0": licence(expr.CCBYNCND40, false),
|
||||
"CC-BY-NC-SA-1.0": licence(expr.CCBYNCSA10, false),
|
||||
"CC-BY-NC-SA-2.0": licence(expr.CCBYNCSA20, false),
|
||||
"CC-BY-NC-SA-2.5": licence(expr.CCBYNCSA25, false),
|
||||
"CC-BY-NC-SA-3.0": licence(expr.CCBYNCSA30, false),
|
||||
"CC-BY-NC-SA-4.0": licence(expr.CCBYNCSA40, false),
|
||||
"CC-BY-ND-1.0": licence(expr.CCBYND10, false),
|
||||
"CC-BY-ND-2.0": licence(expr.CCBYND20, false),
|
||||
"CC-BY-ND-2.5": licence(expr.CCBYND25, false),
|
||||
"CC-BY-ND-3.0": licence(expr.CCBYND30, false),
|
||||
"CC-BY-ND-4.0": licence(expr.CCBYND40, false),
|
||||
"CC-BY-SA-1.0": licence(expr.CCBYSA10, false),
|
||||
"CC-BY-SA-2.0": licence(expr.CCBYSA20, false),
|
||||
"CC-BY-SA-2.5": licence(expr.CCBYSA25, false),
|
||||
"CC-BY-SA-3.0": licence(expr.CCBYSA30, false),
|
||||
"CC-BY-SA-4.0": licence(expr.CCBYSA40, false),
|
||||
"CC0": licence(expr.CC010, false),
|
||||
"CC0-1.0": licence(expr.CC010, false),
|
||||
"CDDL-1": licence(expr.CDDL10, false),
|
||||
"CDDL-1.0": licence(expr.CDDL10, false),
|
||||
"CDDL-1.1": licence(expr.CDDL11, false),
|
||||
"COMMONS-CLAUSE": licence(expr.CommonsClause, false),
|
||||
"CPAL": licence(expr.CPAL10, false),
|
||||
"CPAL-1.0": licence(expr.CPAL10, false),
|
||||
"CPL": licence(expr.CPL10, false),
|
||||
"CPL-1.0": licence(expr.CPL10, false),
|
||||
"ECLIPSE-1.0": licence(expr.EPL10, false),
|
||||
"ECLIPSE-2.0": licence(expr.EPL20, false),
|
||||
"EDL-1.0": licence(expr.BSD3Clause, false),
|
||||
"EGENIX": licence(expr.EGenix, false),
|
||||
"EPL-1.0": licence(expr.EPL10, false),
|
||||
"EPL-2.0": licence(expr.EPL20, false),
|
||||
"EUPL-1.0": licence(expr.EUPL10, false),
|
||||
"EUPL-1.1": licence(expr.EUPL11, false),
|
||||
"EXPAT": licence(expr.MIT, false),
|
||||
"FACEBOOK-2-CLAUSE": licence(expr.Facebook2Clause, false),
|
||||
"FACEBOOK-3-CLAUSE": licence(expr.Facebook3Clause, false),
|
||||
"FACEBOOK-EXAMPLES": licence(expr.FacebookExamples, false),
|
||||
"FREEIMAGE": licence(expr.FreeImage, false),
|
||||
"FTL": licence(expr.FTL, false),
|
||||
"GFDL-1.1": licence(expr.GFDL11, false),
|
||||
"GFDL-1.1-INVARIANTS": licence(expr.GFDL11WithInvariants, false),
|
||||
"GFDL-1.1-NO-INVARIANTS": licence(expr.GFDL11NoInvariants, false),
|
||||
@@ -185,9 +118,6 @@ var mapping = map[string]expr.SimpleExpr{
|
||||
"GPLV2+CE": licence(expr.GPL20withclasspathexception, true),
|
||||
"GUST-FONT": licence(expr.GUSTFont, false),
|
||||
"HSQLDB": licence(expr.BSD3Clause, false),
|
||||
"IMAGEMAGICK": licence(expr.ImageMagick, false),
|
||||
"IPL-1.0": licence(expr.IPL10, false),
|
||||
"ISC": licence(expr.ISC, false),
|
||||
"ISCL": licence(expr.ISC, false),
|
||||
"JQUERY": licence(expr.MIT, false),
|
||||
"LGPL-2": licence(expr.LGPL20, false),
|
||||
@@ -195,78 +125,28 @@ var mapping = map[string]expr.SimpleExpr{
|
||||
"LGPL-2.1": licence(expr.LGPL21, false),
|
||||
"LGPL-3": licence(expr.LGPL30, false),
|
||||
"LGPL-3.0": licence(expr.LGPL30, false),
|
||||
"LGPLLR": licence(expr.LGPLLR, false),
|
||||
"LIBPNG": licence(expr.Libpng, false),
|
||||
"LIL-1.0": licence(expr.Lil10, false),
|
||||
"LINUX-OPENIB": licence(expr.LinuxOpenIB, false),
|
||||
"LPL-1.0": licence(expr.LPL10, false),
|
||||
"LPL-1.02": licence(expr.LPL102, false),
|
||||
"LPPL-1.3C": licence(expr.LPPL13c, false),
|
||||
"MIT": licence(expr.MIT, false),
|
||||
// MIT No Attribution (MIT-0) is not yet supported by google/licenseclassifier
|
||||
"MIT-0": licence(expr.MIT, false),
|
||||
"MIT-LIKE": licence(expr.MIT, false),
|
||||
"MIT-STYLE": licence(expr.MIT, false),
|
||||
"MPL-1": licence(expr.MPL10, false),
|
||||
"MPL-1.0": licence(expr.MPL10, false),
|
||||
"MPL-1.1": licence(expr.MPL11, false),
|
||||
"MPL-2": licence(expr.MPL20, false),
|
||||
"MPL-2.0": licence(expr.MPL20, false),
|
||||
"MS-PL": licence(expr.MSPL, false),
|
||||
"NCSA": licence(expr.NCSA, false),
|
||||
"NPL-1.0": licence(expr.NPL10, false),
|
||||
"NPL-1.1": licence(expr.NPL11, false),
|
||||
"OFL-1.1": licence(expr.OFL11, false),
|
||||
"OPENSSL": licence(expr.OpenSSL, false),
|
||||
"OPENVISION": licence(expr.OpenVision, false),
|
||||
"OSL-1": licence(expr.OSL10, false),
|
||||
"OSL-1.0": licence(expr.OSL10, false),
|
||||
"OSL-1.1": licence(expr.OSL11, false),
|
||||
"OSL-2": licence(expr.OSL20, false),
|
||||
"OSL-2.0": licence(expr.OSL20, false),
|
||||
"OSL-2.1": licence(expr.OSL21, false),
|
||||
"OSL-3": licence(expr.OSL30, false),
|
||||
"OSL-3.0": licence(expr.OSL30, false),
|
||||
"PHP-3.0": licence(expr.PHP30, false),
|
||||
"PHP-3.01": licence(expr.PHP301, false),
|
||||
"PIL": licence(expr.PIL, false),
|
||||
"POSTGRESQL": licence(expr.PostgreSQL, false),
|
||||
"PYTHON-2": licence(expr.Python20, false),
|
||||
"PYTHON-2.0": licence(expr.Python20, false),
|
||||
"PYTHON-2.0-COMPLETE": licence(expr.Python20complete, false),
|
||||
"QPL-1": licence(expr.QPL10, false),
|
||||
"QPL-1.0": licence(expr.QPL10, false),
|
||||
"RUBY": licence(expr.Ruby, false),
|
||||
"SGI-B-1.0": licence(expr.SGIB10, false),
|
||||
"SGI-B-1.1": licence(expr.SGIB11, false),
|
||||
"SGI-B-2.0": licence(expr.SGIB20, false),
|
||||
"SISSL": licence(expr.SISSL, false),
|
||||
"SISSL-1.2": licence(expr.SISSL12, false),
|
||||
"SLEEPYCAT": licence(expr.Sleepycat, false),
|
||||
"UNICODE-DFS-2015": licence(expr.UnicodeDFS2015, false),
|
||||
"UNICODE-DFS-2016": licence(expr.UnicodeDFS2016, false),
|
||||
"UNICODE-TOU": licence(expr.UnicodeTOU, false),
|
||||
"UNLICENSE": licence(expr.Unlicense, false),
|
||||
"UPL-1": licence(expr.UPL10, false),
|
||||
"UPL-1.0": licence(expr.UPL10, false),
|
||||
"W3C": licence(expr.W3C, false),
|
||||
"W3C-19980720": licence(expr.W3C19980720, false),
|
||||
"W3C-20150513": licence(expr.W3C20150513, false),
|
||||
"W3CL": licence(expr.W3C, false),
|
||||
"WTF": licence(expr.WTFPL, false),
|
||||
"WTFPL": licence(expr.WTFPL, false),
|
||||
"X11": licence(expr.X11, false),
|
||||
"XNET": licence(expr.Xnet, false),
|
||||
"ZEND-2": licence(expr.Zend20, false),
|
||||
"ZEND-2.0": licence(expr.Zend20, false),
|
||||
"ZLIB": licence(expr.Zlib, false),
|
||||
"ZLIB-ACKNOWLEDGEMENT": licence(expr.ZlibAcknowledgement, false),
|
||||
"ZOPE-1.1": licence(expr.ZPL11, false),
|
||||
"ZOPE-2.0": licence(expr.ZPL20, false),
|
||||
"ZOPE-2.1": licence(expr.ZPL21, false),
|
||||
"ZPL-1.1": licence(expr.ZPL11, false),
|
||||
"ZPL-2.0": licence(expr.ZPL20, false),
|
||||
"ZPL-2.1": licence(expr.ZPL21, false),
|
||||
"MIT-LIKE": licence(expr.MIT, false),
|
||||
"MIT-STYLE": licence(expr.MIT, false),
|
||||
"MPL-1": licence(expr.MPL10, false),
|
||||
"MPL-2": licence(expr.MPL20, false),
|
||||
"OFL-1.1": licence(expr.OFL11, false),
|
||||
"OPENSSL": licence(expr.OpenSSL, false),
|
||||
"OPENVISION": licence(expr.OpenVision, false),
|
||||
"OSL-1": licence(expr.OSL10, false),
|
||||
"OSL-2": licence(expr.OSL20, false),
|
||||
"OSL-3": licence(expr.OSL30, false),
|
||||
"PIL": licence(expr.PIL, false),
|
||||
"PYTHON-2": licence(expr.Python20, false),
|
||||
"PYTHON-2.0-COMPLETE": licence(expr.Python20complete, false),
|
||||
"QPL-1": licence(expr.QPL10, false),
|
||||
"UPL-1": licence(expr.UPL10, false),
|
||||
"W3CL": licence(expr.W3C, false),
|
||||
"WTF": licence(expr.WTFPL, false),
|
||||
"ZEND-2": licence(expr.Zend20, false),
|
||||
"ZOPE-1.1": licence(expr.ZPL11, false),
|
||||
"ZOPE-2.0": licence(expr.ZPL20, false),
|
||||
"ZOPE-2.1": licence(expr.ZPL21, false),
|
||||
|
||||
// Non simple declared mappings
|
||||
// modified from https://github.com/oss-review-toolkit/ort/blob/fc5389c2cfd9c8b009794c8a11f5c91321b7a730/utils/spdx/src/main/resources/declared-license-mapping.yml
|
||||
@@ -707,6 +587,12 @@ func normalizeSimpleExpr(e expr.SimpleExpr) expr.Expression {
|
||||
if found, ok := mapping[normalized.License]; ok {
|
||||
return expr.SimpleExpr{License: found.License, HasPlus: e.HasPlus || found.HasPlus || normalized.HasPlus}
|
||||
}
|
||||
|
||||
// If not found in mapping, try to get the canonical SPDX license ID
|
||||
if canonical, ok := expr.SPDXLicenseID(normalized.License); ok {
|
||||
return expr.SimpleExpr{License: canonical, HasPlus: e.HasPlus || normalized.HasPlus}
|
||||
}
|
||||
|
||||
return expr.SimpleExpr{License: name, HasPlus: e.HasPlus}
|
||||
}
|
||||
|
||||
|
||||
@@ -384,6 +384,16 @@ func TestNormalize(t *testing.T) {
|
||||
want: "GPL-2.0-with-classpath-exception",
|
||||
wantLicense: expression.SimpleExpr{License: "GPL-2.0-with-classpath-exception"},
|
||||
},
|
||||
// Test canonical SPDX license casing
|
||||
{
|
||||
licenses: []expression.Expression{
|
||||
expression.SimpleExpr{License: "Tcl"},
|
||||
expression.SimpleExpr{License: "tcl"},
|
||||
expression.SimpleExpr{License: "TCL"},
|
||||
},
|
||||
want: "TCL",
|
||||
wantLicense: expression.SimpleExpr{License: "TCL"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
|
||||
@@ -47,6 +47,12 @@ func (s caseInsensitiveStringSet) Contains(item string) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
// Find returns the stored value with its original casing
|
||||
func (s caseInsensitiveStringSet) Find(item string) (string, bool) {
|
||||
value, exists := s[strings.ToLower(item)]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// Size returns the number of items in the set
|
||||
func (s caseInsensitiveStringSet) Size() int {
|
||||
return len(s)
|
||||
|
||||
@@ -431,3 +431,70 @@ func TestCaseInsensitiveSet_Difference(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaseInsensitiveSet_Find(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
set set.Set[string]
|
||||
lookup string
|
||||
wantValue string
|
||||
wantFound bool
|
||||
}{
|
||||
{
|
||||
name: "exact match",
|
||||
set: set.NewCaseInsensitive("Hello", "World"),
|
||||
lookup: "Hello",
|
||||
wantValue: "Hello",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "lowercase lookup",
|
||||
set: set.NewCaseInsensitive("Hello", "World"),
|
||||
lookup: "hello",
|
||||
wantValue: "Hello",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "uppercase lookup",
|
||||
set: set.NewCaseInsensitive("Hello", "World"),
|
||||
lookup: "HELLO",
|
||||
wantValue: "Hello",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "mixed case lookup",
|
||||
set: set.NewCaseInsensitive("Hello", "World"),
|
||||
lookup: "hElLo",
|
||||
wantValue: "Hello",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
set: set.NewCaseInsensitive("Hello", "World"),
|
||||
lookup: "Foo",
|
||||
wantValue: "",
|
||||
wantFound: false,
|
||||
},
|
||||
{
|
||||
name: "preserves first casing",
|
||||
set: set.NewCaseInsensitive("TCL", "Tcl"),
|
||||
lookup: "tcl",
|
||||
wantValue: "TCL",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "empty set",
|
||||
set: set.NewCaseInsensitive(),
|
||||
lookup: "test",
|
||||
wantValue: "",
|
||||
wantFound: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotValue, gotFound := tt.set.Find(tt.lookup)
|
||||
assert.Equal(t, tt.wantValue, gotValue)
|
||||
assert.Equal(t, tt.wantFound, gotFound)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ type Set[T comparable] interface {
|
||||
// Contains checks if an item exists in the set
|
||||
Contains(item T) bool
|
||||
|
||||
// Find returns the stored value for the given item if it exists
|
||||
// For case-insensitive sets, this returns the canonical (original) casing
|
||||
Find(item T) (T, bool)
|
||||
|
||||
// Size returns the number of items in the set
|
||||
Size() int
|
||||
|
||||
|
||||
@@ -38,6 +38,12 @@ func (s unsafeSet[T]) Contains(item T) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
// Find returns the stored value for the given item if it exists
|
||||
func (s unsafeSet[T]) Find(item T) (T, bool) {
|
||||
_, exists := s[item]
|
||||
return item, exists
|
||||
}
|
||||
|
||||
// Size returns the number of items in the set
|
||||
func (s unsafeSet[T]) Size() int {
|
||||
return len(s)
|
||||
|
||||
Reference in New Issue
Block a user