Skip to content

Commit 3e6d5d6

Browse files
👽 Prepare for Elixir 1.19 (#137)
There are a number of deprecations in 1.19 that we need to handle: - the separator character for `mix do` changes from `,` to `+` - regexes can no longer be used in compile-time contexts (module attributes and struct defaults) - regexes can no longer be compared directly with each other; they need to be compared on their `Regex.source` instead There's also a forthcoming deprecation in 1.20 that we need to work around: - `ExUnit.Filters.parse_path/1` is being deprecated in favor of `ExUnit.Filters.parse_paths/1`. The newer function is only available in Elixir 1.16 and newer and we (and Elixir) still support 1.14 and 1.15, so we conditionally use the new function in Elixir 1.20 and above. Closes #138 Co-authored-by: Frank Dugan III <frank3@duganator.com>
1 parent 87521b5 commit 3e6d5d6

File tree

14 files changed

+99
-60
lines changed

14 files changed

+99
-60
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
name: Checks/Tests on OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
1818
runs-on: ubuntu-latest
1919
strategy:
20+
fail-fast: false
2021
matrix:
2122
include:
2223
- elixir: '1.14'
@@ -29,6 +30,11 @@ jobs:
2930
otp: '27'
3031
- elixir: '1.18'
3132
otp: '27'
33+
- elixir: '1.19'
34+
otp: '28'
35+
- elixir: main
36+
experimental: true
37+
otp: '28'
3238
steps:
3339
- name: Set up Elixir
3440
id: setup
@@ -80,7 +86,7 @@ jobs:
8086
run: mix compile --warnings-as-errors
8187

8288
- name: Check formatting
83-
if: ${{matrix.elixir == '1.18'}}
89+
if: ${{matrix.elixir == '1.19'}}
8490
run: mix format --check-formatted
8591

8692
- name: Run tests

.tool-versions

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
erlang 27.2
2-
elixir 1.18.1-otp-27
1+
erlang 28.0
2+
elixir 1.19-otp-28

lib/mix_test_interactive/command_line_formatter.ex

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
defmodule MixTestInteractive.CommandLineFormatter do
22
@moduledoc false
33

4-
@special_chars ~r/[\s&|;<>*?()\[\]{}$`'"]/
5-
@whitespace ~r/\s/
6-
74
def call(command, args) do
85
Enum.map_join([command | args], " ", &format_argument/1)
96
end
107

8+
defmacrop special_chars do
9+
quote do
10+
~r/[\s&|;<>*?()\[\]{}$`'"]/
11+
end
12+
end
13+
14+
defmacrop whitespace do
15+
quote do
16+
~r/\s/
17+
end
18+
end
19+
1120
defp format_argument(arg) do
12-
if arg =~ @special_chars do
21+
if arg =~ special_chars() do
1322
quote_argument(arg)
1423
else
1524
arg
@@ -19,7 +28,7 @@ defmodule MixTestInteractive.CommandLineFormatter do
1928
defp quote_argument(arg) do
2029
cond do
2130
# Prefer double quotes for arguments with only spaces or special characters
22-
String.match?(arg, @whitespace) and not String.contains?(arg, ~s(")) ->
31+
String.match?(arg, whitespace()) and not String.contains?(arg, ~s(")) ->
2332
~s("#{arg}")
2433

2534
# Use single quotes if the argument contains double quotes but no single quotes

lib/mix_test_interactive/config.ex

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule MixTestInteractive.Config do
1010
field :ansi_enabled?, boolean()
1111
field :clear?, boolean(), default: false
1212
field :command, {String.t(), [String.t()]}, default: {"mix", []}
13-
field :exclude, [Regex.t()], default: [~r/\.#/, ~r{priv/repo/migrations}]
13+
field :exclude, [Regex.t()]
1414
field :extra_extensions, [String.t()], default: []
1515
field :runner, module(), default: MixTestInteractive.PortRunner
1616
field :show_timestamp?, boolean(), default: false
@@ -36,12 +36,32 @@ defmodule MixTestInteractive.Config do
3636
end
3737

3838
@doc false
39-
def new do
39+
def new(overrides \\ []) do
4040
os_type = ProcessTree.get(:os_type, default: :os.type())
4141

42-
default_ansi_enabled(%__MODULE__{}, os_type)
42+
defaults = [ansi_enabled?: not match?({:win32, _os_name}, os_type), exclude: [~r/\.#/, ~r{priv/repo/migrations}]]
43+
attrs = Keyword.merge(defaults, overrides)
44+
45+
struct!(%__MODULE__{}, attrs)
46+
end
47+
48+
@doc false
49+
def equal?(%__MODULE__{} = left, %__MODULE__{} = right) do
50+
%{left | exclude: nil} == %{right | exclude: nil} and exclude_matches?(left, right.exclude)
4351
end
4452

53+
@doc false
54+
def exclude_contains?(%__MODULE__{} = config, %Regex{} = regex) do
55+
regex.source in sources(config.exclude)
56+
end
57+
58+
@doc false
59+
def exclude_matches?(%__MODULE__{} = config, exclude) do
60+
sources(config.exclude) == sources(exclude)
61+
end
62+
63+
defp sources(exclude), do: Enum.map(exclude, &Regex.source/1)
64+
4565
defp load(%__MODULE__{} = config, app_key, opts \\ []) do
4666
config_key = Keyword.get(opts, :rename, app_key)
4767
transform = Keyword.get(opts, :transform, & &1)
@@ -59,14 +79,6 @@ defmodule MixTestInteractive.Config do
5979
end
6080
end
6181

62-
defp default_ansi_enabled(%__MODULE__{} = config, {:win32, _os_name} = _os_type) do
63-
%{config | ansi_enabled?: false}
64-
end
65-
66-
defp default_ansi_enabled(%__MODULE__{} = config, _os_type) do
67-
%{config | ansi_enabled?: true}
68-
end
69-
7082
defp parse_command({cmd, args} = command) when is_binary(cmd) and is_list(args), do: command
7183
defp parse_command(command) when is_binary(command), do: {command, []}
7284

lib/mix_test_interactive/pattern_filter.ex

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,19 @@ defmodule MixTestInteractive.PatternFilter do
2525
|> Kernel.++(with_line_number)
2626
end
2727

28-
defp is_line_number_pattern?(pattern) do
29-
case ExUnit.Filters.parse_path(pattern) do
30-
{_path, []} -> false
31-
_ -> true
28+
if Version.compare(System.version(), "1.20.0-dev") == :lt do
29+
defp is_line_number_pattern?(pattern) do
30+
case ExUnit.Filters.parse_path(pattern) do
31+
{_path, []} -> false
32+
_ -> true
33+
end
34+
end
35+
else
36+
defp is_line_number_pattern?(pattern) do
37+
case ExUnit.Filters.parse_paths([pattern]) do
38+
{_path, []} -> false
39+
_ -> true
40+
end
3241
end
3342
end
3443
end

lib/mix_test_interactive/port_runner.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ defmodule MixTestInteractive.PortRunner do
5151
defp enable_ansi(task) do
5252
enable_command = "Application.put_env(:elixir, :ansi_enabled, true)"
5353

54-
["do", "eval", enable_command, ",", task]
54+
["do", "eval", enable_command, "+", task]
5555
end
5656

5757
defp maybe_print_command(%Config{verbose?: false} = _config, _runner_program, _runner_program_args), do: :ok

mix.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule MixTestInteractive.MixProject do
1010
deps: deps(),
1111
description: description(),
1212
docs: docs(),
13-
elixir: "~> 1.13",
13+
elixir: "~> 1.14",
1414
name: "mix test.interactive",
1515
package: package(),
1616
source_url: @source_url,
@@ -32,10 +32,10 @@ defmodule MixTestInteractive.MixProject do
3232

3333
defp deps do
3434
[
35-
{:ex_doc, "~> 0.35.1", only: :dev, runtime: false},
35+
{:ex_doc, "~> 0.38.2", only: :dev, runtime: false},
3636
{:file_system, "~> 0.2 or ~> 1.0"},
3737
{:process_tree, "~> 0.1.3 or ~> 0.2.0"},
38-
{:styler, "~> 1.2", only: [:dev, :test], runtime: false},
38+
{:styler, "~> 1.4", only: [:dev, :test], runtime: false},
3939
{:typed_struct, "~> 0.3.0"}
4040
]
4141
end

mix.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
%{
2-
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
3-
"ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"},
2+
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
3+
"ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"},
44
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
55
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
66
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
7-
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
8-
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
7+
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
8+
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
99
"process_tree": {:hex, :process_tree, "0.2.0", "a934111ff00c1b696731edf35ecc6a4724105e045efa95a1977cac21ebad62cd", [:mix], [], "hexpm", "92901d3e9d2f40f4e09c7774a97ed0d1fac5d3541c62552df8346dfa0f9ee19b"},
10-
"styler": {:hex, :styler, "1.2.1", "28f9e3d4b065c22575c56b8ae03d05188add1b21bec5ae664fc1551e2dfcc41b", [:mix], [], "hexpm", "71dc33980e530d21ca54db9c2075e646faa6e7b744a9d4a3dfb0ff01f56595f0"},
10+
"styler": {:hex, :styler, "1.4.2", "420da8a9d10324625b75690ca9f2468bc00ee6eb78dead827e562368f9feabbb", [:mix], [], "hexpm", "ca22538b203b2424eef99a227e081143b9a9a4b26da75f26d920537fcd778832"},
1111
"typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"},
1212
}

test/mix_test_interactive/command_line_parser_test.exs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ defmodule MixTestInteractive.CommandLineParserTest do
6262
describe "mix test.interactive options" do
6363
test "retains original defaults when no options" do
6464
{:ok, %{config: config}} = CommandLineParser.parse([])
65-
assert config == Config.new()
65+
assert Config.equal?(config, Config.new())
6666
end
6767

6868
test "sets ansi_enabled? flag with --ansi-enabled" do
@@ -106,17 +106,17 @@ defmodule MixTestInteractive.CommandLineParserTest do
106106

107107
test "ignores custom command arguments if command is not specified" do
108108
{:ok, %{config: config}} = CommandLineParser.parse(["--arg", "arg_with_missing_command"])
109-
assert config.command == %Config{}.command
109+
assert config.command == Config.new().command
110110
end
111111

112112
test "configures watch exclusions with --exclude" do
113113
{:ok, %{config: config}} = CommandLineParser.parse(["--exclude", "~$"])
114-
assert config.exclude == [~r/~$/]
114+
assert Config.exclude_matches?(config, [~r/~$/])
115115
end
116116

117117
test "configures multiple watch exclusions with repeated --exclude options" do
118118
{:ok, %{config: config}} = CommandLineParser.parse(["--exclude", "~$", "--exclude", "\.secret\.exs"])
119-
assert config.exclude == [~r/~$/, ~r/.secret.exs/]
119+
assert Config.exclude_matches?(config, [~r/~$/, ~r/.secret.exs/])
120120
end
121121

122122
test "fails if watch exclusion is an invalid Regex" do
@@ -342,7 +342,7 @@ defmodule MixTestInteractive.CommandLineParserTest do
342342
{:ok, %{config: config, settings: settings}} =
343343
CommandLineParser.parse(["--exclude", "~$", "--", "--exclude", "integration"])
344344

345-
assert config.exclude == [~r/~$/]
345+
assert Config.exclude_matches?(config, [~r/~$/])
346346
assert settings.excludes == ["integration"]
347347
end
348348

test/mix_test_interactive/config_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,18 @@ defmodule MixTestInteractive.ConfigTest do
6161
test "takes :exclude from the env" do
6262
Process.put(:exclude, [~r/migration_.*/])
6363
config = Config.load_from_environment()
64-
assert config.exclude == [~r/migration_.*/]
64+
assert Config.exclude_matches?(config, [~r/migration_.*/])
6565
end
6666

6767
test ":exclude contains common editor temp/swap files by default" do
6868
config = Config.load_from_environment()
6969
# Emacs lock symlink
70-
assert ~r/\.#/ in config.exclude
70+
assert Config.exclude_contains?(config, ~r/\.#/)
7171
end
7272

7373
test "excludes default Phoenix migrations directory by default" do
7474
config = Config.load_from_environment()
75-
assert ~r{priv/repo/migrations} in config.exclude
75+
assert Config.exclude_contains?(config, ~r{priv/repo/migrations})
7676
end
7777

7878
test "takes :extra_extensions from the env" do

0 commit comments

Comments
 (0)