1
+ use crate :: core:: dependency:: DepKind ;
1
2
use crate :: core:: FeatureValue :: Dep ;
2
3
use crate :: core:: { Edition , FeatureValue , Package } ;
3
4
use crate :: util:: interning:: InternedString ;
@@ -6,6 +7,7 @@ use annotate_snippets::{Level, Renderer, Snippet};
6
7
use cargo_util_schemas:: manifest:: { TomlLintLevel , TomlToolLints } ;
7
8
use pathdiff:: diff_paths;
8
9
use std:: collections:: HashSet ;
10
+ use std:: fmt:: Display ;
9
11
use std:: ops:: Range ;
10
12
use std:: path:: Path ;
11
13
use toml_edit:: ImDocument ;
@@ -107,6 +109,17 @@ pub enum LintLevel {
107
109
Forbid ,
108
110
}
109
111
112
+ impl Display for LintLevel {
113
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
114
+ match self {
115
+ LintLevel :: Allow => write ! ( f, "allow" ) ,
116
+ LintLevel :: Warn => write ! ( f, "warn" ) ,
117
+ LintLevel :: Deny => write ! ( f, "deny" ) ,
118
+ LintLevel :: Forbid => write ! ( f, "forbid" ) ,
119
+ }
120
+ }
121
+ }
122
+
110
123
impl LintLevel {
111
124
pub fn to_diagnostic_level ( self ) -> Level {
112
125
match self {
@@ -144,10 +157,10 @@ impl From<TomlLintLevel> for LintLevel {
144
157
/// [RFC #3491]: https://rust-lang.github.io/rfcs/3491-remove-implicit-features.html
145
158
const IMPLICIT_FEATURES : Lint = Lint {
146
159
name : "implicit_features" ,
147
- desc : "warn about the use of unstable features " ,
160
+ desc : "implicit features for optional dependencies is deprecated and will be unavailable in the 2024 edition " ,
148
161
groups : & [ ] ,
149
162
default_level : LintLevel :: Allow ,
150
- edition_lint_opts : Some ( ( Edition :: Edition2024 , LintLevel :: Deny ) ) ,
163
+ edition_lint_opts : None ,
151
164
} ;
152
165
153
166
pub fn check_implicit_features (
@@ -157,56 +170,63 @@ pub fn check_implicit_features(
157
170
error_count : & mut usize ,
158
171
gctx : & GlobalContext ,
159
172
) -> CargoResult < ( ) > {
160
- let lint_level = IMPLICIT_FEATURES . level ( lints, pkg. manifest ( ) . edition ( ) ) ;
173
+ let edition = pkg. manifest ( ) . edition ( ) ;
174
+ // In Edition 2024+, instead of creating optional features, the dependencies are unused.
175
+ // See `UNUSED_OPTIONAL_DEPENDENCY`
176
+ if edition >= Edition :: Edition2024 {
177
+ return Ok ( ( ) ) ;
178
+ }
179
+
180
+ let lint_level = IMPLICIT_FEATURES . level ( lints, edition) ;
161
181
if lint_level == LintLevel :: Allow {
162
182
return Ok ( ( ) ) ;
163
183
}
164
184
165
185
let manifest = pkg. manifest ( ) ;
166
- let user_defined_features = manifest. resolved_toml ( ) . features ( ) ;
167
- let features = user_defined_features. map_or ( HashSet :: new ( ) , |f| {
168
- f. keys ( ) . map ( |k| InternedString :: new ( & k) ) . collect ( )
169
- } ) ;
170
- // Add implicit features for optional dependencies if they weren't
171
- // explicitly listed anywhere.
172
- let explicitly_listed = user_defined_features. map_or ( HashSet :: new ( ) , |f| {
173
- f. values ( )
174
- . flatten ( )
175
- . filter_map ( |v| match FeatureValue :: new ( v. into ( ) ) {
176
- Dep { dep_name } => Some ( dep_name) ,
177
- _ => None ,
178
- } )
179
- . collect ( )
180
- } ) ;
186
+ let activated_opt_deps = manifest
187
+ . resolved_toml ( )
188
+ . features ( )
189
+ . map ( |map| {
190
+ map. values ( )
191
+ . flatten ( )
192
+ . filter_map ( |f| match FeatureValue :: new ( InternedString :: new ( f) ) {
193
+ Dep { dep_name } => Some ( dep_name. as_str ( ) ) ,
194
+ _ => None ,
195
+ } )
196
+ . collect :: < HashSet < _ > > ( )
197
+ } )
198
+ . unwrap_or_default ( ) ;
181
199
200
+ let mut emitted_source = None ;
182
201
for dep in manifest. dependencies ( ) {
183
202
let dep_name_in_toml = dep. name_in_toml ( ) ;
184
- if !dep. is_optional ( )
185
- || features. contains ( & dep_name_in_toml)
186
- || explicitly_listed. contains ( & dep_name_in_toml)
187
- {
203
+ if !dep. is_optional ( ) || activated_opt_deps. contains ( dep_name_in_toml. as_str ( ) ) {
188
204
continue ;
189
205
}
190
206
if lint_level == LintLevel :: Forbid || lint_level == LintLevel :: Deny {
191
207
* error_count += 1 ;
192
208
}
209
+ let mut toml_path = vec ! [ dep. kind( ) . kind_table( ) , dep_name_in_toml. as_str( ) ] ;
210
+ let platform = dep. platform ( ) . map ( |p| p. to_string ( ) ) ;
211
+ if let Some ( platform) = platform. as_ref ( ) {
212
+ toml_path. insert ( 0 , platform) ;
213
+ toml_path. insert ( 0 , "target" ) ;
214
+ }
193
215
let level = lint_level. to_diagnostic_level ( ) ;
194
216
let manifest_path = rel_cwd_manifest_path ( path, gctx) ;
195
- let message = level. title ( "unused optional dependency" ) . snippet (
217
+ let mut message = level. title ( IMPLICIT_FEATURES . desc ) . snippet (
196
218
Snippet :: source ( manifest. contents ( ) )
197
219
. origin ( & manifest_path)
198
- . annotation (
199
- level. span (
200
- get_span (
201
- manifest. document ( ) ,
202
- & [ "dependencies" , & dep_name_in_toml] ,
203
- false ,
204
- )
205
- . unwrap ( ) ,
206
- ) ,
207
- )
220
+ . annotation ( level. span ( get_span ( manifest. document ( ) , & toml_path, false ) . unwrap ( ) ) )
208
221
. fold ( true ) ,
209
222
) ;
223
+ if emitted_source. is_none ( ) {
224
+ emitted_source = Some ( format ! (
225
+ "`cargo::{}` is set to `{lint_level}`" ,
226
+ IMPLICIT_FEATURES . name
227
+ ) ) ;
228
+ message = message. footer ( Level :: Note . title ( emitted_source. as_ref ( ) . unwrap ( ) ) ) ;
229
+ }
210
230
let renderer = Renderer :: styled ( ) . term_width (
211
231
gctx. shell ( )
212
232
. err_width ( )
@@ -217,3 +237,114 @@ pub fn check_implicit_features(
217
237
}
218
238
Ok ( ( ) )
219
239
}
240
+
241
+ const UNUSED_OPTIONAL_DEPENDENCY : Lint = Lint {
242
+ name : "unused_optional_dependency" ,
243
+ desc : "unused optional dependency" ,
244
+ groups : & [ ] ,
245
+ default_level : LintLevel :: Warn ,
246
+ edition_lint_opts : None ,
247
+ } ;
248
+
249
+ pub fn unused_dependencies (
250
+ pkg : & Package ,
251
+ path : & Path ,
252
+ lints : & TomlToolLints ,
253
+ error_count : & mut usize ,
254
+ gctx : & GlobalContext ,
255
+ ) -> CargoResult < ( ) > {
256
+ let edition = pkg. manifest ( ) . edition ( ) ;
257
+ // Unused optional dependencies can only exist on edition 2024+
258
+ if edition < Edition :: Edition2024 {
259
+ return Ok ( ( ) ) ;
260
+ }
261
+
262
+ let lint_level = UNUSED_OPTIONAL_DEPENDENCY . level ( lints, edition) ;
263
+ if lint_level == LintLevel :: Allow {
264
+ return Ok ( ( ) ) ;
265
+ }
266
+ let mut emitted_source = None ;
267
+ let manifest = pkg. manifest ( ) ;
268
+ let original_toml = manifest. original_toml ( ) ;
269
+ // Unused dependencies were stripped from the manifest, leaving only the used ones
270
+ let used_dependencies = manifest
271
+ . dependencies ( )
272
+ . into_iter ( )
273
+ . map ( |d| d. name_in_toml ( ) . to_string ( ) )
274
+ . collect :: < HashSet < String > > ( ) ;
275
+ let mut orig_deps = vec ! [
276
+ (
277
+ original_toml. dependencies. as_ref( ) ,
278
+ vec![ DepKind :: Normal . kind_table( ) ] ,
279
+ ) ,
280
+ (
281
+ original_toml. dev_dependencies. as_ref( ) ,
282
+ vec![ DepKind :: Development . kind_table( ) ] ,
283
+ ) ,
284
+ (
285
+ original_toml. build_dependencies. as_ref( ) ,
286
+ vec![ DepKind :: Build . kind_table( ) ] ,
287
+ ) ,
288
+ ] ;
289
+ for ( name, platform) in original_toml. target . iter ( ) . flatten ( ) {
290
+ orig_deps. push ( (
291
+ platform. dependencies . as_ref ( ) ,
292
+ vec ! [ "target" , name, DepKind :: Normal . kind_table( ) ] ,
293
+ ) ) ;
294
+ orig_deps. push ( (
295
+ platform. dev_dependencies . as_ref ( ) ,
296
+ vec ! [ "target" , name, DepKind :: Development . kind_table( ) ] ,
297
+ ) ) ;
298
+ orig_deps. push ( (
299
+ platform. build_dependencies . as_ref ( ) ,
300
+ vec ! [ "target" , name, DepKind :: Normal . kind_table( ) ] ,
301
+ ) ) ;
302
+ }
303
+ for ( deps, toml_path) in orig_deps {
304
+ if let Some ( deps) = deps {
305
+ for name in deps. keys ( ) {
306
+ if !used_dependencies. contains ( name. as_str ( ) ) {
307
+ if lint_level == LintLevel :: Forbid || lint_level == LintLevel :: Deny {
308
+ * error_count += 1 ;
309
+ }
310
+ let toml_path = toml_path
311
+ . iter ( )
312
+ . map ( |s| * s)
313
+ . chain ( std:: iter:: once ( name. as_str ( ) ) )
314
+ . collect :: < Vec < _ > > ( ) ;
315
+ let level = lint_level. to_diagnostic_level ( ) ;
316
+ let manifest_path = rel_cwd_manifest_path ( path, gctx) ;
317
+
318
+ let mut message = level. title ( UNUSED_OPTIONAL_DEPENDENCY . desc ) . snippet (
319
+ Snippet :: source ( manifest. contents ( ) )
320
+ . origin ( & manifest_path)
321
+ . annotation ( level. span (
322
+ get_span ( manifest. document ( ) , toml_path. as_slice ( ) , false ) . unwrap ( ) ,
323
+ ) )
324
+ . fold ( true ) ,
325
+ ) ;
326
+ if emitted_source. is_none ( ) {
327
+ emitted_source = Some ( format ! (
328
+ "`cargo::{}` is set to `{lint_level}`" ,
329
+ UNUSED_OPTIONAL_DEPENDENCY . name
330
+ ) ) ;
331
+ message =
332
+ message. footer ( Level :: Note . title ( emitted_source. as_ref ( ) . unwrap ( ) ) ) ;
333
+ }
334
+ let help = format ! (
335
+ "remove the dependency or activate it in a feature with `dep:{name}`"
336
+ ) ;
337
+ message = message. footer ( Level :: Help . title ( & help) ) ;
338
+ let renderer = Renderer :: styled ( ) . term_width (
339
+ gctx. shell ( )
340
+ . err_width ( )
341
+ . diagnostic_terminal_width ( )
342
+ . unwrap_or ( annotate_snippets:: renderer:: DEFAULT_TERM_WIDTH ) ,
343
+ ) ;
344
+ writeln ! ( gctx. shell( ) . err( ) , "{}" , renderer. render( message) ) ?;
345
+ }
346
+ }
347
+ }
348
+ }
349
+ Ok ( ( ) )
350
+ }
0 commit comments