|
1142 | 1142 | formatted = format_text(str, SciMLStyle())
|
1143 | 1143 | @test contains(formatted, "quote")
|
1144 | 1144 | end
|
| 1145 | + |
| 1146 | + @testset "line break quality" begin |
| 1147 | + # Helper function to analyze line utilization |
| 1148 | + function analyze_line_quality(formatted_code) |
| 1149 | + lines = split(formatted_code, '\n') |
| 1150 | + non_empty_lines = filter(l -> !isempty(strip(l)), lines) |
| 1151 | + |
| 1152 | + if isempty(non_empty_lines) |
| 1153 | + return (max_length=0, avg_length=0.0, num_lines=0, efficiency=0.0) |
| 1154 | + end |
| 1155 | + |
| 1156 | + lengths = [length(rstrip(line)) for line in non_empty_lines] |
| 1157 | + max_length = maximum(lengths) |
| 1158 | + avg_length = sum(lengths) / length(lengths) |
| 1159 | + |
| 1160 | + # Efficiency: how well lines use available space (0-1) |
| 1161 | + # Penalize both very short lines and lines over margin |
| 1162 | + efficiency_scores = map(lengths) do len |
| 1163 | + if len > 92 |
| 1164 | + 0.5 * (92 / len) # Penalty for exceeding margin |
| 1165 | + elseif len < 20 |
| 1166 | + len / 40 # Penalty for very short lines |
| 1167 | + else |
| 1168 | + len / 92 # Normal efficiency |
| 1169 | + end |
| 1170 | + end |
| 1171 | + efficiency = sum(efficiency_scores) / length(efficiency_scores) |
| 1172 | + |
| 1173 | + return (max_length=max_length, avg_length=avg_length, |
| 1174 | + num_lines=length(non_empty_lines), efficiency=efficiency) |
| 1175 | + end |
| 1176 | + |
| 1177 | + # Test 1: Simple expressions should stay on one line when they fit |
| 1178 | + @testset "single line preservation" begin |
| 1179 | + # These should remain on one line |
| 1180 | + single_line_cases = [ |
| 1181 | + "x = a + b * c - d", |
| 1182 | + "result = solve(prob, alg)", |
| 1183 | + "arr[i, j] = value * scale", |
| 1184 | + "y = sin(x) + cos(z) * exp(-t)", |
| 1185 | + "data = [1, 2, 3, 4, 5]", |
| 1186 | + ] |
| 1187 | + |
| 1188 | + for code in single_line_cases |
| 1189 | + formatted = format_text(code, SciMLStyle()) |
| 1190 | + @test !contains(formatted, '\n') |
| 1191 | + @test formatted == code |
| 1192 | + end |
| 1193 | + end |
| 1194 | + |
| 1195 | + # Test 2: Long lines should break efficiently |
| 1196 | + @testset "efficient line breaking" begin |
| 1197 | + # Function call with many parameters |
| 1198 | + str = """ |
| 1199 | + result = solve(NonlinearProblem(f, u0, p), NewtonRaphson(; autodiff=AutoForwardDiff()), abstol=1e-10, reltol=1e-10, maxiters=1000, show_trace=true) |
| 1200 | + """ |
| 1201 | + formatted = format_text(str, SciMLStyle()) |
| 1202 | + quality = analyze_line_quality(formatted) |
| 1203 | + |
| 1204 | + @test quality.max_length <= 92 # Should respect margin |
| 1205 | + @test quality.avg_length > 50 # Should use space efficiently |
| 1206 | + @test quality.efficiency > 0.6 # Good space utilization |
| 1207 | + |
| 1208 | + # Mathematical expression |
| 1209 | + str = """ |
| 1210 | + residual = alpha * (u[i-1, j] + u[i+1, j] - 2u[i, j]) / dx^2 + beta * (u[i, j-1] + u[i, j+1] - 2u[i, j]) / dy^2 + gamma * u[i, j] |
| 1211 | + """ |
| 1212 | + formatted = format_text(str, SciMLStyle()) |
| 1213 | + quality = analyze_line_quality(formatted) |
| 1214 | + |
| 1215 | + # Should break but maintain good line density |
| 1216 | + @test quality.num_lines >= 2 |
| 1217 | + @test quality.avg_length > 40 # Not too many short lines |
| 1218 | + end |
| 1219 | + |
| 1220 | + # Test 3: Type parameters should wrap nicely |
| 1221 | + @testset "type parameter wrapping" begin |
| 1222 | + str = """ |
| 1223 | + function f(x::AbstractArray{T,N}, y::AbstractMatrix{S}) where {T<:Real, S<:Number, N} |
| 1224 | + return x |
| 1225 | + end |
| 1226 | + """ |
| 1227 | + formatted = format_text(str, SciMLStyle()) |
| 1228 | + quality = analyze_line_quality(formatted) |
| 1229 | + |
| 1230 | + # Should fit on 3 lines max |
| 1231 | + @test quality.num_lines <= 3 |
| 1232 | + @test quality.max_length <= 92 |
| 1233 | + |
| 1234 | + # Long type parameter list |
| 1235 | + str = """ |
| 1236 | + struct MyType{T1,T2,T3,T4,T5,T6,T7,T8,T9,T10} <: AbstractType{T1,T2,T3,T4,T5,T6,T7,T8,T9,T10} |
| 1237 | + end |
| 1238 | + """ |
| 1239 | + formatted = format_text(str, SciMLStyle()) |
| 1240 | + lines = split(formatted, '\n') |
| 1241 | + |
| 1242 | + # Should break but not excessively |
| 1243 | + @test length(lines) <= 4 |
| 1244 | + # No single parameter on a line |
| 1245 | + for line in lines |
| 1246 | + if contains(line, "T") && !contains(line, "end") |
| 1247 | + @test contains(line, ",") || contains(line, "{") || contains(line, "}") |
| 1248 | + end |
| 1249 | + end |
| 1250 | + end |
| 1251 | + |
| 1252 | + # Test 4: Array operations should maintain structure |
| 1253 | + @testset "array operation formatting" begin |
| 1254 | + # Array comprehension |
| 1255 | + str = "[f(i, j) * g(k) for i in 1:n, j in 1:m, k in 1:p if condition(i, j, k)]" |
| 1256 | + formatted = format_text(str, SciMLStyle()) |
| 1257 | + |
| 1258 | + # Should keep comprehension readable |
| 1259 | + @test contains(formatted, "for i in 1:n, j in 1:m") |
| 1260 | + quality = analyze_line_quality(formatted) |
| 1261 | + @test quality.efficiency > 0.5 |
| 1262 | + |
| 1263 | + # Matrix literal |
| 1264 | + str = """ |
| 1265 | + A = [ |
| 1266 | + a11 a12 a13 a14 |
| 1267 | + a21 a22 a23 a24 |
| 1268 | + a31 a32 a33 a34 |
| 1269 | + ] |
| 1270 | + """ |
| 1271 | + formatted = format_text(str, SciMLStyle()) |
| 1272 | + # Should preserve matrix structure |
| 1273 | + @test contains(formatted, "a11 a12 a13 a14") |
| 1274 | + @test contains(formatted, "a21 a22 a23 a24") |
| 1275 | + end |
| 1276 | + |
| 1277 | + # Test 5: Function definitions with multiple arguments |
| 1278 | + @testset "function definition formatting" begin |
| 1279 | + # Moderate length - should stay on 2-3 lines |
| 1280 | + str = """ |
| 1281 | + function solve(prob::ODEProblem, alg::Tsit5; abstol=1e-6, reltol=1e-3, saveat=[], callback=nothing) |
| 1282 | + # body |
| 1283 | + end |
| 1284 | + """ |
| 1285 | + formatted = format_text(str, SciMLStyle()) |
| 1286 | + quality = analyze_line_quality(formatted) |
| 1287 | + |
| 1288 | + @test quality.num_lines <= 4 # Reasonable number of lines |
| 1289 | + @test quality.avg_length > 30 # Reasonable line utilization |
| 1290 | + |
| 1291 | + # Very long parameter list |
| 1292 | + str = """ |
| 1293 | + function complex_solver(problem::NonlinearProblem{uType,tType,isinplace}, algorithm::NewtonRaphson{CS,AD,FDT,L,P,ST,CJ}, x0::AbstractVector, p::NamedTuple; abstol::Float64=1e-8, reltol::Float64=1e-8, maxiters::Int=1000, callback=nothing, show_trace::Bool=false) where {uType,tType,isinplace,CS,AD,FDT,L,P,ST,CJ} |
| 1294 | + # body |
| 1295 | + end |
| 1296 | + """ |
| 1297 | + formatted = format_text(str, SciMLStyle()) |
| 1298 | + lines = split(formatted, '\n') |
| 1299 | + |
| 1300 | + # Should break intelligently |
| 1301 | + @test all(length(rstrip(line)) <= 92 for line in lines) |
| 1302 | + # But not too many lines |
| 1303 | + @test length(lines) <= 12 # Complex function may need more lines |
| 1304 | + end |
| 1305 | + |
| 1306 | + # Test 6: Nested expressions should maintain hierarchy |
| 1307 | + @testset "nested expression formatting" begin |
| 1308 | + str = """ |
| 1309 | + result = transform(integrate(differentiate(interpolate(data, method=:cubic), order=2), bounds=(a, b)), scaling=:log) |
| 1310 | + """ |
| 1311 | + formatted = format_text(str, SciMLStyle()) |
| 1312 | + |
| 1313 | + # Should break at outer function calls first |
| 1314 | + @test contains(formatted, "transform(") |
| 1315 | + quality = analyze_line_quality(formatted) |
| 1316 | + @test quality.efficiency > 0.5 |
| 1317 | + |
| 1318 | + # Nested array access |
| 1319 | + str = "value = data[indices[i]][subindices[j]][k]" |
| 1320 | + formatted = format_text(str, SciMLStyle()) |
| 1321 | + # Should keep array access together when reasonable |
| 1322 | + @test !contains(formatted, "[\n") |
| 1323 | + end |
| 1324 | + |
| 1325 | + # Test 7: Mathematical expressions should break at operators |
| 1326 | + @testset "mathematical expression breaking" begin |
| 1327 | + # Long equation |
| 1328 | + str = """ |
| 1329 | + energy = 0.5 * mass * velocity^2 + mass * gravity * height + 0.5 * spring_constant * displacement^2 + friction_coefficient * mass * gravity * distance |
| 1330 | + """ |
| 1331 | + formatted = format_text(str, SciMLStyle()) |
| 1332 | + lines = split(formatted, '\n') |
| 1333 | + |
| 1334 | + # Should break at + operators |
| 1335 | + for line in lines[2:end] |
| 1336 | + stripped = strip(line) |
| 1337 | + if !isempty(stripped) |
| 1338 | + # Continuation lines should start with operator or be aligned |
| 1339 | + @test startswith(stripped, "+") || startswith(stripped, "-") || |
| 1340 | + startswith(stripped, "*") || startswith(stripped, "/") || |
| 1341 | + length(lstrip(line)) < length(line) # Or be indented |
| 1342 | + end |
| 1343 | + end |
| 1344 | + |
| 1345 | + quality = analyze_line_quality(formatted) |
| 1346 | + @test quality.avg_length > 40 # Efficient use of space |
| 1347 | + end |
| 1348 | + |
| 1349 | + # Test 8: Avoid "orphan" parameters |
| 1350 | + @testset "avoid orphan parameters" begin |
| 1351 | + # Should not leave single parameters on lines |
| 1352 | + str = "f(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)" |
| 1353 | + formatted = format_text(str, SciMLStyle()) |
| 1354 | + lines = split(formatted, '\n') |
| 1355 | + |
| 1356 | + for line in lines |
| 1357 | + stripped = strip(line) |
| 1358 | + # If it's just a single letter and comma/paren, it's an orphan |
| 1359 | + if match(r"^[a-z],?\)?$", stripped) !== nothing |
| 1360 | + @test false # Should not have orphan parameters |
| 1361 | + end |
| 1362 | + end |
| 1363 | + end |
| 1364 | + |
| 1365 | + # Test 9: Keyword arguments should group nicely |
| 1366 | + @testset "keyword argument grouping" begin |
| 1367 | + str = """ |
| 1368 | + solve(prob; abstol=1e-8, reltol=1e-8, dtmin=1e-10, dtmax=0.1, maxiters=10000, adaptive=true, save_everystep=false, save_start=true, save_end=true, verbose=true) |
| 1369 | + """ |
| 1370 | + formatted = format_text(str, SciMLStyle()) |
| 1371 | + |
| 1372 | + # Should group related kwargs when possible |
| 1373 | + lines = split(formatted, '\n') |
| 1374 | + # First line should have multiple kwargs if they fit |
| 1375 | + if length(lines) > 1 |
| 1376 | + @test contains(lines[1], ",") || contains(lines[1], ";") |
| 1377 | + end |
| 1378 | + |
| 1379 | + quality = analyze_line_quality(formatted) |
| 1380 | + @test quality.efficiency > 0.6 |
| 1381 | + end |
| 1382 | + |
| 1383 | + # Test 10: Preserve logical grouping |
| 1384 | + @testset "logical grouping preservation" begin |
| 1385 | + # Coordinates should stay together |
| 1386 | + str = "plot(x1, y1, x2, y2, x3, y3; xlabel=\"Time\", ylabel=\"Value\", title=\"Results\")" |
| 1387 | + formatted = format_text(str, SciMLStyle()) |
| 1388 | + |
| 1389 | + # x,y pairs should stay together when possible |
| 1390 | + @test contains(formatted, "x1, y1") || contains(formatted, "x1,y1") |
| 1391 | + |
| 1392 | + # Matrix multiplication chain |
| 1393 | + str = "result = A * B * C * D * E * F" |
| 1394 | + formatted = format_text(str, SciMLStyle()) |
| 1395 | + # Should keep some products together |
| 1396 | + @test length(split(formatted, '\n')) <= 3 |
| 1397 | + end |
| 1398 | + |
| 1399 | + # Test 11: Real-world examples from SciML |
| 1400 | + @testset "SciML real-world examples" begin |
| 1401 | + # From OrdinaryDiffEq |
| 1402 | + str = """ |
| 1403 | + @muladd function perform_step!(integrator, cache::RK4Cache, repeat_step=false) |
| 1404 | + @unpack t, dt, uprev, u, f, p = integrator |
| 1405 | + @unpack k1, k2, k3, k4, tmp = cache |
| 1406 | + f(k1, uprev, p, t) |
| 1407 | + f(k2, uprev + dt/2 * k1, p, t + dt/2) |
| 1408 | + f(k3, uprev + dt/2 * k2, p, t + dt/2) |
| 1409 | + f(k4, uprev + dt * k3, p, t + dt) |
| 1410 | + @. u = uprev + dt/6 * (k1 + 2*k2 + 2*k3 + k4) |
| 1411 | + end |
| 1412 | + """ |
| 1413 | + formatted = format_text(str, SciMLStyle()) |
| 1414 | + quality = analyze_line_quality(formatted) |
| 1415 | + |
| 1416 | + # Should maintain readability |
| 1417 | + @test quality.max_length <= 92 |
| 1418 | + @test quality.avg_length > 30 |
| 1419 | + |
| 1420 | + # From ModelingToolkit |
| 1421 | + str = """ |
| 1422 | + @parameters t σ ρ β |
| 1423 | + @variables x(t) y(t) z(t) |
| 1424 | + D = Differential(t) |
| 1425 | + eqs = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] |
| 1426 | + """ |
| 1427 | + formatted = format_text(str, SciMLStyle()) |
| 1428 | + |
| 1429 | + # Should keep equations readable |
| 1430 | + @test contains(formatted, "D(x) ~ σ * (y - x)") |
| 1431 | + lines = split(formatted, '\n') |
| 1432 | + # Equations should be nicely formatted |
| 1433 | + eq_lines = filter(l -> contains(l, "D("), lines) |
| 1434 | + @test length(eq_lines) > 0 |
| 1435 | + end |
| 1436 | + end |
1145 | 1437 | end
|
0 commit comments