Skip to content

Commit

Permalink
fix: duplicate packages, support pnpm lockfile v6 (#1778)
Browse files Browse the repository at this point in the history
  • Loading branch information
kzantow authored May 23, 2023
1 parent 798af57 commit a3c5550
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 5 deletions.
63 changes: 58 additions & 5 deletions syft/pkg/cataloger/javascript/parse_pnpm_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package javascript
import (
"fmt"
"io"
"regexp"
"strconv"
"strings"

"gopkg.in/yaml.v3"

"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
Expand All @@ -17,8 +20,9 @@ import (
var _ generic.Parser = parsePnpmLock

type pnpmLockYaml struct {
Dependencies map[string]string `json:"dependencies"`
Packages map[string]interface{} `json:"packages"`
Version string `json:"lockfileVersion" yaml:"lockfileVersion"`
Dependencies map[string]interface{} `json:"dependencies" yaml:"dependencies"`
Packages map[string]interface{} `json:"packages" yaml:"packages"`
}

func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
Expand All @@ -34,19 +38,55 @@ func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader
return nil, nil, fmt.Errorf("failed to parse pnpm-lock.yaml file: %w", err)
}

for name, version := range lockFile.Dependencies {
lockVersion, _ := strconv.ParseFloat(lockFile.Version, 64)

for name, info := range lockFile.Dependencies {
version := ""

switch info := info.(type) {
case string:
version = info
case map[string]interface{}:
v, ok := info["version"]
if !ok {
break
}
ver, ok := v.(string)
if ok {
version = parseVersion(ver)
}
default:
log.Tracef("unsupported pnpm dependency type: %+v", info)
continue
}

if hasPkg(pkgs, name, version) {
continue
}

pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version))
}

packageNameRegex := regexp.MustCompile(`^/?([^(]*)(?:\(.*\))*$`)
splitChar := "/"
if lockVersion >= 6.0 {
splitChar = "@"
}

// parse packages from packages section of pnpm-lock.yaml
for nameVersion := range lockFile.Packages {
nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), "/")
nameVersion = packageNameRegex.ReplaceAllString(nameVersion, "$1")
nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), splitChar)

// last element in split array is version
version := nameVersionSplit[len(nameVersionSplit)-1]

// construct name from all array items other than last item (version)
name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], "/")
name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], splitChar)

if hasPkg(pkgs, name, version) {
continue
}

pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version))
}
Expand All @@ -55,3 +95,16 @@ func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader

return pkgs, nil, nil
}

func hasPkg(pkgs []pkg.Package, name, version string) bool {
for _, p := range pkgs {
if p.Name == name && p.Version == version {
return true
}
}
return false
}

func parseVersion(version string) string {
return strings.SplitN(version, "(", 2)[0]
}
92 changes: 92 additions & 0 deletions syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,95 @@ func TestParsePnpmLock(t *testing.T) {

pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
}

func TestParsePnpmV6Lock(t *testing.T) {
var expectedRelationships []artifact.Relationship
fixture := "test-fixtures/pnpm-v6/pnpm-lock.yaml"

locationSet := source.NewLocationSet(source.NewLocation(fixture))

expectedPkgs := []pkg.Package{
{
Name: "@testing-library/jest-dom",
Version: "5.16.5",
PURL: "pkg:npm/%40testing-library/jest-dom@5.16.5",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@testing-library/react",
Version: "13.4.0",
PURL: "pkg:npm/%40testing-library/react@13.4.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@testing-library/user-event",
Version: "13.5.0",
PURL: "pkg:npm/%40testing-library/user-event@13.5.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "react",
Version: "18.2.0",
PURL: "pkg:npm/react@18.2.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "react-dom",
Version: "18.2.0",
PURL: "pkg:npm/react-dom@18.2.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "web-vitals",
Version: "2.1.4",
PURL: "pkg:npm/web-vitals@2.1.4",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@babel/core",
Version: "7.21.4",
PURL: "pkg:npm/%40babel/core@7.21.4",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@types/eslint",
Version: "8.37.0",
PURL: "pkg:npm/%40types/eslint@8.37.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "read-cache",
Version: "1.0.0",
PURL: "pkg:npm/read-cache@1.0.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "schema-utils",
Version: "3.1.2",
PURL: "pkg:npm/schema-utils@3.1.2",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
}

pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
}
127 changes: 127 additions & 0 deletions syft/pkg/cataloger/javascript/test-fixtures/pnpm-v6/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a3c5550

Please sign in to comment.