Packages
credo
1.6.7
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.7-rc.0
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.2-rc.4
1.7.2-rc.3
1.7.2-rc.2
1.7.2-rc.1
1.7.2-rc.0
1.7.1
1.7.0
1.7.0-rc.2
1.7.0-rc.1
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.5
1.5.0-rc.4
1.5.0-rc.3
1.5.0-rc.2
1.5.0-rc.1
1.4.1
1.4.0
1.4.0-rc.2
1.4.0-rc.1
1.3.2
1.3.1
1.3.0
1.3.0-rc3
1.3.0-rc2
1.3.0-rc1
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc4
1.2.0-rc3
1.2.0-rc2
1.2.0-rc1
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.1.0-rc3
1.1.0-rc2
1.1.0-rc1
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.1-rc1
1.0.0
1.0.0-rc1
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.9.0-rc8
0.9.0-rc7
0.9.0-rc6
0.9.0-rc5
0.9.0-rc4
0.9.0-rc3
0.9.0-rc2
0.9.0-rc1
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.8.0-rc7
0.8.0-rc6
0.8.0-rc5
0.8.0-rc4
0.8.0-rc3
0.8.0-rc2
0.8.0-rc1
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.6.0-rc2
0.6.0-rc1
0.5.3
0.5.2
0.5.1
0.5.0
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.10-dev
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-beta5
0.4.0-beta4
0.4.0-beta3
0.4.0-beta2
0.4.0-beta1
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.3.0-dev2
0.3.0-dev
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.1-dev
A static code analysis tool with a focus on code consistency and teaching.
Current section
Files
Jump to
Current section
Files
lib/credo/code/heredocs.ex
defmodule Credo.Code.Heredocs do
@moduledoc """
This module lets you strip heredocs from source code.
"""
alias Credo.Code.InterpolationHelper
alias Credo.SourceFile
alphabet = ~w(a b c d e f g h i j k l m n o p q r s t u v w x y z)
sigil_delimiters = [
{"(", ")"},
{"[", "]"},
{"{", "}"},
{"<", ">"},
{"|", "|"},
{"/", "/"},
{"\"", "\""},
{"'", "'"}
]
all_sigil_chars =
Enum.flat_map(alphabet, fn a ->
[a, String.upcase(a)]
end)
all_sigil_starts = Enum.map(all_sigil_chars, fn c -> "~#{c}" end)
non_removable_normal_sigils =
sigil_delimiters
|> Enum.flat_map(fn {b, e} ->
Enum.flat_map(all_sigil_starts, fn start ->
[{"#{start}#{b}", e}, {"#{start}#{b}", e}]
end)
end)
|> Enum.uniq()
non_removable_normal_sigil_ends = Enum.map(sigil_delimiters, &elem(&1, 1))
removable_heredoc_sigil_delimiters = [
{"\"\"\"", "\"\"\""},
{"'''", "'''"}
]
removable_heredoc_sigils =
removable_heredoc_sigil_delimiters
|> Enum.flat_map(fn {b, e} ->
Enum.flat_map(all_sigil_starts, fn start ->
[{"#{start}#{b}", e}, {"#{start}#{b}", e}]
end)
end)
|> Enum.uniq()
removable_heredoc_sigil_ends = Enum.map(removable_heredoc_sigil_delimiters, &elem(&1, 1))
@doc """
Replaces all characters inside heredocs
with the equivalent amount of white-space.
"""
def replace_with_spaces(
source_file,
replacement \\ " ",
interpolation_replacement \\ " ",
empty_line_replacement \\ "",
filename \\ "nofilename"
) do
{source, filename} = SourceFile.source_and_filename(source_file, filename)
source
|> InterpolationHelper.replace_interpolations(interpolation_replacement, filename)
|> parse_code("", replacement, empty_line_replacement)
end
defp parse_code("", acc, _replacement, _empty_line_replacement) do
acc
end
for {sigil_start, sigil_end} <- removable_heredoc_sigils do
defp parse_code(
<<unquote(sigil_start)::utf8, t::binary>>,
acc,
replacement,
empty_line_replacement
) do
parse_removable_heredoc_sigil(
t,
acc <> unquote(sigil_start),
unquote(sigil_end),
replacement,
empty_line_replacement,
"",
byte_size(acc <> unquote(sigil_start))
)
end
end
for {sigil_start, sigil_end} <- non_removable_normal_sigils do
defp parse_code(
<<unquote(sigil_start)::utf8, t::binary>>,
acc,
replacement,
empty_line_replacement
) do
parse_non_removable_normal_sigil(
t,
acc <> unquote(sigil_start),
unquote(sigil_end),
replacement,
empty_line_replacement
)
end
end
defp parse_code(<<"\"\"\""::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_heredoc(
t,
acc <> ~s("""),
replacement,
empty_line_replacement,
~s("""),
"",
byte_size(acc <> ~s("""))
)
end
defp parse_code(<<"\'\'\'"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_heredoc(
t,
acc <> ~s('''),
replacement,
empty_line_replacement,
~s('''),
"",
byte_size(acc <> ~s('''))
)
end
defp parse_code(<<"\\\""::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_code(t, acc <> "\\\"", replacement, empty_line_replacement)
end
defp parse_code(<<"#"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_comment(t, acc <> "#", replacement, empty_line_replacement)
end
defp parse_code(<<"?\""::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_code(t, acc <> "?\"", replacement, empty_line_replacement)
end
defp parse_code(<<"?'"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_code(t, acc <> "?\'", replacement, empty_line_replacement)
end
defp parse_code(<<"'"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_charlist(t, acc <> "'", replacement, empty_line_replacement)
end
defp parse_code(<<"\""::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_string_literal(t, acc <> "\"", replacement, empty_line_replacement)
end
defp parse_code(<<h::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_code(t, acc <> <<h::utf8>>, replacement, empty_line_replacement)
end
defp parse_code(str, acc, replacement, empty_line_replacement) when is_binary(str) do
{h, t} = String.next_codepoint(str)
parse_code(t, acc <> h, replacement, empty_line_replacement)
end
#
# Charlists
#
defp parse_charlist("", acc, _replacement, _empty_line_replacement) do
acc
end
defp parse_charlist(<<"\\\\"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_charlist(t, acc <> "\\\\", replacement, empty_line_replacement)
end
defp parse_charlist(<<"\\\'"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_charlist(t, acc <> "\\\'", replacement, empty_line_replacement)
end
defp parse_charlist(<<"\'"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_code(t, acc <> "'", replacement, empty_line_replacement)
end
defp parse_charlist(<<"\n"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_charlist(t, acc <> "\n", replacement, empty_line_replacement)
end
defp parse_charlist(str, acc, replacement, empty_line_replacement) when is_binary(str) do
{h, t} = String.next_codepoint(str)
parse_comment(t, acc <> h, replacement, empty_line_replacement)
end
#
# Comments
#
defp parse_comment("", acc, _replacement, _empty_line_replacement) do
acc
end
defp parse_comment(<<"\n"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_code(t, acc <> "\n", replacement, empty_line_replacement)
end
defp parse_comment(str, acc, replacement, empty_line_replacement) when is_binary(str) do
{h, t} = String.next_codepoint(str)
parse_comment(t, acc <> h, replacement, empty_line_replacement)
end
#
# "Normal" Sigils (e.g. `~S"..."` or `~s(...)`)
#
for sigil_end <- non_removable_normal_sigil_ends do
defp parse_non_removable_normal_sigil(
"",
acc,
unquote(sigil_end),
_replacement,
_empty_line_replacement
) do
acc
end
defp parse_non_removable_normal_sigil(
<<"\\\\"::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement
) do
parse_non_removable_normal_sigil(
t,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement
)
end
defp parse_non_removable_normal_sigil(
<<unquote("\\#{sigil_end}")::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement
) do
parse_non_removable_normal_sigil(
t,
acc <> replacement <> replacement,
unquote(sigil_end),
replacement,
empty_line_replacement
)
end
defp parse_non_removable_normal_sigil(
<<unquote(sigil_end)::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement
) do
parse_code(t, acc <> unquote(sigil_end), replacement, empty_line_replacement)
end
defp parse_non_removable_normal_sigil(
<<"\n"::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement
) do
parse_non_removable_normal_sigil(
t,
acc <> "\n",
unquote(sigil_end),
replacement,
empty_line_replacement
)
end
defp parse_non_removable_normal_sigil(
str,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement
) do
{h, t} = String.next_codepoint(str)
parse_non_removable_normal_sigil(
t,
acc <> h,
unquote(sigil_end),
replacement,
empty_line_replacement
)
end
end
#
# Removable Sigils (e.g. `~S"""`)
#
for sigil_end <- removable_heredoc_sigil_ends do
defp parse_removable_heredoc_sigil(
"",
acc,
unquote(sigil_end),
_replacement,
_empty_line_replacement,
_current_line,
_byte_index_heredoc_start
) do
acc
end
defp parse_removable_heredoc_sigil(
<<"\\\\"::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement,
current_line,
byte_index_heredoc_start
) do
parse_removable_heredoc_sigil(
t,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement,
current_line,
byte_index_heredoc_start
)
end
defp parse_removable_heredoc_sigil(
<<unquote("\\#{sigil_end}")::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement,
current_line,
byte_index_heredoc_start
) do
parse_removable_heredoc_sigil(
t,
acc <> replacement <> replacement,
unquote(sigil_end),
replacement,
empty_line_replacement,
current_line <> replacement <> replacement,
byte_index_heredoc_start
)
end
defp parse_removable_heredoc_sigil(
<<unquote(sigil_end)::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement,
current_line,
byte_index_heredoc_start
) do
acc = pad_replaced_heredoc(acc, unquote(sigil_end), current_line, byte_index_heredoc_start)
parse_code(t, acc <> unquote(sigil_end), replacement, empty_line_replacement)
end
defp parse_removable_heredoc_sigil(
<<"\n"::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement,
current_line,
byte_index_heredoc_start
) do
acc =
if current_line == "\n" do
acc <> empty_line_replacement
else
acc
end
parse_removable_heredoc_sigil(
t,
acc <> "\n",
unquote(sigil_end),
replacement,
empty_line_replacement,
"\n",
byte_index_heredoc_start
)
end
defp parse_removable_heredoc_sigil(
<<_::utf8, t::binary>>,
acc,
unquote(sigil_end),
replacement,
empty_line_replacement,
current_line,
byte_index_heredoc_start
) do
parse_removable_heredoc_sigil(
t,
acc <> replacement,
unquote(sigil_end),
replacement,
empty_line_replacement,
current_line <> replacement,
byte_index_heredoc_start
)
end
end
#
# Heredocs
#
defp parse_heredoc(
"",
acc,
_replacement,
_empty_line_replacement,
_here_doc_delimiter,
_current_line,
_byte_index_heredoc_start
) do
acc
end
defp parse_heredoc(
<<"\\\\"::utf8, t::binary>>,
acc,
replacement,
empty_line_replacement,
here_doc_delimiter,
current_line,
byte_index_heredoc_start
) do
parse_heredoc(
t,
acc,
replacement,
empty_line_replacement,
here_doc_delimiter,
current_line,
byte_index_heredoc_start
)
end
defp parse_heredoc(
<<"\\\""::utf8, t::binary>>,
acc,
replacement,
empty_line_replacement,
here_doc_delimiter,
current_line,
byte_index_heredoc_start
) do
parse_heredoc(
t,
acc,
replacement,
empty_line_replacement,
here_doc_delimiter,
current_line,
byte_index_heredoc_start
)
end
defp parse_heredoc(
<<"\"\"\""::utf8, t::binary>>,
acc,
replacement,
empty_line_replacement,
"\"\"\"",
current_line,
byte_index_heredoc_start
) do
acc = pad_replaced_heredoc(acc, ~s("""), current_line, byte_index_heredoc_start)
parse_code(t, acc <> ~s("""), replacement, empty_line_replacement)
end
defp parse_heredoc(
<<"\'\'\'"::utf8, t::binary>>,
acc,
replacement,
empty_line_replacement,
"\'\'\'",
current_line,
byte_index_heredoc_start
) do
acc = pad_replaced_heredoc(acc, ~s('''), current_line, byte_index_heredoc_start)
parse_code(t, acc <> ~s('''), replacement, empty_line_replacement)
end
defp parse_heredoc(
<<"\n"::utf8, t::binary>>,
acc,
replacement,
empty_line_replacement,
here_doc_delimiter,
current_line,
byte_index_heredoc_start
) do
acc =
if current_line == "\n" do
acc <> empty_line_replacement
else
acc
end
parse_heredoc(
t,
acc <> "\n",
replacement,
empty_line_replacement,
here_doc_delimiter,
"\n",
byte_index_heredoc_start
)
end
defp parse_heredoc(
<<_::utf8, t::binary>>,
acc,
replacement,
empty_line_replacement,
here_doc_delimiter,
current_line,
byte_index_heredoc_start
) do
parse_heredoc(
t,
acc <> replacement,
replacement,
empty_line_replacement,
here_doc_delimiter,
current_line <> replacement,
byte_index_heredoc_start
)
end
#
# String Literals
#
defp parse_string_literal("", acc, _replacement, _empty_line_replacement) do
acc
end
defp parse_string_literal(<<"\\\\"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_string_literal(t, acc <> "\\\\", replacement, empty_line_replacement)
end
defp parse_string_literal(<<"\\\""::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_string_literal(t, acc <> "\\\"", replacement, empty_line_replacement)
end
defp parse_string_literal(<<"\""::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_code(t, acc <> ~s("), replacement, empty_line_replacement)
end
defp parse_string_literal(<<"\n"::utf8, t::binary>>, acc, replacement, empty_line_replacement) do
parse_string_literal(t, acc <> "\n", replacement, empty_line_replacement)
end
defp parse_string_literal(str, acc, replacement, empty_line_replacement) when is_binary(str) do
{h, t} = String.next_codepoint(str)
parse_string_literal(t, acc <> h, replacement, empty_line_replacement)
end
defp pad_replaced_heredoc(acc, _delimiter, current_line, byte_index_heredoc_start) do
no_of_chars_to_replace = String.length(current_line) - 1
pad_string = "\n" <> String.pad_leading("", no_of_chars_to_replace)
start_binary = binary_part(acc, 0, byte_index_heredoc_start)
new_acc =
acc
|> binary_part(byte_index_heredoc_start, byte_size(acc) - byte_index_heredoc_start)
|> String.replace(~r/\n(.{#{no_of_chars_to_replace}})/, pad_string)
start_binary <> new_acc
end
end