Repository: G-Rath/osv-detector
Pull request #18 (2022-03-04 19:13:50)

fix: parse versions that include build strings in `yarn.lock` files
I completely forgot about versions with build strings when writing the `yarn.lock` parser 🤦

I've also added a log call to put a warning into stderr incase we're still not finding versions - I'm pretty sure this change has us capturing all valid versions, but it's better than just silently having an empty version while I figure out how to approach the general problem of how to handle "soft" fails and logging.


Patch:
diff --git a/detector/parsers/fixtures/yarn/versions-with-build-strings.v1.lock b/detector/parsers/fixtures/yarn/versions-with-build-strings.v1.lock
new file mode 100644
index 0000000..c6e8d05
--- /dev/null
+++ b/detector/parsers/fixtures/yarn/versions-with-build-strings.v1.lock
@@ -0,0 +1,40 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+css-tree@1.0.0-alpha.37:
+  version "1.0.0-alpha.37"
+  resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz"
+  integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==
+  dependencies:
+    mdn-data "2.0.4"
+    source-map "^0.6.1"
+
+gensync@^1.0.0-beta.2:
+  version "1.0.0-beta.2"
+  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+  integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+node-fetch@3.0.0-beta.9:
+  version "3.0.0-beta.9"
+  resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz"
+  integrity sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==
+  dependencies:
+    data-uri-to-buffer "^3.0.1"
+    fetch-blob "^2.1.1"
+
+resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.20.0:
+  version "1.20.0"
+  resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz"
+  integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
+  dependencies:
+    is-core-module "^2.2.0"
+    path-parse "^1.0.6"
+
+resolve@^2.0.0-next.3:
+  version "2.0.0-next.3"
+  resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz"
+  integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==
+  dependencies:
+    is-core-module "^2.2.0"
+    path-parse "^1.0.6"
diff --git a/detector/parsers/fixtures/yarn/versions-with-build-strings.v2.lock b/detector/parsers/fixtures/yarn/versions-with-build-strings.v2.lock
new file mode 100644
index 0000000..dc4c1eb
--- /dev/null
+++ b/detector/parsers/fixtures/yarn/versions-with-build-strings.v2.lock
@@ -0,0 +1,76 @@
+# This file is generated by running "yarn install" inside your project.
+# Manual changes might be lost - proceed with caution!
+
+__metadata:
+  version: 5
+  cacheKey: 8
+
+"@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3":
+  version: 2.1.8-no-fsevents.3
+  resolution: "@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3"
+  checksum: ee55cc9241aeea7eb94b8a8551bfa4246c56c53bc71ecda0a2104018fcc328ba5723b33686bdf9cc65d4df4ae65e8016b89e0bbdeb94e0309fe91bb9ced42344
+  languageName: node
+  linkType: hard
+
+"gensync@npm:^1.0.0-beta.2":
+  version: 1.0.0-beta.2
+  resolution: "gensync@npm:1.0.0-beta.2"
+  checksum: a7437e58c6be12aa6c90f7730eac7fa9833dc78872b4ad2963d2031b00a3367a93f98aec75f9aaac7220848e4026d67a8655e870b24f20a543d103c0d65952ec
+  languageName: node
+  linkType: hard
+
+"eslint-plugin-jest@workspace:.":
+  version: 0.0.0-use.local
+  resolution: "eslint-plugin-jest@workspace:."
+  dependencies:
+    "@babel/cli": ^7.4.4
+    "@babel/core": ^7.4.4
+    "@babel/preset-env": ^7.4.4
+    "@babel/preset-typescript": ^7.3.3
+    "@commitlint/cli": ^16.0.0
+    "@commitlint/config-conventional": ^16.0.0
+    "@schemastore/package": ^0.0.6
+    "@semantic-release/changelog": ^6.0.0
+    "@semantic-release/git": ^10.0.0
+    "@types/dedent": ^0.7.0
+    "@types/jest": ^27.0.0
+    "@types/node": ^16.0.0
+    "@types/prettier": ^2.0.0
+    "@typescript-eslint/eslint-plugin": ^5.0.0
+    "@typescript-eslint/parser": ^5.0.0
+    "@typescript-eslint/utils": ^5.10.0
+    babel-jest: ^27.0.0
+    babel-plugin-replace-ts-export-assignment: ^0.0.2
+    dedent: ^0.7.0
+    eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+    eslint-config-prettier: ^8.3.0
+    eslint-plugin-eslint-comments: ^3.1.2
+    eslint-plugin-eslint-config: ^2.0.0
+    eslint-plugin-eslint-plugin: ^4.0.1
+    eslint-plugin-import: ^2.25.1
+    eslint-plugin-node: ^11.0.0
+    eslint-plugin-prettier: ^3.4.1
+    eslint-remote-tester: ^2.1.0
+    eslint-remote-tester-repositories: ^0.0.4
+    husky: ^7.0.2
+    is-ci: ^3.0.0
+    jest: ^27.0.0
+    jest-runner-eslint: ^1.0.0
+    lint-staged: ^12.0.0
+    pinst: ^2.0.0
+    prettier: ^2.0.5
+    rimraf: ^3.0.0
+    semantic-release: ^19.0.0
+    semver: ^7.3.5
+    ts-node: ^10.2.1
+    typescript: ^4.4.0
+  peerDependencies:
+    "@typescript-eslint/eslint-plugin": ^5.0.0
+    eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+  peerDependenciesMeta:
+    "@typescript-eslint/eslint-plugin":
+      optional: true
+    jest:
+      optional: true
+  languageName: unknown
+  linkType: soft
diff --git a/detector/parsers/parse-yarn-lock.go b/detector/parsers/parse-yarn-lock.go
index ef12318..707e290 100644
--- a/detector/parsers/parse-yarn-lock.go
+++ b/detector/parsers/parse-yarn-lock.go
@@ -62,7 +62,7 @@ func extractYarnPackageName(str string) string {
 }
 
 func determineYarnPackageVersion(group []string) string {
-	re := regexp.MustCompile(`^ {2}version:? "?([\d.]+)"?$`)
+	re := regexp.MustCompile(`^ {2}version:? "?([\w-.]+)"?$`)
 
 	for _, s := range group {
 		matched := re.FindStringSubmatch(s)
@@ -77,9 +77,20 @@ func determineYarnPackageVersion(group []string) string {
 }
 
 func parsePackageGroup(group []string) PackageDetails {
+	name := extractYarnPackageName(group[0])
+	version := determineYarnPackageVersion(group)
+
+	if version == "" {
+		_, _ = fmt.Fprintf(
+			os.Stderr,
+			"Failed to determine version of %s while parsing a yarn.lock - please report this!\n",
+			name,
+		)
+	}
+
 	return PackageDetails{
-		Name:      extractYarnPackageName(group[0]),
-		Version:   determineYarnPackageVersion(group),
+		Name:      name,
+		Version:   version,
 		Ecosystem: YarnEcosystem,
 	}
 }


Test patch:
diff --git a/detector/parsers/parse-yarn-lock-v1_test.go b/detector/parsers/parse-yarn-lock-v1_test.go
index 1d25698..043157a 100644
--- a/detector/parsers/parse-yarn-lock-v1_test.go
+++ b/detector/parsers/parse-yarn-lock-v1_test.go
@@ -145,3 +145,41 @@ func TestYarnLock_v1_ScopedPackages(t *testing.T) {
 		},
 	})
 }
+
+func TestYarnLock_v1_VersionsWithBuildString(t *testing.T) {
+	t.Parallel()
+
+	packages, err := parsers.ParseYarnLock("fixtures/yarn/versions-with-build-strings.v1.lock")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []parsers.PackageDetails{
+		{
+			Name:      "css-tree",
+			Version:   "1.0.0-alpha.37",
+			Ecosystem: parsers.YarnEcosystem,
+		},
+		{
+			Name:      "gensync",
+			Version:   "1.0.0-beta.2",
+			Ecosystem: parsers.YarnEcosystem,
+		},
+		{
+			Name:      "node-fetch",
+			Version:   "3.0.0-beta.9",
+			Ecosystem: parsers.YarnEcosystem,
+		},
+		{
+			Name:      "resolve",
+			Version:   "1.20.0",
+			Ecosystem: parsers.YarnEcosystem,
+		},
+		{
+			Name:      "resolve",
+			Version:   "2.0.0-next.3",
+			Ecosystem: parsers.YarnEcosystem,
+		},
+	})
+}
diff --git a/detector/parsers/parse-yarn-lock-v2_test.go b/detector/parsers/parse-yarn-lock-v2_test.go
index 561a6fb..7928268 100644
--- a/detector/parsers/parse-yarn-lock-v2_test.go
+++ b/detector/parsers/parse-yarn-lock-v2_test.go
@@ -122,3 +122,31 @@ func TestYarnLock_v2_ScopedPackages(t *testing.T) {
 		},
 	})
 }
+
+func TestYarnLock_v2_VersionsWithBuildString(t *testing.T) {
+	t.Parallel()
+
+	packages, err := parsers.ParseYarnLock("fixtures/yarn/versions-with-build-strings.v2.lock")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []parsers.PackageDetails{
+		{
+			Name:      "@nicolo-ribaudo/chokidar-2",
+			Version:   "2.1.8-no-fsevents.3",
+			Ecosystem: parsers.YarnEcosystem,
+		},
+		{
+			Name:      "gensync",
+			Version:   "1.0.0-beta.2",
+			Ecosystem: parsers.YarnEcosystem,
+		},
+		{
+			Name:      "eslint-plugin-jest",
+			Version:   "0.0.0-use.local",
+			Ecosystem: parsers.YarnEcosystem,
+		},
+	})
+}