Skip to content

Commit

Permalink
Fix heredoc parsing in Strings
Browse files Browse the repository at this point in the history
Refs #924
  • Loading branch information
rrrene committed Feb 2, 2022
1 parent 0e14926 commit 7f10178
Show file tree
Hide file tree
Showing 4 changed files with 1,602 additions and 32 deletions.
77 changes: 46 additions & 31 deletions lib/credo/code/strings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,19 @@ defmodule Credo.Code.Strings do
parse_heredoc(
t,
acc <> unquote(sigil_start),
"",
replacement,
unquote(sigil_end)
)
end
end

defp parse_code(<<"\"\"\""::utf8, t::binary>>, acc, replacement) do
parse_heredoc(t, acc <> ~s("""), replacement, ~s("""))
parse_heredoc(t, acc <> ~s("""), "", replacement, ~s("""))
end

defp parse_code(<<"\'\'\'"::utf8, t::binary>>, acc, replacement) do
parse_heredoc(t, acc <> ~s('''), replacement, ~s('''))
parse_heredoc(t, acc <> ~s('''), "", replacement, ~s('''))
end

for {sigil_start, sigil_end} <- all_string_sigils do
Expand Down Expand Up @@ -387,57 +388,71 @@ defmodule Credo.Code.Strings do
# Heredocs
#

defp parse_heredoc(<<"\"\"\""::utf8, t::binary>>, acc, "" = replacement, "\"\"\"") do
parse_code(t, acc <> "\"\"\"", replacement)
defp parse_heredoc(<<"\"\"\""::utf8, t::binary>>, acc, heredoc_acc, "" = replacement, "\"\"\"") do
parse_code(t, acc <> heredoc_acc <> "\"\"\"", replacement)
end

defp parse_heredoc(<<"\"\"\""::utf8, t::binary>>, acc, " " = replacement, "\"\"\"") do
parse_code(t, acc <> "\"\"\"", replacement)
defp parse_heredoc(<<"\"\"\""::utf8, t::binary>>, acc, heredoc_acc, " " = replacement, "\"\"\"") do
parse_code(t, acc <> heredoc_acc <> "\"\"\"", replacement)
end

defp parse_heredoc(<<"\"\"\""::utf8, t::binary>>, acc, replacement, "\"\"\"") do
acc =
Regex.replace(~r/(\n[#{replacement}]+)(\"\"\")\z/m, acc <> "\"\"\"", fn _, x ->
"\n#{String.pad_trailing("", String.length(x))}\"\"\""
end)
defp parse_heredoc(<<"\"\"\""::utf8, t::binary>>, acc, heredoc_acc, replacement, "\"\"\"") do
heredoc_acc = heredoc_acc <> "\"\"\""

heredoc_acc =
case Regex.run(~r/\n([#{replacement}]+)\"\"\"\z/m, heredoc_acc) do
[_, indent_string] ->
x = String.length(indent_string)
Regex.replace(~r/^(.{#{x}})/m, heredoc_acc, String.pad_trailing("", x))

_ ->
heredoc_acc
end

parse_code(t, acc, replacement)
parse_code(t, acc <> heredoc_acc, replacement)
end

defp parse_heredoc(<<"\'\'\'"::utf8, t::binary>>, acc, "" = replacement, "\'\'\'") do
parse_code(t, acc, replacement)
defp parse_heredoc(<<"\'\'\'"::utf8, t::binary>>, acc, heredoc_acc, "" = replacement, "\'\'\'") do
parse_code(t, acc <> heredoc_acc <> "\'\'\'", replacement)
end

defp parse_heredoc(<<"\'\'\'"::utf8, t::binary>>, acc, " " = replacement, "\'\'\'") do
parse_code(t, acc <> "\'\'\'", replacement)
defp parse_heredoc(<<"\'\'\'"::utf8, t::binary>>, acc, heredoc_acc, " " = replacement, "\'\'\'") do
parse_code(t, acc <> heredoc_acc <> "\'\'\'", replacement)
end

defp parse_heredoc(<<"\'\'\'"::utf8, t::binary>>, acc, replacement, "\'\'\'") do
acc =
Regex.replace(~r/(\n[#{replacement}]+)(\'\'\')\z/m, acc <> "\'\'\'", fn _, x ->
"\n#{String.pad_trailing("", String.length(x))}\'\'\'"
end)
defp parse_heredoc(<<"\'\'\'"::utf8, t::binary>>, acc, heredoc_acc, replacement, "\'\'\'") do
heredoc_acc = heredoc_acc <> "\'\'\'"

heredoc_acc =
case Regex.run(~r/\n([#{replacement}]+)\'\'\'\z/m, heredoc_acc) do
[_, indent_string] ->
x = String.length(indent_string)
Regex.replace(~r/^(.{#{x}})/m, heredoc_acc, String.pad_trailing("", x))

_ ->
heredoc_acc
end

parse_code(t, acc, replacement)
parse_code(t, acc <> heredoc_acc, replacement)
end

defp parse_heredoc("", acc, _replacement, _delimiter) do
defp parse_heredoc("", acc, _heredoc_acc, _replacement, _delimiter) do
acc
end

defp parse_heredoc(<<"\\\\"::utf8, t::binary>>, acc, replacement, delimiter) do
parse_heredoc(t, acc, replacement, delimiter)
defp parse_heredoc(<<"\\\\"::utf8, t::binary>>, acc, heredoc_acc, replacement, delimiter) do
parse_heredoc(t, acc, heredoc_acc, replacement, delimiter)
end

defp parse_heredoc(<<"\\\""::utf8, t::binary>>, acc, replacement, delimiter) do
parse_heredoc(t, acc, replacement, delimiter)
defp parse_heredoc(<<"\\\""::utf8, t::binary>>, acc, heredoc_acc, replacement, delimiter) do
parse_heredoc(t, acc, heredoc_acc, replacement, delimiter)
end

defp parse_heredoc(<<"\n"::utf8, t::binary>>, acc, replacement, delimiter) do
parse_heredoc(t, acc <> "\n", replacement, delimiter)
defp parse_heredoc(<<"\n"::utf8, t::binary>>, acc, heredoc_acc, replacement, delimiter) do
parse_heredoc(t, acc, heredoc_acc <> "\n", replacement, delimiter)
end

defp parse_heredoc(<<_::utf8, t::binary>>, acc, replacement, delimiter) do
parse_heredoc(t, acc <> replacement, replacement, delimiter)
defp parse_heredoc(<<_::utf8, t::binary>>, acc, heredoc_acc, replacement, delimiter) do
parse_heredoc(t, acc, heredoc_acc <> replacement, replacement, delimiter)
end
end
36 changes: 36 additions & 0 deletions test/credo/code/strings_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,24 @@ defmodule Credo.Code.StringsTest do
assert result == result2, "Strings.replace_with_spaces/2 should be idempotent"
end

@tag slow: :disk_io
test "it should produce valid code /6" do
example_code = File.read!("test/fixtures/example_code/browser2.ex")

result =
example_code
|> to_source_file()
|> Strings.replace_with_spaces(".", ".")

result2 =
result
|> Strings.replace_with_spaces(".", ".")

assert match?({:ok, _}, Code.string_to_quoted(result))

assert result == result2, "Strings.replace_with_spaces/2 should be idempotent"
end

@tag slow: :disk_io
test "it should produce valid code with empty string as replacement /5" do
example_code = File.read!("test/fixtures/example_code/browser.ex")
Expand All @@ -263,4 +281,22 @@ defmodule Credo.Code.StringsTest do

assert result == result2, "Strings.replace_with_spaces/2 should be idempotent"
end

@tag slow: :disk_io
test "it should produce valid code with empty string as replacement /6" do
example_code = File.read!("test/fixtures/example_code/browser2.ex")

result =
example_code
|> to_source_file()
|> Strings.replace_with_spaces("", "")

result2 =
result
|> Strings.replace_with_spaces("", "")

assert match?({:ok, _}, Code.string_to_quoted(result))

assert result == result2, "Strings.replace_with_spaces/2 should be idempotent"
end
end
2 changes: 1 addition & 1 deletion test/fixtures/example_code/browser.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule Wallaby.Browser do
@moduledoc """
@moduledoc ~S"""
The Browser module is the entrypoint for interacting with a real browser.
By default, action only work with elements that are visible to a real user.
Expand Down
Loading

0 comments on commit 7f10178

Please sign in to comment.