5
5
//
6
6
//
7
7
8
+ use std:: collections:: BTreeMap ;
8
9
use std:: collections:: HashMap ;
9
10
use std:: collections:: HashSet ;
10
11
@@ -17,6 +18,7 @@ use stdext::*;
17
18
use tower_lsp:: lsp_types:: Diagnostic ;
18
19
use tower_lsp:: lsp_types:: DiagnosticSeverity ;
19
20
use tree_sitter:: Node ;
21
+ use tree_sitter:: Point ;
20
22
use tree_sitter:: Range ;
21
23
22
24
use crate :: lsp;
@@ -64,7 +66,7 @@ pub struct DiagnosticContext<'a> {
64
66
/// The symbols exported by packages loaded via `library()` calls in this
65
67
/// document. Currently global. TODO: Store individual exports in a BTreeMap
66
68
/// sorted by position in the source?
67
- pub library_symbols : HashSet < String > ,
69
+ pub library_symbols : BTreeMap < Point , HashSet < String > > ,
68
70
69
71
// Whether or not we're inside of a formula.
70
72
pub in_formula : bool ,
@@ -88,7 +90,7 @@ impl<'a> DiagnosticContext<'a> {
88
90
workspace_symbols : HashSet :: new ( ) ,
89
91
installed_packages : HashSet :: new ( ) ,
90
92
library,
91
- library_symbols : HashSet :: new ( ) ,
93
+ library_symbols : BTreeMap :: new ( ) ,
92
94
in_formula : false ,
93
95
in_call_like_arguments : false ,
94
96
}
@@ -99,8 +101,8 @@ impl<'a> DiagnosticContext<'a> {
99
101
symbols. insert ( name. to_string ( ) , location) ;
100
102
}
101
103
102
- pub fn has_definition ( & self , name : & str ) -> bool {
103
- // First, check document symbols.
104
+ // First, check document symbols.
105
+ pub fn has_definition ( & self , name : & str , start_position : Point ) -> bool {
104
106
for symbols in & self . document_symbols {
105
107
if symbols. contains_key ( name) {
106
108
return true ;
@@ -112,10 +114,15 @@ impl<'a> DiagnosticContext<'a> {
112
114
return true ;
113
115
}
114
116
115
- // Finally, check library symbols from library() calls.
116
- // TODO: Take `Node` and check by position.
117
- if self . library_symbols . contains ( name) {
118
- return true ;
117
+ // Finally, check package symbols from `library()` calls.
118
+ // Check all symbols exported by `library()` before the given position.
119
+ for ( library_position, exports) in self . library_symbols . iter ( ) {
120
+ if * library_position > start_position {
121
+ break ;
122
+ }
123
+ if exports. contains ( name) {
124
+ return true ;
125
+ }
119
126
}
120
127
121
128
// Finally, check session symbols.
@@ -823,7 +830,12 @@ fn handle_library_call(node: Node, context: &mut DiagnosticContext) -> anyhow::R
823
830
// Insert exports globablly for now
824
831
if let Some ( package) = context. library . get ( & package_name) {
825
832
for symbol in & package. namespace . exports {
826
- context. library_symbols . insert ( symbol. clone ( ) ) ;
833
+ let pos = node. end_position ( ) ;
834
+ context
835
+ . library_symbols
836
+ . entry ( pos)
837
+ . or_insert_with ( HashSet :: new)
838
+ . insert ( symbol. clone ( ) ) ;
827
839
}
828
840
} else {
829
841
lsp:: log_warn!( "Can't get exports from package {package_name} because it is not installed." )
@@ -990,7 +1002,7 @@ fn check_symbol_in_scope(
990
1002
991
1003
// Skip if a symbol with this name is in scope.
992
1004
let name = context. contents . node_slice ( & node) ?. to_string ( ) ;
993
- if context. has_definition ( name. as_str ( ) ) {
1005
+ if context. has_definition ( name. as_str ( ) , node . start_position ( ) ) {
994
1006
return false . ok ( ) ;
995
1007
}
996
1008
@@ -1008,13 +1020,19 @@ fn check_symbol_in_scope(
1008
1020
1009
1021
#[ cfg( test) ]
1010
1022
mod tests {
1023
+ use std:: path:: PathBuf ;
1024
+
1011
1025
use harp:: eval:: RParseEvalOptions ;
1012
1026
use once_cell:: sync:: Lazy ;
1013
1027
use tower_lsp:: lsp_types:: Position ;
1014
1028
1015
1029
use crate :: interface:: console_inputs;
1016
1030
use crate :: lsp:: diagnostics:: generate_diagnostics;
1017
1031
use crate :: lsp:: documents:: Document ;
1032
+ use crate :: lsp:: inputs:: library:: Library ;
1033
+ use crate :: lsp:: inputs:: package:: Description ;
1034
+ use crate :: lsp:: inputs:: package:: Namespace ;
1035
+ use crate :: lsp:: inputs:: package:: Package ;
1018
1036
use crate :: lsp:: state:: WorldState ;
1019
1037
use crate :: r_task;
1020
1038
@@ -1504,13 +1522,6 @@ foo
1504
1522
#[ test]
1505
1523
fn test_library_static_exports ( ) {
1506
1524
r_task ( || {
1507
- use std:: path:: PathBuf ;
1508
-
1509
- use crate :: lsp:: inputs:: library:: Library ;
1510
- use crate :: lsp:: inputs:: package:: Description ;
1511
- use crate :: lsp:: inputs:: package:: Namespace ;
1512
- use crate :: lsp:: inputs:: package:: Package ;
1513
-
1514
1525
// `mockpkg` exports `foo` and `bar`
1515
1526
let namespace = Namespace {
1516
1527
exports : vec ! [ "foo" . to_string( ) , "bar" . to_string( ) ] ,
@@ -1588,4 +1599,80 @@ foo
1588
1599
assert_eq ! ( diagnostics. len( ) , 0 ) ;
1589
1600
} ) ;
1590
1601
}
1602
+
1603
+ #[ test]
1604
+ fn test_library_static_exports_multiple_packages ( ) {
1605
+ r_task ( || {
1606
+ // pkg1 exports `foo` and `bar`
1607
+ let namespace1 = Namespace {
1608
+ exports : vec ! [ "foo" . to_string( ) , "bar" . to_string( ) ] ,
1609
+ imports : vec ! [ ] ,
1610
+ bulk_imports : vec ! [ ] ,
1611
+ } ;
1612
+ let description1 = Description {
1613
+ name : "pkg1" . to_string ( ) ,
1614
+ version : "1.0.0" . to_string ( ) ,
1615
+ depends : vec ! [ ] ,
1616
+ } ;
1617
+ let package1 = Package {
1618
+ path : PathBuf :: from ( "/mock/path1" ) ,
1619
+ description : description1,
1620
+ namespace : namespace1,
1621
+ } ;
1622
+
1623
+ // pkg2 exports `bar` and `baz`
1624
+ let namespace2 = Namespace {
1625
+ exports : vec ! [ "bar" . to_string( ) , "baz" . to_string( ) ] ,
1626
+ imports : vec ! [ ] ,
1627
+ bulk_imports : vec ! [ ] ,
1628
+ } ;
1629
+ let description2 = Description {
1630
+ name : "pkg2" . to_string ( ) ,
1631
+ version : "1.0.0" . to_string ( ) ,
1632
+ depends : vec ! [ ] ,
1633
+ } ;
1634
+ let package2 = Package {
1635
+ path : PathBuf :: from ( "/mock/path2" ) ,
1636
+ description : description2,
1637
+ namespace : namespace2,
1638
+ } ;
1639
+
1640
+ let library = Library :: new ( vec ! [ ] )
1641
+ . insert ( "pkg1" , package1)
1642
+ . insert ( "pkg2" , package2) ;
1643
+
1644
+ let console_scopes = vec ! [ vec![ "library" . to_string( ) ] ] ;
1645
+ let state = WorldState {
1646
+ library,
1647
+ console_scopes,
1648
+ ..Default :: default ( )
1649
+ } ;
1650
+
1651
+ // Code with two library calls at different points
1652
+ let code = "
1653
+ foo # not in scope
1654
+ bar # not in scope
1655
+ baz # not in scope
1656
+
1657
+ library(pkg1)
1658
+ foo # in scope
1659
+ bar # in scope
1660
+ baz # not in scope
1661
+
1662
+ library(pkg2)
1663
+ foo # in scope
1664
+ bar # in scope
1665
+ baz # in scope
1666
+ " ;
1667
+ let document = Document :: new ( code, None ) ;
1668
+ let diagnostics = generate_diagnostics ( document, state. clone ( ) ) ;
1669
+
1670
+ let messages: Vec < _ > = diagnostics. iter ( ) . map ( |d| d. message . clone ( ) ) . collect ( ) ;
1671
+ assert ! ( messages. iter( ) . any( |m| m. contains( "No symbol named 'foo'" ) ) ) ;
1672
+ assert ! ( messages. iter( ) . any( |m| m. contains( "No symbol named 'bar'" ) ) ) ;
1673
+ assert ! ( messages. iter( ) . any( |m| m. contains( "No symbol named 'baz'" ) ) ) ;
1674
+ assert ! ( messages. iter( ) . any( |m| m. contains( "No symbol named 'baz'" ) ) ) ;
1675
+ assert_eq ! ( messages. len( ) , 4 ) ;
1676
+ } ) ;
1677
+ }
1591
1678
}
0 commit comments