From 66f596733592610b655d88ed7d6715cb29331d5f Mon Sep 17 00:00:00 2001 From: Marek Kaput Date: Thu, 19 Feb 2026 13:40:10 +0100 Subject: [PATCH 1/4] refactor(test): unify parsing assertions I have noticed a proliferation of various ways how to assert Spitfire conformance with Elixir parser. This commit introduces uniform assertion macros library, so we: 1. have a single place to modify, 2. have a single format of assertion messages, 3. and significantly reduce LOC in test files. --- test/test_helper.exs | 67 +++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/test/test_helper.exs b/test/test_helper.exs index e022c9a..b7f35de 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,33 +1,54 @@ -defmodule Spitfire.TestHelpers do - @moduledoc false - defmacro lhs == rhs do - quote do - lhs = - Macro.prewalk(unquote(lhs), fn - {t, meta, a} -> - {t, [], a} +defmodule Spitfire.Assertions do + @moduledoc """ + Reusable assertion macros and check functions used in Spitfire tests. + """ - ast -> - ast - end) - - rhs = - Macro.prewalk(unquote(rhs), fn - {t, meta, a} -> - {t, [], a} + @doc """ + A wrapper over `Spitfire.parse/1` that normalizes returned `:error` tuple. + """ + def spitfire_parse(code) do + case Spitfire.parse(code) do + {:ok, ast} -> {:ok, ast} + {:error, _ast, _errors} -> {:error, :parse_error} + {:error, :no_fuel_remaining} -> {:error, :no_fuel_remaining} + end + end - ast -> - ast - end) + @doc """ + Converts a string of Elixir code into a quoted expression in a format + usable for comparison with Spitfire output. + """ + def s2q(code, opts \\ []) do + Code.string_to_quoted( + code, + Keyword.merge([columns: true, token_metadata: true, emit_warnings: false], opts) + ) + end - if true do - import Kernel - import unquote(__MODULE__), except: [==: 2] + @doc """ + Asserts that Spitfire output conforms to Elixir parser output for a code snippet. + """ + defmacro assert_conforms(code) do + quote location: :keep do + spitfire = unquote(__MODULE__).spitfire_parse(unquote(code)) + elixir = unquote(__MODULE__).s2q(unquote(code)) - assert lhs == rhs + case elixir do + {:ok, _} -> assert spitfire == elixir + {:error, _} -> assert {:error, _} = spitfire end end end + + @doc """ + Asserts that both Elixir parser and Spitfire parser return errors for a code snippet. + """ + defmacro assert_errors(code) do + quote location: :keep do + assert {:error, _} = unquote(__MODULE__).s2q(unquote(code)) + assert {:error, _} = unquote(__MODULE__).spitfire_parse(unquote(code)) + end + end end ExUnit.start(exclude: [:skip]) From aeda023a1470214b513c984b5d6aa810e5f3a7f1 Mon Sep 17 00:00:00 2001 From: Marek Kaput Date: Thu, 19 Feb 2026 13:40:37 +0100 Subject: [PATCH 2/4] refactor(test): use Spitfire.Assertions in conformance_test.exs --- test/conformance_test.exs | 47 ++------------------------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/test/conformance_test.exs b/test/conformance_test.exs index c3568e3..23939e4 100644 --- a/test/conformance_test.exs +++ b/test/conformance_test.exs @@ -7,6 +7,8 @@ defmodule Spitfire.ConformanceTest do """ use ExUnit.Case, async: true + import Spitfire.Assertions, only: [assert_conforms: 1] + # ============================================================================= # TERMINALS - Basic building blocks that are valid as standalone expressions # ============================================================================= @@ -2895,49 +2897,4 @@ defmodule Spitfire.ConformanceTest do assert_conforms("^!u\n;") assert_conforms("foo()\n;") end - - # ============================================================================= - # Helper function - # ============================================================================= - - defp assert_conforms(code, _opts \\ []) do - reference = s2q(code) - - case reference do - {:ok, expected_ast} -> - actual = spitfire_parse(code) - - assert actual == reference, - """ - AST mismatch for: #{inspect(code)} - - Reference: - #{inspect(expected_ast, pretty: true)} - - Actual: - #{inspect(actual, pretty: true)} - """ - - {:error, _} -> - # Reference parser errors - skip, nothing to validate conformance against - :ok - end - end - - defp s2q(code) do - Code.string_to_quoted( - code, - columns: true, - token_metadata: true, - emit_warnings: false - ) - end - - defp spitfire_parse(code) do - case Spitfire.parse(code) do - {:ok, ast} -> {:ok, ast} - {:error, _ast, _errors} -> {:error, :parse_error} - {:error, :no_fuel_remaining} -> {:error, :no_fuel_remaining} - end - end end From 6d8fa20070d791d9fe808fd49734b79d24019d0b Mon Sep 17 00:00:00 2001 From: Marek Kaput Date: Thu, 19 Feb 2026 13:41:24 +0100 Subject: [PATCH 3/4] refactor(test): use Spitfire.Assertions in operators_test.exs --- test/operators_test.exs | 1290 +++++++++++++-------------------------- 1 file changed, 426 insertions(+), 864 deletions(-) diff --git a/test/operators_test.exs b/test/operators_test.exs index 6592a5e..a50c5b0 100644 --- a/test/operators_test.exs +++ b/test/operators_test.exs @@ -29,1228 +29,1006 @@ defmodule Spitfire.OperatorsTest do """ use ExUnit.Case, async: true + import Spitfire.Assertions, only: [assert_conforms: 1] + describe "unary @ operator" do test "module attribute" do - code = "@foo" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo") end test "module attribute with value" do - code = "@foo 1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo 1") end test "nested module attributes" do - code = "@foo @bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo @bar") end test "@ has highest precedence" do - code = "@foo + 1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo + 1") end test "@ with dot access" do - code = "@foo.bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo.bar") end end describe "unary + and - operators" do test "unary plus" do - code = "+1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("+1") end test "unary minus" do - code = "-1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("-1") end test "unary plus with identifier" do - code = "+foo" - assert spitfire_parse(code) == s2q(code) + assert_conforms("+foo") end test "unary minus with identifier" do - code = "-foo" - assert spitfire_parse(code) == s2q(code) + assert_conforms("-foo") end test "double unary minus" do - code = "- -1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("- -1") end test "unary minus with binary minus" do - code = "1 - -2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 - -2") end test "unary plus with binary plus" do - code = "1 + +2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + +2") end end describe "unary ! operator" do test "boolean not" do - code = "!true" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!true") end test "double negation" do - code = "!!true" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!!true") end test "! with expression" do - code = "!foo" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!foo") end test "! has higher precedence than binary operators" do - code = "!a && b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!a && b") end test "! has higher precedence than ==" do - code = "!a == b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!a == b") end end describe "unary ^ operator (pin)" do test "pin operator" do - code = "^foo" - assert spitfire_parse(code) == s2q(code) + assert_conforms("^foo") end test "pin in pattern match" do - code = "^foo = bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("^foo = bar") end test "pin with access" do - code = "^foo[0]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("^foo[0]") end test "pin with parens" do - code = "^foo(0)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("^foo(0)") end end describe "unary not operator" do test "not operator" do - code = "not true" - assert spitfire_parse(code) == s2q(code) + assert_conforms("not true") end test "not with expression" do - code = "not foo" - assert spitfire_parse(code) == s2q(code) + assert_conforms("not foo") end test "not has higher precedence than and" do - code = "not a and b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("not a and b") end test "not has higher precedence than or" do - code = "not a or b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("not a or b") end end describe "dot operator - left associativity" do test "simple dot access" do - code = "foo.bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo.bar") end test "chained dot access - left associative" do - code = "foo.bar.baz" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo.bar.baz") end test "multiple chained dots" do - code = "a.b.c.d.e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a.b.c.d.e") end test "dot with function call" do - code = "foo.bar()" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo.bar()") end test "chained function calls" do - code = "foo.bar().baz()" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo.bar().baz()") end test "dot has higher precedence than +" do - code = "foo.bar + 1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo.bar + 1") end test "dot has higher precedence than *" do - code = "foo.bar * 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo.bar * 2") end test "dot on alias" do - code = "Foo.Bar.baz" - assert spitfire_parse(code) == s2q(code) + assert_conforms("Foo.Bar.baz") end end describe "** operator - left associativity" do test "simple power" do - code = "2 ** 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 ** 3") end test "chained power - left associative" do # 2 ** 3 ** 4 should be (2 ** 3) ** 4 - code = "2 ** 3 ** 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 ** 3 ** 4") end test "power has higher precedence than *" do - code = "2 * 3 ** 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 * 3 ** 4") end test "power has higher precedence than +" do - code = "1 + 2 ** 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 ** 3") end test "power with unary minus" do - code = "-2 ** 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("-2 ** 3") end test "power with parentheses" do - code = "2 ** (3 ** 4)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 ** (3 ** 4)") end end describe "* and / operators - left associativity" do test "simple multiplication" do - code = "2 * 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 * 3") end test "simple division" do - code = "6 / 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("6 / 2") end test "chained multiplication - left associative" do - code = "2 * 3 * 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 * 3 * 4") end test "chained division - left associative" do - code = "24 / 4 / 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("24 / 4 / 2") end test "mixed * and / - left associative" do - code = "2 * 3 / 4 * 5" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 * 3 / 4 * 5") end test "* and / have higher precedence than +" do - code = "1 + 2 * 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 * 3") end test "* and / have higher precedence than -" do - code = "10 - 6 / 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("10 - 6 / 2") end test "* has lower precedence than **" do - code = "2 * 3 ** 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 * 3 ** 2") end end describe "+ and - operators - left associativity" do test "simple addition" do - code = "1 + 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2") end test "simple subtraction" do - code = "5 - 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("5 - 3") end test "chained addition - left associative" do - code = "1 + 2 + 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 + 3") end test "chained subtraction - left associative" do - code = "10 - 5 - 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("10 - 5 - 2") end test "mixed + and - - left associative" do - code = "1 + 2 - 3 + 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 - 3 + 4") end test "+ has lower precedence than *" do - code = "1 + 2 * 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 * 3") end test "- has lower precedence than /" do - code = "10 - 6 / 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("10 - 6 / 2") end test "+ has higher precedence than ++" do - code = "a + b ++ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a + b ++ c") end end describe "++ operator - right associativity" do test "simple concatenation" do - code = "[1] ++ [2]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[1] ++ [2]") end test "chained ++ - right associative" do # a ++ b ++ c should be a ++ (b ++ c) - code = "a ++ b ++ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ b ++ c") end test "multiple chained ++" do - code = "a ++ b ++ c ++ d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ b ++ c ++ d") end test "++ has lower precedence than +" do - code = "a + b ++ c + d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a + b ++ c + d") end end describe "-- operator - right associativity" do test "simple subtraction" do - code = "[1, 2] -- [1]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[1, 2] -- [1]") end test "chained -- - right associative" do - code = "a -- b -- c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a -- b -- c") end end describe "+++ operator - right associativity" do test "simple +++" do - code = "a +++ b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a +++ b") end test "chained +++ - right associative" do - code = "a +++ b +++ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a +++ b +++ c") end end describe "--- operator - right associativity" do test "simple ---" do - code = "a --- b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a --- b") end test "chained --- - right associative" do - code = "a --- b --- c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a --- b --- c") end end describe ".. operator - right associativity" do test "simple range" do - code = "1..10" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1..10") end test "range with step" do - code = "1..10//2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1..10//2") end test "chained .. - right associative" do - code = "a..b..c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a..b..c") end test ".. has lower precedence than +" do - code = "1 + 2..3 + 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2..3 + 4") end end describe "<> operator - right associativity" do test "simple binary concatenation" do - code = ~S'"a" <> "b"' - assert spitfire_parse(code) == s2q(code) + assert_conforms(~S'"a" <> "b"') end test "chained <> - right associative" do - code = ~S'a <> b <> c' - assert spitfire_parse(code) == s2q(code) + assert_conforms(~S'a <> b <> c') end test "multiple chained <>" do - code = ~S'a <> b <> c <> d' - assert spitfire_parse(code) == s2q(code) + assert_conforms(~S'a <> b <> c <> d') end end describe "mixed right associative list operators" do test "++ and -- share right associativity" do - code = "a ++ b -- c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ b -- c") end test "range operator shares precedence with ++" do - code = "a ++ b .. c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ b .. c") end test "<> and ++ share precedence and right associativity" do - code = "a <> b ++ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <> b ++ c") end end describe "in operator - left associativity" do test "simple in" do - code = "a in [1, 2, 3]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in [1, 2, 3]") end test "in with range" do - code = "a in 1..10" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in 1..10") end test "in has lower precedence than ++" do - code = "a in b ++ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b ++ c") end test "in has higher precedence than |>" do - code = "a in b |> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b |> c") end end describe "not in operator - left associativity" do test "simple not in" do - code = "a not in [1, 2, 3]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a not in [1, 2, 3]") end test "not in with range" do - code = "a not in 1..10" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a not in 1..10") end end describe "|> operator - left associativity" do test "simple pipe" do - code = "a |> b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b") end test "chained pipe - left associative" do - code = "a |> b |> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b |> c") end test "multiple chained pipes" do - code = "a |> b |> c |> d |> e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b |> c |> d |> e") end test "|> has lower precedence than in" do - code = "a in b |> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b |> c") end test "|> has higher precedence than <" do - code = "a |> b < c |> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b < c |> d") end end describe "<<< operator - left associativity" do test "simple <<<" do - code = "a <<< b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<< b") end test "chained <<< - left associative" do - code = "a <<< b <<< c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<< b <<< c") end end describe ">>> operator - left associativity" do test "simple >>>" do - code = "a >>> b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a >>> b") end test "chained >>> - left associative" do - code = "a >>> b >>> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a >>> b >>> c") end end describe "<<~ operator - left associativity" do test "simple <<~" do - code = "a <<~ b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<~ b") end test "chained <<~ - left associative" do - code = "a <<~ b <<~ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<~ b <<~ c") end end describe "~>> operator - left associativity" do test "simple ~>>" do - code = "a ~>> b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ~>> b") end test "chained ~>> - left associative" do - code = "a ~>> b ~>> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ~>> b ~>> c") end end describe "<~ operator - left associativity" do test "simple <~" do - code = "a <~ b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <~ b") end test "chained <~ - left associative" do - code = "a <~ b <~ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <~ b <~ c") end end describe "~> operator - left associativity" do test "simple ~>" do - code = "a ~> b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ~> b") end test "chained ~> - left associative" do - code = "a ~> b ~> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ~> b ~> c") end end describe "<~> operator - left associativity" do test "simple <~>" do - code = "a <~> b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <~> b") end test "chained <~> - left associative" do - code = "a <~> b <~> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <~> b <~> c") end end describe "mixed pipeline family operators" do test "operators in the pipeline family stay left associative" do - code = "a <<< b |> c ~>> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<< b |> c ~>> d") end end describe "< operator - left associativity" do test "simple less than" do - code = "a < b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b") end test "chained < - left associative" do - code = "a < b < c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b < c") end test "< has lower precedence than |>" do - code = "a |> b < c |> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b < c |> d") end test "< has higher precedence than ==" do - code = "a < b == c < d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b == c < d") end end describe "> operator - left associativity" do test "simple greater than" do - code = "a > b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a > b") end test "chained > - left associative" do - code = "a > b > c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a > b > c") end end describe "<= operator - left associativity" do test "simple less than or equal" do - code = "a <= b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <= b") end test "chained <= - left associative" do - code = "a <= b <= c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <= b <= c") end end describe ">= operator - left associativity" do test "simple greater than or equal" do - code = "a >= b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a >= b") end test "chained >= - left associative" do - code = "a >= b >= c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a >= b >= c") end end describe "mixed comparison operators" do test "< and > mixed - left associative" do - code = "a < b > c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b > c") end test "<= and >= mixed - left associative" do - code = "a <= b >= c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <= b >= c") end test "all comparison operators mixed" do - code = "a < b <= c > d >= e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b <= c > d >= e") end end describe "== operator - left associativity" do test "simple equality" do - code = "a == b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b") end test "chained == - left associative" do - code = "a == b == c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b == c") end test "== has lower precedence than <" do - code = "a < b == c < d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b == c < d") end test "== has higher precedence than &&" do - code = "a == b && c == d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b && c == d") end end describe "!= operator - left associativity" do test "simple inequality" do - code = "a != b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a != b") end test "chained != - left associative" do - code = "a != b != c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a != b != c") end end describe "=~ operator - left associativity" do test "simple match" do - code = ~S'"hello" =~ ~r/ell/' - assert spitfire_parse(code) == s2q(code) + assert_conforms(~S'"hello" =~ ~r/ell/') end test "chained =~ - left associative" do - code = "a =~ b =~ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a =~ b =~ c") end end describe "=== operator - left associativity" do test "simple strict equality" do - code = "a === b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a === b") end test "chained === - left associative" do - code = "a === b === c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a === b === c") end end describe "!== operator - left associativity" do test "simple strict inequality" do - code = "a !== b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a !== b") end test "chained !== - left associative" do - code = "a !== b !== c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a !== b !== c") end end describe "mixed equality operators" do test "== and != mixed - left associative" do - code = "a == b != c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b != c") end test "=== and !== mixed - left associative" do - code = "a === b !== c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a === b !== c") end test "all equality operators mixed" do - code = "a == b != c === d !== e =~ f" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b != c === d !== e =~ f") end end describe "&& operator - left associativity" do test "simple and" do - code = "a && b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b") end test "chained && - left associative" do - code = "a && b && c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b && c") end test "&& has lower precedence than ==" do - code = "a == b && c == d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b && c == d") end test "&& has higher precedence than ||" do - code = "a && b || c && d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b || c && d") end end describe "&&& operator - left associativity" do test "simple &&&" do - code = "a &&& b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a &&& b") end test "chained &&& - left associative" do - code = "a &&& b &&& c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a &&& b &&& c") end end describe "and operator - left associativity" do test "simple and" do - code = "a and b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b") end test "chained and - left associative" do - code = "a and b and c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b and c") end test "and has lower precedence than ==" do - code = "a == b and c == d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b and c == d") end test "and has higher precedence than or" do - code = "a and b or c and d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b or c and d") end end describe "mixed AND operators" do test "&& and and mixed" do - code = "a && b and c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b and c") end test "&&& with && and and" do - code = "a &&& b && c and d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a &&& b && c and d") end end describe "|| operator - left associativity" do test "simple or" do - code = "a || b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a || b") end test "chained || - left associative" do - code = "a || b || c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a || b || c") end test "|| has lower precedence than &&" do - code = "a && b || c && d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b || c && d") end test "|| has higher precedence than =" do - code = "a = b || c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b || c") end end describe "||| operator - left associativity" do test "simple |||" do - code = "a ||| b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ||| b") end test "chained ||| - left associative" do - code = "a ||| b ||| c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ||| b ||| c") end end describe "or operator - left associativity" do test "simple or" do - code = "a or b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a or b") end test "chained or - left associative" do - code = "a or b or c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a or b or c") end test "or has lower precedence than and" do - code = "a and b or c and d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b or c and d") end end describe "mixed OR operators" do test "|| and or mixed" do - code = "a || b or c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a || b or c") end test "||| with || and or" do - code = "a ||| b || c or d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ||| b || c or d") end end describe "= operator - right associativity" do test "simple match" do - code = "a = b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b") end test "chained = - right associative" do # a = b = c should be a = (b = c) - code = "a = b = c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b = c") end test "multiple chained =" do - code = "a = b = c = d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b = c = d") end test "= has lower precedence than ||" do - code = "a = b || c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b || c") end test "= has higher precedence than &" do - code = "a = &b/1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = &b/1") end test "pattern match with tuple" do - code = "{a, b} = {1, 2}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("{a, b} = {1, 2}") end test "pattern match with list" do - code = "[h | t] = [1, 2, 3]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[h | t] = [1, 2, 3]") end end describe "& operator - unary" do test "capture function" do - code = "&foo/1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&foo/1") end test "capture with module" do - code = "&Foo.bar/2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&Foo.bar/2") end test "capture with expression" do - code = "&(&1 + 1)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&(&1 + 1)") end test "capture with multiple args" do - code = "&(&1 + &2)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&(&1 + &2)") end test "& has lower precedence than =" do - code = "f = &foo/1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("f = &foo/1") end end describe "... operator - unary" do test "bare ellipsis expression" do - code = "..." - assert spitfire_parse(code) == s2q(code) + assert_conforms("...") end test "ellipsis inside anonymous function body" do - code = "fn -> ... end" - assert spitfire_parse(code) == s2q(code) + assert_conforms("fn -> ... end") end test "= has higher precedence than ..." do - code = "... = a = b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("... = a = b") end test "ellipsis keeps inner arithmetic precedence" do - code = "... + 1 * 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("... + 1 * 2") end test "ellipsis can be captured" do - code = "&..." - assert spitfire_parse(code) == s2q(code) + assert_conforms("&...") end test "ellipsis as term before infix operator" do - code = "... * 1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("... * 1") end test "ellipsis as term before range" do - code = "... .. 1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("... .. 1") end end describe "=> operator - right associativity in maps" do test "simple map with =>" do - code = "%{a => b}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{a => b}") end test "map with multiple =>" do - code = "%{a => b, c => d}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{a => b, c => d}") end test "nested map with =>" do - code = "%{a => %{b => c}}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{a => %{b => c}}") end test "=> with complex keys" do - code = "%{1 + 2 => 3}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{1 + 2 => 3}") end test "=> with complex values" do - code = "%{a => b + c}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{a => b + c}") end end describe "| operator - right associativity" do test "simple cons" do - code = "[a | b]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[a | b]") end test "cons with multiple elements" do - code = "[a, b | c]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[a, b | c]") end test "map update with |" do - code = "%{map | a: 1}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{map | a: 1}") end test "struct update with |" do - code = "%Foo{struct | a: 1}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%Foo{struct | a: 1}") end test "| is right associative across repeated operators" do - code = "a | b | c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a | b | c") end test "| has lower precedence than =>" do - code = "%{a => b | c}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{a => b | c}") end end describe ":: operator - right associativity" do test "simple type annotation" do - code = "a :: integer" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a :: integer") end test "bitstring type" do - code = "<>" - assert spitfire_parse(code) == s2q(code) + assert_conforms("<>") end test "bitstring with size" do - code = "<>" - assert spitfire_parse(code) == s2q(code) + assert_conforms("<>") end test "bitstring with multiple specs" do - code = "<>" - assert spitfire_parse(code) == s2q(code) + assert_conforms("<>") end test "chained :: - right associative" do - code = "a :: b :: c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a :: b :: c") end test ":: has lower precedence than |" do - code = "a | b :: c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a | b :: c") end end describe "when operator - right associativity" do test "simple guard" do - code = "def foo(a) when is_integer(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo(a) when is_integer(a), do: a") end test "simple guard - bracket" do - code = "def foo[a] when is_integer(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo[a] when is_integer(a), do: a") end test "simple guard - string" do - code = "def \"asd\" when is_integer(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def \"asd\" when is_integer(a), do: a") - code = "def 'asd' when is_integer(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def 'asd' when is_integer(a), do: a") - code = "def \"\"\"\nasd\n\"\"\" when is_integer(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def \"\"\"\nasd\n\"\"\" when is_integer(a), do: a") - code = "def '''\nasd\n\''' when is_integer(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def '''\nasd\n\''' when is_integer(a), do: a") - code = "def ~c'asd' when is_integer(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def ~c'asd' when is_integer(a), do: a") - code = "def :\"asd\" when is_integer(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def :\"asd\" when is_integer(a), do: a") end test "multiple guards with and" do - code = "def foo(a) when is_integer(a) and a > 0, do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo(a) when is_integer(a) and a > 0, do: a") end test "multiple guards with or" do - code = "def foo(a) when is_integer(a) or is_float(a), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo(a) when is_integer(a) or is_float(a), do: a") end test "chained when - right associative" do - code = "a when b when c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a when b when c") end test "when has lower precedence than ::" do - code = "a :: b when c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a :: b when c") end end describe "<- operator - left associativity" do test "simple generator" do - code = "for x <- [1, 2, 3], do: x" - assert spitfire_parse(code) == s2q(code) + assert_conforms("for x <- [1, 2, 3], do: x") end test "multiple generators" do - code = "for x <- xs, y <- ys, do: {x, y}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("for x <- xs, y <- ys, do: {x, y}") end test "with expression" do - code = "with {:ok, a} <- foo(), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("with {:ok, a} <- foo(), do: a") end test "<- has lower precedence than when" do - code = "for x when is_integer(x) <- xs, do: x" - assert spitfire_parse(code) == s2q(code) + assert_conforms("for x when is_integer(x) <- xs, do: x") end test "<- groups left to right when chained" do - code = "a <- b <- c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <- b <- c") end end describe "\\\\ operator - left associativity" do test "simple default argument" do - code = "def foo(a \\\\ 1), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo(a \\\\ 1), do: a") end test "multiple default arguments" do - code = "def foo(a \\\\ 1, b \\\\ 2), do: {a, b}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo(a \\\\ 1, b \\\\ 2), do: {a, b}") end test "default with complex expression" do - code = "def foo(a \\\\ 1 + 2), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo(a \\\\ 1 + 2), do: a") end test "\\\\ groups left to right when chained" do - code = "a \\\\ b \\\\ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a \\\\ b \\\\ c") end end describe "precedence interactions" do test "arithmetic vs logical" do - code = "1 + 2 == 3 and 4 - 1 == 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 == 3 and 4 - 1 == 3") end test "comparison vs logical" do - code = "a < b && c > d || e <= f" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b && c > d || e <= f") end test "pipe vs arithmetic" do - code = "1 + 2 |> foo() |> bar() + 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 |> foo() |> bar() + 3") end test "all arithmetic operators" do - code = "1 + 2 * 3 ** 4 / 5 - 6" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 * 3 ** 4 / 5 - 6") end test "unary and binary operators" do - code = "-1 + -2 * -3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("-1 + -2 * -3") end test "complex expression with many operators" do - code = "@foo.bar |> baz() == 1 + 2 * 3 and !flag || default" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo.bar |> baz() == 1 + 2 * 3 and !flag || default") end test "range with arithmetic" do - code = "1 + 2..3 * 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2..3 * 4") end test "list operators with comparison" do - code = "[1] ++ [2] == [1, 2]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[1] ++ [2] == [1, 2]") end test "string concat with comparison" do - code = ~S'"a" <> "b" == "ab"' - assert spitfire_parse(code) == s2q(code) + assert_conforms(~S'"a" <> "b" == "ab"') end test "match with pipe" do - code = "result = a |> b |> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("result = a |> b |> c") end test "capture with match" do - code = "fun = &Foo.bar/2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("fun = &Foo.bar/2") end test "deep nesting of operators" do - code = "a + b * c ** d / e - f" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a + b * c ** d / e - f") end test "boolean expression chain" do - code = "a and b or c and d or e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b or c and d or e") end test "relaxed boolean chain" do - code = "a && b || c && d || e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b || c && d || e") end test "mixed boolean operators" do - code = "a and b && c or d || e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b && c or d || e") end end @@ -1260,24 +1038,19 @@ defmodule Spitfire.OperatorsTest do # The AST should show the leftmost operation as the innermost # Multiplication - code = "a * b * c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a * b * c") # Addition - code = "a + b + c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a + b + c") # Comparison - code = "a < b < c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b < c") # Logical and - code = "a && b && c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b && c") # Pipe - code = "a |> b |> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b |> c") end end @@ -1287,1045 +1060,834 @@ defmodule Spitfire.OperatorsTest do # The AST should show the rightmost operation as the innermost # List concat - code = "a ++ b ++ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ b ++ c") # Match - code = "a = b = c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b = c") # Type - code = "a :: b :: c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a :: b :: c") # When - code = "a when b when c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a when b when c") # Range - code = "a..b..c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a..b..c") end end describe "nullary .. operator" do test "bare range" do - code = ".." - assert spitfire_parse(code) == s2q(code) + assert_conforms("..") end test "nullary range in function call" do - code = "Enum.to_list(..)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("Enum.to_list(..)") end end describe "..// operator" do test "simple range with step" do - code = "1..10//2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1..10//2") end test "range with negative step" do - code = "10..1//-1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("10..1//-1") end test "range with step and variables" do - code = "a..b//c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a..b//c") end test "range with step has correct precedence with +" do - code = "1 + 2..3 + 4//5" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2..3 + 4//5") end test "range with step in comprehension" do - code = "for i <- 1..10//2, do: i" - assert spitfire_parse(code) == s2q(code) + assert_conforms("for i <- 1..10//2, do: i") end end describe "precedence boundaries: @ (highest) vs . (second highest)" do test "@ binds tighter than dot on result" do - code = "@foo.bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo.bar") end test "@ with chained dots" do - code = "@foo.bar.baz" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo.bar.baz") end end describe "precedence boundaries: . vs unary +/-/!/^/not" do test "dot binds tighter than unary minus" do - code = "-foo.bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("-foo.bar") end test "dot binds tighter than unary plus" do - code = "+foo.bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("+foo.bar") end test "dot binds tighter than unary not" do - code = "!foo.bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!foo.bar") end test "dot binds tighter than not keyword" do - code = "not foo.bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("not foo.bar") end test "dot binds tighter than pin" do - code = "^foo.bar" - assert spitfire_parse(code) == s2q(code) + assert_conforms("^foo.bar") end end describe "precedence boundaries: unary +/-/!/^/not vs **" do test "unary minus binds tighter than **" do - code = "-2 ** 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("-2 ** 3") end test "unary plus binds tighter than **" do - code = "+2 ** 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("+2 ** 3") end test "unary ! with ** operand" do - code = "!a ** b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!a ** b") end end describe "precedence boundaries: ** vs * /" do test "** binds tighter than *" do - code = "2 * 3 ** 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 * 3 ** 4") end test "** binds tighter than /" do - code = "8 / 2 ** 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("8 / 2 ** 2") end test "* after ** expression" do - code = "2 ** 3 * 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 ** 3 * 4") end test "/ after ** expression" do - code = "2 ** 3 / 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 ** 3 / 4") end end describe "precedence boundaries: * / vs + -" do test "* binds tighter than +" do - code = "1 + 2 * 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 * 3") end test "/ binds tighter than -" do - code = "6 - 4 / 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("6 - 4 / 2") end test "* binds tighter than -" do - code = "6 - 2 * 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("6 - 2 * 2") end test "/ binds tighter than +" do - code = "1 + 6 / 2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 6 / 2") end end describe "precedence boundaries: + - vs ++ -- +++ --- .. <>" do test "+ binds tighter than ++" do - code = "a + b ++ c + d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a + b ++ c + d") end test "- binds tighter than --" do - code = "a - b -- c - d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a - b -- c - d") end test "+ binds tighter than .." do - code = "1 + 2..3 + 4" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2..3 + 4") end test "+ binds tighter than <>" do - code = "a + b <> c + d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a + b <> c + d") end test "- binds tighter than +++" do - code = "a - b +++ c - d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a - b +++ c - d") end test "- binds tighter than ---" do - code = "a - b --- c - d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a - b --- c - d") end end describe "precedence boundaries: ++ -- +++ --- .. <> vs in/not in" do test "++ binds tighter than in" do - code = "a in b ++ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b ++ c") end test "-- binds tighter than in" do - code = "a in b -- c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b -- c") end test ".. binds tighter than in" do - code = "a in 1..10" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in 1..10") end test "<> binds tighter than in" do - code = "a in b <> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b <> c") end test "++ binds tighter than not in" do - code = "a not in b ++ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a not in b ++ c") end end describe "precedence boundaries: in/not in vs |> <<< >>> <<~ ~>> <~ ~> <~>" do test "in binds tighter than |>" do - code = "a in b |> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b |> c") end test "not in binds tighter than |>" do - code = "a not in b |> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a not in b |> c") end test "in binds tighter than <<<" do - code = "a in b <<< c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b <<< c") end test "in binds tighter than >>>" do - code = "a in b >>> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b >>> c") end test "in binds tighter than ~>" do - code = "a in b ~> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b ~> c") end test "in binds tighter than <~" do - code = "a in b <~ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b <~ c") end test "in binds tighter than <~>" do - code = "a in b <~> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b <~> c") end test "in binds tighter than <<~" do - code = "a in b <<~ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b <<~ c") end test "in binds tighter than ~>>" do - code = "a in b ~>> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a in b ~>> c") end end describe "precedence boundaries: |> <<< >>> <<~ ~>> <~ ~> <~> vs < > <= >=" do test "|> binds tighter than <" do - code = "a |> b < c |> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b < c |> d") end test "|> binds tighter than >" do - code = "a |> b > c |> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b > c |> d") end test "|> binds tighter than <=" do - code = "a |> b <= c |> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b <= c |> d") end test "|> binds tighter than >=" do - code = "a |> b >= c |> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b >= c |> d") end test "<<< binds tighter than <" do - code = "a <<< b < c <<< d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<< b < c <<< d") end test ">>> binds tighter than >" do - code = "a >>> b > c >>> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a >>> b > c >>> d") end test "~> binds tighter than <" do - code = "a ~> b < c ~> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ~> b < c ~> d") end end describe "precedence boundaries: < > <= >= vs == != =~ === !==" do test "< binds tighter than ==" do - code = "a < b == c < d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b == c < d") end test "> binds tighter than !=" do - code = "a > b != c > d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a > b != c > d") end test "<= binds tighter than ===" do - code = "a <= b === c <= d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <= b === c <= d") end test ">= binds tighter than !==" do - code = "a >= b !== c >= d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a >= b !== c >= d") end test "< binds tighter than =~" do - code = "a < b =~ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b =~ c") end end describe "precedence boundaries: == != =~ === !== vs && &&& and" do test "== binds tighter than &&" do - code = "a == b && c == d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b && c == d") end test "!= binds tighter than &&&" do - code = "a != b &&& c != d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a != b &&& c != d") end test "=== binds tighter than and" do - code = "a === b and c === d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a === b and c === d") end test "!== binds tighter than &&" do - code = "a !== b && c !== d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a !== b && c !== d") end test "=~ binds tighter than and" do - code = "a =~ b and c =~ d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a =~ b and c =~ d") end end describe "precedence boundaries: && &&& and vs || ||| or" do test "&& binds tighter than ||" do - code = "a && b || c && d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b || c && d") end test "&&& binds tighter than |||" do - code = "a &&& b ||| c &&& d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a &&& b ||| c &&& d") end test "and binds tighter than or" do - code = "a and b or c and d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b or c and d") end test "&& binds tighter than or" do - code = "a && b or c && d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b or c && d") end test "and binds tighter than ||" do - code = "a and b || c and d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b || c and d") end end describe "precedence boundaries: || ||| or vs =" do test "|| binds tighter than =" do - code = "a = b || c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b || c") end test "||| binds tighter than =" do - code = "a = b ||| c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b ||| c") end test "or binds tighter than =" do - code = "a = b or c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b or c") end test "|| on both sides of =" do - code = "a || b = c || d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a || b = c || d") end end describe "precedence boundaries: = vs & ..." do test "= binds tighter than &" do - code = "f = &foo/1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("f = &foo/1") end test "& captures result of =" do - code = "&(a = b)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&(a = b)") end end describe "precedence boundaries: & vs =>" do test "& and => in map" do - code = "%{&foo/1 => 1}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{&foo/1 => 1}") end test "=> with capture on both sides" do - code = "%{&foo/1 => &bar/2}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{&foo/1 => &bar/2}") end end describe "precedence boundaries: => vs |" do test "=> binds tighter than | in map" do - code = "%{a => b | c}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{a => b | c}") end test "| in map update with =>" do - code = "%{map | a => b}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{map | a => b}") end end describe "precedence boundaries: | vs ::" do test "| binds tighter than ::" do - code = "a | b :: c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a | b :: c") end test ":: after | in type spec context" do - code = "@spec foo(a | b :: c)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec foo(a | b :: c)") end test ":: after | in type spec context - bracket" do - code = "@spec foo[a | b :: c]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec foo[a | b :: c]") end test ":: after | in type spec context - string" do - code = "@spec \"sdc\", (a | b :: c)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec \"sdc\", (a | b :: c)") - code = "@spec 'sdc', (a | b :: c)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec 'sdc', (a | b :: c)") - code = "@spec :\"sdc\", (a | b :: c)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec :\"sdc\", (a | b :: c)") - code = "@spec \"\"\"\nsdc\n\"\"\", (a | b :: c)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec \"\"\"\nsdc\n\"\"\", (a | b :: c)") - code = "@spec '''\nsdc\n''', (a | b :: c)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec '''\nsdc\n''', (a | b :: c)") - code = "@spec ~c'sdc', (a | b :: c)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec ~c'sdc', (a | b :: c)") end end describe "precedence boundaries: :: vs when" do test ":: binds tighter than when" do - code = "a :: b when c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a :: b when c") end test "when with typed parameter" do - code = "@spec foo(a :: integer) when a: term" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec foo(a :: integer) when a: term") end end describe "precedence boundaries: when vs <- \\\\" do test "when binds tighter than <-" do - code = "for x when is_integer(x) <- xs, do: x" - assert spitfire_parse(code) == s2q(code) + assert_conforms("for x when is_integer(x) <- xs, do: x") end test "when binds tighter than \\\\" do - code = "x when true \\\\ default" - assert spitfire_parse(code) == s2q(code) + assert_conforms("x when true \\\\ default") end test "<- with when guard" do - code = "for x when x > 0 <- list, do: x" - assert spitfire_parse(code) == s2q(code) + assert_conforms("for x when x > 0 <- list, do: x") end end describe "same precedence level: ++ -- +++ --- .. <>" do test "++ and -- at same precedence" do - code = "a ++ b -- c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ b -- c") end test "++ and .. at same precedence" do - code = "a ++ 1..10" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ 1..10") end test "-- and <> at same precedence" do - code = "a -- b <> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a -- b <> c") end test "+++ and --- at same precedence" do - code = "a +++ b --- c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a +++ b --- c") end test "all list ops mixed" do - code = "a ++ b -- c +++ d --- e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ b -- c +++ d --- e") end test "<> and ++ and .." do - code = "a <> b ++ 1..10" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <> b ++ 1..10") end end describe "same precedence level: |> <<< >>> <<~ ~>> <~ ~> <~>" do test "|> and <<< at same precedence" do - code = "a |> b <<< c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b <<< c") end test "|> and >>> at same precedence" do - code = "a |> b >>> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b >>> c") end test "<<< and >>> at same precedence" do - code = "a <<< b >>> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<< b >>> c") end test "<<~ and ~>> at same precedence" do - code = "a <<~ b ~>> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<~ b ~>> c") end test "<~ and ~> at same precedence" do - code = "a <~ b ~> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <~ b ~> c") end test "<~> with other arrow ops" do - code = "a <~> b |> c ~> d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <~> b |> c ~> d") end test "all arrow ops mixed" do - code = "a |> b <<< c >>> d <<~ e ~>> f <~ g ~> h <~> i" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b <<< c >>> d <<~ e ~>> f <~ g ~> h <~> i") end end describe "same precedence level: < > <= >=" do test "< and > at same precedence" do - code = "a < b > c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b > c") end test "<= and >= at same precedence" do - code = "a <= b >= c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <= b >= c") end test "< and >= at same precedence" do - code = "a < b >= c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b >= c") end test "> and <= at same precedence" do - code = "a > b <= c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a > b <= c") end test "all comparison ops" do - code = "a < b > c <= d >= e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b > c <= d >= e") end end describe "same precedence level: == != =~ === !==" do test "== and != at same precedence" do - code = "a == b != c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b != c") end test "=== and !== at same precedence" do - code = "a === b !== c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a === b !== c") end test "== and =~ at same precedence" do - code = "a == b =~ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b =~ c") end test "all equality ops" do - code = "a == b != c =~ d === e !== f" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a == b != c =~ d === e !== f") end end describe "same precedence level: && &&& and" do test "&& and &&& at same precedence" do - code = "a && b &&& c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b &&& c") end test "&& and and at same precedence" do - code = "a && b and c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b and c") end test "&&& and and at same precedence" do - code = "a &&& b and c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a &&& b and c") end test "all AND ops" do - code = "a && b &&& c and d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a && b &&& c and d") end end describe "same precedence level: || ||| or" do test "|| and ||| at same precedence" do - code = "a || b ||| c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a || b ||| c") end test "|| and or at same precedence" do - code = "a || b or c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a || b or c") end test "||| and or at same precedence" do - code = "a ||| b or c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ||| b or c") end test "all OR ops" do - code = "a || b ||| c or d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a || b ||| c or d") end end describe "same precedence level: <- \\\\" do test "<- and \\\\ at same precedence" do - code = "a <- b \\\\ c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <- b \\\\ c") end test "\\\\ and <- at same precedence" do - code = "a \\\\ b <- c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a \\\\ b <- c") end end describe "edge cases" do test "parentheses override precedence" do - code = "(1 + 2) * 3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("(1 + 2) * 3") end test "nested parentheses" do - code = "((1 + 2) * (3 + 4))" - assert spitfire_parse(code) == s2q(code) + assert_conforms("((1 + 2) * (3 + 4))") end test "operator with newline" do - code = "1 +\n2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 +\n2") end test "chained operators with newlines" do - code = "1 +\n2 *\n3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 +\n2 *\n3") end test "pipe with newlines" do - code = "a\n|> b\n|> c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a\n|> b\n|> c") end test "unary operators with parens" do - code = "-(1 + 2)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("-(1 + 2)") end test "not with parens" do - code = "not (a and b)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("not (a and b)") end test "! with parens" do - code = "!(a && b)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!(a && b)") end test "access syntax with operators" do - code = "foo[a + b]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo[a + b]") end test "function call with operator expression" do - code = "foo(a + b, c * d)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo(a + b, c * d)") end test "deeply nested arithmetic" do - code = "1 + 2 * 3 ** 4 / 5 - 6 + 7 * 8" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 + 2 * 3 ** 4 / 5 - 6 + 7 * 8") end test "mixed unary operators" do - code = "- - - -a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("- - - -a") end test "unary not chain" do - code = "not not not a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("not not not a") end test "unary ! chain" do - code = "!!!a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("!!!a") end test "complex pipe chain" do - code = "a |> b() |> c(1, 2) |> d.e() |> f" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b() |> c(1, 2) |> d.e() |> f") end test "binary operator after pipe" do - code = "a |> b + c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> b + c") end test "comparison chain" do - code = "1 < 2 <= 3 > 0 >= -1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("1 < 2 <= 3 > 0 >= -1") end test "match in if condition" do - code = "if a = b, do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("if a = b, do: a") end test "operator in anonymous function" do - code = "fn a, b -> a + b end" - assert spitfire_parse(code) == s2q(code) + assert_conforms("fn a, b -> a + b end") end test "operator in case clause" do - code = "case a do\n x when x > 0 -> x * 2\n _ -> 0\nend" - assert spitfire_parse(code) == s2q(code) + assert_conforms("case a do\n x when x > 0 -> x * 2\n _ -> 0\nend") end test "multiple matches on same line" do - code = "a = b = c = 1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a = b = c = 1") end test "range in case pattern" do - code = "case x do\n n when n in 1..10 -> :small\n _ -> :large\nend" - assert spitfire_parse(code) == s2q(code) + assert_conforms("case x do\n n when n in 1..10 -> :small\n _ -> :large\nend") end end describe "full precedence chain tests" do test "expression using operators from many precedence levels" do # @ > . > unary > ** > * > + > ++ > in > |> > < > == > && > || > = - code = "result = a || b && c == d < e |> f in g ++ h + i * j ** k" - assert spitfire_parse(code) == s2q(code) + assert_conforms("result = a || b && c == d < e |> f in g ++ h + i * j ** k") end test "complex boolean expression with all logical operators" do - code = "a and b && c or d || e and f && g or h" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a and b && c or d || e and f && g or h") end test "arithmetic with all arithmetic operators" do - code = "a ** b * c / d + e - f" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ** b * c / d + e - f") end test "list operations chain" do - code = "a ++ b -- c ++ d -- e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a ++ b -- c ++ d -- e") end test "all comparison operators in sequence" do - code = "a < b > c <= d >= e == f != g === h !== i =~ j" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a < b > c <= d >= e == f != g === h !== i =~ j") end test "@ with full operator chain" do - code = "@foo + 1 * 2 == 3 and true" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@foo + 1 * 2 == 3 and true") end test "dot call with full operator chain" do - code = "a.b + c.d * e.f == g.h" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a.b + c.d * e.f == g.h") end test "capture with complex expression" do - code = "&(&1 + &2 * &3)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&(&1 + &2 * &3)") end test "match with complex right side" do - code = "{a, b} = c ++ d |> e" - assert spitfire_parse(code) == s2q(code) + assert_conforms("{a, b} = c ++ d |> e") end test "guard clause with multiple operators" do - code = "def foo(a, b) when is_integer(a) and a > 0 and b < 100 or is_float(a), do: a + b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo(a, b) when is_integer(a) and a > 0 and b < 100 or is_float(a), do: a + b") end test "type spec with union and constraints" do - code = "@spec foo(a :: integer | float, b :: atom) :: boolean when a: number" - assert spitfire_parse(code) == s2q(code) + assert_conforms("@spec foo(a :: integer | float, b :: atom) :: boolean when a: number") end test "comprehension with complex generators and filters" do - code = "for x <- xs, x > 0, y <- ys, x + y < 10, do: {x, y}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("for x <- xs, x > 0, y <- ys, x + y < 10, do: {x, y}") end test "with expression with multiple clauses" do - code = "with {:ok, a} <- foo(), {:ok, b} <- bar(a), c = a + b, do: c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("with {:ok, a} <- foo(), {:ok, b} <- bar(a), c = a + b, do: c") end test "bitstring with multiple type specs" do - code = "<>" - assert spitfire_parse(code) == s2q(code) + assert_conforms("<>") end test "map with arrow and expression values" do - code = "%{a + b => c * d, e => f || g}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%{a + b => c * d, e => f || g}") end test "struct with update and expressions" do - code = "%Foo{bar | a: b + c, d: e * f}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("%Foo{bar | a: b + c, d: e * f}") end test "nested data structures with operators" do - code = "[a: b + c, d: [e * f, g | h]]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[a: b + c, d: [e * f, g | h]]") end end describe "regression and tricky cases" do test "minus after dot without space" do - code = "a.b-c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a.b-c") end test "plus after dot without space" do - code = "a.b+c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a.b+c") end test "unary minus in function call" do - code = "foo(-1)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo(-1)") end test "unary minus in list" do - code = "[-1, -2, -3]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[-1, -2, -3]") end test "unary plus in tuple" do - code = "{+1, +2}" - assert spitfire_parse(code) == s2q(code) + assert_conforms("{+1, +2}") end test "not in with complex expression" do - code = "a + b not in c ++ d" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a + b not in c ++ d") end test "double pipe confusion" do # || is boolean or, | is cons/union - code = "a || [b | c]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a || [b | c]") end test "for generator rhs with || [] inside assoc-map list value" do - code = ~S(%{"s" => [for x <- y || [] do x end]}) - - assert spitfire_parse(code) == s2q(code) + assert_conforms(~S(%{"s" => [for x <- y || [] do x end]})) end test "triple less than" do # <<< is custom operator, << is bitstring - code = "a <<< b" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a <<< b") end test "power with negative exponent" do - code = "2 ** -3" - assert spitfire_parse(code) == s2q(code) + assert_conforms("2 ** -3") end test "range with negative bounds" do - code = "-10..-1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("-10..-1") end test "range with negative step" do - code = "10..1//-1" - assert spitfire_parse(code) == s2q(code) + assert_conforms("10..1//-1") end test "capture with arithmetic" do - code = "&(&1 + 1)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&(&1 + 1)") end test "pin in match with binary operator" do - code = "^a = b + c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("^a = b + c") end test "when in anonymous function" do - code = "fn x when x > 0 -> x end" - assert spitfire_parse(code) == s2q(code) + assert_conforms("fn x when x > 0 -> x end") end test "multiple when clauses" do - code = "fn x when is_integer(x) when x > 0 -> x end" - assert spitfire_parse(code) == s2q(code) + assert_conforms("fn x when is_integer(x) when x > 0 -> x end") end test "default argument with complex expression" do - code = "def foo(a \\\\ 1 + 2 * 3), do: a" - assert spitfire_parse(code) == s2q(code) + assert_conforms("def foo(a \\\\ 1 + 2 * 3), do: a") end test "access in operator expression" do - code = "a[b] + c[d]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a[b] + c[d]") end test "parens in operator expression" do - code = "a(b) + c(d)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a(b) + c(d)") end test "dot call on result of operator" do - code = "(a + b).c" - assert spitfire_parse(code) == s2q(code) + assert_conforms("(a + b).c") end test "operators in sigil" do - code = "~w(a + b)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("~w(a + b)") end test "string interpolation with operators" do - code = ~S'"result: #{a + b * c}"' - assert spitfire_parse(code) == s2q(code) + assert_conforms(~S'"result: #{a + b * c}"') end test "heredoc with operators" do - code = ~s'"""\n\#{a + b}\n"""' - assert spitfire_parse(code) == s2q(code) + assert_conforms(~s'"""\n\#{a + b}\n"""') end test "keyword list with operator values" do - code = "[a: b + c, d: e * f]" - assert spitfire_parse(code) == s2q(code) + assert_conforms("[a: b + c, d: e * f]") end test "keyword argument in function call" do - code = "foo(a, b: c + d)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("foo(a, b: c + d)") end test "pipe into keyword function" do - code = "a |> foo(b: c)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("a |> foo(b: c)") end test "capture with module function" do - code = "&Mod.fun/2" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&Mod.fun/2") end test "capture placeholder in operator" do - code = "&(&1 <> &2)" - assert spitfire_parse(code) == s2q(code) + assert_conforms("&(&1 <> &2)") end end - - defp spitfire_parse(code, _options \\ []) do - case Spitfire.parse(code) do - {:ok, ast} -> {:ok, ast} - {:error, _ast, _errors} -> {:error, :parse_error} - {:error, :no_fuel_remaining} -> {:error, :no_fuel_remaining} - end - end - - defp s2q(code, opts \\ []) do - Code.string_to_quoted( - code, - Keyword.merge([columns: true, token_metadata: true, emit_warnings: false], opts) - ) - end end From a7d61dbba82dadb9c2759b298fef4f613ffaeb25 Mon Sep 17 00:00:00 2001 From: Marek Kaput Date: Thu, 19 Feb 2026 14:01:19 +0100 Subject: [PATCH 4/4] refactor(test): use Spitfire.Assertions in spitfire_test.exs --- test/spitfire_test.exs | 596 +++++++++++++++-------------------------- 1 file changed, 213 insertions(+), 383 deletions(-) diff --git a/test/spitfire_test.exs b/test/spitfire_test.exs index 1dff579..a935ff8 100644 --- a/test/spitfire_test.exs +++ b/test/spitfire_test.exs @@ -1,42 +1,34 @@ defmodule SpitfireTest do use ExUnit.Case, async: true + import Spitfire.Assertions, only: [assert_conforms: 1, s2q: 2] + doctest Spitfire describe "valid code" do test "semicolons" do - code = "res = Foo.Bar.run(1, 2, 3); IO.inspect(res)" - - assert Spitfire.parse(code) == s2q(code) + assert_conforms("res = Foo.Bar.run(1, 2, 3); IO.inspect(res)") - code = ~S''' + assert_conforms(~S''' res = Foo.Bar.run(1, 2, 3); IO.inspect(res) - ''' + ''') - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' fn one -> IO.inspect(one); one end - ''' + ''') - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' def foo, do: IO.inspect("bob"); "bob" - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' foo do: IO.inspect("bob"); "bob" - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "parses valid elixir" do - code = """ + assert_conforms(""" defmodule Foo do use AnotherMod.Nested, some: :option @@ -46,9 +38,7 @@ defmodule SpitfireTest do :ok end end - """ - - assert Spitfire.parse(code) == s2q(code) + """) end test "access syntax" do @@ -70,28 +60,24 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "token metadata" do - code = ~S''' + assert_conforms(~S''' foo do 1 + 1 end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' foo do bar do 1 + 1 end end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "type syntax" do @@ -122,135 +108,101 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "parses unary operators" do - code = ~S''' + assert_conforms(~S''' ^foo - ''' + ''') - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' ^ foo - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "parses numbers" do - code = """ + assert_conforms(""" 111_111 - """ - - assert Spitfire.parse(code) == s2q(code) + """) - code = """ + assert_conforms(""" 1.4 - """ - - assert Spitfire.parse(code) == s2q(code) + """) end test "parses strings" do - code = ~s''' + assert_conforms(~s''' "foobar" - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' """ foobar """ - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "parses charlists" do - code = ~s''' + assert_conforms(~s''' 'foobar' - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S""" + assert_conforms(~S""" ''' foobar ''' - """ + """) - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' 'foo#{alice}bar' - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' 'foo#{ alice }bar' - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S""" + assert_conforms(~S""" ''' foo#{alice}bar ''' - """ - - assert Spitfire.parse(code) == s2q(code) + """) - code = ~S""" + assert_conforms(~S""" ''' foo#{ alice }bar ''' - """ - - assert Spitfire.parse(code) == s2q(code) + """) end test "parses atoms" do - code = ~s''' + assert_conforms(~s''' :foobar - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~s''' + assert_conforms(~s''' :"," - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' :"foo#{}" - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' :"foo#{bar}" - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "parses left stab" do - code = """ + assert_conforms(""" apple <- apples - """ - - assert Spitfire.parse(code) == s2q(code) + """) end # these tests do not test against Code.string_to_quoted because these code fragments parse as errors @@ -418,7 +370,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -432,7 +384,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -448,18 +400,16 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "parses variable identifiers" do - code = ~s''' + assert_conforms(~s''' foobar alice bob - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "parses lists" do @@ -488,7 +438,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -523,18 +473,16 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "another thing" do - code = ~S''' + assert_conforms(~S''' case foo do :kw_identifier when is_list or is_map -> &parse_kw_identifier/1 end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "parses pattern matching in list" do @@ -548,7 +496,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -587,7 +535,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -619,7 +567,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -683,18 +631,14 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "parses ambiguous map update" do - code = ~S'%{a do :ok end | b c, d => e}' - - assert Spitfire.parse(code) == s2q(code) + assert_conforms(~S'%{a do :ok end | b c, d => e}') - code = ~S'%{a do :ok end | b c, d => e, f => g}' - - assert Spitfire.parse(code) == s2q(code) + assert_conforms(~S'%{a do :ok end | b c, d => e, f => g}') end test "parses structs" do @@ -734,7 +678,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -886,7 +830,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -954,7 +898,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -1034,55 +978,43 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "multi line grouped expressions" do - code = ~S''' + assert_conforms(~S''' ( min_line = line(meta) max_line = closing_line(meta) Enum.any?(comments, fn %{line: line} -> line > min_line and line < max_line end) ) - ''' + ''') - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' (min_line = line(meta) max_line = closing_line(meta) Enum.any?(comments, fn %{line: line} -> line > min_line and line < max_line end)) - ''' + ''') - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' (foo -> bar baz -> boo) - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "when syntax inside normal expression" do - code = ~S''' + assert_conforms(~S''' match?(x when is_nil(x), x) - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "when syntax inside keyword list" do - code = ~S'[a: b when c]' - - assert Spitfire.parse(code) == s2q(code) + assert_conforms(~S'[a: b when c]') end test "in_match_op inside keyword list" do - code = ~S'[a: b <- c]' - - assert Spitfire.parse(code) == s2q(code) + assert_conforms(~S'[a: b <- c]') end test "case expr" do @@ -1123,7 +1055,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -1139,7 +1071,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -1227,7 +1159,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -1264,48 +1196,38 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "parses nested stab expressions" do - assert Spitfire.parse(~s'fn :a -> 1; :b -> 2 end') == s2q(~s'fn :a -> 1; :b -> 2 end') - assert Spitfire.parse(~s'fn x -> fn y -> x + y end end') == s2q(~s'fn x -> fn y -> x + y end end') - assert Spitfire.parse(~s'fn x, y -> x + y end') == s2q(~s'fn x, y -> x + y end') - - assert Spitfire.parse(~s'fn {:ok, x} -> x; {:error, _} -> nil end') == - s2q(~s'fn {:ok, x} -> x; {:error, _} -> nil end') - - assert Spitfire.parse(~s'fn a -> fn b -> fn c -> a + b + c end end end') == - s2q(~s'fn a -> fn b -> fn c -> a + b + c end end end') - - assert Spitfire.parse(~s'fn x -> Enum.map(x, fn y -> y * 2 end) end') == - s2q(~s'fn x -> Enum.map(x, fn y -> y * 2 end) end') + assert_conforms(~s'fn :a -> 1; :b -> 2 end') + assert_conforms(~s'fn x -> fn y -> x + y end end') + assert_conforms(~s'fn x, y -> x + y end') + assert_conforms(~s'fn {:ok, x} -> x; {:error, _} -> nil end') + assert_conforms(~s'fn a -> fn b -> fn c -> a + b + c end end end') + assert_conforms(~s'fn x -> Enum.map(x, fn y -> y * 2 end) end') end test "parses multi-line stab expressions" do - code = ~S''' + assert_conforms(~S''' fn :a -> 1 :b -> 2 :c -> 3 end - ''' + ''') - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' fn x when is_integer(x) -> x * 2 x when is_binary(x) -> String.length(x) end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' case x do {:ok, val} -> val @@ -1314,11 +1236,9 @@ defmodule SpitfireTest do _ -> nil end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' cond do x > 0 -> :positive @@ -1327,11 +1247,9 @@ defmodule SpitfireTest do true -> :zero end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' receive do {:msg, data} -> handle(data) @@ -1341,11 +1259,9 @@ defmodule SpitfireTest do 1000 -> :timeout end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' try do risky_operation() rescue @@ -1354,40 +1270,27 @@ defmodule SpitfireTest do e in ArgumentError -> handle_argument(e) end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "parses match operator" do - codes = [ - ~s''' - foo = :bar - ''' - ] - - for code <- codes do - assert Spitfire.parse(code) == s2q(code) - end + assert_conforms(~s''' + foo = :bar + ''') end test "parses nil" do - code = "nil" - assert Spitfire.parse(code) == s2q(code) + assert_conforms("nil") end test "parses booleans" do - code = "false" - assert Spitfire.parse(code) == s2q(code) + assert_conforms("false") - code = "true" - assert Spitfire.parse(code) == s2q(code) + assert_conforms("true") end test "parses right stab argument with parens" do - code = "if true do (x, y) -> x end" - - assert Spitfire.parse(code) == s2q(code) + assert_conforms("if true do (x, y) -> x end") end test "parses cond expression" do @@ -1403,20 +1306,18 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "|> operator" do - code = ~S''' + assert_conforms(~S''' def parse(code) do parser = code |> new() |> next_token() |> next_token() parse_program(parser) end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "when operator" do @@ -1479,7 +1380,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -1501,7 +1402,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -1525,12 +1426,12 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "case with" do - code = ~S''' + assert_conforms(~S''' Repo.transaction(fn -> case with {:ok, api_key} <- api_key_changeset @@ -1546,13 +1447,11 @@ defmodule SpitfireTest do а -> а end end) - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "big test" do - code = ~S''' + assert_conforms(~S''' if prefix == nil do {row, col} = token_loc(parser.current_token) @@ -1629,13 +1528,11 @@ defmodule SpitfireTest do end end end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "function def with case expression with anon function inside" do - code = ~S''' + assert_conforms(~S''' def bar(foo) do case foo do :foo -> @@ -1646,13 +1543,11 @@ defmodule SpitfireTest do end) end end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "case expression with anon function inside" do - code = ~S''' + assert_conforms(~S''' case foo do :foo -> :ok @@ -1661,13 +1556,11 @@ defmodule SpitfireTest do item.name end) end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "not really sure" do - code = ~S''' + assert_conforms(~S''' defp parse_stab_expression(parser, lhs) do case current_token(parser) do :<- -> @@ -1735,25 +1628,21 @@ defmodule SpitfireTest do {ast, eat_eol(parser)} end end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "lonely parens" do - code = ~S''' + assert_conforms(~S''' () (()) foo do () end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "multi line for clause" do - code = ~S''' + assert_conforms(~S''' for {^function, arity} <- exports, (if docs do find_doc_with_content(docs, function, arity) @@ -1762,19 +1651,15 @@ defmodule SpitfireTest do end) do h_mod_fun_arity(module, function, arity) end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "ambiguous op" do - code = "@all_info -1" - assert Spitfire.parse(code) == s2q(code) + assert_conforms("@all_info -1") end test "range op" do - code = ".." - assert Spitfire.parse(code) == s2q(code) + assert_conforms("..") end test "range operator precedence and nested ranges" do @@ -1789,12 +1674,12 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "from nx repo" do - code = ~S''' + assert_conforms(~S''' def a, do: [ b: :c, @@ -1804,23 +1689,19 @@ defmodule SpitfireTest do x end, "g"} ] - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "from ecto repo" do - code = ~S''' + assert_conforms(~S''' @switches [ repo: [:string, :keep], ] - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "big with" do - code = ~S''' + assert_conforms(~S''' with {:ok, _} <- bar(fn a -> with :d <- b do :f @@ -1828,53 +1709,41 @@ defmodule SpitfireTest do end) do :ok end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "bitstrings" do - code = ~S'<>' - - assert Spitfire.parse(code) == s2q(code) + assert_conforms(~S'<>') - code = ~S''' + assert_conforms(~S''' << ?., char, rest::binary >> - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "anonymous function typespecs" do - code = ~S''' + assert_conforms(~S''' @spec start_link((-> term), GenServer.options()) :: on_start - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' @spec get(agent, (state -> a), timeout) :: a when a: var - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "rescue with def" do - code = ~S''' + assert_conforms(~S''' def foo(%mod{} = bar) do :ok end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "multiple block identifiers" do - code = ~S''' + assert_conforms(~S''' try do foo() rescue @@ -1884,28 +1753,22 @@ defmodule SpitfireTest do {:ok, value} -> value :error -> default end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "starts with a comment" do - code = """ + assert_conforms(""" # hi there some_code = :foo - """ - - assert Spitfire.parse(code) == s2q(code) + """) end test "default args" do - code = ~S''' + assert_conforms(~S''' def foo(arg \\ :value) do :ok end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "literal encoder" do @@ -1987,54 +1850,42 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "parses string interpolation" do - code = ~S''' + assert_conforms(~S''' "foo#{alice}bar" - ''' + ''') - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' "foo#{ alice }bar" - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' "foo#{}bar" - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' """ foo#{alice}bar """ - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' """ foo#{ alice }bar """ - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' "#{foo}" - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "end of expression metadata" do @@ -2063,7 +1914,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -2078,7 +1929,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -2091,12 +1942,12 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "from nextls test" do - code = ~S''' + assert_conforms(~S''' defmodule Foo do defstruct [:foo, bar: "yo"] @@ -2113,9 +1964,7 @@ defmodule SpitfireTest do :something end end - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "unquote_splicing" do @@ -2140,7 +1989,7 @@ defmodule SpitfireTest do ] for code <- codes do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -2151,30 +2000,24 @@ defmodule SpitfireTest do end test "ellipsis_op ..." do - code = ~S''' + assert_conforms(~S''' @callback a([B.spec(), ...], C.t(), D.t()) :: [ E.spec(), ... ] - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') - code = ~S''' + assert_conforms(~S''' ... + 1 * 2 - ''' + ''') - assert Spitfire.parse(code) == s2q(code) - - code = ~S''' + assert_conforms(~S''' @type fun :: (... -> any()) - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "blocks inside an anon function as a parameter" do - code = ~S""" + assert_conforms(~S""" M.m fn -> what a do :alice @@ -2182,9 +2025,7 @@ defmodule SpitfireTest do :bob end end - """ - - assert Spitfire.parse(code) == s2q(code) + """) end test "parens on a macro with a do block on the right side of a match operator" do @@ -2203,7 +2044,7 @@ defmodule SpitfireTest do """ ] do # code |> Spitfire.parse!() |> Macro.to_string() |> IO.puts() - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -2215,7 +2056,7 @@ defmodule SpitfireTest do ~S|['➡️': x]|, ~S|foo.'➡️'| ] do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end @@ -2228,79 +2069,75 @@ defmodule SpitfireTest do :erlang.'=<'(left, right) """ ] do - assert Spitfire.parse(code) == s2q(code) + assert_conforms(code) end end test "operators on unmatched expression" do - code = ~S''' + assert_conforms(~S''' +case a do _ -> b end |> c ** d >>> e - ''' - - assert Spitfire.parse(code) == s2q(code) + ''') end test "when in bracketless kw list" do - code = ~S'with a <- b, do: c when a' - - assert Spitfire.parse(code) == s2q(code) + assert_conforms(~S'with a <- b, do: c when a') end # These were found by property tests but were not triggering reliably # We have them here to make sure they don't regress test "property test regression cases" do # Prefix operators in struct types - assert Spitfire.parse("%?0{}") == s2q("%?0{}") - assert Spitfire.parse("%^@_{}") == s2q("%^@_{}") - assert Spitfire.parse("%-..{}") == s2q("%-..{}") - assert Spitfire.parse("%!:c{}") == s2q("%!:c{}") - assert Spitfire.parse("%~a<>{}") == s2q("%~a<>{}") - assert Spitfire.parse("%!A{}") == s2q("%!A{}") - assert Spitfire.parse("%@A{}") == s2q("%@A{}") - assert Spitfire.parse("%@:rd{}") == s2q("%@:rd{}") - assert Spitfire.parse("%!0{}") == s2q("%!0{}") + assert_conforms("%?0{}") + assert_conforms("%^@_{}") + assert_conforms("%-..{}") + assert_conforms("%!:c{}") + assert_conforms("%~a<>{}") + assert_conforms("%!A{}") + assert_conforms("%@A{}") + assert_conforms("%@:rd{}") + assert_conforms("%!0{}") # Nested module attributes - assert Spitfire.parse("%@@u{}") == s2q("%@@u{}") + assert_conforms("%@@u{}") # Quoted atoms - assert Spitfire.parse(~s(%:""{})) == s2q(~s(%:""{})) + assert_conforms(~s(%:""{})) # Range operator in struct types - assert Spitfire.parse("%..{}") == s2q("%..{}") + assert_conforms("%..{}") # Char tokens after module attributes in struct types - assert Spitfire.parse("%@?w{}") == s2q("%@?w{}") + assert_conforms("%@?w{}") # Empty char list after module attributes in struct types - assert Spitfire.parse("%@''{}") == s2q(~s(%@''{})) + assert_conforms(~s(%@''{})) # Float in struct types - assert Spitfire.parse("%0.0{}") == s2q("%0.0{}") + assert_conforms("%0.0{}") # Bin strings after module attributes in struct types - assert Spitfire.parse(~s(%@"foo"{})) == s2q(~s(%@"foo"{})) + assert_conforms(~s(%@"foo"{})) # Capture operator in struct types - assert Spitfire.parse("%&0{}") == s2q("%&0{}") + assert_conforms("%&0{}") # Boolean literals in struct types - assert Spitfire.parse("%false{}") == s2q("%false{}") - assert Spitfire.parse("%true{}") == s2q("%true{}") + assert_conforms("%false{}") + assert_conforms("%true{}") # Struct arg inside struct arg - assert Spitfire.parse("%%{}{}") == s2q("%%{}{}") - assert Spitfire.parse("%+[]{}") == s2q("%+[]{}") + assert_conforms("%%{}{}") + assert_conforms("%+[]{}") # In-match operator (<-) in map keys - should be part of key, not wrap it - assert Spitfire.parse("%{s\\\\r => 1}") == s2q("%{s\\\\r => 1}") + assert_conforms("%{s\\\\r => 1}") # Struct type with dot-call target - assert Spitfire.parse("%e.(){}") == s2q("%e.(){}") - assert Spitfire.parse("%e.(1){}") == s2q("%e.(1){}") - assert Spitfire.parse("%e.(a, b){}") == s2q("%e.(a, b){}") + assert_conforms("%e.(){}") + assert_conforms("%e.(1){}") + assert_conforms("%e.(a, b){}") end end @@ -3772,13 +3609,6 @@ defmodule SpitfireTest do end end - defp s2q(code, opts \\ []) do - Code.string_to_quoted( - code, - Keyword.merge([columns: true, token_metadata: true, emit_warnings: false], opts) - ) - end - defp s2qwc(code, opts \\ []) do Code.string_to_quoted_with_comments( code,