Skip to content

Commit b1a51f0

Browse files
committed
fix(node): Do not fail completely if parsing a single package fails
Previously, when parsing failed for a single Yarn2 package in a chunk, the whole analysis was aborted. Change this to instead create issues with detailed information about the offending package, and continue parsing the remaning packages. Signed-off-by: Sebastian Schuberth <sebastian@doubleopen.org>
1 parent cf5616f commit b1a51f0

2 files changed

Lines changed: 41 additions & 5 deletions

File tree

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

Lines changed: 26 additions & 2 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.io.IOException
2324

2425
import kotlinx.serialization.SerialName
2526
import kotlinx.serialization.Serializable
@@ -49,9 +50,32 @@ internal fun parsePackageJson(file: File): PackageJson = parsePackageJson(file.r
4950

5051
internal fun parsePackageJson(json: String): PackageJson = parsePackageJson(JSON.parseToJsonElement(json))
5152

52-
internal fun parsePackageJsons(jsons: String): List<PackageJson> =
53+
internal fun parsePackageJsons(jsons: String): List<Result<PackageJson>> =
5354
jsons.byteInputStream().use { input ->
54-
JSON.decodeToSequence<JsonElement>(input).mapTo(mutableListOf()) { parsePackageJson(it) }
55+
JSON.decodeToSequence<JsonElement>(input).mapTo(mutableListOf()) { element ->
56+
runCatching {
57+
parsePackageJson(element)
58+
}.recoverCatching {
59+
if (element !is JsonObject) throw it
60+
61+
// Try to enrich the exception with more package details that are still available.
62+
val name = (element["name"] as? JsonPrimitive)?.content
63+
val version = (element["version"] as? JsonPrimitive)?.content
64+
val homepage = (element["homepage"] as? JsonPrimitive)?.content
65+
val gitHead = (element["gitHead"] as? JsonPrimitive)?.content
66+
67+
val message = buildString {
68+
append("Error parsing package JSON metadata for package")
69+
if (name != null) append(" named '$name'")
70+
if (version != null) append(" in version $version")
71+
if (homepage != null) append(" hosted at $homepage")
72+
if (homepage != null) append(" in revision $gitHead")
73+
append(".")
74+
}
75+
76+
throw IOException(message, it)
77+
}
78+
}
5579
}
5680

5781
internal fun parsePackageJson(element: JsonElement): PackageJson {

plugins/package-managers/node/src/main/kotlin/yarn2/Yarn2.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import kotlinx.coroutines.async
2626
import kotlinx.coroutines.awaitAll
2727

2828
import org.ossreviewtoolkit.analyzer.PackageManagerFactory
29+
import org.ossreviewtoolkit.model.Issue
2930
import org.ossreviewtoolkit.model.ProjectAnalyzerResult
3031
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
3132
import org.ossreviewtoolkit.model.config.Excludes
33+
import org.ossreviewtoolkit.model.createAndLogIssue
3234
import org.ossreviewtoolkit.model.utils.DependencyGraphBuilder
3335
import org.ossreviewtoolkit.plugins.api.OrtPlugin
3436
import org.ossreviewtoolkit.plugins.api.OrtPluginOption
@@ -42,6 +44,7 @@ import org.ossreviewtoolkit.plugins.packagemanagers.node.getInstalledModulesDirs
4244
import org.ossreviewtoolkit.plugins.packagemanagers.node.getNames
4345
import org.ossreviewtoolkit.plugins.packagemanagers.node.parsePackageJson
4446
import org.ossreviewtoolkit.plugins.packagemanagers.node.parsePackageJsons
47+
import org.ossreviewtoolkit.utils.common.collectMessages
4548
import org.ossreviewtoolkit.utils.common.div
4649
import org.ossreviewtoolkit.utils.common.realFile
4750
import org.ossreviewtoolkit.utils.common.withoutPrefix
@@ -82,9 +85,11 @@ class Yarn2(override val descriptor: PluginDescriptor = Yarn2Factory.descriptor,
8285
NodePackageManager(NodePackageManagerType.YARN2) {
8386
override val globsForDefinitionFiles = listOf(NodePackageManagerType.DEFINITION_FILE)
8487
internal val yarn2Command = Yarn2Command(config.corepackEnabled)
88+
89+
private val issues = mutableListOf<Issue>()
8590
private val moduleInfoResolver = ModuleInfoResolver { workingDir, moduleIds ->
8691
runBlocking(Dispatchers.IO.limitedParallelism(20)) {
87-
moduleIds.chunked(YARN_NPM_INFO_CHUNK_SIZE).map { chunk ->
92+
val resultChunks = moduleIds.chunked(YARN_NPM_INFO_CHUNK_SIZE).map { chunk ->
8893
async {
8994
val process = yarn2Command.run(
9095
"npm",
@@ -99,7 +104,13 @@ class Yarn2(override val descriptor: PluginDescriptor = Yarn2Factory.descriptor,
99104

100105
parsePackageJsons(process.stdout)
101106
}
102-
}.awaitAll().flatten().toSet()
107+
}.awaitAll()
108+
109+
resultChunks.flatten().mapNotNullTo(mutableSetOf()) { result ->
110+
result.onFailure {
111+
issues += createAndLogIssue(it.collectMessages())
112+
}.getOrNull()
113+
}
103114
}
104115
}
105116

@@ -155,7 +166,8 @@ class Yarn2(override val descriptor: PluginDescriptor = Yarn2Factory.descriptor,
155166

156167
ProjectAnalyzerResult(
157168
project = project.copy(scopeNames = scopes.getNames()),
158-
packages = emptySet()
169+
packages = emptySet(),
170+
issues = issues
159171
)
160172
}
161173
}

0 commit comments

Comments
 (0)