-
Notifications
You must be signed in to change notification settings - Fork 80
Reject deprecated SPDX licenses, but be lenient with committed LICENSE files #754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "name": "ambiguous-gfdl-fixture", | ||
| "version": "1.0.0", | ||
| "license": "GFDL-1.3" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "name": "deprecated-agpl-fixture", | ||
| "version": "1.0.0", | ||
| "license": "AGPL-3.0" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ import Registry.App.Prelude | |
|
|
||
| import Codec.JSON.DecodeError as CJ.DecodeError | ||
| import Data.Array as Array | ||
| import Data.Array.NonEmpty as NonEmptyArray | ||
| import Data.Codec as Codec | ||
| import Data.Codec.JSON as CJ | ||
| import Data.Codec.JSON.Common as CJ.Common | ||
|
|
@@ -74,9 +75,18 @@ bowerfileToPursJson | |
| :: Bowerfile | ||
| -> Either String { license :: License, description :: Maybe String, dependencies :: Map PackageName Range } | ||
| bowerfileToPursJson (Bowerfile { description, dependencies, license }) = do | ||
| parsedLicense <- case Array.mapMaybe (hush <<< License.parse) license of | ||
| [] -> Left "No valid SPDX license found in bower.json" | ||
| multiple -> Right $ License.joinWith License.And multiple | ||
| let | ||
| parsedLicenses = license <#> \rawLicense -> | ||
| lmap (\err -> " - " <> rawLicense <> ": " <> err) $ License.parse rawLicense | ||
| { fail: parseErrors, success: validLicenses } = partitionEithers parsedLicenses | ||
|
|
||
| parsedLicense <- | ||
| if not (Array.null parseErrors) then do | ||
| Left $ "Invalid SPDX license(s) in bower.json:\n" <> String.joinWith "\n" parseErrors | ||
|
Comment on lines
+84
to
+85
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to error out even in the case where we might have some valid identifiers from the bowerfile? If yes then we should have an explanation comment here, because I found it surprising
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, on second thought, this is a best effort cross-referencing so we should retain the existing behavior in which we drop licenses which fail parsing |
||
| else do | ||
| case NonEmptyArray.fromArray validLicenses of | ||
| Nothing -> Left "No valid SPDX license found in bower.json" | ||
| Just multiple -> Right $ License.joinWith License.And multiple | ||
|
|
||
| parsedDeps <- parseDependencies dependencies | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,41 +1,40 @@ | ||
| import parse from "spdx-expression-parse"; | ||
| import currentIds from "spdx-license-ids/index.json" with { type: "json" }; | ||
| import deprecatedIds from "spdx-license-ids/deprecated.json" with { type: "json" }; | ||
|
|
||
| export const parseSPDXLicenseIdImpl = (onError, onSuccess, identifier) => { | ||
| try { | ||
| parse(identifier); | ||
| return onSuccess(identifier); | ||
| } catch (_) { | ||
| return onError(`Invalid SPDX identifier ${identifier}`); | ||
| export { currentIds, deprecatedIds }; | ||
|
|
||
| const toTree = (onError, node, onLeaf, onAnd, onOr) => { | ||
| if (node.license != null) { | ||
| const plus = node.plus === true; | ||
| const exception = node.exception ?? ""; | ||
| return onLeaf(node.license)(plus)(exception); | ||
| } | ||
| }; | ||
|
|
||
| // Extract all license IDs from a parsed SPDX expression AST. | ||
| // The AST structure from spdx-expression-parse is: | ||
| // - Simple: { license: 'MIT' } | ||
| // - With exception: { license: 'GPL-2.0', exception: 'Classpath-exception-2.0' } | ||
| // - Compound: { left: {...}, conjunction: 'and'|'or', right: {...} } | ||
| const extractLicenseIds = (ast) => { | ||
| const ids = new Set(); | ||
|
|
||
| const walk = (node) => { | ||
| if (!node) return; | ||
| if (node.license) { | ||
| // Normalize to uppercase for case-insensitive comparison | ||
| ids.add(node.license.toUpperCase()); | ||
| if (node.left != null && node.right != null) { | ||
| const left = toTree(onError, node.left, onLeaf, onAnd, onOr); | ||
| const right = toTree(onError, node.right, onLeaf, onAnd, onOr); | ||
|
|
||
| if (node.conjunction === "and") { | ||
| return onAnd(left)(right); | ||
| } | ||
| if (node.left) walk(node.left); | ||
| if (node.right) walk(node.right); | ||
| }; | ||
|
|
||
| walk(ast); | ||
| return Array.from(ids); | ||
| if (node.conjunction === "or") { | ||
| return onOr(left)(right); | ||
| } | ||
|
|
||
| return onError(`Unsupported SPDX conjunction '${String(node.conjunction)}'`); | ||
| } | ||
|
|
||
| return onError("Unsupported SPDX AST node"); | ||
| }; | ||
|
|
||
| export const extractLicenseIdsImpl = (onError, onSuccess, expression) => { | ||
| export const parseLicenseTreeImpl = (onError, onLeaf, onAnd, onOr, expression) => { | ||
| try { | ||
| const ast = parse(expression); | ||
| return onSuccess(extractLicenseIds(ast)); | ||
| } catch (_) { | ||
| return onError(`Invalid SPDX expression: ${expression}`); | ||
| return toTree(onError, ast, onLeaf, onAnd, onOr); | ||
| } catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| return onError(`Invalid SPDX expression '${expression}': ${message}`); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above – do we want to fail here if just some license in the set doesn't parse?