// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #![allow(clippy::unicode_not_nfc)] use expect_test::{Expect, expect}; use indoc::indoc; fn check(input: &str, expect: &Expect) { let actual = super::format_str(input); expect.assert_eq(&actual); } fn check_edits(input: &str, expect: &Expect) { let actual = super::calculate_format_edits(input); expect.assert_debug_eq(&actual); } // Removing trailing whitespace from lines #[test] fn remove_trailing_spaces() { let extra_spaces = " "; let input = format!( "/// Doc Comment with trailing spaces{extra_spaces} operation Foo() : Unit {{ // Comment with trailing spaces{extra_spaces} let x = 3; // In-line comment with trailing spaces{extra_spaces} let y = 4;{extra_spaces} }} " ); check( input.as_str(), &expect![[r#" /// Doc Comment with trailing spaces operation Foo() : Unit { // Comment with trailing spaces let x = 3; // In-line comment with trailing spaces let y = 4; } "#]], ); } #[test] fn preserve_string_trailing_spaces() { let extra_spaces = " "; let input = format!( "\"Hello{extra_spaces} World\"" ); assert!(super::calculate_format_edits(input.as_str()).is_empty()); } // Namespace items begin on their own lines #[test] fn namespace_items_begin_on_their_own_lines() { check( "operation Foo() : Unit {} function Bar() : Unit {}", &expect![[r#" operation Foo() : Unit {} function Bar() : Unit {}"#]], ); } // Functor specializations begin on their own lines #[test] fn functor_specs_begin_on_their_own_lines() { check( "operation Foo() : Unit { body ... {} adjoint ... {} controlled (c, ...) {} controlled adjoint (c, ...) {} }", &expect![[r#" operation Foo() : Unit { body ... {} adjoint ... {} controlled (c, ...) {} controlled adjoint (c, ...) {} }"#]], ); } #[test] fn single_space_between_adjoint_controlled_func_spec_keywords() { check( indoc! {" operation Foo() : Unit { body ... {} adjoint ... {} controlled (c, ...) {} controlled adjoint (c, ...) {} } operation Bar() : Unit { body ... {} adjoint ... {} controlled (c, ...) {} adjoint controlled (c, ...) {} }"}, &expect![[r#" operation Foo() : Unit { body ... {} adjoint ... {} controlled (c, ...) {} controlled adjoint (c, ...) {} } operation Bar() : Unit { body ... {} adjoint ... {} controlled (c, ...) {} adjoint controlled (c, ...) {} }"#]], ); } // Single spaces before generator keywords #[test] fn single_spaces_before_generator_keywords() { check( indoc! {" operation Foo() : Unit { body ... intrinsic adjoint ... invert controlled (c, ...) distribute controlled adjoint (c, ...) auto adjoint ... self }"}, &expect![[r#" operation Foo() : Unit { body ... intrinsic adjoint ... invert controlled (c, ...) distribute controlled adjoint (c, ...) auto adjoint ... self }"#]], ); } // Single spaces around most binary operators #[test] fn singe_space_around_arithmetic_bin_ops() { // Note that `-` is missing at this time due to it being unsupported for formatting. check( indoc! {" 1+2; 1 * 2; 4 /2; 3% 2; "}, &expect![[r#" 1 + 2; 1 * 2; 4 / 2; 3 % 2; "#]], ); } #[test] fn singe_space_around_bit_wise_bin_ops() { check( indoc! {" 1&&&2; 1 ||| 2; 4 ^^^2; 3<<< 2; 2 >>> 3; "}, &expect![[r#" 1 &&& 2; 1 ||| 2; 4 ^^^ 2; 3 <<< 2; 2 >>> 3; "#]], ); } #[test] fn singe_space_around_boolean_bin_ops() { check( indoc! {" true and false; true or false; "}, &expect![[r#" true and false; true or false; "#]], ); } #[test] fn singe_space_around_bin_op_equals() { check( indoc! {" let x += y; let x -=y; let x*= y; let x /= y; let x %= y; "}, &expect![[r#" let x += y; let x -= y; let x *= y; let x /= y; let x %= y; "#]], ); } #[test] fn singe_space_around_equals() { check("let x = 3;", &expect!["let x = 3;"]); } #[test] fn singe_space_around_colon() { check("let x : Int = 3;", &expect!["let x : Int = 3;"]); } #[test] fn singe_space_around_comp_ops() { // Note that `<` and `>` are missing at this time due to them being unsupported for formatting. check( indoc! {" x <=y; x >= y; x == y; x != y; "}, &expect![[r#" x <= y; x >= y; x == y; x != y; "#]], ); } #[test] fn singe_space_around_ternary() { check("x? 3| 4", &expect!["x ? 3 | 4"]); } #[test] fn singe_space_around_copy() { check("x w/3 <- 4", &expect!["x w/ 3 <- 4"]); } #[test] fn singe_space_around_copy_and_update() { check("x w/=3 <- 4", &expect!["x w/= 3 <- 4"]); } #[test] fn singe_space_around_lambda_ops() { check( indoc! {" let x = () -> (); let y = ()=>(); "}, &expect![[r#" let x = () -> (); let y = () => (); "#]], ); } #[test] fn singe_space_around_characteristic_expr() { check( "operation Foo() : Unit is Adj+Ctl {}", &expect!["operation Foo() : Unit is Adj + Ctl {}"], ); } #[test] fn singe_space_around_functors() { check( "Controlled Adjoint Foo()", &expect!["Controlled Adjoint Foo()"], ); } #[test] fn singe_space_around_as() { check( "open thing as other;", &expect!["open thing as other;"], ); } // No space between unary operators and their operand #[test] fn no_space_before_unwrap() { check("let x = foo !;", &expect!["let x = foo!;"]); } #[test] fn no_space_after_bit_negation() { check("let x = ~~~ 3;", &expect!["let x = ~~~3;"]); } #[test] fn single_space_around_boolean_negation() { check("let x = not 3;", &expect!["let x = not 3;"]); } // No space after open parentheses and brackets and before close parentheses and brackets #[test] fn no_space_for_parentheses() { check("( 12, 13, 14 )", &expect!["(12, 13, 14)"]); } #[test] fn no_space_for_brackets() { check("[ 12 + 13 + 14 ]", &expect!["[12 + 13 + 14]"]); } // No space after open string-interpolation argument braces and before close string-interpolation argument braces #[test] fn no_space_for_string_interpolation_argument_braces() { check( r#"let x = $"First { 1 + 1 } Third";"#, &expect![[r#"let x = $"First {1 + 1} Third";"#]], ); } // No space before commas or semicolons #[test] fn no_space_before_comma() { check("(12 , 13 , 14)", &expect!["(12, 13, 14)"]); } #[test] fn no_space_before_semicolons() { check("let x = 3 ;", &expect!["let x = 3;"]); } // Newline after semicolons #[test] fn newline_after_semicolon() { check( "let x = 3; let y = 2;", &expect![[r#" let x = 3; let y = 2;"#]], ); } #[test] fn preserve_eol_comment() { let input = indoc! {"let x = 3; // End-of-line Comment let y = 2; "}; assert!(super::calculate_format_edits(input).is_empty()); } // Newline before declaration keywords #[test] fn newline_before_let() { check( "let x = 3; {} let y = 2;", &expect![[r#" let x = 3; {} let y = 2;"#]], ); } #[test] fn newline_before_mutable() { check( "mutable x = 3; {} mutable y = 2;", &expect![[r#" mutable x = 3; {} mutable y = 2;"#]], ); } #[test] fn newline_before_set() { check( "set x = 3; {} set y = 2;", &expect![[r#" set x = 3; {} set y = 2;"#]], ); } #[test] fn newline_before_use() { check( "use q = Qubit(); {} use w = Qubit();", &expect![[r#" use q = Qubit(); {} use w = Qubit();"#]], ); } #[test] fn newline_before_borrow() { check( "borrow q = Qubit(); {} borrow w = Qubit();", &expect![[r#" borrow q = Qubit(); {} borrow w = Qubit();"#]], ); } // Single space before control-flow-helper keywords #[test] fn single_space_before_in() { check("for x in 0..2 {}", &expect![[r#"for x in 0..2 {}"#]]); } #[test] fn single_space_before_until() { check( "repeat {} until x fixup {}", &expect![[r#" repeat {} until x fixup {}"#]], ); } #[test] fn single_space_before_elif_and_else() { check( "if x {} elif y {} else {}", &expect!["if x {} elif y {} else {}"], ); } #[test] fn single_space_before_apply() { check("within {} apply {}", &expect!["within {} apply {}"]); } // No space between caller expressions and argument tuple #[test] fn no_space_in_front_of_argument_tuple() { check("Foo (1, 2, 3)", &expect!["Foo(1, 2, 3)"]); } #[test] fn no_space_in_front_of_parameter_tuple() { check( "operation Foo (x : Int, y : Int) : Unit {}", &expect!["operation Foo(x : Int, y : Int) : Unit {}"], ); } // No space between array expressions and indexing brackets #[test] fn no_space_in_front_of_array_indexing() { check("arr [4]", &expect!["arr[4]"]); } // No space around `.`, `..`, and `::` operators #[test] fn no_space_around_dot_operator() { check("let x = thing . other;", &expect!["let x = thing.other;"]); } #[test] fn no_space_around_range_operator() { check("let x = 1 .. 4;", &expect!["let x = 1..4;"]); } #[test] fn no_space_around_field_operator() { check("let x = thing :: other;", &expect!["let x = thing::other;"]); } // No space between the `…` operator and any possible operands on either side #[test] fn no_space_around_full_range_in_slice() { check("let x = y[ ... ];", &expect!["let x = y[...];"]); } #[test] fn no_space_between_open_end_range_and_operand() { check("let x = 15 ...;", &expect!["let x = 15...;"]); } #[test] fn no_space_between_open_start_range_and_operand() { check("let x = ... 15;", &expect!["let x = ...15;"]); } // Single space before open brace and newline after, except empty blocks have no space #[test] fn single_space_before_open_brace_and_newline_after() { check( indoc! {r#" operation Foo() : Unit{ let x = 3; } operation Bar() : Unit { { let x = 3; }{ let x = 4; } } "#}, &expect![[r#" operation Foo() : Unit { let x = 3; } operation Bar() : Unit { { let x = 3; } { let x = 4; } } "#]], ); } #[test] fn remove_spaces_between_empty_delimiters() { check( indoc! {r#" operation Foo() : Unit { } operation Bar() : Unit { operation Baz() : Unit { } let x = { }; let y : Int[] = [ ]; let z = ( ); } "#}, &expect![[r#" operation Foo() : Unit {} operation Bar() : Unit { operation Baz() : Unit {} let x = {}; let y : Int[] = []; let z = (); } "#]], ); } // Single space before literals #[test] fn single_space_before_literals() { check( indoc! {" let x = 15; let x = 0xF; let x = 15.0; let x = 15L; let x = \"Fifteen\"; let x = $\"Fifteen\"; let x = PauliI; let x = PauliX; let x = PauliY; let x = PauliZ; let x = true; let x = false; let x = One; let x = Zero; "}, &expect![[r#" let x = 15; let x = 0xF; let x = 15.0; let x = 15L; let x = "Fifteen"; let x = $"Fifteen"; let x = PauliI; let x = PauliX; let x = PauliY; let x = PauliZ; let x = true; let x = false; let x = One; let x = Zero; "#]], ); } // Single space before types #[test] fn single_space_before_types() { check( "let x : (Int, Double, String[], (BigInt, Unit), ('T, )) => 'T = foo;", &expect![[r#"let x : (Int, Double, String[], (BigInt, Unit), ('T, )) => 'T = foo;"#]], ); } // Single space before variables #[test] fn single_space_before_idents() { check("let x = foo;", &expect!["let x = foo;"]); } // Formatter continues after error token #[test] fn formatter_continues_after_error_token() { check( indoc! {" let x : ' T = foo; let x : ` T = foo; let x : & T = foo; let x : || T = foo; let x : ^^ T = foo; "}, &expect![[r#" let x : ' T = foo; let x : ` T = foo; let x : & T = foo; let x : || T = foo; let x : ^^ T = foo; "#]], ); } #[test] fn formatter_does_not_crash_on_non_terminating_string() { let _ = super::calculate_format_edits("let x = \"Hello World"); } // Correct indentation, which increases by four spaces when a delimited block is opened and decreases when block is closed #[test] fn formatting_corrects_indentation() { check( r#" /// First /// Second /// Third namespace MyQuantumProgram { import Std.Diagnostics.*; @EntryPoint() operation Main() : Int { let x = 3; let y = 4; // Comment return 5; } } "#, &expect![[r#" /// First /// Second /// Third namespace MyQuantumProgram { import Std.Diagnostics.*; @EntryPoint() operation Main() : Int { let x = 3; let y = 4; // Comment return 5; } } "#]], ); } #[test] fn nested_delimiter_indentation() { check( indoc! {r#" let x = [ (1) ]; let y = 3; "#}, &expect![[r#" let x = [ (1) ]; let y = 3; "#]], ); } #[test] fn delimiter_comments() { check( indoc! {r#" let x = [ // this is a comment (1) ]; let y = 3; "#}, &expect![[r#" let x = [ // this is a comment (1) ]; let y = 3; "#]], ); } #[test] fn brace_no_newlines() { check( indoc! {r#" { Foo(); Bar(); Baz() } "#}, &expect![[r#" { Foo(); Bar(); Baz() } "#]], ); } #[test] fn brace_newlines() { check( indoc! {r#" { Foo(); Bar(); Baz() } "#}, &expect![[r#" { Foo(); Bar(); Baz() } "#]], ); } #[test] fn parens_no_newlines() { check( indoc! {r#" ( Foo(), Bar(), Baz() ) "#}, &expect![[r#" (Foo(), Bar(), Baz()) "#]], ); } #[test] fn parens_newlines() { check( indoc! {r#" ( Foo(), Bar(), Baz() ) "#}, &expect![[r#" ( Foo(), Bar(), Baz() ) "#]], ); } #[test] fn bracket_no_newlines() { check( indoc! {r#" [ Foo(), Bar(), Baz() ] "#}, &expect![[r#" [Foo(), Bar(), Baz()] "#]], ); } #[test] fn bracket_newlines() { check( indoc! {r#" [ Foo(), Bar(), Baz() ] "#}, &expect![[r#" [ Foo(), Bar(), Baz() ] "#]], ); } #[test] fn semi_no_context_uses_newlines() { check( indoc! {r#" Foo(); Bar(); Baz() "#}, &expect![[r#" Foo(); Bar(); Baz() "#]], ); } #[test] fn comma_no_context_uses_space() { check( indoc! {r#" Foo(), Bar(), Baz() "#}, &expect![[r#" Foo(), Bar(), Baz() "#]], ); } #[test] fn type_param_lists_have_no_spaces_around_delims() { check( indoc! {r#" { operation Foo < 'A, 'B, 'C > (a : 'A, b : 'B, c : 'C) : Unit {} } "#}, &expect![[r#" { operation Foo<'A, 'B, 'C>(a : 'A, b : 'B, c : 'C) : Unit {} } "#]], ); } #[test] fn greater_than_and_less_than_bin_ops_have_spaces() { check(indoc! {r#"xz;"#}, &expect!["x < y > z;"]); } #[test] fn delimiter_newlines_indentation() { check( r#" let x = [ a, b, c ]; let y = [ a, b, c ]; let z = [ a, b, c ]; "#, &expect![[r#" let x = [a, b, c]; let y = [a, b, c]; let z = [ a, b, c ]; "#]], ); } #[test] fn preserve_string_indentation() { let input = r#""Hello World""#; assert!(super::calculate_format_edits(input).is_empty()); } // Will respect user new-lines and indentation added into expressions #[test] fn newline_after_brace_before_value() { check( indoc! {r#" { let x = 3; } x "#}, &expect![[r#" { let x = 3; } x "#]], ); } #[test] fn newline_after_brace_before_functor() { check( indoc! {r#" { let x = 3; } Adjoint Foo(); "#}, &expect![[r#" { let x = 3; } Adjoint Foo(); "#]], ); } #[test] fn newline_after_brace_before_not_keyword() { check( indoc! {r#" { let x = 3; } not true "#}, &expect![[r#" { let x = 3; } not true "#]], ); } #[test] fn newline_after_brace_before_starter_keyword() { check( indoc! {r#" { let x = 3; } if true {} "#}, &expect![[r#" { let x = 3; } if true {} "#]], ); } #[test] fn newline_after_brace_before_brace() { check( indoc! {r#" { let x = 3; } {} "#}, &expect![[r#" { let x = 3; } {} "#]], ); } #[test] fn space_after_brace_before_operator() { check( indoc! {r#" { let x = 3; } + {} "#}, &expect![[r#" { let x = 3; } + {} "#]], ); } #[test] fn newline_after_brace_before_delim() { check( indoc! {r#" {} () {} [] "#}, &expect![[r#" {} () {} [] "#]], ); } // Copy operator can have single space or newline #[test] fn copy_operator_with_newline_is_indented() { check( indoc! {r#" let x = arr w/ 0 <- 10 w/ 1 <- 11 "#}, &expect![[r#" let x = arr w/ 0 <- 10 w/ 1 <- 11 "#]], ); } #[test] fn copy_operator_with_space_has_single_space() { check( indoc! {r#" let x = arr w/ 0 <- 10 w/ 1 <- 11 "#}, &expect![[r#" let x = arr w/ 0 <- 10 w/ 1 <- 11 "#]], ); } #[test] fn no_space_around_carrot() { check( indoc! {r#" {} ^ {} 1 ^ 2 "#}, &expect![[r#" {}^{} 1^2 "#]], ); } #[test] fn no_space_around_ellipse() { check( indoc! {r#" {} ... {} "#}, &expect![[r#" {}...{} "#]], ); } #[test] fn single_space_after_spec_decl_ellipse() { check( indoc! {r#" body ...auto adjoint ...{} "#}, &expect![[r#" body ... auto adjoint ... {} "#]], ); } // Remove extra whitespace from start of code #[test] fn remove_extra_whitespace_from_start_of_code() { let input = indoc! {r#" namespace Foo {}"#}; check(input, &expect!["namespace Foo {}"]); } // Extra test cases for sanity #[test] fn preserve_comments_at_start_of_file() { let input = indoc! {r#" // Initial Comment namespace Foo {}"#}; assert!(super::calculate_format_edits(input).is_empty()); } #[test] fn format_with_crlf() { let content = indoc! {"//qsharp\r\n\r\noperation Foo() : Unit {\r\n\r\n}\r\n"}; check_edits( content, &expect![[r#" [ TextEdit { new_text: "", span: Span { lo: 36, hi: 40, }, }, ] "#]], ); check( content, &expect![["//qsharp\r\n\r\noperation Foo() : Unit {}\r\n"]], ); } #[test] fn format_does_not_edit_magic_comment() { let content = indoc! {"\r\n\r\n //qsharp \r\n\r\noperation Foo() : Unit {\r\n\r\n}\r\n"}; check_edits( content, &expect![[r#" [ TextEdit { new_text: "", span: Span { lo: 0, hi: 8, }, }, TextEdit { new_text: "//qsharp", span: Span { lo: 8, hi: 20, }, }, TextEdit { new_text: "", span: Span { lo: 48, hi: 52, }, }, ] "#]], ); check( content, &expect![["//qsharp\r\n\r\noperation Foo() : Unit {}\r\n"]], ); } #[test] fn sample_has_no_formatting_changes() { let input = indoc! {r#" /// # Sample /// Joint Measurement /// /// # Description /// Joint measurements, also known as Pauli measurements, are a generalization /// of 2-outcome measurements to multiple qubits and other bases. namespace Sample { import Std.Diagnostics.*; @EntryPoint() operation Main() : (Result, Result[]) { // Prepare an entangled state. use qs = Qubit[2]; // |00〉 H(qs[0]); // 1/sqrt(2)(|00〉 + |10〉) CNOT(qs[0], qs[1]); // 1/sqrt(2)(|00〉 + |11〉) // Show the quantum state before performing the joint measurement. DumpMachine(); // The below code uses a joint measurement as a way to check the parity // of the first two qubits. In this case, the parity measurement result // will always be `Zero`. // Notice how the state was not collapsed by the joint measurement. let parityResult = Measure([PauliZ, PauliZ], qs[...1]); DumpMachine(); // However, if we perform a measurement just on the first qubit, we can // see how the state collapses. let firstQubitResult = M(qs[0]); DumpMachine(); // Measuring the last qubit does not change the quantum state // since the state of the second qubit collapsed when the first qubit // was measured because they were entangled. let secondQubitResult = M(qs[1]); DumpMachine(); ResetAll(qs); return (parityResult, [firstQubitResult, secondQubitResult]); } } "#}; assert!(super::calculate_format_edits(input).is_empty()); } #[test] fn format_export_statement_no_newlines() { let input = "export Microsoft.Quantum.Diagnostics, Foo.Bar.Baz;"; check( input, &expect![["export Microsoft.Quantum.Diagnostics, Foo.Bar.Baz;"]], ); } #[test] fn format_glob_import() { let input = "import Microsoft.Quantum.*, Foo.Bar.Baz as SomethingElse, AnotherThing;"; check( input, &expect![[r#" import Microsoft.Quantum.*, Foo.Bar.Baz as SomethingElse, AnotherThing;"#]], ); } #[test] fn no_newlines_glob() { let input = "import foo, bar, baz.quux.*;"; check(input, &expect!["import foo, bar, baz.quux.*;"]); } #[test] fn format_export_statement_newlines() { let input = "export Microsoft.Quantum.Diagnostics, Foo.Bar.Baz;"; check( input, &expect![[r#" export Microsoft.Quantum.Diagnostics, Foo.Bar.Baz;"#]], ); } #[test] #[allow(clippy::too_many_lines)] fn export_fmt_within_namespace() { let input = r#" namespace Microsoft.Quantum.Arrays { export All, Any, Chunks, CircularlyShifted, ColumnAt, Count, Diagonal, DrawMany, Enumerated, Excluding, Filtered, FlatMapped, Flattened, Fold, ForEach, Head, HeadAndRest, IndexOf, IndexRange, Interleaved, IsEmpty, IsRectangularArray, IsSorted, IsSquareArray, Mapped, MappedByIndex, MappedOverRange, Most, MostAndTail, Padded, Partitioned, Rest, Reversed, SequenceI, SequenceL, Sorted, Subarray, Swapped, Transposed, Tail, Unzipped, Where, Windows, Zipped; } "#; check( input, &expect![[r#" namespace Microsoft.Quantum.Arrays { export All, Any, Chunks, CircularlyShifted, ColumnAt, Count, Diagonal, DrawMany, Enumerated, Excluding, Filtered, FlatMapped, Flattened, Fold, ForEach, Head, HeadAndRest, IndexOf, IndexRange, Interleaved, IsEmpty, IsRectangularArray, IsSorted, IsSquareArray, Mapped, MappedByIndex, MappedOverRange, Most, MostAndTail, Padded, Partitioned, Rest, Reversed, SequenceI, SequenceL, Sorted, Subarray, Swapped, Transposed, Tail, Unzipped, Where, Windows, Zipped; } "#]], ); }