Skip to content

Commit

Permalink
refactor: use map[string][]string for patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitriyLewen committed Jul 26, 2024
1 parent eb0ca05 commit 42a5ec0
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 33 deletions.
23 changes: 17 additions & 6 deletions pkg/dependency/parser/nodejs/yarn/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"io"
"regexp"
"sort"
"strings"

"github.com/samber/lo"
Expand Down Expand Up @@ -127,7 +128,7 @@ func ignoreProtocol(protocol string) bool {
return false
}

func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps []ftypes.Dependency) {
func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps ftypes.Dependencies) {
// find dependencies by patterns
for pkgID, depPatterns := range dependsOn {
depIDs := lo.Map(depPatterns, func(pattern string, index int) string {
Expand Down Expand Up @@ -269,14 +270,20 @@ func parseDependency(line string) (string, error) {
}
}

func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, map[string]string, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, map[string][]string, error) {
lineNumber := 1
var pkgs []ftypes.Package
var pkgs ftypes.Packages

// patternIDs holds mapping between patterns and library IDs
// patternIDs holds mapping between patterns and package IDs
// e.g. ajv@^6.5.5 => [email protected]
// This is needed to update dependencies from `DependsOn`.
patternIDs := make(map[string]string)

// patternIDs holds mapping between package ID and patterns
// e.g. `@babel/[email protected]` => [`@babel/helper-regex@^7.0.0`, `@babel/helper-regex@^7.4.4`]
// This is needed to compare package patterns with patterns from package.json files in `fanal` package.
pkgIDPatterns := make(map[string][]string)

scanner := bufio.NewScanner(r)
scanner.Split(p.scanBlocks)
dependsOn := make(map[string][]string)
Expand All @@ -298,6 +305,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
Locations: []ftypes.Location{lib.Location},
})

pkgIDPatterns[pkgID] = lib.Patterns
for _, pattern := range lib.Patterns {
// e.g.
// combined-stream@^1.0.6 => [email protected]
Expand All @@ -313,10 +321,13 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
return nil, nil, nil, xerrors.Errorf("failed to scan yarn.lock, got scanner error: %s", err.Error())
}

// Replace dependency patterns with library IDs
// Replace dependency patterns with package IDs
// e.g. ajv@^6.5.5 => [email protected]
deps := parseResults(patternIDs, dependsOn)
return pkgs, deps, lo.Invert(patternIDs), nil

sort.Sort(pkgs)
sort.Sort(deps)
return pkgs, deps, pkgIDPatterns, nil
}

func packageID(name, version string) string {
Expand Down
18 changes: 0 additions & 18 deletions pkg/dependency/parser/nodejs/yarn/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,29 +304,11 @@ func TestParse(t *testing.T) {
got, deps, _, err := NewParser().Parse(f)
require.NoError(t, err)

sortPkgs(got)
sortPkgs(tt.want)
assert.Equal(t, tt.want, got)

if tt.wantDeps != nil {
sortDeps(deps)
sortDeps(tt.wantDeps)
assert.Equal(t, tt.wantDeps, deps)
}
})
}
}

func sortPkgs(pkgs ftypes.Packages) {
sort.Sort(pkgs)
for _, pkg := range pkgs {
sort.Sort(pkg.Locations)
}
}

func sortDeps(deps ftypes.Dependencies) {
sort.Sort(deps)
for _, dep := range deps {
sort.Strings(dep.DependsOn)
}
}
14 changes: 5 additions & 9 deletions pkg/fanal/analyzer/language/nodejs/yarn/yarn.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"path"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"

Expand Down Expand Up @@ -43,7 +44,6 @@ var fragmentRegexp = regexp.MustCompile(`(\S+):(@?.*?)(@(.*?)|)$`)
type yarnAnalyzer struct {
logger *log.Logger
packageJsonParser *packagejson.Parser
lockParser language.Parser
comparer npm.Comparer
license *license.License
}
Expand All @@ -58,7 +58,7 @@ func newYarnAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error
}

type parserWithPatterns struct {
patterns map[string]string
patterns map[string][]string
}

func (p *parserWithPatterns) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) {
Expand Down Expand Up @@ -158,13 +158,9 @@ func (a yarnAnalyzer) Version() int {
return version
}

func (a yarnAnalyzer) parseYarnLock(filePath string, r io.Reader) (*types.Application, error) {
return language.Parse(types.Yarn, filePath, r, a.lockParser)
}

// analyzeDependencies analyzes the package.json file next to yarn.lock,
// distinguishing between direct and transitive dependencies as well as production and development dependencies.
func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application, patterns map[string]string) error {
func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application, patterns map[string][]string) error {
packageJsonPath := path.Join(dir, types.NpmPkg)
directDeps, directDevDeps, err := a.parsePackageJsonDependencies(fsys, packageJsonPath)
if errors.Is(err, fs.ErrNotExist) {
Expand Down Expand Up @@ -205,7 +201,7 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App
}

func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]types.Package,
directDeps, patterns map[string]string, dev bool) (map[string]types.Package, error) {
directDeps map[string]string, patterns map[string][]string, dev bool) (map[string]types.Package, error) {

// Identify direct dependencies
directPkgs := make(map[string]types.Package)
Expand All @@ -225,7 +221,7 @@ func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]t
// Try to find an exact match to the pattern.
// In some cases, patterns from yarn.lock and package.json may not match (e.g., yarn v2 uses the allowed version for ID).
// Therefore, if the patterns don't match - compare versions.
if pattern, found := patterns[pkg.ID]; !found || pattern != dependency.ID(types.Yarn, pkg.Name, constraint) {
if pkgPatterns, found := patterns[pkg.ID]; !found || !slices.Contains(pkgPatterns, dependency.ID(types.Yarn, pkg.Name, constraint)) {
// npm has own comparer to compare versions
if match, err := a.comparer.MatchVersion(pkg.Version, constraint); err != nil {
return nil, xerrors.Errorf("unable to match version for %s", pkg.Name)
Expand Down

0 comments on commit 42a5ec0

Please sign in to comment.