55pub mod catalog;
66pub mod groovy;
77pub mod kotlin;
8+ pub mod properties;
9+ pub mod settings;
810
911use crate :: error:: Result ;
1012use crate :: types:: GradleDependency ;
1113use std:: any:: Any ;
14+ use std:: collections:: HashMap ;
1215use tower_lsp_server:: ls_types:: { Position , Range , Uri } ;
1316
1417pub use deps_core:: lsp_helpers:: LineOffsetTable ;
@@ -18,20 +21,61 @@ pub struct GradleParseResult {
1821 pub uri : Uri ,
1922}
2023
24+ /// Resolves `$var` and `${var}` references in dependency versions using the given properties map.
25+ ///
26+ /// If a version is a variable reference and the variable is found in `properties`,
27+ /// the version is replaced with the resolved value. The version_range is kept as-is
28+ /// (pointing to the variable reference in source).
29+ pub fn resolve_variables ( deps : & mut [ GradleDependency ] , properties : & HashMap < String , String > ) {
30+ for dep in deps. iter_mut ( ) {
31+ if let Some ( ref ver) = dep. version_req
32+ && let Some ( resolved) = resolve_variable_ref ( ver, properties)
33+ {
34+ dep. version_req = Some ( resolved) ;
35+ }
36+ }
37+ }
38+
39+ /// Returns the resolved value if `value` is a `$name` or `${name}` reference. Returns `None` otherwise.
40+ fn resolve_variable_ref ( value : & str , properties : & HashMap < String , String > ) -> Option < String > {
41+ let trimmed = value. trim ( ) ;
42+ if let Some ( name) = trimmed. strip_prefix ( "${" ) . and_then ( |s| s. strip_suffix ( '}' ) ) {
43+ properties. get ( name) . cloned ( )
44+ } else if let Some ( name) = trimmed. strip_prefix ( '$' ) {
45+ properties. get ( name) . cloned ( )
46+ } else {
47+ None
48+ }
49+ }
50+
2151pub fn parse_gradle ( content : & str , uri : & Uri ) -> Result < GradleParseResult > {
2252 let path = uri. path ( ) . to_string ( ) ;
23- if path. ends_with ( "libs.versions.toml" ) {
24- catalog:: parse_version_catalog ( content, uri)
53+ let mut result = if path. ends_with ( "libs.versions.toml" ) {
54+ catalog:: parse_version_catalog ( content, uri) ?
55+ } else if path. ends_with ( "settings.gradle.kts" ) || path. ends_with ( "settings.gradle" ) {
56+ settings:: parse_settings ( content, uri) ?
2557 } else if path. ends_with ( ".gradle.kts" ) {
26- kotlin:: parse_kotlin_dsl ( content, uri)
58+ kotlin:: parse_kotlin_dsl ( content, uri) ?
2759 } else if path. ends_with ( ".gradle" ) {
28- groovy:: parse_groovy_dsl ( content, uri)
60+ groovy:: parse_groovy_dsl ( content, uri) ?
2961 } else {
30- Ok ( GradleParseResult {
62+ return Ok ( GradleParseResult {
3163 dependencies : vec ! [ ] ,
3264 uri : uri. clone ( ) ,
33- } )
65+ } ) ;
66+ } ;
67+
68+ // Resolve variable references for build files (not catalogs or settings)
69+ if ( path. ends_with ( "build.gradle.kts" ) || path. ends_with ( "build.gradle" ) )
70+ && let Some ( dir) = std:: path:: Path :: new ( & path) . parent ( )
71+ {
72+ let props = properties:: load_gradle_properties ( dir) ;
73+ if !props. is_empty ( ) {
74+ resolve_variables ( & mut result. dependencies , & props) ;
75+ }
3476 }
77+
78+ Ok ( result)
3579}
3680
3781impl deps_core:: ParseResult for GradleParseResult {
@@ -136,12 +180,94 @@ mod tests {
136180 }
137181
138182 #[ test]
139- fn test_dispatch_unknown ( ) {
183+ fn test_dispatch_settings_gradle ( ) {
184+ let content = "pluginManagement {\n plugins {\n id \" org.jetbrains.kotlin.jvm\" version \" 2.1.10\" \n }\n }\n " ;
140185 let uri = make_uri ( "/project/settings.gradle" ) ;
186+ let result = parse_gradle ( content, & uri) . unwrap ( ) ;
187+ assert_eq ! ( result. dependencies. len( ) , 1 ) ;
188+ }
189+
190+ #[ test]
191+ fn test_dispatch_settings_gradle_kts ( ) {
192+ let content = "pluginManagement {\n plugins {\n id(\" org.springframework.boot\" ) version \" 3.2.0\" \n }\n }\n " ;
193+ let uri = make_uri ( "/project/settings.gradle.kts" ) ;
194+ let result = parse_gradle ( content, & uri) . unwrap ( ) ;
195+ assert_eq ! ( result. dependencies. len( ) , 1 ) ;
196+ }
197+
198+ #[ test]
199+ fn test_dispatch_unknown ( ) {
200+ let uri = make_uri ( "/project/something.xml" ) ;
141201 let result = parse_gradle ( "" , & uri) . unwrap ( ) ;
142202 assert ! ( result. dependencies. is_empty( ) ) ;
143203 }
144204
205+ #[ test]
206+ fn test_resolve_variables_dollar_brace ( ) {
207+ let props: HashMap < String , String > =
208+ [ ( "kotlinVersion" . to_string ( ) , "2.1.10" . to_string ( ) ) ] . into ( ) ;
209+ let mut deps = vec ! [ GradleDependency {
210+ group_id: "org.jetbrains.kotlin" . into( ) ,
211+ artifact_id: "kotlin-stdlib" . into( ) ,
212+ name: "org.jetbrains.kotlin:kotlin-stdlib" . into( ) ,
213+ name_range: Range :: default ( ) ,
214+ version_req: Some ( "${kotlinVersion}" . into( ) ) ,
215+ version_range: None ,
216+ configuration: "implementation" . into( ) ,
217+ } ] ;
218+ resolve_variables ( & mut deps, & props) ;
219+ assert_eq ! ( deps[ 0 ] . version_req, Some ( "2.1.10" . into( ) ) ) ;
220+ }
221+
222+ #[ test]
223+ fn test_resolve_variables_dollar_plain ( ) {
224+ let props: HashMap < String , String > =
225+ [ ( "springVersion" . to_string ( ) , "3.2.0" . to_string ( ) ) ] . into ( ) ;
226+ let mut deps = vec ! [ GradleDependency {
227+ group_id: "org.springframework.boot" . into( ) ,
228+ artifact_id: "spring-boot-starter" . into( ) ,
229+ name: "org.springframework.boot:spring-boot-starter" . into( ) ,
230+ name_range: Range :: default ( ) ,
231+ version_req: Some ( "$springVersion" . into( ) ) ,
232+ version_range: None ,
233+ configuration: "implementation" . into( ) ,
234+ } ] ;
235+ resolve_variables ( & mut deps, & props) ;
236+ assert_eq ! ( deps[ 0 ] . version_req, Some ( "3.2.0" . into( ) ) ) ;
237+ }
238+
239+ #[ test]
240+ fn test_resolve_variables_not_found_keeps_raw ( ) {
241+ let props: HashMap < String , String > = HashMap :: new ( ) ;
242+ let mut deps = vec ! [ GradleDependency {
243+ group_id: "com.example" . into( ) ,
244+ artifact_id: "lib" . into( ) ,
245+ name: "com.example:lib" . into( ) ,
246+ name_range: Range :: default ( ) ,
247+ version_req: Some ( "$unknownVar" . into( ) ) ,
248+ version_range: None ,
249+ configuration: "implementation" . into( ) ,
250+ } ] ;
251+ resolve_variables ( & mut deps, & props) ;
252+ assert_eq ! ( deps[ 0 ] . version_req, Some ( "$unknownVar" . into( ) ) ) ;
253+ }
254+
255+ #[ test]
256+ fn test_resolve_variables_literal_version_unchanged ( ) {
257+ let props: HashMap < String , String > = [ ( "v" . to_string ( ) , "9.9.9" . to_string ( ) ) ] . into ( ) ;
258+ let mut deps = vec ! [ GradleDependency {
259+ group_id: "com.example" . into( ) ,
260+ artifact_id: "lib" . into( ) ,
261+ name: "com.example:lib" . into( ) ,
262+ name_range: Range :: default ( ) ,
263+ version_req: Some ( "1.2.3" . into( ) ) ,
264+ version_range: None ,
265+ configuration: "implementation" . into( ) ,
266+ } ] ;
267+ resolve_variables ( & mut deps, & props) ;
268+ assert_eq ! ( deps[ 0 ] . version_req, Some ( "1.2.3" . into( ) ) ) ;
269+ }
270+
145271 #[ test]
146272 fn test_parse_result_trait ( ) {
147273 use deps_core:: ParseResult ;
0 commit comments