@@ -20,22 +20,20 @@ func (p *GradleParser) Parse(manifestFile string) ([]models.Package, error) {
2020 return nil , fmt .Errorf ("failed to read manifest file: %w" , err )
2121 }
2222
23- lines := strings . Split ( string (content ), " \n " )
23+ manifestContent := string (content )
2424
2525 // Extract variables
26- variables := extractVariables (manifestFile , string ( content ) )
26+ variables := extractVariables (manifestFile , manifestContent )
2727
2828 var packages []models.Package
2929
3030 // Parse main dependencies
31- mainDeps := parseDependencies (string ( content ), lines , variables , false )
31+ mainDeps := parseDependencies (manifestContent , variables )
3232 for i := range mainDeps {
3333 mainDeps [i ].FilePath = manifestFile
3434 }
3535 packages = append (packages , mainDeps ... )
3636
37- // Note: Buildscript dependencies are also parsed as main for simplicity
38-
3937 return packages , nil
4038}
4139
@@ -99,73 +97,166 @@ func extractVariables(manifestFile, content string) map[string]string {
9997 return vars
10098}
10199
100+ type dependencyStatement struct {
101+ Line int
102+ Text string
103+ }
104+
102105// parseDependencies parses dependencies from the content
103- func parseDependencies (content string , lines [] string , variables map [string ]string , isBuildscript bool ) []models.Package {
106+ func parseDependencies (content string , variables map [string ]string ) []models.Package {
104107 var packages []models.Package
105108
106- // Patterns for different dependency declarations
107- patterns := []* regexp.Regexp {
108- // String notation: implementation 'group:name:version'
109- regexp .MustCompile (`(?i)(implementation|api|compile|runtime|testImplementation|testCompile|androidTestImplementation|classpath)\s*['"]([^'"]+)['"]` ),
110- regexp .MustCompile (`(?i)(implementation|api|compile|runtime|testImplementation|testCompile|androidTestImplementation|classpath)\s*\(\s*['"]([^'"]+)['"]\s*\)` ),
111- // Map notation: implementation group: 'g', name: 'n', version: 'v'
112- regexp .MustCompile (`(?i)(implementation|api|compile|runtime|testImplementation|testCompile|androidTestImplementation|classpath)\s*group\s*:\s*['"]([^'"]+)['"]\s*,\s*name\s*:\s*['"]([^'"]+)['"]\s*,\s*version\s*:\s*['"]([^'"]+)['"]` ),
113- regexp .MustCompile (`(?i)(implementation|api|compile|runtime|testImplementation|testCompile|androidTestImplementation|classpath)\s*\(\s*group\s*:\s*['"]([^'"]+)['"]\s*,\s*name\s*:\s*['"]([^'"]+)['"]\s*,\s*version\s*:\s*['"]([^'"]+)['"]\s*\)` ),
114- }
115-
116- depsLines := strings .Split (content , "\n " )
117- for _ , line := range depsLines {
118- line = strings .TrimSpace (line )
119- if line == "" {
109+ statements := extractDependencyStatements (content )
110+ for _ , stmt := range statements {
111+ for _ , pkg := range parseDependencyStatement (stmt .Text , variables ) {
112+ pkg .Locations = []models.Location {{Line : stmt .Line }}
113+ packages = append (packages , pkg )
114+ }
115+ }
116+
117+ return packages
118+ }
119+
120+ func extractDependencyStatements (content string ) []dependencyStatement {
121+ startPattern := regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\b` )
122+ var statements []dependencyStatement
123+ var buffer strings.Builder
124+ active := false
125+ startLine := 0
126+
127+ lines := strings .Split (content , "\n " )
128+ for i , raw := range lines {
129+ line := strings .TrimSpace (raw )
130+ if line == "" || strings .HasPrefix (line , "//" ) || strings .HasPrefix (line , "/*" ) || strings .HasPrefix (line , "*" ) {
120131 continue
121132 }
122133
123- for _ , pattern := range patterns {
124- matches := pattern .FindStringSubmatch (line )
125- if len (matches ) > 0 {
126- var group , name , version string
127- if len (matches ) == 3 {
128- // String notation
129- depStr := resolveVariables (matches [2 ], variables )
130- parts := strings .Split (depStr , ":" )
131- if len (parts ) >= 2 {
132- group = parts [0 ]
133- name = parts [1 ]
134- if len (parts ) > 2 {
135- version = parts [2 ]
136- }
137- }
138- } else if len (matches ) == 5 {
139- // Map notation
140- group = resolveVariables (matches [2 ], variables )
141- name = resolveVariables (matches [3 ], variables )
142- version = resolveVariables (matches [4 ], variables )
134+ if ! active {
135+ if startPattern .MatchString (line ) {
136+ active = true
137+ startLine = i + 1
138+ buffer .Reset ()
139+ buffer .WriteString (line )
140+ if dependencyStatementComplete (buffer .String ()) {
141+ statements = append (statements , dependencyStatement {Line : startLine , Text : buffer .String ()})
142+ active = false
143143 }
144+ }
145+ continue
146+ }
147+
148+ buffer .WriteString (" " )
149+ buffer .WriteString (line )
150+ if dependencyStatementComplete (buffer .String ()) {
151+ statements = append (statements , dependencyStatement {Line : startLine , Text : buffer .String ()})
152+ active = false
153+ }
154+ }
144155
145- if group != "" && name != "" {
146- // Handle version ranges and classifiers
147- cleanVersion := cleanVersion (version )
148-
149- // Find line number
150- lineNum := findLineNumber (content , line )
151-
152- packages = append (packages , models.Package {
153- PackageManager : "gradle" ,
154- PackageName : group + ":" + name ,
155- Version : cleanVersion ,
156- FilePath : "" , // Will be set later
157- Locations : []models.Location {
158- {Line : lineNum },
159- },
160- })
156+ return statements
157+ }
158+
159+ func dependencyStatementComplete (statement string ) bool {
160+ patterns := []* regexp.Regexp {
161+ regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\s*['"]([^'"\)]+)['"]` ),
162+ regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\s*\(\s*['"]([^'"\)]+)['"]\s*\)` ),
163+ regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\s*group\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*name\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*version\s*[:=]\s*['"]([^'"]+)['"]` ),
164+ regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\s*\(\s*group\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*name\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*version\s*[:=]\s*['"]([^'"]+)['"]\s*\)` ),
165+ regexp .MustCompile (`(?i)group\s*[:=]\s*['"]([^'"]+)['"].*name\s*[:=]\s*['"]([^'"]+)['"].*version\s*[:=]\s*['"]([^'"]+)['"]` ),
166+ regexp .MustCompile (`(?i)group\s*[:=]\s*[^,\s]+.*name\s*[:=]\s*[^,\s]+.*version\s*[:=]\s*[^,\s]+` ),
167+ }
168+
169+ for _ , pattern := range patterns {
170+ if pattern .MatchString (statement ) {
171+ return true
172+ }
173+ }
174+
175+ return false
176+ }
177+
178+ func parseDependencyStatement (statement string , variables map [string ]string ) []models.Package {
179+ var packages []models.Package
180+
181+ patterns := []* regexp.Regexp {
182+ regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\s*['"]([^'"\)]+)['"]` ),
183+ regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\s*\(\s*['"]([^'"\)]+)['"]\s*\)` ),
184+ regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\s*group\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*name\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*version\s*[:=]\s*['"]([^'"]+)['"]` ),
185+ regexp .MustCompile (`(?i)\b(implementation|api|compile|compileOnly|runtime|runtimeOnly|testImplementation|testCompile|testRuntimeOnly|androidTestImplementation|annotationProcessor|classpath|kapt)\s*\(\s*group\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*name\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*version\s*[:=]\s*['"]([^'"]+)['"]\s*\)` ),
186+ }
187+
188+ for _ , pattern := range patterns {
189+ matches := pattern .FindStringSubmatch (statement )
190+ if len (matches ) > 0 {
191+ var group , name , version string
192+ if len (matches ) == 3 {
193+ depStr := resolveVariables (matches [2 ], variables )
194+ parts := strings .Split (depStr , ":" )
195+ if len (parts ) >= 2 {
196+ group = parts [0 ]
197+ name = parts [1 ]
198+ if len (parts ) > 2 {
199+ version = strings .Join (parts [2 :], ":" )
200+ }
161201 }
202+ } else if len (matches ) == 5 {
203+ group = resolveVariables (matches [2 ], variables )
204+ name = resolveVariables (matches [3 ], variables )
205+ version = resolveVariables (matches [4 ], variables )
162206 }
207+
208+ if group != "" && name != "" {
209+ packages = append (packages , models.Package {
210+ PackageManager : "gradle" ,
211+ PackageName : group + ":" + name ,
212+ Version : cleanVersion (version ),
213+ FilePath : "" ,
214+ Locations : []models.Location {{}},
215+ })
216+ }
217+ }
218+ }
219+
220+ if len (packages ) == 0 {
221+ if pkg := parseDependencyKeyValue (statement , variables ); pkg != nil {
222+ packages = append (packages , * pkg )
163223 }
164224 }
165225
166226 return packages
167227}
168228
229+ func parseDependencyKeyValue (statement string , variables map [string ]string ) * models.Package {
230+ fields := map [string ]string {}
231+
232+ patterns := []* regexp.Regexp {
233+ regexp .MustCompile (`(?i)(group|name|version)\s*[:=]\s*['"]([^'"]+)['"]` ),
234+ regexp .MustCompile (`(?i)(group|name|version)\s*[:=]\s*([A-Za-z_][A-Za-z0-9_]*)` ),
235+ }
236+
237+ for _ , pattern := range patterns {
238+ for _ , match := range pattern .FindAllStringSubmatch (statement , - 1 ) {
239+ if len (match ) > 2 {
240+ key := strings .ToLower (match [1 ])
241+ value := match [2 ]
242+ fields [key ] = resolveVariables (value , variables )
243+ }
244+ }
245+ }
246+
247+ if fields ["group" ] == "" || fields ["name" ] == "" {
248+ return nil
249+ }
250+
251+ return & models.Package {
252+ PackageManager : "gradle" ,
253+ PackageName : fields ["group" ] + ":" + fields ["name" ],
254+ Version : cleanVersion (fields ["version" ]),
255+ FilePath : "" ,
256+ Locations : []models.Location {{}},
257+ }
258+ }
259+
169260// resolveVariables replaces ${var} or $var with values
170261func resolveVariables (str string , variables map [string ]string ) string {
171262 // ${var}
0 commit comments