sway_fmt/
fmt.rs

1use super::code_builder::CodeBuilder;
2use crate::traversal::{traverse_for_changes, Change};
3use ropey::Rope;
4use std::sync::Arc;
5use sway_core::BuildConfig;
6
7/// Returns number of lines and formatted text.
8/// Formatting is done as a 2-step process.
9/// Firstly, certain Sway types (like Enums and Structs) are formatted separately in isolation,
10/// depending on their type.
11/// Secondly, after that, the whole file is formatted/cleaned and checked
12/// for smaller things like extra newlines, indentation and similar.
13pub fn get_formatted_data(
14    file: Arc<str>,
15    formatting_options: FormattingOptions,
16    build_config: Option<&BuildConfig>,
17) -> Result<(usize, String), Vec<String>> {
18    let parsed_res = sway_core::parse(file.clone(), build_config);
19
20    match parsed_res.value {
21        Some(parse_program) => {
22            // 1 Step: get all individual changes/updates of a Sway file
23            let changes = traverse_for_changes(&parse_program.root.tree);
24            let mut rope_file = Rope::from_str(&file);
25
26            let mut offset: i32 = 0;
27            for change in changes {
28                // for each update, calculate their newly position
29                // and add it in the existing file
30                let (new_offset, start, end) = calculate_offset(offset, &change);
31                offset = new_offset;
32
33                rope_file.remove(start..end);
34                rope_file.insert(start, &change.text);
35            }
36
37            // 2 Step: CodeBuilder goes through each line of a Sway file and cleans it up
38            let mut code_builder = CodeBuilder::new(formatting_options.tab_size);
39
40            let file = rope_file.to_string();
41            let lines: Vec<&str> = file.split('\n').collect();
42
43            // todo: handle lengthy lines of code
44            for line in lines {
45                code_builder.format_and_add(line);
46            }
47
48            Ok(code_builder.get_final_edits())
49        }
50        None => Err(parsed_res
51            .errors
52            .iter()
53            .map(|e| format!("{} at line: {}", e, e.line_col().0.line,))
54            .collect()),
55    }
56}
57
58fn calculate_offset(current_offset: i32, change: &Change) -> (i32, usize, usize) {
59    let start = change.start as i32 + current_offset;
60    let end = change.end as i32 + current_offset;
61    let offset = current_offset + (start + change.text.len() as i32) - end;
62
63    (offset, start as usize, end as usize)
64}
65
66#[derive(Debug, Clone, Copy)]
67pub struct FormattingOptions {
68    pub align_fields: bool,
69    pub tab_size: u32,
70}
71
72impl FormattingOptions {
73    pub fn default() -> Self {
74        Self {
75            align_fields: true,
76            tab_size: 4,
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::FormattingOptions;
84
85    use super::get_formatted_data;
86    const OPTIONS: FormattingOptions = FormattingOptions {
87        align_fields: false,
88        tab_size: 4,
89    };
90
91    #[test]
92    fn test_indentation() {
93        let correct_sway_code = r#"script;
94
95fn main() {
96    // this is a comment
97    let o = 123;
98
99    let p = {
100        /* this is some
101            multi line stuff t
102        
103        */
104        123;
105    };
106
107    add(1, 2);
108}
109
110pub fn add(a: u32, b: u32) -> u32 {
111    a + b
112}
113"#;
114        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
115        assert!(result.is_ok());
116        let (_, formatted_code) = result.unwrap();
117        assert_eq!(correct_sway_code, formatted_code);
118
119        let sway_code = r#"script;
120
121fn main() {
122    // this is a comment
123    let o = 123;
124
125                let 
126p   
127    
128    
129            =
130    
131    
132        {
133        /* this is some
134            multi line stuff t
135        
136        */
137        123       
138        
139        
140                        ;
141    
142    
143    };
144
145    add(        1,    
146    
147                                                        2 
148    
149    
150            )     ;
151}
152
153pub
154fn 
155add
156    (
157    a:u32   , 
158            b: u32)             ->u32{
159    a +b}
160
161"#;
162
163        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
164        assert!(result.is_ok());
165        let (_, formatted_code) = result.unwrap();
166        assert_eq!(correct_sway_code, formatted_code);
167    }
168
169    #[test]
170    fn test_multiline_string() {
171        let correct_sway_code = r#"script;
172
173fn main() {
174    let multiline_string = "       sadsa
175    sadsad
176        sadasd sadsdsa
177    sadasd
178        sadasd sadasd
179    ";
180
181    storage {
182        foo: u64 = 0,
183        bar: i32 = 0,
184    }
185    storage.foo;
186}
187"#;
188
189        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
190        assert!(result.is_ok());
191        let (_, formatted_code) = result.unwrap();
192        assert_eq!(correct_sway_code, formatted_code);
193
194        let sway_code = r#"script;
195
196fn main(){
197    let multiline_string="       sadsa
198    sadsad
199        sadasd sadsdsa
200    sadasd
201        sadasd sadasd
202    "          
203               ;
204
205storage {
206            foo: u64 = 0, bar:i32 = 0,  
207            }
208            storage.foo ;    
209}
210"#;
211
212        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
213        assert!(result.is_ok());
214        let (_, formatted_code) = result.unwrap();
215        assert_eq!(correct_sway_code, formatted_code);
216    }
217
218    #[test]
219    fn test_whitespace_handling() {
220        let correct_sway_code = r#"script;
221
222fn main() {
223    let word = "word";
224    let num = 12;
225
226    let multi = {
227        let k = 12;
228        k
229    };
230}
231"#;
232
233        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
234        assert!(result.is_ok());
235        let (_, formatted_code) = result.unwrap();
236        assert_eq!(correct_sway_code, formatted_code);
237
238        let sway_code = r#"script;
239
240fn main() {
241    let word="word";
242    let num=               12           ;
243
244    let multi = {
245        let k         = 12;
246                    k
247    }
248    
249    
250                ;
251}
252"#;
253
254        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
255        assert!(result.is_ok());
256        let (_, formatted_code) = result.unwrap();
257        assert_eq!(correct_sway_code, formatted_code);
258    }
259
260    #[test]
261    fn test_comments() {
262        let correct_sway_code = r#"script;
263
264fn main() {
265    // this is a comment
266    let o = 123; // this is an inline comment
267    /*
268        asdasd
269    asdasdsad asdasdasd */
270
271    /* multiline closed on the same line */
272    let p = {
273        /* this is some
274            multi line stuff t
275        
276         */
277        123;
278    }; // comment here as well
279} // comment here too
280
281// example struct with comments
282struct Example { // first comment
283    prop: bool, // second comment
284    age: u32, // another comment
285} // comment as well
286"#;
287
288        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
289        assert!(result.is_ok());
290        let (_, formatted_code) = result.unwrap();
291        assert_eq!(correct_sway_code, formatted_code);
292
293        let sway_code = r#"script;
294
295fn main() {
296    // this is a comment
297    let o = 123;            // this is an inline comment
298     /*
299        asdasd
300    asdasdsad asdasdasd */
301
302         /* multiline closed on the same line */
303    let p = {
304        /* this is some
305            multi line stuff t
306        
307         */
308        123;
309    };     // comment here as well
310} // comment here too
311
312 // example struct with comments
313struct Example {    // first comment
314    prop: bool,// second comment
315    age: u32// another comment
316}   // comment as well
317"#;
318
319        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
320        assert!(result.is_ok());
321        let (_, formatted_code) = result.unwrap();
322        assert_eq!(correct_sway_code, formatted_code);
323    }
324
325    #[test]
326    fn test_data_types() {
327        let correct_sway_code = r#"script;
328
329fn main() {
330    let rgb: Rgb = Rgb {
331        red: 255,
332        blue: 0,
333        green: 0,
334    };
335
336    if (true) {
337        let rgb: Rgb = Rgb {
338            red: 255,
339            blue: 0,
340            green: 0,
341        };
342    }
343}
344
345struct Rgb {
346    red: u64,
347    green: u64,
348    blue: u64,
349}
350
351struct Structure {
352    age: u32,
353
354    name: string,
355}
356
357struct Structure {
358    age: u32, /* completely meaningless multiline comment
359        not sure why would anyone write this but let's deal with it as well!
360    */
361    name: string,
362}
363
364struct Structure {
365    age: u32,
366    name: string, // super comment
367}
368
369struct Structure {
370    age: u32,
371    name: string, // super comment
372}
373
374struct Vehicle {
375    age: u32,
376    name: string, // some comment middle of nowhere
377}
378
379struct Environment {
380    age: u32,
381    name: string,
382} // lost my train of thought
383
384struct Person { // first comment
385    age: u32, // second comment
386    name: string, // third comment
387} // fourth comment
388
389pub fn get_age() -> u32 {
390    99
391}
392
393pub fn read_example() -> Example {
394    Example {
395        age: get_age(),
396        name: "Example face",
397    }
398}
399
400struct Example {
401    age: u32,
402    name: string,
403}
404
405struct C {
406    /// a docstring
407    a: A,
408    /// b docstring
409    b: byte,
410}
411
412struct A {
413    a: u64,
414    b: u64,
415}
416
417fn get_gas() -> A {
418    A {
419        a: asm() {
420            ggas
421        },
422        b: asm() {
423            cgas
424        },
425    }
426}
427"#;
428
429        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
430        assert!(result.is_ok());
431        let (_, formatted_code) = result.unwrap();
432        assert_eq!(correct_sway_code, formatted_code);
433
434        let sway_code = r#"script;
435
436fn main() {
437    let rgb:Rgb=Rgb {
438        red: 255,
439        blue: 0,
440        green: 0,
441    };
442
443    if(true){
444        let rgb: Rgb = Rgb {
445            red:255,      blue: 0,
446                green: 0,
447        };
448    }
449}
450
451struct Rgb {
452    red: u64,
453    green: u64,
454    blue: u64,
455}
456
457struct Structure {
458
459    age: u32,
460
461    name: string,
462
463}
464
465struct Structure {
466    age: u32, /* completely meaningless multiline comment
467        not sure why would anyone write this but let's deal with it as well!
468    */
469    name: string
470}
471
472struct Structure {
473    age: u32,
474    name: string// super comment
475}
476
477struct Structure {
478    age: u32,
479    name: string, // super comment
480}
481
482struct Vehicle 
483          { age:       u32,          name: string , // some comment middle of nowhere
484}
485
486struct Environment{age:u32,name:string} // lost my train of thought
487
488struct Person {// first comment
489    age: u32,// second comment
490    name: string,          // third comment
491} // fourth comment
492
493pub fn get_age() -> u32 {
494     99
495}
496
497pub fn read_example() -> Example {
498    Example {
499        age: get_age()     ,name: "Example face"
500    }
501}
502
503struct Example {age: u32,    name: string}
504
505struct C {
506/// a docstring
507a: A,
508/// b docstring
509b: byte,
510}
511
512struct A {
513a: u64,
514b: u64,
515}
516
517fn get_gas() -> A {
518A {
519a: asm() {
520ggas
521},
522b: asm() {
523cgas
524}
525}
526}
527"#;
528
529        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
530        assert!(result.is_ok());
531        let (_, formatted_code) = result.unwrap();
532        assert_eq!(correct_sway_code, formatted_code);
533    }
534
535    #[test]
536    fn test_enums() {
537        let correct_sway_code = r#"script;
538
539pub fn main() {
540    let k = Story::Pain;
541}
542
543enum Story {
544    Pain: (),
545    Gain: (),
546}
547
548enum StoryA {
549    Pain: (),
550    Gain: (),
551}
552
553pub fn tell_a_story() -> Story {
554    Story::Gain
555}
556"#;
557
558        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
559        assert!(result.is_ok());
560        let (_, formatted_code) = result.unwrap();
561        assert_eq!(correct_sway_code, formatted_code);
562
563        let sway_code = r#"script;
564
565        pub fn main() {
566            let k = 
567                    Story          :: Pain;
568        
569        
570        
571        }
572        
573        
574        enum Story {
575           Pain:(),
576            Gain:()
577        }
578        
579        enum StoryA {Pain:(),Gain:()}
580        
581        pub fn tell_a_story() ->Story {
582                Story   :: Gain
583        }
584"#;
585
586        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
587        assert!(result.is_ok());
588        let (_, formatted_code) = result.unwrap();
589        assert_eq!(correct_sway_code, formatted_code);
590    }
591
592    #[test]
593    fn test_comparison_operators() {
594        let correct_sway_code = r#"script;
595
596fn main() {
597    if 1 >= 0 {
598    } else if 4 <= 0 {
599    } else if 5 == 0 {
600    } else if 4 != 4 {
601    } else {
602    }
603}
604
605fn one_liner() -> bool {
606    if 1 >= 0 {
607        true
608    } else if 1 <= 0 {
609        true
610    } else {
611        true
612    }
613}
614"#;
615
616        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
617        assert!(result.is_ok());
618        let (_, formatted_code) = result.unwrap();
619        assert_eq!(correct_sway_code, formatted_code);
620
621        let sway_code = r#"script;
622
623        fn main() {
624            if 1 >= 0 {
625        
626            }       else if 4 <= 0 
627            
628            {
629        
630            } 
631        else if 5 == 0 { } else if 4 != 4 
632        
633        {
634        
635            } 
636                    else {
637        
638            }    
639        }        
640
641        fn one_liner() -> bool {
642            if 1 >= 0 { true } else if 1 <= 0 { true } else { true }
643        }
644"#;
645
646        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
647        assert!(result.is_ok());
648        let (_, formatted_code) = result.unwrap();
649        assert_eq!(correct_sway_code, formatted_code);
650    }
651
652    #[test]
653    // Test that the use statements with multiple imports are properly formatted
654    fn test_use_statement() {
655        let test_sway = r#"script;
656use std::chain::{panic,log_u8};
657use std::chain::assert;
658use std::hash::{sha256,               keccak256    };
659use a::b::{c,d::{f,e}};
660use a::b::{c,d::{f,self}};
661
662fn main() {
663}
664"#;
665        let expected_sway = r#"script;
666use std::chain::{log_u8, panic};
667use std::chain::assert;
668use std::hash::{keccak256, sha256};
669use a::b::{c, d::{e, f}};
670use a::b::{c, d::{self, f}};
671
672fn main() {
673}
674"#;
675        let result = get_formatted_data(test_sway.into(), OPTIONS, None);
676        assert!(result.is_ok());
677        let (_, formatted_code) = result.unwrap();
678        println!("{}\n{}", formatted_code, expected_sway);
679        assert_eq!(formatted_code, expected_sway);
680    }
681
682    #[test]
683    fn test_logical_not_operator() {
684        let correct_sway_code = r#"script;
685fn main() {
686    let a = true;
687    let b = false;
688
689    if (!a) {
690    } else if !(!a) {
691    } else if !(a == b) {
692    }
693}
694"#;
695
696        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
697        assert!(result.is_ok());
698        let (_, formatted_code) = result.unwrap();
699        assert_eq!(correct_sway_code, formatted_code);
700
701        let sway_code = r#"script;
702fn main() {
703    let a = true;
704    let b = false;
705
706    if ( ! a ) {
707    } else if ! (   !  
708        a ) {
709    } else if !   ( a == b) {}
710}
711"#;
712
713        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
714        assert!(result.is_ok());
715        let (_, formatted_code) = result.unwrap();
716        assert_eq!(correct_sway_code, formatted_code);
717    }
718
719    #[test]
720    fn test_string_in_brackets() {
721        let correct_sway_code = r#"script;
722fn main() {
723    let sha_hashed_str = sha256("Fastest Modular Execution Layer!");
724}
725"#;
726
727        let result = get_formatted_data(correct_sway_code.into(), OPTIONS, None);
728        assert!(result.is_ok());
729        let (_, formatted_code) = result.unwrap();
730        assert_eq!(correct_sway_code, formatted_code);
731
732        let sway_code = r#"script;
733fn main() {
734    let sha_hashed_str = sha256(  "Fastest Modular Execution Layer!"  );
735}
736"#;
737
738        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
739        assert!(result.is_ok());
740        let (_, formatted_code) = result.unwrap();
741        assert_eq!(correct_sway_code, formatted_code);
742    }
743
744    #[test]
745    fn test_op_equal() {
746        let correct_sway_code = r#"script;
747fn main() {
748    let mut a = 1;
749    a += 1;
750    a -= 1;
751    a /= 1;
752    a *= 1;
753    a <<= 1;
754    a >>= 1;
755}
756"#;
757        let sway_code = r#"script;
758fn main() {
759    let mut a = 1;
760    a += 1;
761    a -= 1;
762    a /= 1;
763    a *= 1;
764    a <<= 1;
765    a >>= 1;
766}
767"#;
768        let result = get_formatted_data(sway_code.into(), OPTIONS, None);
769        assert!(result.is_ok());
770        let (_, formatted_code) = result.unwrap();
771        assert_eq!(correct_sway_code, formatted_code);
772    }
773}