Skip to content

Commit 8fff0da

Browse files
Add comprehensive semantic safety tests for SciMLStyle
- Add tests for whitespace-sensitive array literals (space vs semicolon) - Test operator precedence preservation - Test coefficient syntax (2x vs 2 x) - Test macro scope preservation - Test string/char/regex/command literals - Test ternary operator associativity - Test anonymous functions and comprehensions - Test broadcasting and type syntax - Test all Julia language constructs for semantic preservation - Add 69 new semantic safety tests These tests ensure the formatter never changes program meaning, only whitespace and formatting. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 5b13770 commit 8fff0da

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed

test/sciml_style.jl

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,4 +847,299 @@
847847
@test !contains(formatted, "][j\n")
848848
end
849849
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
8501145
end

0 commit comments

Comments
 (0)