@@ -25,6 +25,7 @@ use std::path::PathBuf;
25
25
use gazebo:: prelude:: * ;
26
26
use itertools:: Either ;
27
27
use lsp_types:: Diagnostic ;
28
+ use lsp_types:: Range ;
28
29
use lsp_types:: Url ;
29
30
use starlark:: environment:: FrozenModule ;
30
31
use starlark:: environment:: Globals ;
@@ -50,6 +51,13 @@ pub(crate) enum ContextMode {
50
51
Run ,
51
52
}
52
53
54
+ #[ derive( Debug , Clone ) ]
55
+ pub ( crate ) struct BazelInfo {
56
+ pub ( crate ) workspace_root : PathBuf ,
57
+ pub ( crate ) output_base : PathBuf ,
58
+ pub ( crate ) execroot : PathBuf ,
59
+ }
60
+
53
61
#[ derive( Debug ) ]
54
62
pub ( crate ) struct Context {
55
63
pub ( crate ) mode : ContextMode ,
@@ -58,6 +66,7 @@ pub(crate) struct Context {
58
66
pub ( crate ) module : Option < Module > ,
59
67
pub ( crate ) builtin_docs : HashMap < LspUrl , String > ,
60
68
pub ( crate ) builtin_symbols : HashMap < String , LspUrl > ,
69
+ pub ( crate ) bazel_info : Option < BazelInfo > ,
61
70
}
62
71
63
72
/// The outcome of evaluating (checking, parsing or running) given starlark code.
@@ -110,6 +119,7 @@ impl Context {
110
119
module,
111
120
builtin_docs,
112
121
builtin_symbols,
122
+ bazel_info : None ,
113
123
} )
114
124
}
115
125
@@ -241,6 +251,147 @@ impl Context {
241
251
}
242
252
}
243
253
254
+ fn handle_local_bazel_repository (
255
+ info : & Option < BazelInfo > ,
256
+ path : & str ,
257
+ path_buf : PathBuf ,
258
+ current_file_dir : & PathBuf ,
259
+ ) -> Result < PathBuf , ResolveLoadError > {
260
+ match info {
261
+ None => Err ( ResolveLoadError :: MissingBazelInfo ( path_buf) ) ,
262
+ Some ( info) => {
263
+ let malformed_err = ResolveLoadError :: PathMalformed ( path_buf. clone ( ) ) ;
264
+ let mut split_parts = path. trim_start_match ( "//" ) . split ( ':' ) ;
265
+ let package = split_parts. next ( ) . ok_or ( malformed_err. clone ( ) ) ?;
266
+ let target = split_parts. next ( ) . ok_or ( malformed_err. clone ( ) ) ?;
267
+ match split_parts. next ( ) . is_some ( ) {
268
+ true => Err ( malformed_err. clone ( ) ) ,
269
+ false => {
270
+ let file_path = PathBuf :: from ( package) . join ( target) ;
271
+ let root_path = current_file_dir
272
+ . ancestors ( )
273
+ . find ( |a| match a. read_dir ( ) {
274
+ Ok ( mut entries) => entries
275
+ . find ( |f| match f {
276
+ Ok ( f) => [ "MODULE.bazel" , "WORKSPACE" , "WORKSPACE.bazel" ]
277
+ . contains ( & f. file_name ( ) . to_str ( ) . unwrap_or ( "" ) ) ,
278
+ _ => false ,
279
+ } )
280
+ . is_some ( ) ,
281
+ _ => false ,
282
+ } )
283
+ . unwrap_or ( & info. workspace_root ) ;
284
+ Ok ( root_path. join ( file_path) )
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+ fn handle_remote_bazel_repository (
291
+ info : & Option < BazelInfo > ,
292
+ path : & str ,
293
+ path_buf : PathBuf ,
294
+ ) -> Result < PathBuf , ResolveLoadError > {
295
+ match info {
296
+ None => Err ( ResolveLoadError :: MissingBazelInfo ( path_buf) ) ,
297
+ Some ( info) => {
298
+ let malformed_err = ResolveLoadError :: PathMalformed ( path_buf. clone ( ) ) ;
299
+ let mut split_parts = path. trim_start_match ( "@" ) . split ( "//" ) ;
300
+ let repository = split_parts. next ( ) . ok_or ( malformed_err. clone ( ) ) ?;
301
+ split_parts = split_parts. next ( ) . ok_or ( malformed_err. clone ( ) ) ?. split ( ":" ) ;
302
+ let package = split_parts. next ( ) . ok_or ( malformed_err. clone ( ) ) ?;
303
+ let target = split_parts. next ( ) . ok_or ( malformed_err. clone ( ) ) ?;
304
+ match split_parts. next ( ) . is_some ( ) {
305
+ true => Err ( malformed_err. clone ( ) ) ,
306
+ false => {
307
+ let execroot_dirname =
308
+ info. execroot . file_name ( ) . ok_or ( malformed_err. clone ( ) ) ?;
309
+
310
+ if repository == execroot_dirname {
311
+ Ok ( info. workspace_root . join ( package) . join ( target) )
312
+ } else {
313
+ Ok ( info
314
+ . output_base
315
+ . join ( "external" )
316
+ . join ( repository)
317
+ . join ( package)
318
+ . join ( target) )
319
+ }
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
325
+
326
+ fn get_relative_file (
327
+ current_file_dir : & PathBuf ,
328
+ path : & str ,
329
+ pathbuf : PathBuf ,
330
+ ) -> Result < PathBuf , ResolveLoadError > {
331
+ let malformed_err = ResolveLoadError :: MissingCurrentFilePath ( pathbuf. clone ( ) ) ;
332
+ let mut split_parts = path. split ( ":" ) ;
333
+ let package = split_parts. next ( ) . ok_or ( malformed_err. clone ( ) ) ?;
334
+ let target = split_parts. next ( ) . ok_or ( malformed_err. clone ( ) ) ?;
335
+ match split_parts. next ( ) . is_some ( ) {
336
+ true => Err ( malformed_err. clone ( ) ) ,
337
+ false => Ok ( current_file_dir. join ( package) . join ( target) ) ,
338
+ }
339
+ }
340
+ fn label_into_file (
341
+ bazel_info : & Option < BazelInfo > ,
342
+ path : & str ,
343
+ current_file_path : & PathBuf ,
344
+ ) -> Result < PathBuf , ResolveLoadError > {
345
+ let current_file_dir = current_file_path. parent ( ) ;
346
+ let path_buf = PathBuf :: from ( path) ;
347
+
348
+ if path. starts_with ( "@" ) {
349
+ handle_remote_bazel_repository ( bazel_info, path, path_buf. clone ( ) )
350
+ } else if path. starts_with ( "//" ) {
351
+ handle_local_bazel_repository ( bazel_info, path, path_buf. clone ( ) , current_file_path)
352
+ } else if path. contains ( ":" ) {
353
+ match current_file_dir {
354
+ Some ( dir) => get_relative_file ( & dir. to_path_buf ( ) , path, path_buf. clone ( ) ) ,
355
+ None => Err ( ResolveLoadError :: MissingCurrentFilePath ( path_buf) ) ,
356
+ }
357
+ } else {
358
+ match ( current_file_dir, path_buf. is_absolute ( ) ) {
359
+ ( _, true ) => Ok ( path_buf) ,
360
+ ( Some ( current_file_dir) , false ) => Ok ( current_file_dir. join ( & path_buf) ) ,
361
+ ( None , false ) => Err ( ResolveLoadError :: MissingCurrentFilePath ( path_buf) ) ,
362
+ }
363
+ }
364
+ }
365
+
366
+ fn replace_fake_file_with_build_target ( fake_file : PathBuf ) -> Option < PathBuf > {
367
+ fake_file. parent ( ) . and_then ( |p| {
368
+ let build = p. join ( "BUILD" ) ;
369
+ let build_bazel = p. join ( "BUILD.bazel" ) ;
370
+ if build. exists ( ) {
371
+ Some ( build)
372
+ } else if build_bazel. exists ( ) {
373
+ Some ( build_bazel)
374
+ } else {
375
+ None
376
+ }
377
+ } )
378
+ }
379
+
380
+ fn find_location_in_build_file (
381
+ info : Option < BazelInfo > ,
382
+ literal : String ,
383
+ current_file_pathbuf : PathBuf ,
384
+ ast : & AstModule ,
385
+ ) -> anyhow:: Result < Option < Range > > {
386
+ let resolved_file = label_into_file ( & info, literal. as_str ( ) , & current_file_pathbuf) ?;
387
+ let basename = resolved_file. file_name ( ) . and_then ( |f| f. to_str ( ) ) . ok_or (
388
+ ResolveLoadError :: ResolvedDoesNotExist ( resolved_file. clone ( ) ) ,
389
+ ) ?;
390
+ let resolved_span = ast
391
+ . find_function_call_with_name ( basename)
392
+ . and_then ( |r| Some ( Range :: from ( r) ) ) ;
393
+ Ok ( resolved_span)
394
+ }
244
395
impl LspContext for Context {
245
396
fn parse_file_with_contents ( & self , uri : & LspUrl , content : String ) -> LspEvalResult {
246
397
match uri {
@@ -257,16 +408,23 @@ impl LspContext for Context {
257
408
}
258
409
259
410
fn resolve_load ( & self , path : & str , current_file : & LspUrl ) -> anyhow:: Result < LspUrl > {
260
- let path = PathBuf :: from ( path) ;
261
411
match current_file {
262
412
LspUrl :: File ( current_file_path) => {
263
- let current_file_dir = current_file_path. parent ( ) ;
264
- let absolute_path = match ( current_file_dir, path. is_absolute ( ) ) {
265
- ( _, true ) => Ok ( path) ,
266
- ( Some ( current_file_dir) , false ) => Ok ( current_file_dir. join ( & path) ) ,
267
- ( None , false ) => Err ( ResolveLoadError :: MissingCurrentFilePath ( path) ) ,
413
+ let mut resolved_file = label_into_file ( & self . bazel_info , path, current_file_path) ?;
414
+ resolved_file = match resolved_file. canonicalize ( ) {
415
+ Ok ( f) => {
416
+ if f. exists ( ) {
417
+ Ok ( f)
418
+ } else {
419
+ replace_fake_file_with_build_target ( resolved_file. clone ( ) )
420
+ . ok_or ( ResolveLoadError :: ResolvedDoesNotExist ( resolved_file) )
421
+ }
422
+ }
423
+ _ => replace_fake_file_with_build_target ( resolved_file. clone ( ) )
424
+ . ok_or ( ResolveLoadError :: ResolvedDoesNotExist ( resolved_file) ) ,
268
425
} ?;
269
- Ok ( Url :: from_file_path ( absolute_path) . unwrap ( ) . try_into ( ) ?)
426
+
427
+ Ok ( Url :: from_file_path ( resolved_file) . unwrap ( ) . try_into ( ) ?)
270
428
}
271
429
_ => Err (
272
430
ResolveLoadError :: WrongScheme ( "file://" . to_owned ( ) , current_file. clone ( ) ) . into ( ) ,
@@ -279,11 +437,26 @@ impl LspContext for Context {
279
437
literal : & str ,
280
438
current_file : & LspUrl ,
281
439
) -> anyhow:: Result < Option < StringLiteralResult > > {
440
+ let current_file_pathbuf = current_file. path ( ) . to_path_buf ( ) ;
282
441
self . resolve_load ( literal, current_file) . map ( |url| {
283
- Some ( StringLiteralResult {
284
- url,
285
- location_finder : None ,
286
- } )
442
+ let p = url. path ( ) ;
443
+ // TODO: we can always give literal location finder
444
+ // TODO: but if its a file it will always try to resolve the location but won't be able to and an error will be printed
445
+ if p. ends_with ( "BUILD" ) || p. ends_with ( "BUILD.bazel" ) {
446
+ let info = self . bazel_info . clone ( ) ;
447
+ let literal_copy = literal. to_owned ( ) ;
448
+ Some ( StringLiteralResult {
449
+ url,
450
+ location_finder : Some ( box |ast: & AstModule , _url| {
451
+ find_location_in_build_file ( info, literal_copy, current_file_pathbuf, ast)
452
+ } ) ,
453
+ } )
454
+ } else {
455
+ Some ( StringLiteralResult {
456
+ url,
457
+ location_finder : None ,
458
+ } )
459
+ }
287
460
} )
288
461
}
289
462
0 commit comments