Skip to content

Commit 8f0c2c0

Browse files
fix(node): Tolerate unused malformed package.json files under node_modules
As a side effect of cd56fa7 the Yarn analyzer now tries to parse `package.json` files from test directories, which were previously not part of the `node_modules` directory. This also applies to Yarn2. This causes an error if the installed module contains additional `package.json` files which are unused, such as for test project like e.g. [1]. [1]: https://github.com/browserify/resolve/blob/v1.22.2/test/resolver/malformed_package_json/package.json Co-authored-by: Frank Viernau <frank_viernau@epam.com> Signed-off-by: Marcel Bochtler <marcel.bochtler@bosch.com>
1 parent f9cfa61 commit 8f0c2c0

File tree

6 files changed

+320
-4
lines changed

6 files changed

+320
-4
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
---
2+
project:
3+
id: "Yarn::yarn-project-with-dep-with-invalid-package-json:1.0.0"
4+
definition_file_path: "plugins/package-managers/node/src/funTest/assets/projects/synthetic/yarn/invalid-package-json/package.json"
5+
authors:
6+
- "The Author"
7+
declared_licenses:
8+
- "Apache-2.0"
9+
declared_licenses_processed:
10+
spdx_expression: "Apache-2.0"
11+
vcs:
12+
type: "Git"
13+
url: "https://github.com/oss-review-toolkit/ort.git"
14+
revision: ""
15+
path: ""
16+
vcs_processed:
17+
type: "Git"
18+
url: "<REPLACE_URL_PROCESSED>"
19+
revision: "<REPLACE_REVISION>"
20+
path: "<REPLACE_PATH>"
21+
description: "Yarn test project with yarn.lock."
22+
homepage_url: ""
23+
scopes:
24+
- name: "dependencies"
25+
dependencies:
26+
- id: "NPM::resolve:1.22.2"
27+
dependencies:
28+
- id: "NPM::is-core-module:2.16.1"
29+
dependencies:
30+
- id: "NPM::hasown:2.0.2"
31+
dependencies:
32+
- id: "NPM::function-bind:1.1.2"
33+
- id: "NPM::path-parse:1.0.7"
34+
- id: "NPM::supports-preserve-symlinks-flag:1.0.0"
35+
packages:
36+
- id: "NPM::function-bind:1.1.2"
37+
purl: "pkg:npm/function-bind@1.1.2"
38+
authors:
39+
- "Raynos"
40+
declared_licenses:
41+
- "MIT"
42+
declared_licenses_processed:
43+
spdx_expression: "MIT"
44+
description: "Implementation of Function.prototype.bind"
45+
homepage_url: "https://github.com/Raynos/function-bind"
46+
binary_artifact:
47+
url: ""
48+
hash:
49+
value: ""
50+
algorithm: ""
51+
source_artifact:
52+
url: "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
53+
hash:
54+
value: "2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
55+
algorithm: "SHA-1"
56+
vcs:
57+
type: "Git"
58+
url: "https://github.com/Raynos/function-bind.git"
59+
revision: "40197beb5f4cf89dd005f0b268256c1e4716ea81"
60+
path: ""
61+
vcs_processed:
62+
type: "Git"
63+
url: "https://github.com/Raynos/function-bind.git"
64+
revision: "40197beb5f4cf89dd005f0b268256c1e4716ea81"
65+
path: ""
66+
- id: "NPM::hasown:2.0.2"
67+
purl: "pkg:npm/hasown@2.0.2"
68+
authors:
69+
- "Jordan Harband"
70+
declared_licenses:
71+
- "MIT"
72+
declared_licenses_processed:
73+
spdx_expression: "MIT"
74+
description: "A robust, ES3 compatible, \"has own property\" predicate."
75+
homepage_url: "https://github.com/inspect-js/hasOwn#readme"
76+
binary_artifact:
77+
url: ""
78+
hash:
79+
value: ""
80+
algorithm: ""
81+
source_artifact:
82+
url: "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
83+
hash:
84+
value: "003eaf91be7adc372e84ec59dc37252cedb80003"
85+
algorithm: "SHA-1"
86+
vcs:
87+
type: "Git"
88+
url: "git+https://github.com/inspect-js/hasOwn.git"
89+
revision: "d00d35005baf16a33d691a13f8ad627f35040742"
90+
path: ""
91+
vcs_processed:
92+
type: "Git"
93+
url: "https://github.com/inspect-js/hasOwn.git"
94+
revision: "d00d35005baf16a33d691a13f8ad627f35040742"
95+
path: ""
96+
- id: "NPM::is-core-module:2.16.1"
97+
purl: "pkg:npm/is-core-module@2.16.1"
98+
authors:
99+
- "Jordan Harband"
100+
declared_licenses:
101+
- "MIT"
102+
declared_licenses_processed:
103+
spdx_expression: "MIT"
104+
description: "Is this specifier a node.js core module?"
105+
homepage_url: "https://github.com/inspect-js/is-core-module"
106+
binary_artifact:
107+
url: ""
108+
hash:
109+
value: ""
110+
algorithm: ""
111+
source_artifact:
112+
url: "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz"
113+
hash:
114+
value: "2a98801a849f43e2add644fbb6bc6229b19a4ef4"
115+
algorithm: "SHA-1"
116+
vcs:
117+
type: "Git"
118+
url: "git+https://github.com/inspect-js/is-core-module.git"
119+
revision: "7c4284853357b2fcd49d42010d5a2b8a8420905f"
120+
path: ""
121+
vcs_processed:
122+
type: "Git"
123+
url: "https://github.com/inspect-js/is-core-module.git"
124+
revision: "7c4284853357b2fcd49d42010d5a2b8a8420905f"
125+
path: ""
126+
- id: "NPM::path-parse:1.0.7"
127+
purl: "pkg:npm/path-parse@1.0.7"
128+
authors:
129+
- "Javier Blanco <http://jbgutierrez.info>"
130+
declared_licenses:
131+
- "MIT"
132+
declared_licenses_processed:
133+
spdx_expression: "MIT"
134+
description: "Node.js path.parse() ponyfill"
135+
homepage_url: "https://github.com/jbgutierrez/path-parse#readme"
136+
binary_artifact:
137+
url: ""
138+
hash:
139+
value: ""
140+
algorithm: ""
141+
source_artifact:
142+
url: "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
143+
hash:
144+
value: "fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
145+
algorithm: "SHA-1"
146+
vcs:
147+
type: "Git"
148+
url: "https://github.com/jbgutierrez/path-parse.git"
149+
revision: "9f1db2802ffbc572e6b447f66126697e33b0055e"
150+
path: ""
151+
vcs_processed:
152+
type: "Git"
153+
url: "https://github.com/jbgutierrez/path-parse.git"
154+
revision: "9f1db2802ffbc572e6b447f66126697e33b0055e"
155+
path: ""
156+
- id: "NPM::resolve:1.22.2"
157+
purl: "pkg:npm/resolve@1.22.2"
158+
authors:
159+
- "James Halliday"
160+
declared_licenses:
161+
- "MIT"
162+
declared_licenses_processed:
163+
spdx_expression: "MIT"
164+
description: "resolve like require.resolve() on behalf of files asynchronously and\
165+
\ synchronously"
166+
homepage_url: "https://github.com/browserify/resolve#readme"
167+
binary_artifact:
168+
url: ""
169+
hash:
170+
value: ""
171+
algorithm: ""
172+
source_artifact:
173+
url: "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz"
174+
hash:
175+
value: "0ed0943d4e301867955766c9f3e1ae6d01c6845f"
176+
algorithm: "SHA-1"
177+
vcs:
178+
type: "Git"
179+
url: "git://github.com/browserify/resolve.git"
180+
revision: "c2f9ce254f0157b5e2e53e9aee0403c510909f7d"
181+
path: ""
182+
vcs_processed:
183+
type: "Git"
184+
url: "https://github.com/browserify/resolve.git"
185+
revision: "c2f9ce254f0157b5e2e53e9aee0403c510909f7d"
186+
path: ""
187+
- id: "NPM::supports-preserve-symlinks-flag:1.0.0"
188+
purl: "pkg:npm/supports-preserve-symlinks-flag@1.0.0"
189+
authors:
190+
- "Jordan Harband"
191+
declared_licenses:
192+
- "MIT"
193+
declared_licenses_processed:
194+
spdx_expression: "MIT"
195+
description: "Determine if the current node version supports the `--preserve-symlinks`\
196+
\ flag."
197+
homepage_url: "https://github.com/inspect-js/node-supports-preserve-symlinks-flag#readme"
198+
binary_artifact:
199+
url: ""
200+
hash:
201+
value: ""
202+
algorithm: ""
203+
source_artifact:
204+
url: "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
205+
hash:
206+
value: "6eda4bd344a3c94aea376d4cc31bc77311039e09"
207+
algorithm: "SHA-1"
208+
vcs:
209+
type: "Git"
210+
url: "git+https://github.com/inspect-js/node-supports-preserve-symlinks-flag.git"
211+
revision: "1f7cac19c0c298cf40b3f2f3c735477ad579ac61"
212+
path: ""
213+
vcs_processed:
214+
type: "Git"
215+
url: "https://github.com/inspect-js/node-supports-preserve-symlinks-flag.git"
216+
revision: "1f7cac19c0c298cf40b3f2f3c735477ad579ac61"
217+
path: ""
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "yarn-project-with-dep-with-invalid-package-json",
3+
"version": "1.0.0",
4+
"description": "Yarn test project with yarn.lock.",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/oss-review-toolkit/ort.git"
8+
},
9+
"license": "Apache-2.0",
10+
"author": "The Author <the.author@mail.example> (https://www.the.author.example/index.html)",
11+
"private": true,
12+
"dependencies": {
13+
"resolve": "1.22.2"
14+
}
15+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
function-bind@^1.1.2:
6+
version "1.1.2"
7+
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
8+
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
9+
10+
hasown@^2.0.2:
11+
version "2.0.2"
12+
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
13+
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
14+
dependencies:
15+
function-bind "^1.1.2"
16+
17+
is-core-module@^2.11.0:
18+
version "2.16.1"
19+
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
20+
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
21+
dependencies:
22+
hasown "^2.0.2"
23+
24+
path-parse@^1.0.7:
25+
version "1.0.7"
26+
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
27+
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
28+
29+
resolve@1.22.2:
30+
version "1.22.2"
31+
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
32+
integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
33+
dependencies:
34+
is-core-module "^2.11.0"
35+
path-parse "^1.0.7"
36+
supports-preserve-symlinks-flag "^1.0.0"
37+
38+
supports-preserve-symlinks-flag@^1.0.0:
39+
version "1.0.0"
40+
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
41+
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==

plugins/package-managers/node/src/funTest/kotlin/yarn/YarnFunTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.ossreviewtoolkit.plugins.packagemanagers.node.yarn
2121

2222
import io.kotest.core.spec.style.StringSpec
23+
import io.kotest.matchers.collections.beEmpty
2324
import io.kotest.matchers.should
2425

2526
import org.ossreviewtoolkit.analyzer.analyze
@@ -51,6 +52,16 @@ class YarnFunTest : StringSpec({
5152
result.toYaml() should matchExpectedResult(expectedResultFile, definitionFile)
5253
}
5354

55+
"Resolve dependencies for a project which installs a module with an invalid unused 'package.json'" {
56+
val definitionFile = getAssetFile("projects/synthetic/yarn/invalid-package-json/package.json")
57+
val expectedResultFile = getAssetFile("projects/synthetic/yarn/invalid-package-json-expected-output.yml")
58+
59+
val result = YarnFactory.create().resolveSingleProject(definitionFile, resolveScopes = true)
60+
61+
result.issues should beEmpty()
62+
result.toYaml() should matchExpectedResult(expectedResultFile, definitionFile)
63+
}
64+
5465
"Resolve dependencies for a project depending on Babel correctly" {
5566
val definitionFile = getAssetFile("projects/synthetic/yarn/babel/package.json")
5667
val expectedResultFile = getAssetFile("projects/synthetic/yarn/babel-expected-output.yml")

plugins/package-managers/node/src/main/kotlin/Utils.kt

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.ossreviewtoolkit.plugins.packagemanagers.node
2121

2222
import java.io.File
23+
import java.util.LinkedList
2324

2425
import org.ossreviewtoolkit.analyzer.PackageManager.Companion.processPackageVcs
2526
import org.ossreviewtoolkit.analyzer.parseAuthorString
@@ -245,7 +246,37 @@ internal val PackageJson.moduleId: String get() =
245246
/**
246247
* Return the directories of all modules which have been installed in the 'node_modules' dir within [moduleDir].
247248
*/
248-
internal fun getInstalledModulesDirs(moduleDir: File): Set<File> =
249-
moduleDir.resolve("node_modules").walk().filter {
250-
it.isFile && it.name == NodePackageManagerType.DEFINITION_FILE
251-
}.mapTo(mutableSetOf()) { it.parentFile.realFile }
249+
internal fun getInstalledModulesDirs(projectDir: File): Set<File> {
250+
val modulesDirsToProcess = LinkedList<File>().apply { add(projectDir.realFile) }
251+
val discoveredModulesDirs = mutableSetOf<File>()
252+
253+
while (modulesDirsToProcess.isNotEmpty()) {
254+
val currentModule = modulesDirsToProcess.removeFirst()
255+
if (!discoveredModulesDirs.add(currentModule)) continue
256+
257+
modulesDirsToProcess += getChildModuleDirs(currentModule)
258+
}
259+
260+
return discoveredModulesDirs
261+
}
262+
263+
/**
264+
* Find all direct dependency module directories within the node_modules directory of the given [moduleDir].
265+
* This handles both regular modules and namespaced (@organization) modules.
266+
*/
267+
private fun getChildModuleDirs(moduleDir: File): Set<File> {
268+
val nodeModulesDir = moduleDir.resolve("node_modules").takeIf { it.isDirectory } ?: return emptySet()
269+
270+
fun File.isModuleDir(): Boolean =
271+
isDirectory && !isHidden && !name.startsWith("@") && resolve(NodePackageManagerType.DEFINITION_FILE).isFile
272+
273+
val nodeModulesDirFiles = nodeModulesDir.walk().maxDepth(1)
274+
val childModuleDirsWithoutNamespace = nodeModulesDirFiles.filter { it.isModuleDir() }
275+
val childModuleDirsWithNamespace = nodeModulesDirFiles.filter {
276+
it.isDirectory && !it.isHidden && it.name.startsWith("@")
277+
}.flatMap { namespaceDir ->
278+
namespaceDir.walk().maxDepth(1).filter { it.isModuleDir() }
279+
}
280+
281+
return (childModuleDirsWithoutNamespace + childModuleDirsWithNamespace).mapTo(mutableSetOf()) { it.realFile }
282+
}

plugins/package-managers/node/src/test/kotlin/NodePackageManagerDetectionTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ class NodePackageManagerDetectionTest : WordSpec({
242242

243243
filteredFiles.map { it.relativeTo(projectDir).invariantSeparatorsPath } should containExactlyInAnyOrder(
244244
"yarn/babel/package.json",
245+
"yarn/invalid-package-json/package.json",
245246
"yarn/project-with-lockfile/package.json",
246247
"yarn/workspaces/package.json"
247248
)

0 commit comments

Comments
 (0)