Skip to content

Commit 280e83f

Browse files
committed
Handle yarn.lock dependencies with @ in constraint #3931
Signed-off-by: Jono Yang <jyang@nexb.com>
1 parent 5d50052 commit 280e83f

File tree

4 files changed

+193
-7
lines changed

4 files changed

+193
-7
lines changed

src/packagedcode/npm.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def assemble(cls, package_data, resource, codebase, package_adder):
207207

208208
@classmethod
209209
def yield_npm_dependencies_and_resources(cls, package_resource, package_data, package_uid, codebase, package_adder):
210-
210+
211211
# in all cases yield possible dependencies
212212
yield from yield_dependencies_from_package_data(package_data, package_resource.path, package_uid)
213213

@@ -424,7 +424,7 @@ def get_workspace_members(cls, workspaces, codebase, workspace_root_path):
424424
workspace_members.append(resource)
425425

426426
# Case 3: This is a complex glob pattern, we are doing a full codebase walk
427-
# and glob matching each resource
427+
# and glob matching each resource
428428
else:
429429
for resource in workspace_root_path:
430430
if NpmPackageJsonHandler.is_datafile(resource.location) and fnmatch.fnmatch(
@@ -469,7 +469,7 @@ def update_workspace_members(cls, workspace_members, codebase):
469469
workspace_package_versions_by_base_purl[base_purl] = version
470470

471471
# Update workspace member package information from
472-
# workspace level data
472+
# workspace level data
473473
for base_purl, dependency in workspace_dependencies_by_base_purl.items():
474474
extracted_requirement = dependency.get('extracted_requirement')
475475
if 'workspace' in extracted_requirement:
@@ -1011,6 +1011,14 @@ def parse(cls, location, package_only=False):
10111011
if '"' in ns_name:
10121012
ns_name = ns_name.replace('"', '')
10131013
ns, _ , name = ns_name.rpartition('/')
1014+
1015+
# sometimes constraints appear in the form of
1016+
# wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
1017+
if '@' in constraint:
1018+
# "npm:wrap-ansi" should be appended to `name`, joined
1019+
# with an "@"
1020+
constraint_package, _, constraint = constraint.partition('@')
1021+
name = f'{name}@{constraint_package}'
10141022
sub_dependencies.append((ns, name, constraint,))
10151023

10161024
elif line.startswith(' ' * 2):
@@ -1112,7 +1120,7 @@ def parse(cls, location, package_only=False):
11121120
resolved_package=resolved_package_data.to_dict(),
11131121
)
11141122

1115-
if not dep_purl in dependencies_by_purl:
1123+
if not dep_purl in dependencies_by_purl:
11161124
dependencies_by_purl[dep_purl] = dep.to_dict()
11171125
else:
11181126
# FIXME: We have duplicate dependencies because of aliases
@@ -1176,7 +1184,7 @@ def parse(cls, location, package_only=False):
11761184
_, name_version = sections
11771185
elif len(sections) == 3:
11781186
_, namespace, name_version = sections
1179-
1187+
11801188
name, version = name_version.split("@")
11811189
elif major_v == "5" or is_shrinkwrap:
11821190
if len(sections) == 3:
@@ -1264,7 +1272,7 @@ def parse(cls, location, package_only=False):
12641272
for key in extra_data_fields:
12651273
value = data.get(key, None)
12661274
if value is not None:
1267-
extra_data_deps[key] = value
1275+
extra_data_deps[key] = value
12681276

12691277
dependency_data = models.DependentPackage(
12701278
purl=purl,
@@ -1762,7 +1770,7 @@ def deps_mapper(deps, package, field_name, is_direct=True):
17621770
deps_by_name[npm_name] = d
17631771

17641772
for fqname, requirement in deps.items():
1765-
# Handle cases in ``resolutions`` with ``**``
1773+
# Handle cases in ``resolutions`` with ``**``
17661774
# "resolutions": {
17671775
# "**/@typescript-eslint/eslint-plugin": "^4.1.1",
17681776
if fqname.startswith('**'):
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
"@isaacs/cliui@^8.0.2":
6+
version "8.0.2"
7+
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
8+
integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
9+
dependencies:
10+
string-width "^5.1.2"
11+
string-width-cjs "npm:string-width@^4.2.0"
12+
strip-ansi "^7.0.1"
13+
strip-ansi-cjs "npm:strip-ansi@^6.0.1"
14+
wrap-ansi "^8.1.0"
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
[
2+
{
3+
"type": "npm",
4+
"namespace": null,
5+
"name": null,
6+
"version": null,
7+
"qualifiers": {},
8+
"subpath": null,
9+
"primary_language": "JavaScript",
10+
"description": null,
11+
"release_date": null,
12+
"parties": [],
13+
"keywords": [],
14+
"homepage_url": null,
15+
"download_url": null,
16+
"size": null,
17+
"sha1": null,
18+
"md5": null,
19+
"sha256": null,
20+
"sha512": null,
21+
"bug_tracking_url": null,
22+
"code_view_url": null,
23+
"vcs_url": null,
24+
"copyright": null,
25+
"holder": null,
26+
"declared_license_expression": null,
27+
"declared_license_expression_spdx": null,
28+
"license_detections": [],
29+
"other_license_expression": null,
30+
"other_license_expression_spdx": null,
31+
"other_license_detections": [],
32+
"extracted_license_statement": null,
33+
"notice_text": null,
34+
"source_packages": [],
35+
"file_references": [],
36+
"is_private": false,
37+
"is_virtual": false,
38+
"extra_data": {},
39+
"dependencies": [
40+
{
41+
"purl": "pkg:npm/%40isaacs/cliui@8.0.2",
42+
"extracted_requirement": "^8.0.2",
43+
"scope": "dependencies",
44+
"is_runtime": true,
45+
"is_optional": false,
46+
"is_resolved": true,
47+
"is_direct": false,
48+
"resolved_package": {
49+
"type": "npm",
50+
"namespace": "@isaacs",
51+
"name": "cliui",
52+
"version": "8.0.2",
53+
"qualifiers": {},
54+
"subpath": null,
55+
"primary_language": "JavaScript",
56+
"description": null,
57+
"release_date": null,
58+
"parties": [],
59+
"keywords": [],
60+
"homepage_url": null,
61+
"download_url": "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
62+
"size": null,
63+
"sha1": "b37667b7bc181c168782259bab42474fbf52b550",
64+
"md5": null,
65+
"sha256": null,
66+
"sha512": "3bc8dc8da6d76a578e1bd0d0d3e0115d66414df9cfe16340ab3ba224aee5978e009b118abff2763384cf8f18d8df39c109fbc15c5cee726d6dc1dc85c9b16a10",
67+
"bug_tracking_url": null,
68+
"code_view_url": null,
69+
"vcs_url": null,
70+
"copyright": null,
71+
"holder": null,
72+
"declared_license_expression": null,
73+
"declared_license_expression_spdx": null,
74+
"license_detections": [],
75+
"other_license_expression": null,
76+
"other_license_expression_spdx": null,
77+
"other_license_detections": [],
78+
"extracted_license_statement": null,
79+
"notice_text": null,
80+
"source_packages": [],
81+
"file_references": [],
82+
"is_private": false,
83+
"is_virtual": true,
84+
"extra_data": {},
85+
"dependencies": [
86+
{
87+
"purl": "pkg:npm/string-width",
88+
"extracted_requirement": "^5.1.2",
89+
"scope": "dependencies",
90+
"is_runtime": true,
91+
"is_optional": false,
92+
"is_resolved": false,
93+
"is_direct": true,
94+
"resolved_package": {},
95+
"extra_data": {}
96+
},
97+
{
98+
"purl": "pkg:npm/string-width-cjs%40%22npm:string-width",
99+
"extracted_requirement": "^4.2.0",
100+
"scope": "dependencies",
101+
"is_runtime": true,
102+
"is_optional": false,
103+
"is_resolved": false,
104+
"is_direct": true,
105+
"resolved_package": {},
106+
"extra_data": {}
107+
},
108+
{
109+
"purl": "pkg:npm/strip-ansi",
110+
"extracted_requirement": "^7.0.1",
111+
"scope": "dependencies",
112+
"is_runtime": true,
113+
"is_optional": false,
114+
"is_resolved": false,
115+
"is_direct": true,
116+
"resolved_package": {},
117+
"extra_data": {}
118+
},
119+
{
120+
"purl": "pkg:npm/strip-ansi-cjs%40%22npm:strip-ansi",
121+
"extracted_requirement": "^6.0.1",
122+
"scope": "dependencies",
123+
"is_runtime": true,
124+
"is_optional": false,
125+
"is_resolved": false,
126+
"is_direct": true,
127+
"resolved_package": {},
128+
"extra_data": {}
129+
},
130+
{
131+
"purl": "pkg:npm/wrap-ansi",
132+
"extracted_requirement": "^8.1.0",
133+
"scope": "dependencies",
134+
"is_runtime": true,
135+
"is_optional": false,
136+
"is_resolved": false,
137+
"is_direct": true,
138+
"resolved_package": {},
139+
"extra_data": {}
140+
}
141+
],
142+
"repository_homepage_url": "https://www.npmjs.com/package/@isaacs/cliui",
143+
"repository_download_url": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
144+
"api_data_url": "https://registry.npmjs.org/@isaacs%2fcliui/8.0.2",
145+
"datasource_id": "yarn_lock_v1",
146+
"purl": "pkg:npm/%40isaacs/cliui@8.0.2"
147+
},
148+
"extra_data": {}
149+
}
150+
],
151+
"repository_homepage_url": null,
152+
"repository_download_url": null,
153+
"api_data_url": null,
154+
"datasource_id": "yarn_lock_v1",
155+
"purl": null
156+
}
157+
]

tests/packagedcode/test_npm.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,13 @@ def test_npm_yarn_with_package_json_resolve_dependencies(self):
360360
expected_file, result_file, remove_uuid=True, regen=REGEN_TEST_FIXTURES
361361
)
362362

363+
def test_npm_yarn_lock_v1_parse_with_other_version_constraint(self):
364+
test_file = self.get_test_loc('npm/yarn-lock/v1-other-constraint/yarn.lock')
365+
expected_loc = self.get_test_loc(
366+
'npm/yarn-lock/v1-other-constraint/yarn.lock-expected')
367+
packages = npm.YarnLockV1Handler.parse(test_file)
368+
self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES)
369+
363370
def test_is_datafile_pnpm_shrinkwrap_yaml(self):
364371
test_file = self.get_test_loc('npm/pnpm/shrinkwrap/v3/vuepack/shrinkwrap.yaml')
365372
assert npm.PnpmShrinkwrapYamlHandler.is_datafile(test_file)

0 commit comments

Comments
 (0)