@@ -70,6 +70,9 @@ pub enum Channel {
70
70
}
71
71
72
72
impl Channel {
73
+ #[ cfg( test) ]
74
+ pub ( crate ) const ALL : [ Self ; 3 ] = [ Self :: Stable , Self :: Beta , Self :: Nightly ] ;
75
+
73
76
#[ cfg( test) ]
74
77
pub ( crate ) fn to_str ( self ) -> & ' static str {
75
78
match self {
@@ -358,6 +361,55 @@ pub struct CompileResponse {
358
361
pub code : String ,
359
362
}
360
363
364
+ #[ derive( Debug , Clone ) ]
365
+ pub struct FormatRequest {
366
+ pub channel : Channel ,
367
+ pub crate_type : CrateType ,
368
+ pub edition : Edition ,
369
+ pub code : String ,
370
+ }
371
+
372
+ impl FormatRequest {
373
+ pub ( crate ) fn delete_previous_main_request ( & self ) -> DeleteFileRequest {
374
+ delete_previous_primary_file_request ( self . crate_type )
375
+ }
376
+
377
+ pub ( crate ) fn write_main_request ( & self ) -> WriteFileRequest {
378
+ write_primary_file_request ( self . crate_type , & self . code )
379
+ }
380
+
381
+ pub ( crate ) fn execute_cargo_request ( & self ) -> ExecuteCommandRequest {
382
+ ExecuteCommandRequest {
383
+ cmd : "cargo" . to_owned ( ) ,
384
+ args : vec ! [ "fmt" . to_owned( ) ] ,
385
+ envs : Default :: default ( ) ,
386
+ cwd : None ,
387
+ }
388
+ }
389
+ }
390
+
391
+ impl CargoTomlModifier for FormatRequest {
392
+ fn modify_cargo_toml ( & self , mut cargo_toml : toml:: Value ) -> toml:: Value {
393
+ if self . edition == Edition :: Rust2024 {
394
+ cargo_toml = modify_cargo_toml:: set_feature_edition2024 ( cargo_toml) ;
395
+ }
396
+
397
+ cargo_toml = modify_cargo_toml:: set_edition ( cargo_toml, self . edition . to_cargo_toml_key ( ) ) ;
398
+
399
+ if let Some ( crate_type) = self . crate_type . to_library_cargo_toml_key ( ) {
400
+ cargo_toml = modify_cargo_toml:: set_crate_type ( cargo_toml, crate_type) ;
401
+ }
402
+ cargo_toml
403
+ }
404
+ }
405
+
406
+ #[ derive( Debug , Clone ) ]
407
+ pub struct FormatResponse {
408
+ pub success : bool ,
409
+ pub exit_detail : String ,
410
+ pub code : String ,
411
+ }
412
+
361
413
#[ derive( Debug , Clone ) ]
362
414
pub struct WithOutput < T > {
363
415
pub response : T ,
@@ -495,6 +547,33 @@ where
495
547
. await
496
548
}
497
549
550
+ pub async fn format (
551
+ & self ,
552
+ request : FormatRequest ,
553
+ ) -> Result < WithOutput < FormatResponse > , FormatError > {
554
+ use format_error:: * ;
555
+
556
+ self . select_channel ( request. channel )
557
+ . await
558
+ . context ( CouldNotStartContainerSnafu ) ?
559
+ . format ( request)
560
+ . await
561
+ }
562
+
563
+ pub async fn begin_format (
564
+ & self ,
565
+ token : CancellationToken ,
566
+ request : FormatRequest ,
567
+ ) -> Result < ActiveFormatting , FormatError > {
568
+ use format_error:: * ;
569
+
570
+ self . select_channel ( request. channel )
571
+ . await
572
+ . context ( CouldNotStartContainerSnafu ) ?
573
+ . begin_format ( token, request)
574
+ . await
575
+ }
576
+
498
577
pub async fn idle ( & mut self ) -> Result < ( ) > {
499
578
let Self {
500
579
stable,
@@ -767,6 +846,89 @@ impl Container {
767
846
} )
768
847
}
769
848
849
+ async fn format (
850
+ & self ,
851
+ request : FormatRequest ,
852
+ ) -> Result < WithOutput < FormatResponse > , FormatError > {
853
+ let token = Default :: default ( ) ;
854
+
855
+ let ActiveFormatting {
856
+ task,
857
+ stdout_rx,
858
+ stderr_rx,
859
+ } = self . begin_format ( token, request) . await ?;
860
+
861
+ WithOutput :: try_absorb ( task, stdout_rx, stderr_rx) . await
862
+ }
863
+
864
+ async fn begin_format (
865
+ & self ,
866
+ token : CancellationToken ,
867
+ request : FormatRequest ,
868
+ ) -> Result < ActiveFormatting , FormatError > {
869
+ use format_error:: * ;
870
+
871
+ let delete_previous_main = request. delete_previous_main_request ( ) ;
872
+ let write_main = request. write_main_request ( ) ;
873
+ let execute_cargo = request. execute_cargo_request ( ) ;
874
+ let read_output = ReadFileRequest {
875
+ path : request. crate_type . primary_path ( ) . to_owned ( ) ,
876
+ } ;
877
+
878
+ let delete_previous_main = self . commander . one ( delete_previous_main) ;
879
+ let write_main = self . commander . one ( write_main) ;
880
+ let modify_cargo_toml = self . modify_cargo_toml . modify_for ( & request) ;
881
+
882
+ let ( delete_previous_main, write_main, modify_cargo_toml) =
883
+ join ! ( delete_previous_main, write_main, modify_cargo_toml) ;
884
+
885
+ delete_previous_main. context ( CouldNotDeletePreviousCodeSnafu ) ?;
886
+ write_main. context ( CouldNotWriteCodeSnafu ) ?;
887
+ modify_cargo_toml. context ( CouldNotModifyCargoTomlSnafu ) ?;
888
+
889
+ let SpawnCargo {
890
+ task,
891
+ stdin_tx,
892
+ stdout_rx,
893
+ stderr_rx,
894
+ } = self
895
+ . spawn_cargo_task ( token, execute_cargo)
896
+ . await
897
+ . context ( CouldNotStartCargoSnafu ) ?;
898
+
899
+ drop ( stdin_tx) ;
900
+
901
+ let commander = self . commander . clone ( ) ;
902
+ let task = async move {
903
+ let ExecuteCommandResponse {
904
+ success,
905
+ exit_detail,
906
+ } = task
907
+ . await
908
+ . context ( CargoTaskPanickedSnafu ) ?
909
+ . context ( CargoFailedSnafu ) ?;
910
+
911
+ let file = commander
912
+ . one ( read_output)
913
+ . await
914
+ . context ( CouldNotReadCodeSnafu ) ?;
915
+ let code = String :: from_utf8 ( file. 0 ) . context ( CodeNotUtf8Snafu ) ?;
916
+
917
+ Ok ( FormatResponse {
918
+ success,
919
+ exit_detail,
920
+ code,
921
+ } )
922
+ }
923
+ . boxed ( ) ;
924
+
925
+ Ok ( ActiveFormatting {
926
+ task,
927
+ stdout_rx,
928
+ stderr_rx,
929
+ } )
930
+ }
931
+
770
932
async fn spawn_cargo_task (
771
933
& self ,
772
934
token : CancellationToken ,
@@ -949,6 +1111,53 @@ pub enum CompileError {
949
1111
CodeNotUtf8 { source : std:: string:: FromUtf8Error } ,
950
1112
}
951
1113
1114
+ pub struct ActiveFormatting {
1115
+ pub task : BoxFuture < ' static , Result < FormatResponse , FormatError > > ,
1116
+ pub stdout_rx : mpsc:: Receiver < String > ,
1117
+ pub stderr_rx : mpsc:: Receiver < String > ,
1118
+ }
1119
+
1120
+ impl fmt:: Debug for ActiveFormatting {
1121
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
1122
+ f. debug_struct ( "ActiveFormatting" )
1123
+ . field ( "task" , & "<future>" )
1124
+ . field ( "stdout_rx" , & self . stdout_rx )
1125
+ . field ( "stderr_rx" , & self . stderr_rx )
1126
+ . finish ( )
1127
+ }
1128
+ }
1129
+
1130
+ #[ derive( Debug , Snafu ) ]
1131
+ #[ snafu( module) ]
1132
+ pub enum FormatError {
1133
+ #[ snafu( display( "Could not start the container" ) ) ]
1134
+ CouldNotStartContainer { source : Error } ,
1135
+
1136
+ #[ snafu( display( "Could not modify Cargo.toml" ) ) ]
1137
+ CouldNotModifyCargoToml { source : ModifyCargoTomlError } ,
1138
+
1139
+ #[ snafu( display( "Could not delete previous source code" ) ) ]
1140
+ CouldNotDeletePreviousCode { source : CommanderError } ,
1141
+
1142
+ #[ snafu( display( "Could not write source code" ) ) ]
1143
+ CouldNotWriteCode { source : CommanderError } ,
1144
+
1145
+ #[ snafu( display( "Could not start Cargo task" ) ) ]
1146
+ CouldNotStartCargo { source : SpawnCargoError } ,
1147
+
1148
+ #[ snafu( display( "The Cargo task panicked" ) ) ]
1149
+ CargoTaskPanicked { source : tokio:: task:: JoinError } ,
1150
+
1151
+ #[ snafu( display( "Cargo task failed" ) ) ]
1152
+ CargoFailed { source : SpawnCargoError } ,
1153
+
1154
+ #[ snafu( display( "Could not read the compilation output" ) ) ]
1155
+ CouldNotReadCode { source : CommanderError } ,
1156
+
1157
+ #[ snafu( display( "The compilation output was not UTF-8" ) ) ]
1158
+ CodeNotUtf8 { source : std:: string:: FromUtf8Error } ,
1159
+ }
1160
+
952
1161
struct SpawnCargo {
953
1162
task : JoinHandle < Result < ExecuteCommandResponse , SpawnCargoError > > ,
954
1163
stdin_tx : mpsc:: Sender < String > ,
@@ -2282,6 +2491,93 @@ mod tests {
2282
2491
Ok ( ( ) )
2283
2492
}
2284
2493
2494
+ const ARBITRARY_FORMAT_REQUEST : FormatRequest = FormatRequest {
2495
+ channel : Channel :: Stable ,
2496
+ crate_type : CrateType :: Binary ,
2497
+ edition : Edition :: Rust2015 ,
2498
+ code : String :: new ( ) ,
2499
+ } ;
2500
+
2501
+ const ARBITRARY_FORMAT_INPUT : & str = "fn main(){1+1;}" ;
2502
+ #[ rustfmt:: skip]
2503
+ const ARBITRARY_FORMAT_OUTPUT : & [ & str ] = & [
2504
+ "fn main() {" ,
2505
+ " 1 + 1;" ,
2506
+ "}"
2507
+ ] ;
2508
+
2509
+ #[ tokio:: test]
2510
+ #[ snafu:: report]
2511
+ async fn format ( ) -> Result < ( ) > {
2512
+ let coordinator = new_coordinator ( ) . await ;
2513
+
2514
+ let req = FormatRequest {
2515
+ code : ARBITRARY_FORMAT_INPUT . into ( ) ,
2516
+ ..ARBITRARY_FORMAT_REQUEST
2517
+ } ;
2518
+
2519
+ let response = coordinator. format ( req) . with_timeout ( ) . await . unwrap ( ) ;
2520
+
2521
+ assert ! ( response. success, "stderr: {}" , response. stderr) ;
2522
+ let lines = response. code . lines ( ) . collect :: < Vec < _ > > ( ) ;
2523
+ assert_eq ! ( ARBITRARY_FORMAT_OUTPUT , lines) ;
2524
+
2525
+ Ok ( ( ) )
2526
+ }
2527
+
2528
+ #[ tokio:: test]
2529
+ #[ snafu:: report]
2530
+ async fn format_channel ( ) -> Result < ( ) > {
2531
+ for channel in Channel :: ALL {
2532
+ let coordinator = new_coordinator ( ) . await ;
2533
+
2534
+ let req = FormatRequest {
2535
+ channel,
2536
+ code : ARBITRARY_FORMAT_INPUT . into ( ) ,
2537
+ ..ARBITRARY_FORMAT_REQUEST
2538
+ } ;
2539
+
2540
+ let response = coordinator. format ( req) . with_timeout ( ) . await . unwrap ( ) ;
2541
+
2542
+ assert ! ( response. success, "stderr: {}" , response. stderr) ;
2543
+ let lines = response. code . lines ( ) . collect :: < Vec < _ > > ( ) ;
2544
+ assert_eq ! ( ARBITRARY_FORMAT_OUTPUT , lines) ;
2545
+ }
2546
+
2547
+ Ok ( ( ) )
2548
+ }
2549
+
2550
+ #[ tokio:: test]
2551
+ #[ snafu:: report]
2552
+ async fn format_edition ( ) -> Result < ( ) > {
2553
+ let cases = [
2554
+ ( "fn main() { async { 1 } }" , [ false , true , true , true ] ) ,
2555
+ ( "fn main() { gen { 1 } }" , [ false , false , false , true ] ) ,
2556
+ ] ;
2557
+
2558
+ for ( code, works_in) in cases {
2559
+ let coordinator = new_coordinator ( ) . await ;
2560
+
2561
+ for ( edition, works) in Edition :: ALL . into_iter ( ) . zip ( works_in) {
2562
+ let req = FormatRequest {
2563
+ edition,
2564
+ code : code. into ( ) ,
2565
+ channel : Channel :: Nightly , // To allow 2024 while it is unstable
2566
+ ..ARBITRARY_FORMAT_REQUEST
2567
+ } ;
2568
+
2569
+ let response = coordinator. format ( req) . with_timeout ( ) . await . unwrap ( ) ;
2570
+
2571
+ assert_eq ! ( response. success, works, "{code} in {edition:?}" ) ;
2572
+ }
2573
+ }
2574
+
2575
+ Ok ( ( ) )
2576
+ }
2577
+
2578
+ // The next set of tests are broader than the functionality of a
2579
+ // single operation.
2580
+
2285
2581
#[ tokio:: test]
2286
2582
#[ snafu:: report]
2287
2583
async fn compile_clears_old_main_rs ( ) -> Result < ( ) > {
0 commit comments