diff --git a/src/packagedcode/npm.py b/src/packagedcode/npm.py index 38bcbba287..8b2e786418 100644 --- a/src/packagedcode/npm.py +++ b/src/packagedcode/npm.py @@ -1780,6 +1780,9 @@ def deps_mapper(deps, package, field_name, is_direct=True): ns, name = split_scoped_package_name(fqname) if not name: continue + if '@' in requirement: + requirement_package, _, requirement = requirement.partition('@') + name = f'{name}@{requirement_package}' purl = PackageURL(type='npm', namespace=ns, name=name).to_string() # optionalDependencies override the dependencies with the same name diff --git a/tests/packagedcode/data/npm/special_extracted_requirements/package.json b/tests/packagedcode/data/npm/special_extracted_requirements/package.json new file mode 100755 index 0000000000..fb910a8600 --- /dev/null +++ b/tests/packagedcode/data/npm/special_extracted_requirements/package.json @@ -0,0 +1,68 @@ +{ + "name": "@tapjs/tapjs", + "private": true, + "workspaces": [ + "src/*" + ], + "type": "module", + "prettier": { + "experimentalTernaries": true, + "semi": false, + "printWidth": 70, + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "jsxSingleQuote": false, + "bracketSameLine": true, + "arrowParens": "avoid", + "endOfLine": "lf" + }, + "devDependencies": { + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1" + }, + "scripts": { + "start": "npm run start -w src/docs", + "predocsbuild": "npm run bootstrap", + "docsbuild": "npm run build -w src/docs", + "pretest": "rimraf src/test/test-built/dist/node_modules", + "presnap": "rimraf src/test/test-built/dist/node_modules", + "test": "nx run-many -t test", + "test:bootstrap": "bash ./scripts/test-bootstrap.sh", + "snap": "TAP_TYPECHECK=0 TAP_TIMEOUT=240 nx run-many -t snap", + "format": "nx run-many -t format", + "typedoc": "typedoc", + "bootstrap": "bash ./scripts/bootstrap.sh", + "build": "node ./scripts/default-build.mjs", + "pindeps": "node ./scripts/version.mjs pindeps", + "v": "node ./scripts/version.mjs", + "p": "bash ./scripts/bump-changed.sh", + "pub": "npm run v -- pub", + "deploy:docs": "npm run deploy:prod -w src/docs", + "postv": "npm run deploy:docs", + "pj": "node scripts/normalize-package-json.js src/*/package.json" + }, + "repository": "https://github.com/tapjs/tapjs", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + }, + "overrides": { + "braces@3": "^3.0.3", + "axios@1.0.0 - 1.5.1": "^1.7.2", + "netlify-cli": { + "braces": "^3.0.3", + "micromatch": "^4.0.7", + "chokidar": { + "braces": "^3.0.3" + }, + "http-proxy-middleware": { + "micromatch": { + ".": "^4.0.7", + "braces": "^3.0.3" + } + } + }, + "micromatch@4.0.5": "^4.0.7", + "tar@6.1.11": "6.2" + } +} diff --git a/tests/packagedcode/data/npm/special_extracted_requirements/package.json.expected b/tests/packagedcode/data/npm/special_extracted_requirements/package.json.expected new file mode 100755 index 0000000000..07e89067ca --- /dev/null +++ b/tests/packagedcode/data/npm/special_extracted_requirements/package.json.expected @@ -0,0 +1,88 @@ +[ + { + "type": "npm", + "namespace": "@tapjs", + "name": "tapjs", + "version": null, + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "https://github.com/tapjs/tapjs", + "copyright": null, + "holder": null, + "declared_license_expression": "blueoak-1.0.0", + "declared_license_expression_spdx": "BlueOak-1.0.0", + "license_detections": [ + { + "license_expression": "blueoak-1.0.0", + "license_expression_spdx": "BlueOak-1.0.0", + "matches": [ + { + "license_expression": "blueoak-1.0.0", + "spdx_license_expression": "BlueOak-1.0.0", + "from_file": null, + "start_line": 1, + "end_line": 1, + "matcher": "1-spdx-id", + "score": 100.0, + "matched_length": 4, + "match_coverage": 100.0, + "rule_relevance": 100, + "rule_identifier": "spdx-license-identifier-blueoak_1_0_0-a27d3d91aab5047de087c05901869c5f4a1f12e7", + "rule_url": null, + "matched_text": "BlueOak-1.0.0" + } + ], + "identifier": "blueoak_1_0_0-3c25fef0-5634-6497-b6c8-0c16ab8320b3" + } + ], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": "- BlueOak-1.0.0\n", + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": true, + "is_virtual": false, + "extra_data": { + "workspaces": [ + "src/*" + ], + "engines": { + "node": "20 || >=22" + } + }, + "dependencies": [ + { + "purl": "pkg:npm/strip-ansi-cjs%40npm:strip-ansi", + "extracted_requirement": "^6.0.1", + "scope": "devDependencies", + "is_runtime": false, + "is_optional": true, + "is_resolved": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "npm_package_json", + "purl": "pkg:npm/%40tapjs/tapjs" + } +] \ No newline at end of file diff --git a/tests/packagedcode/test_npm.py b/tests/packagedcode/test_npm.py index cae68b4c13..30acb962e2 100644 --- a/tests/packagedcode/test_npm.py +++ b/tests/packagedcode/test_npm.py @@ -393,6 +393,12 @@ def test_parse_pnpm_shrinkwrap_yaml(self): packages = npm.PnpmLockYamlHandler.parse(test_file) self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES) + def test_parse_package_json_special_dep_requirements(self): + test_file = self.get_test_loc('npm/special_extracted_requirements/package.json') + expected_loc = self.get_test_loc('npm/special_extracted_requirements/package.json.expected') + packages = npm.NpmPackageJsonHandler.parse(test_file) + self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES) + def test_pnpm_scan_with_workspace_package_json(self): test_folder = self.get_test_loc('npm/pnpm/pnpm-lock/v5/cobe/') expected_file = self.get_test_loc('npm/pnpm/pnpm-lock/v5/cobe-scan.expected.json')