@@ -9,6 +9,7 @@ use std::fs;
9
9
use std:: path:: PathBuf ;
10
10
11
11
use crate :: lsp:: inputs:: package_description:: Description ;
12
+ use crate :: lsp:: inputs:: package_index:: Index ;
12
13
use crate :: lsp:: inputs:: package_namespace:: Namespace ;
13
14
14
15
/// Represents an R package and its metadata relevant for static analysis.
@@ -22,17 +23,28 @@ pub struct Package {
22
23
pub namespace : Namespace ,
23
24
24
25
// List of symbols exported via NAMESPACE `export()` directives and via
25
- // `DocType{data}`. Note the latter should only apply to packages with
26
- // `LazyData: true` but currently applies to all packages, as a stopgap
27
- // to prevent spurious diagnostics (we accept false negatives to avoid
28
- // annoying false positives).
26
+ // documented symbols listed in INDEX. The latter is a stopgap to ensure we
27
+ // support exported datasets and prevent spurious diagnostics (we accept
28
+ // false negatives to avoid annoying false positives).
29
29
pub exported_symbols : Vec < String > ,
30
30
}
31
31
32
32
impl Package {
33
- pub fn new ( path : PathBuf , description : Description , namespace : Namespace ) -> Self {
33
+ pub fn new (
34
+ path : PathBuf ,
35
+ description : Description ,
36
+ namespace : Namespace ,
37
+ index : Index ,
38
+ ) -> Self {
34
39
// Compute exported symbols. Start from explicit NAMESPACE exports.
35
- let exported_symbols = namespace. exports . clone ( ) ;
40
+ let mut exported_symbols = namespace. exports . clone ( ) ;
41
+
42
+ // Add all documented symbols. This should cover documented datasets.
43
+ exported_symbols. extend ( index. names . iter ( ) . cloned ( ) ) ;
44
+
45
+ // Sort and deduplicate (we expect lots of duplicates)
46
+ exported_symbols. sort ( ) ;
47
+ exported_symbols. dedup ( ) ;
36
48
37
49
Self {
38
50
path,
@@ -44,7 +56,7 @@ impl Package {
44
56
45
57
#[ cfg( test) ]
46
58
pub fn from_parts ( path : PathBuf , description : Description , namespace : Namespace ) -> Self {
47
- Self :: new ( path, description, namespace)
59
+ Self :: new ( path, description, namespace, Index :: default ( ) )
48
60
}
49
61
50
62
/// Load a package from a given path.
@@ -73,10 +85,22 @@ impl Package {
73
85
Namespace :: default ( )
74
86
} ;
75
87
88
+ let index = match Index :: load_from_folder ( package_path) {
89
+ Ok ( index) => index,
90
+ Err ( err) => {
91
+ tracing:: warn!(
92
+ "Can't load INDEX file from `{path}`: {err:?}" ,
93
+ path = package_path. to_string_lossy( )
94
+ ) ;
95
+ Index :: default ( )
96
+ } ,
97
+ } ;
98
+
76
99
Ok ( Some ( Self :: new (
77
100
package_path. to_path_buf ( ) ,
78
101
description,
79
102
namespace,
103
+ index,
80
104
) ) )
81
105
}
82
106
@@ -95,9 +119,98 @@ impl Package {
95
119
"`Package` field in `DESCRIPTION` doesn't match folder name '{name}'"
96
120
) ) ;
97
121
}
122
+
98
123
Ok ( Some ( pkg) )
99
124
} else {
100
125
Ok ( None )
101
126
}
102
127
}
103
128
}
129
+
130
+ #[ cfg( test) ]
131
+ mod tests {
132
+ use super :: * ;
133
+ use crate :: lsp:: inputs:: package_description:: Description ;
134
+ use crate :: lsp:: inputs:: package_index:: Index ;
135
+ use crate :: lsp:: inputs:: package_namespace:: Namespace ;
136
+
137
+ fn new_package ( name : & str , ns : Namespace , index : Index ) -> Package {
138
+ Package :: new (
139
+ std:: path:: PathBuf :: from ( "/fake" ) ,
140
+ Description {
141
+ name : name. to_string ( ) ,
142
+ ..Description :: default ( )
143
+ } ,
144
+ ns,
145
+ index,
146
+ )
147
+ }
148
+
149
+ #[ test]
150
+ fn exported_symbols_are_sorted_and_unique ( ) {
151
+ let mut ns = Namespace :: default ( ) ;
152
+ ns. exports = vec ! [ "b" . to_string( ) , "a" . to_string( ) , "a" . to_string( ) ] ;
153
+
154
+ let mut index = Index :: default ( ) ;
155
+ index. names = vec ! [ "c" . to_string( ) , "a" . to_string( ) , "a" . to_string( ) ] ;
156
+
157
+ let pkg = new_package ( "foo" , ns, index) ;
158
+ assert_eq ! ( pkg. exported_symbols, vec![ "a" , "b" , "c" ] ) ;
159
+ }
160
+
161
+ #[ test]
162
+ fn exported_symbols_empty_when_none ( ) {
163
+ let ns = Namespace :: default ( ) ;
164
+ let idx = Index :: default ( ) ;
165
+ let pkg = new_package ( "foo" , ns, idx) ;
166
+ assert ! ( pkg. exported_symbols. is_empty( ) ) ;
167
+ }
168
+
169
+ #[ test]
170
+ fn load_from_folder_reads_description_namespace_and_index ( ) {
171
+ let dir = temp_palmerpenguin ( ) ;
172
+
173
+ let pkg = Package :: load_from_folder ( dir. path ( ) ) . unwrap ( ) . unwrap ( ) ;
174
+
175
+ // Should include all exports and all index names, sorted and deduped
176
+ assert_eq ! ( pkg. exported_symbols, vec![
177
+ "path_to_file" ,
178
+ "penguins" ,
179
+ "penguins_raw"
180
+ ] ) ;
181
+ assert_eq ! ( pkg. description. name, "penguins" ) ;
182
+ }
183
+ }
184
+
185
+ #[ cfg( test) ]
186
+ pub ( crate ) fn temp_palmerpenguin ( ) -> tempfile:: TempDir {
187
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
188
+
189
+ // Write DESCRIPTION
190
+ let description = "\
191
+ Package: penguins
192
+ Version: 1.0
193
+ " ;
194
+ fs:: write ( dir. path ( ) . join ( "DESCRIPTION" ) , description) . unwrap ( ) ;
195
+
196
+ // Write NAMESPACE
197
+ let namespace = "\
198
+ export(path_to_file)
199
+ export(penguins)
200
+ " ;
201
+ fs:: write ( dir. path ( ) . join ( "NAMESPACE" ) , namespace) . unwrap ( ) ;
202
+
203
+ // Write INDEX
204
+ let index = "\
205
+ path_to_file Get file path to 'penguins.csv' and
206
+ 'penguins_raw.csv' files
207
+ penguins Size measurements for adult foraging penguins
208
+ near Palmer Station, Antarctica
209
+ penguins_raw Penguin size, clutch, and blood isotope data
210
+ for foraging adults near Palmer Station,
211
+ Antarctica
212
+ " ;
213
+ fs:: write ( dir. path ( ) . join ( "INDEX" ) , index) . unwrap ( ) ;
214
+
215
+ dir
216
+ }
0 commit comments