@@ -25,6 +25,7 @@ use std::path::PathBuf;
2525use gazebo:: prelude:: * ;
2626use itertools:: Either ;
2727use lsp_types:: Diagnostic ;
28+ use lsp_types:: Range ;
2829use lsp_types:: Url ;
2930use starlark:: environment:: FrozenModule ;
3031use starlark:: environment:: Globals ;
@@ -50,6 +51,13 @@ pub(crate) enum ContextMode {
5051 Run ,
5152}
5253
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+
5361#[ derive( Debug ) ]
5462pub ( crate ) struct Context {
5563 pub ( crate ) mode : ContextMode ,
@@ -58,6 +66,7 @@ pub(crate) struct Context {
5866 pub ( crate ) module : Option < Module > ,
5967 pub ( crate ) builtin_docs : HashMap < LspUrl , String > ,
6068 pub ( crate ) builtin_symbols : HashMap < String , LspUrl > ,
69+ pub ( crate ) bazel_info : Option < BazelInfo > ,
6170}
6271
6372/// The outcome of evaluating (checking, parsing or running) given starlark code.
@@ -110,6 +119,7 @@ impl Context {
110119 module,
111120 builtin_docs,
112121 builtin_symbols,
122+ bazel_info : None ,
113123 } )
114124 }
115125
@@ -241,6 +251,147 @@ impl Context {
241251 }
242252}
243253
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+ }
244395impl LspContext for Context {
245396 fn parse_file_with_contents ( & self , uri : & LspUrl , content : String ) -> LspEvalResult {
246397 match uri {
@@ -257,16 +408,23 @@ impl LspContext for Context {
257408 }
258409
259410 fn resolve_load ( & self , path : & str , current_file : & LspUrl ) -> anyhow:: Result < LspUrl > {
260- let path = PathBuf :: from ( path) ;
261411 match current_file {
262412 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) ) ,
268425 } ?;
269- Ok ( Url :: from_file_path ( absolute_path) . unwrap ( ) . try_into ( ) ?)
426+
427+ Ok ( Url :: from_file_path ( resolved_file) . unwrap ( ) . try_into ( ) ?)
270428 }
271429 _ => Err (
272430 ResolveLoadError :: WrongScheme ( "file://" . to_owned ( ) , current_file. clone ( ) ) . into ( ) ,
@@ -279,11 +437,26 @@ impl LspContext for Context {
279437 literal : & str ,
280438 current_file : & LspUrl ,
281439 ) -> anyhow:: Result < Option < StringLiteralResult > > {
440+ let current_file_pathbuf = current_file. path ( ) . to_path_buf ( ) ;
282441 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+ }
287460 } )
288461 }
289462
0 commit comments