|
847 | 847 | @test !contains(formatted, "][j\n")
|
848 | 848 | end
|
849 | 849 | end
|
| 850 | + |
| 851 | + @testset "semantic safety" begin |
| 852 | + # Test 1: Whitespace-sensitive array literals |
| 853 | + # Space means horizontal concatenation, ; or newline means vertical |
| 854 | + str = "[1 2 3]" # 1×3 matrix |
| 855 | + @test format_text(str, SciMLStyle()) == str |
| 856 | + |
| 857 | + str = "[1; 2; 3]" # 3×1 matrix |
| 858 | + @test format_text(str, SciMLStyle()) == str |
| 859 | + |
| 860 | + str = "[1 2; 3 4]" # 2×2 matrix |
| 861 | + @test format_text(str, SciMLStyle()) == str |
| 862 | + |
| 863 | + # Test that we don't accidentally change array dimensions |
| 864 | + str = """ |
| 865 | + A = [1 2 3 |
| 866 | + 4 5 6] |
| 867 | + """ |
| 868 | + formatted = format_text(str, SciMLStyle()) |
| 869 | + @test contains(formatted, "1 2 3") # Ensure spaces preserved |
| 870 | + @test !contains(formatted, "1; 2; 3") # Ensure no semicolons added |
| 871 | + |
| 872 | + # Test 2: Operator precedence preservation |
| 873 | + precedence_tests = [ |
| 874 | + ("x = a + b * c", "x = a + b * c"), |
| 875 | + ("x = a * b + c", "x = a * b + c"), |
| 876 | + ("x = a^b^c", "x = a^b^c"), # Right associative |
| 877 | + ("x = a / b * c", "x = a / b * c"), # Left associative |
| 878 | + ("x = -a^2", "x = -a^2"), # Unary minus precedence |
| 879 | + ] |
| 880 | + |
| 881 | + for (input, expected) in precedence_tests |
| 882 | + @test format_text(input, SciMLStyle()) == expected |
| 883 | + end |
| 884 | + |
| 885 | + # Test 3: Coefficient syntax preservation |
| 886 | + # In Julia, 2x means 2*x, but 2 x is an error |
| 887 | + str = "y = 2x + 3y - 4z" |
| 888 | + @test format_text(str, SciMLStyle()) == str |
| 889 | + |
| 890 | + str = "y = 2(x + y)" # Coefficient with parentheses |
| 891 | + @test format_text(str, SciMLStyle()) == str |
| 892 | + |
| 893 | + # Test 4: Macro scope preservation |
| 894 | + str = "@. x = y + z" |
| 895 | + formatted = format_text(str, SciMLStyle()) |
| 896 | + @test formatted == str |
| 897 | + @test contains(formatted, "@.") |
| 898 | + |
| 899 | + str = "@views x[1:n] = y[1:n]" |
| 900 | + formatted = format_text(str, SciMLStyle()) |
| 901 | + @test contains(formatted, "@views") |
| 902 | + @test !contains(formatted, "@views\n") # Macro not separated from expression |
| 903 | + |
| 904 | + # Test 5: String and character literals (must not break) |
| 905 | + str = "msg = \"This is a long string that should not be broken\"" |
| 906 | + @test format_text(str, SciMLStyle()) == str |
| 907 | + |
| 908 | + str = "c = 'a'" |
| 909 | + @test format_text(str, SciMLStyle()) == str |
| 910 | + |
| 911 | + # Test 6: Ternary operator associativity |
| 912 | + str = "x = a ? b : c ? d : e" # Right associative |
| 913 | + formatted = format_text(str, SciMLStyle()) |
| 914 | + # Ensure it doesn't become (a ? b : c) ? d : e |
| 915 | + @test Meta.parse(str) == Meta.parse(formatted) |
| 916 | + |
| 917 | + # Test 7: Short-circuit evaluation order |
| 918 | + str = "a && b || c && d" |
| 919 | + formatted = format_text(str, SciMLStyle()) |
| 920 | + @test Meta.parse(str) == Meta.parse(formatted) |
| 921 | + |
| 922 | + # Test 8: Anonymous function syntax |
| 923 | + str = "f = x -> x^2" |
| 924 | + @test format_text(str, SciMLStyle()) == str |
| 925 | + |
| 926 | + str = "g = (x, y) -> x + y" |
| 927 | + @test format_text(str, SciMLStyle()) == str |
| 928 | + |
| 929 | + # Test 9: Array comprehension preservation |
| 930 | + str = "[i * j for i in 1:3, j in 1:3]" |
| 931 | + formatted = format_text(str, SciMLStyle()) |
| 932 | + @test contains(formatted, "for i in 1:3, j in 1:3") |
| 933 | + |
| 934 | + # Test 10: Broadcasting syntax |
| 935 | + str = "x .= y .+ z .* w" |
| 936 | + @test format_text(str, SciMLStyle()) == str |
| 937 | + |
| 938 | + str = "sin.(x) .+ cos.(y)" |
| 939 | + @test format_text(str, SciMLStyle()) == str |
| 940 | + |
| 941 | + # Test 11: Type parameter syntax |
| 942 | + str = "Vector{Float64}" |
| 943 | + @test format_text(str, SciMLStyle()) == str |
| 944 | + |
| 945 | + str = "Dict{String,Int}" |
| 946 | + formatted = format_text(str, SciMLStyle()) |
| 947 | + @test formatted == str || formatted == "Dict{String, Int}" # Space after comma is ok |
| 948 | + |
| 949 | + # Test 12: Splatting and slurping |
| 950 | + str = "f(x...)" |
| 951 | + @test format_text(str, SciMLStyle()) == str |
| 952 | + |
| 953 | + str = "g(a, b..., c)" |
| 954 | + @test format_text(str, SciMLStyle()) == str |
| 955 | + |
| 956 | + # Test 13: Range syntax preservation |
| 957 | + str = "1:10" |
| 958 | + @test format_text(str, SciMLStyle()) == str |
| 959 | + |
| 960 | + str = "1:2:10" |
| 961 | + @test format_text(str, SciMLStyle()) == str |
| 962 | + |
| 963 | + # Test 14: Rational number syntax |
| 964 | + str = "1//2" |
| 965 | + @test format_text(str, SciMLStyle()) == str |
| 966 | + |
| 967 | + # Test 15: Complex number syntax |
| 968 | + str = "1 + 2im" |
| 969 | + @test format_text(str, SciMLStyle()) == str |
| 970 | + |
| 971 | + # Test 16: Symbol syntax |
| 972 | + str = ":symbol" |
| 973 | + @test format_text(str, SciMLStyle()) == str |
| 974 | + |
| 975 | + str = ":(a + b)" |
| 976 | + @test format_text(str, SciMLStyle()) == str |
| 977 | + |
| 978 | + # Test 17: Interpolation in strings |
| 979 | + str = "\"x = \$x, y = \$(y + 1)\"" |
| 980 | + @test format_text(str, SciMLStyle()) == str |
| 981 | + |
| 982 | + # Test 18: Command literals |
| 983 | + str = "`echo hello`" |
| 984 | + @test format_text(str, SciMLStyle()) == str |
| 985 | + |
| 986 | + # Test 19: Version number literals |
| 987 | + str = "v\"1.2.3\"" |
| 988 | + @test format_text(str, SciMLStyle()) == str |
| 989 | + |
| 990 | + # Test 20: Regex literals |
| 991 | + str = "r\"[a-z]+\"" |
| 992 | + @test format_text(str, SciMLStyle()) == str |
| 993 | + |
| 994 | + # Test 21: Critical array literal semantic preservation |
| 995 | + # These MUST not change dimensions |
| 996 | + |
| 997 | + # Horizontal concatenation (space) |
| 998 | + str = "[1 2 3]" |
| 999 | + formatted = format_text(str, SciMLStyle()) |
| 1000 | + @test formatted == "[1 2 3]" # Must preserve spaces |
| 1001 | + |
| 1002 | + # Vertical concatenation (semicolon) |
| 1003 | + str = "[1; 2; 3]" |
| 1004 | + formatted = format_text(str, SciMLStyle()) |
| 1005 | + @test formatted == "[1; 2; 3]" # Must preserve semicolons |
| 1006 | + |
| 1007 | + # Mixed (creates matrix) |
| 1008 | + str = "[1 2; 3 4]" |
| 1009 | + formatted = format_text(str, SciMLStyle()) |
| 1010 | + @test formatted == "[1 2; 3 4]" # Must preserve exact structure |
| 1011 | + |
| 1012 | + # Test 21b: More array literal edge cases |
| 1013 | + # Mixed spaces and semicolons |
| 1014 | + str = "[1 2; 3 4; 5 6]" # 3×2 matrix |
| 1015 | + @test format_text(str, SciMLStyle()) == str |
| 1016 | + |
| 1017 | + # Nested arrays |
| 1018 | + str = "[[1, 2], [3, 4]]" |
| 1019 | + @test format_text(str, SciMLStyle()) == str |
| 1020 | + |
| 1021 | + # Array with trailing comma (1-element array vs scalar) |
| 1022 | + str = "[1,]" # 1-element array |
| 1023 | + formatted = format_text(str, SciMLStyle()) |
| 1024 | + @test formatted == "[1]" || formatted == "[1,]" # Both are valid |
| 1025 | + |
| 1026 | + # Empty arrays with type |
| 1027 | + str = "Float64[]" |
| 1028 | + @test format_text(str, SciMLStyle()) == str |
| 1029 | + |
| 1030 | + str = "Vector{Int}()" |
| 1031 | + @test format_text(str, SciMLStyle()) == str |
| 1032 | + |
| 1033 | + # Test 22: Tuple vs array distinction |
| 1034 | + str = "(1, 2, 3)" # Tuple |
| 1035 | + @test format_text(str, SciMLStyle()) == str |
| 1036 | + |
| 1037 | + str = "(1,)" # 1-element tuple (comma required) |
| 1038 | + @test format_text(str, SciMLStyle()) == str |
| 1039 | + |
| 1040 | + # Test 23: Generator expressions |
| 1041 | + str = "(x^2 for x in 1:10)" |
| 1042 | + formatted = format_text(str, SciMLStyle()) |
| 1043 | + @test contains(formatted, "for x in 1:10") |
| 1044 | + |
| 1045 | + # Test 24: Multi-line array construction preservation |
| 1046 | + str = """ |
| 1047 | + A = [ |
| 1048 | + 1 2 3 |
| 1049 | + 4 5 6 |
| 1050 | + 7 8 9 |
| 1051 | + ] |
| 1052 | + """ |
| 1053 | + formatted = format_text(str, SciMLStyle()) |
| 1054 | + # Should preserve the matrix structure |
| 1055 | + @test contains(formatted, "1 2 3") |
| 1056 | + @test contains(formatted, "4 5 6") |
| 1057 | + @test contains(formatted, "7 8 9") |
| 1058 | + |
| 1059 | + # Test 25: Coefficient with array indexing |
| 1060 | + str = "y = 2A[i, j]" # 2*A[i,j], not 2 A[i,j] |
| 1061 | + @test format_text(str, SciMLStyle()) == str |
| 1062 | + |
| 1063 | + # Test 26: Multiple dispatch syntax |
| 1064 | + str = "f(::Type{T}) where T = T" |
| 1065 | + formatted = format_text(str, SciMLStyle()) |
| 1066 | + @test contains(formatted, "where") && contains(formatted, "T") && contains(formatted, "Type{T}") |
| 1067 | + |
| 1068 | + # Test 27: Subtype syntax |
| 1069 | + str = "T <: Number" |
| 1070 | + @test format_text(str, SciMLStyle()) == str |
| 1071 | + |
| 1072 | + # Test 28: Union types |
| 1073 | + str = "Union{Int, Float64}" |
| 1074 | + @test format_text(str, SciMLStyle()) == str |
| 1075 | + |
| 1076 | + # Test 29: Parametric types with constraints |
| 1077 | + str = "struct Foo{T<:Real} end" |
| 1078 | + formatted = format_text(str, SciMLStyle()) |
| 1079 | + @test contains(formatted, "T<:Real") || contains(formatted, "T <: Real") |
| 1080 | + |
| 1081 | + # Test 30: Named tuples |
| 1082 | + str = "(a=1, b=2)" |
| 1083 | + formatted = format_text(str, SciMLStyle()) |
| 1084 | + # Named tuples might get spaces around = |
| 1085 | + @test formatted == str || formatted == "(a = 1, b = 2)" |
| 1086 | + |
| 1087 | + # Test 31: Keyword arguments |
| 1088 | + str = "f(x; y=1, z=2)" |
| 1089 | + formatted = format_text(str, SciMLStyle()) |
| 1090 | + @test contains(formatted, "; y") # Semicolon preserved |
| 1091 | + |
| 1092 | + # Test 32: Do block syntax |
| 1093 | + str = """ |
| 1094 | + map(1:3) do x |
| 1095 | + x^2 |
| 1096 | + end |
| 1097 | + """ |
| 1098 | + formatted = format_text(str, SciMLStyle()) |
| 1099 | + @test contains(formatted, "do x") |
| 1100 | + |
| 1101 | + # Test 33: Let block with multiple bindings |
| 1102 | + str = "let x = 1, y = 2; x + y end" |
| 1103 | + formatted = format_text(str, SciMLStyle()) |
| 1104 | + # Check semantic equivalence by comparing string representation without line numbers |
| 1105 | + str_ast = replace(string(Meta.parse(str)), r"#= \S+:\d+ =#" => "") |
| 1106 | + form_ast = replace(string(Meta.parse(formatted)), r"#= \S+:\d+ =#" => "") |
| 1107 | + @test str_ast == form_ast |
| 1108 | + |
| 1109 | + # Test 34: Destructuring |
| 1110 | + str = "a, b, c = 1, 2, 3" |
| 1111 | + @test format_text(str, SciMLStyle()) == str |
| 1112 | + |
| 1113 | + # Test 35: Unicode operators |
| 1114 | + str = "x ∈ A ∩ B" |
| 1115 | + @test format_text(str, SciMLStyle()) == str |
| 1116 | + |
| 1117 | + # Test 36: Pipe operator |
| 1118 | + str = "x |> f |> g" |
| 1119 | + @test format_text(str, SciMLStyle()) == str |
| 1120 | + |
| 1121 | + # Test 37: Pairs syntax |
| 1122 | + str = "a => b" |
| 1123 | + @test format_text(str, SciMLStyle()) == str |
| 1124 | + |
| 1125 | + # Test 38: Quote expressions |
| 1126 | + str = ":(x + y)" |
| 1127 | + @test format_text(str, SciMLStyle()) == str |
| 1128 | + |
| 1129 | + # Test 39: Escaped identifiers |
| 1130 | + str = "var\"strange name\"" |
| 1131 | + @test format_text(str, SciMLStyle()) == str |
| 1132 | + |
| 1133 | + # Test 40: Line number nodes (should be preserved in macros) |
| 1134 | + str = """ |
| 1135 | + macro foo() |
| 1136 | + quote |
| 1137 | + x = 1 |
| 1138 | + y = 2 |
| 1139 | + end |
| 1140 | + end |
| 1141 | + """ |
| 1142 | + formatted = format_text(str, SciMLStyle()) |
| 1143 | + @test contains(formatted, "quote") |
| 1144 | + end |
850 | 1145 | end
|
0 commit comments