@@ -410,6 +410,54 @@ pub struct FormatResponse {
410
410
pub code : String ,
411
411
}
412
412
413
+ #[ derive( Debug , Clone ) ]
414
+ pub struct ClippyRequest {
415
+ pub channel : Channel ,
416
+ pub crate_type : CrateType ,
417
+ pub edition : Edition ,
418
+ pub code : String ,
419
+ }
420
+
421
+ impl ClippyRequest {
422
+ pub ( crate ) fn delete_previous_main_request ( & self ) -> DeleteFileRequest {
423
+ delete_previous_primary_file_request ( self . crate_type )
424
+ }
425
+
426
+ pub ( crate ) fn write_main_request ( & self ) -> WriteFileRequest {
427
+ write_primary_file_request ( self . crate_type , & self . code )
428
+ }
429
+
430
+ pub ( crate ) fn execute_cargo_request ( & self ) -> ExecuteCommandRequest {
431
+ ExecuteCommandRequest {
432
+ cmd : "cargo" . to_owned ( ) ,
433
+ args : vec ! [ "clippy" . to_owned( ) ] ,
434
+ envs : Default :: default ( ) ,
435
+ cwd : None ,
436
+ }
437
+ }
438
+ }
439
+
440
+ impl CargoTomlModifier for ClippyRequest {
441
+ fn modify_cargo_toml ( & self , mut cargo_toml : toml:: Value ) -> toml:: Value {
442
+ if self . edition == Edition :: Rust2024 {
443
+ cargo_toml = modify_cargo_toml:: set_feature_edition2024 ( cargo_toml) ;
444
+ }
445
+
446
+ cargo_toml = modify_cargo_toml:: set_edition ( cargo_toml, self . edition . to_cargo_toml_key ( ) ) ;
447
+
448
+ if let Some ( crate_type) = self . crate_type . to_library_cargo_toml_key ( ) {
449
+ cargo_toml = modify_cargo_toml:: set_crate_type ( cargo_toml, crate_type) ;
450
+ }
451
+ cargo_toml
452
+ }
453
+ }
454
+
455
+ #[ derive( Debug , Clone ) ]
456
+ pub struct ClippyResponse {
457
+ pub success : bool ,
458
+ pub exit_detail : String ,
459
+ }
460
+
413
461
#[ derive( Debug , Clone ) ]
414
462
pub struct WithOutput < T > {
415
463
pub response : T ,
@@ -574,6 +622,33 @@ where
574
622
. await
575
623
}
576
624
625
+ pub async fn clippy (
626
+ & self ,
627
+ request : ClippyRequest ,
628
+ ) -> Result < WithOutput < ClippyResponse > , ClippyError > {
629
+ use clippy_error:: * ;
630
+
631
+ self . select_channel ( request. channel )
632
+ . await
633
+ . context ( CouldNotStartContainerSnafu ) ?
634
+ . clippy ( request)
635
+ . await
636
+ }
637
+
638
+ pub async fn begin_clippy (
639
+ & self ,
640
+ token : CancellationToken ,
641
+ request : ClippyRequest ,
642
+ ) -> Result < ActiveClippy , ClippyError > {
643
+ use clippy_error:: * ;
644
+
645
+ self . select_channel ( request. channel )
646
+ . await
647
+ . context ( CouldNotStartContainerSnafu ) ?
648
+ . begin_clippy ( token, request)
649
+ . await
650
+ }
651
+
577
652
pub async fn idle ( & mut self ) -> Result < ( ) > {
578
653
let Self {
579
654
stable,
@@ -929,6 +1004,78 @@ impl Container {
929
1004
} )
930
1005
}
931
1006
1007
+ async fn clippy (
1008
+ & self ,
1009
+ request : ClippyRequest ,
1010
+ ) -> Result < WithOutput < ClippyResponse > , ClippyError > {
1011
+ let token = Default :: default ( ) ;
1012
+
1013
+ let ActiveClippy {
1014
+ task,
1015
+ stdout_rx,
1016
+ stderr_rx,
1017
+ } = self . begin_clippy ( token, request) . await ?;
1018
+
1019
+ WithOutput :: try_absorb ( task, stdout_rx, stderr_rx) . await
1020
+ }
1021
+
1022
+ async fn begin_clippy (
1023
+ & self ,
1024
+ token : CancellationToken ,
1025
+ request : ClippyRequest ,
1026
+ ) -> Result < ActiveClippy , ClippyError > {
1027
+ use clippy_error:: * ;
1028
+
1029
+ let delete_previous_main = request. delete_previous_main_request ( ) ;
1030
+ let write_main = request. write_main_request ( ) ;
1031
+ let execute_cargo = request. execute_cargo_request ( ) ;
1032
+
1033
+ let delete_previous_main = self . commander . one ( delete_previous_main) ;
1034
+ let write_main = self . commander . one ( write_main) ;
1035
+ let modify_cargo_toml = self . modify_cargo_toml . modify_for ( & request) ;
1036
+
1037
+ let ( delete_previous_main, write_main, modify_cargo_toml) =
1038
+ join ! ( delete_previous_main, write_main, modify_cargo_toml) ;
1039
+
1040
+ delete_previous_main. context ( CouldNotDeletePreviousCodeSnafu ) ?;
1041
+ write_main. context ( CouldNotWriteCodeSnafu ) ?;
1042
+ modify_cargo_toml. context ( CouldNotModifyCargoTomlSnafu ) ?;
1043
+
1044
+ let SpawnCargo {
1045
+ task,
1046
+ stdin_tx,
1047
+ stdout_rx,
1048
+ stderr_rx,
1049
+ } = self
1050
+ . spawn_cargo_task ( token, execute_cargo)
1051
+ . await
1052
+ . context ( CouldNotStartCargoSnafu ) ?;
1053
+
1054
+ drop ( stdin_tx) ;
1055
+
1056
+ let task = async move {
1057
+ let ExecuteCommandResponse {
1058
+ success,
1059
+ exit_detail,
1060
+ } = task
1061
+ . await
1062
+ . context ( CargoTaskPanickedSnafu ) ?
1063
+ . context ( CargoFailedSnafu ) ?;
1064
+
1065
+ Ok ( ClippyResponse {
1066
+ success,
1067
+ exit_detail,
1068
+ } )
1069
+ }
1070
+ . boxed ( ) ;
1071
+
1072
+ Ok ( ActiveClippy {
1073
+ task,
1074
+ stdout_rx,
1075
+ stderr_rx,
1076
+ } )
1077
+ }
1078
+
932
1079
async fn spawn_cargo_task (
933
1080
& self ,
934
1081
token : CancellationToken ,
@@ -1158,6 +1305,47 @@ pub enum FormatError {
1158
1305
CodeNotUtf8 { source : std:: string:: FromUtf8Error } ,
1159
1306
}
1160
1307
1308
+ pub struct ActiveClippy {
1309
+ pub task : BoxFuture < ' static , Result < ClippyResponse , ClippyError > > ,
1310
+ pub stdout_rx : mpsc:: Receiver < String > ,
1311
+ pub stderr_rx : mpsc:: Receiver < String > ,
1312
+ }
1313
+
1314
+ impl fmt:: Debug for ActiveClippy {
1315
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
1316
+ f. debug_struct ( "ActiveClippy" )
1317
+ . field ( "task" , & "<future>" )
1318
+ . field ( "stdout_rx" , & self . stdout_rx )
1319
+ . field ( "stderr_rx" , & self . stderr_rx )
1320
+ . finish ( )
1321
+ }
1322
+ }
1323
+
1324
+ #[ derive( Debug , Snafu ) ]
1325
+ #[ snafu( module) ]
1326
+ pub enum ClippyError {
1327
+ #[ snafu( display( "Could not start the container" ) ) ]
1328
+ CouldNotStartContainer { source : Error } ,
1329
+
1330
+ #[ snafu( display( "Could not modify Cargo.toml" ) ) ]
1331
+ CouldNotModifyCargoToml { source : ModifyCargoTomlError } ,
1332
+
1333
+ #[ snafu( display( "Could not delete previous source code" ) ) ]
1334
+ CouldNotDeletePreviousCode { source : CommanderError } ,
1335
+
1336
+ #[ snafu( display( "Could not write source code" ) ) ]
1337
+ CouldNotWriteCode { source : CommanderError } ,
1338
+
1339
+ #[ snafu( display( "Could not start Cargo task" ) ) ]
1340
+ CouldNotStartCargo { source : SpawnCargoError } ,
1341
+
1342
+ #[ snafu( display( "The Cargo task panicked" ) ) ]
1343
+ CargoTaskPanicked { source : tokio:: task:: JoinError } ,
1344
+
1345
+ #[ snafu( display( "Cargo task failed" ) ) ]
1346
+ CargoFailed { source : SpawnCargoError } ,
1347
+ }
1348
+
1161
1349
struct SpawnCargo {
1162
1350
task : JoinHandle < Result < ExecuteCommandResponse , SpawnCargoError > > ,
1163
1351
stdin_tx : mpsc:: Sender < String > ,
@@ -2652,6 +2840,76 @@ mod tests {
2652
2840
Ok ( ( ) )
2653
2841
}
2654
2842
2843
+ const ARBITRARY_CLIPPY_REQUEST : ClippyRequest = ClippyRequest {
2844
+ channel : Channel :: Stable ,
2845
+ crate_type : CrateType :: Library ( LibraryType :: Rlib ) ,
2846
+ edition : Edition :: Rust2021 ,
2847
+ code : String :: new ( ) ,
2848
+ } ;
2849
+
2850
+ #[ tokio:: test]
2851
+ #[ snafu:: report]
2852
+ async fn clippy ( ) -> Result < ( ) > {
2853
+ let coordinator = new_coordinator ( ) . await ;
2854
+
2855
+ let req = ClippyRequest {
2856
+ code : r#"
2857
+ fn main() {
2858
+ let a = 0.0 / 0.0;
2859
+ println!("NaN is {}", a);
2860
+ }
2861
+ "#
2862
+ . into ( ) ,
2863
+ ..ARBITRARY_CLIPPY_REQUEST
2864
+ } ;
2865
+
2866
+ let response = coordinator. clippy ( req) . with_timeout ( ) . await . unwrap ( ) ;
2867
+
2868
+ assert ! ( !response. success, "stderr: {}" , response. stderr) ;
2869
+ assert_contains ! ( response. stderr, "deny(clippy::eq_op)" ) ;
2870
+ assert_contains ! ( response. stderr, "warn(clippy::zero_divided_by_zero)" ) ;
2871
+
2872
+ Ok ( ( ) )
2873
+ }
2874
+
2875
+ #[ tokio:: test]
2876
+ #[ snafu:: report]
2877
+ async fn clippy_edition ( ) -> Result < ( ) > {
2878
+ let cases = [ (
2879
+ "#![deny(clippy::single_element_loop)]
2880
+ fn a() { for i in [1] { dbg!(i); } }" ,
2881
+ [ true , true , false , false ] ,
2882
+ ) ] ;
2883
+
2884
+ let tests = cases. into_iter ( ) . flat_map ( |( code, expected_to_be_clean) | {
2885
+ Edition :: ALL . into_iter ( ) . zip ( expected_to_be_clean) . map (
2886
+ move |( edition, expected_to_be_clean) | async move {
2887
+ let coordinator = new_coordinator ( ) . await ;
2888
+
2889
+ let req = ClippyRequest {
2890
+ edition,
2891
+ code : code. into ( ) ,
2892
+ ..ARBITRARY_CLIPPY_REQUEST
2893
+ } ;
2894
+
2895
+ let response = coordinator. clippy ( req) . with_timeout ( ) . await . unwrap ( ) ;
2896
+
2897
+ assert_eq ! (
2898
+ response. success, expected_to_be_clean,
2899
+ "{code:?} in {edition:?}, {}" ,
2900
+ response. stderr
2901
+ ) ;
2902
+
2903
+ Ok ( ( ) )
2904
+ } ,
2905
+ )
2906
+ } ) ;
2907
+
2908
+ try_join_all ( tests) . with_timeout ( ) . await ?;
2909
+
2910
+ Ok ( ( ) )
2911
+ }
2912
+
2655
2913
// The next set of tests are broader than the functionality of a
2656
2914
// single operation.
2657
2915
0 commit comments