diff --git a/pkg/java/pom/artifact.go b/pkg/java/pom/artifact.go index 4317959d..78de17ae 100644 --- a/pkg/java/pom/artifact.go +++ b/pkg/java/pom/artifact.go @@ -26,6 +26,9 @@ type artifact struct { Module bool Root bool Direct bool + + StartLine int + EndLine int } func newArtifact(groupID, artifactID, version string, licenses []string, props map[string]string) artifact { diff --git a/pkg/java/pom/parse.go b/pkg/java/pom/parse.go index 11d37b8b..a992bf6f 100644 --- a/pkg/java/pom/parse.go +++ b/pkg/java/pom/parse.go @@ -131,6 +131,13 @@ func (p *parser) parseRoot(root artifact) ([]types.Library, []types.Dependency, if err != nil { return nil, nil, err } + + // We don't need to store the location of module dependencies to avoid confusion. + moduleLibs = lo.Map(moduleLibs, func(lib types.Library, _ int) types.Library { + lib.Locations = nil + return lib + }) + libs = append(libs, moduleLibs...) if moduleDeps != nil { deps = append(deps, moduleDeps...) @@ -148,6 +155,11 @@ func (p *parser) parseRoot(root artifact) ([]types.Library, []types.Dependency, if uniqueArt.Direct { art.Direct = true } + // We don't need to overwrite dependency location for hard links + if uniqueArt.EndLine != 0 { + art.StartLine = uniqueArt.StartLine + art.EndLine = uniqueArt.EndLine + } } result, err := p.resolve(art, rootDepManagement) @@ -185,9 +197,12 @@ func (p *parser) parseRoot(root artifact) ([]types.Library, []types.Dependency, if !art.IsEmpty() { // Override the version uniqArtifacts[art.Name()] = artifact{ - Version: art.Version, - Licenses: result.artifact.Licenses, - Direct: art.Direct, + Version: art.Version, + Licenses: result.artifact.Licenses, + Direct: art.Direct, + Root: art.Root, + StartLine: art.StartLine, + EndLine: art.EndLine, } // save only dependency names @@ -208,6 +223,15 @@ func (p *parser) parseRoot(root artifact) ([]types.Library, []types.Dependency, License: art.JoinLicenses(), Indirect: !art.Direct, } + // We need to add location only for deps from `Dependencies` tag of base pom.xml file + if art.Direct && !art.Root && art.EndLine != 0 { + lib.Locations = types.Locations{ + { + StartLine: art.StartLine, + EndLine: art.EndLine, + }, + } + } libs = append(libs, lib) // Convert dependency names into dependency IDs @@ -409,7 +433,7 @@ func (p *parser) mergeDependencies(parent, child []artifact, exclusions map[stri var deps []artifact unique := map[string]struct{}{} - for _, d := range append(parent, child...) { + for _, d := range append(child, parent...) { if excludeDep(exclusions, d) { continue } @@ -466,6 +490,13 @@ func (p *parser) parseParent(currentPath string, parent pomParent) (analysisResu return analysisResult{}, xerrors.Errorf("analyze error: %w", err) } + // We don't need to store the location of parent dependencies to avoid confusion. + result.dependencies = lo.Map(result.dependencies, func(a artifact, _ int) artifact { + a.StartLine = 0 + a.EndLine = 0 + return a + }) + p.cache.put(target, result) return result, nil @@ -642,22 +673,3 @@ func parsePom(r io.Reader) (*pomXML, error) { } return parsed, nil } - -func (deps *pomDependencies) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - *deps = pomDependencies{} - for { - var dep pomDependency - err := d.Decode(&dep) - if err == io.EOF { - break - } else if err != nil { - return err - } - - endLine, _ := d.InputPos() - dep.endLine = endLine - - (*deps).Dependency = append((*deps).Dependency, dep) - } - return nil -} diff --git a/pkg/java/pom/parse_test.go b/pkg/java/pom/parse_test.go index 398e9ade..a61cce1a 100644 --- a/pkg/java/pom/parse_test.go +++ b/pkg/java/pom/parse_test.go @@ -40,6 +40,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 33, + EndLine: 36, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -67,6 +73,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 33, + EndLine: 36, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -88,6 +100,12 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-offline:2.3.4", Name: "org.example:example-offline", Version: "2.3.4", + Locations: types.Locations{ + { + StartLine: 18, + EndLine: 21, + }, + }, }, }, }, @@ -107,6 +125,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 34, + EndLine: 37, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -133,6 +157,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "2.0.0", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 19, + EndLine: 22, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -159,6 +189,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "2.0.0", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 18, + EndLine: 21, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -190,6 +226,12 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 23, + EndLine: 26, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -250,6 +292,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 26, + EndLine: 29, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -284,6 +332,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-child", Version: "2.0.0", License: "Apache 2.0", + Locations: types.Locations{ + { + StartLine: 29, + EndLine: 32, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -317,6 +371,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 27, + EndLine: 30, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -342,6 +402,12 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-api:1.1.1", Name: "org.example:example-api", Version: "1.1.1", + Locations: types.Locations{ + { + StartLine: 19, + EndLine: 22, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -369,6 +435,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 26, + EndLine: 29, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -401,11 +473,23 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 33, + EndLine: 36, + }, + }, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 37, + EndLine: 41, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -451,11 +535,23 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-dependency2:2.3.4", Name: "org.example:example-dependency2", Version: "2.3.4", + Locations: types.Locations{ + { + StartLine: 18, + EndLine: 22, + }, + }, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 14, + EndLine: 17, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -508,11 +604,23 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 33, + EndLine: 37, + }, + }, }, { ID: "org.example:example-nested:3.3.4", Name: "org.example:example-nested", Version: "3.3.4", + Locations: types.Locations{ + { + StartLine: 29, + EndLine: 32, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -566,6 +674,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 34, + EndLine: 37, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -593,6 +707,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 42, + EndLine: 45, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -624,6 +744,12 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-nested:3.3.3", Name: "org.example:example-nested", Version: "3.3.3", + Locations: types.Locations{ + { + StartLine: 25, + EndLine: 28, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -655,16 +781,34 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-dependency2:2.3.4", Name: "org.example:example-dependency2", Version: "2.3.4", + Locations: types.Locations{ + { + StartLine: 25, + EndLine: 35, + }, + }, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", + Locations: types.Locations{ + { + StartLine: 21, + EndLine: 24, + }, + }, }, { ID: "org.example:example-nested:3.3.3", Name: "org.example:example-nested", Version: "3.3.3", + Locations: types.Locations{ + { + StartLine: 36, + EndLine: 46, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -817,6 +961,12 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-nested:3.3.3", Name: "org.example:example-nested", Version: "3.3.3", + Locations: types.Locations{ + { + StartLine: 21, + EndLine: 24, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -842,7 +992,7 @@ func TestPom_Parse(t *testing.T) { }, { name: "transitive dependencyManagement should not be inherited", - inputFile: "testdata/transitive-dependency-management/pom.xml", + inputFile: filepath.Join("testdata", "transitive-dependency-management", "pom.xml"), local: true, want: []types.Library{ // Managed dependencies (org.example:example-api:1.7.30) in org.example:example-dependency-management3 @@ -858,6 +1008,12 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-dependency-management3:1.1.1", Name: "org.example:example-dependency-management3", Version: "1.1.1", + Locations: types.Locations{ + { + StartLine: 15, + EndLine: 18, + }, + }, }, { ID: "org.example:example-dependency:1.2.3", @@ -908,6 +1064,12 @@ func TestPom_Parse(t *testing.T) { Name: "org.example:example-api", Version: "1.7.30", License: "The Apache Software License, Version 2.0", + Locations: types.Locations{ + { + StartLine: 28, + EndLine: 31, + }, + }, }, }, wantDeps: []types.Dependency{ @@ -934,6 +1096,12 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-not-found:999", Name: "org.example:example-not-found", Version: "999", + Locations: types.Locations{ + { + StartLine: 22, + EndLine: 25, + }, + }, }, }, wantDeps: []types.Dependency{ diff --git a/pkg/java/pom/pom.go b/pkg/java/pom/pom.go index acd02e23..f73bba7f 100644 --- a/pkg/java/pom/pom.go +++ b/pkg/java/pom/pom.go @@ -184,7 +184,8 @@ type pomDependency struct { Scope string `xml:"scope"` Optional bool `xml:"optional"` Exclusions pomExclusions `xml:"exclusions"` - endLine int + StartLine int + EndLine int } type pomExclusions struct { @@ -213,6 +214,8 @@ func (d pomDependency) Resolve(props map[string]string, depManagement, rootDepMa Scope: evaluateVariable(d.Scope, props, nil), Optional: d.Optional, Exclusions: d.Exclusions, + StartLine: d.StartLine, + EndLine: d.EndLine, } // If this dependency is managed in the root POM, @@ -266,6 +269,8 @@ func (d pomDependency) ToArtifact(exclusions map[string]struct{}) artifact { ArtifactID: d.ArtifactID, Version: newVersion(d.Version), Exclusions: exclusions, + StartLine: d.StartLine, + EndLine: d.EndLine, } } @@ -276,7 +281,7 @@ type property struct { Value string `xml:",chardata"` } -func (props *properties) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { +func (props *properties) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error { *props = properties{} for { var p property @@ -292,6 +297,33 @@ func (props *properties) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er return nil } +func (deps *pomDependencies) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error { + *deps = pomDependencies{} + prevEndLine := -1 + for { + var dep pomDependency + err := d.Decode(&dep) + if err == io.EOF { + break + } else if err != nil { + return err + } + + dep.EndLine, _ = d.InputPos() + // InputPos() returns line number of `dependency` end tag (). + // So we use ending line of previous dependency, incremented by one, for starting line of current dependency + dep.StartLine = prevEndLine + 1 + // For first dependency, we use end line of the current dependency, decreasing it by three (ArtifactID, GroupID and Version) + if dep.StartLine == 0 { + dep.StartLine = dep.EndLine - 3 + } + prevEndLine = dep.EndLine + + (*deps).Dependency = append((*deps).Dependency, dep) + } + return nil +} + func findDep(name string, depManagement []pomDependency) (pomDependency, bool) { return lo.Find(depManagement, func(item pomDependency) bool { return item.Name() == name diff --git a/pkg/java/pom/testdata/parent-properties/pom.xml b/pkg/java/pom/testdata/parent-properties/pom.xml index 9a9ffd70..42ffe5a2 100644 --- a/pkg/java/pom/testdata/parent-properties/pom.xml +++ b/pkg/java/pom/testdata/parent-properties/pom.xml @@ -41,7 +41,7 @@ org.example example-api - ${api.version} + 2.2.2