Skip to content

Commit 0256460

Browse files
committed
Fix false positives for SpaceInParentheses
1 parent 7c4a214 commit 0256460

File tree

7 files changed

+343
-66
lines changed

7 files changed

+343
-66
lines changed

lib/credo/check/consistency/space_in_parentheses.ex

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,9 @@ defmodule Credo.Check.Consistency.SpaceInParentheses do
4040
issue_meta = IssueMeta.for(source_file, params)
4141
allow_empty_enums = Params.get(params, :allow_empty_enums, __MODULE__)
4242

43-
lines_with_issues =
44-
@collector.find_locations_not_matching(expected, source_file, allow_empty_enums)
43+
lines_with_issues = @collector.find_locations_not_matching(expected, source_file, allow_empty_enums)
4544

46-
lines_with_issues
47-
|> Enum.map(fn location ->
45+
Enum.map(lines_with_issues, fn location ->
4846
format_issue(issue_meta, [{:message, message_for(expected)} | location])
4947
end)
5048
end

lib/credo/check/consistency/space_in_parentheses/collector.ex

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,29 @@ defmodule Credo.Check.Consistency.SpaceInParentheses.Collector do
33

44
use Credo.Check.Consistency.Collector
55

6+
def collect_from_source_file(source_file) do
7+
Credo.Code.Token.reduce(source_file, &spaces(&1, &2, &3, &4), %{})
8+
end
9+
610
def collect_matches(source_file, _params) do
7-
# dbg(Credo.Code.to_tokens(source_file), limit: :infinity)
11+
collection = collect_from_source_file(source_file)
812

9-
Credo.Code.Token.reduce(source_file, &spaces(&1, &2, &3, &4), %{})
10-
|> Map.new(fn {key, value} ->
13+
Map.new(collection, fn {key, value} ->
1114
{key, Enum.count(value)}
1215
end)
1316
end
1417

1518
def find_locations_not_matching(expected, source_file, allow_empty_enums) do
19+
collection = collect_from_source_file(source_file)
20+
1621
actual =
1722
case expected do
1823
:with_space when allow_empty_enums == true -> :without_space_allow_empty_enums
1924
:with_space -> :without_space
2025
:without_space -> :with_space
2126
end
2227

23-
Credo.Code.Token.reduce(source_file, &spaces(&1, &2, &3, &4), %{})
28+
collection
2429
|> Map.get(actual)
2530
|> List.wrap()
2631
end
@@ -37,6 +42,14 @@ defmodule Credo.Check.Consistency.SpaceInParentheses.Collector do
3742
do_spaces(no_space_between?, empty_enum?, location, acc)
3843
end
3944

45+
defp spaces(_prev, {:"[", {line, col, _}}, {:"]", {line, col2, _}} = _next, acc) when col2 - col == 1 do
46+
do_spaces(true, true, [trigger: "[]", line_no: line, column: col], acc)
47+
end
48+
49+
defp spaces(_prev, {:"{", {line, col, _}}, {:"}", {line, col2, _}} = _next, acc) when col2 - col == 1 do
50+
do_spaces(true, true, [trigger: "{}", line_no: line, column: col], acc)
51+
end
52+
4053
# ignores `, ]` at the end of a list
4154
defp spaces({:",", {line, _col, _}}, {:"]", {line, _col2, _}}, _next, acc), do: acc
4255

@@ -45,9 +58,7 @@ defmodule Credo.Check.Consistency.SpaceInParentheses.Collector do
4558
defp spaces({:"{", {line, _col, _}}, {:"}", {line, _col2, _}}, _next, acc), do: acc
4659

4760
defp spaces(_prev, {paren, {_line, _col, _}} = t1, next, acc) when paren in [:"(", :"[", :"{"] do
48-
# dbg(:opening)
4961
{line_no, col_start, _line_no_end, col_end} = Credo.Code.Token.position(t1)
50-
# dbg(next)
5162
{line_no2, col_start2, _line_no_end, _col_end} = Credo.Code.Token.position(next)
5263

5364
empty_enum? =
@@ -58,17 +69,19 @@ defmodule Credo.Check.Consistency.SpaceInParentheses.Collector do
5869
_ -> false
5970
end
6071

61-
no_space_between? = line_no == line_no2 && col_end == col_start2
62-
location = [trigger: "#{paren}", line_no: line_no, column: col_start]
72+
if Credo.Code.Token.eol?(next) || line_no != line_no2 do
73+
acc
74+
else
75+
no_space_between? = line_no == line_no2 && col_end == col_start2
76+
location = [trigger: "#{paren}", line_no: line_no, column: col_start]
6377

64-
do_spaces(no_space_between?, empty_enum?, location, acc)
78+
do_spaces(no_space_between?, empty_enum?, location, acc)
79+
end
6580
end
6681

6782
defp spaces(prev, {paren, {_, _, _}} = t1, _next, acc) when paren in [:"}", :"]", :")"] do
68-
# dbg(:closing)
69-
70-
{line_no, _col_start, _line_no_end, col_end} = Credo.Code.Token.position(prev)
71-
{line_no2, col_start2, _line_no_end, _col_end} = Credo.Code.Token.position(t1)
83+
{line_no, _col_start, _line_no_end, col_end} = prev |> Credo.Code.Token.position()
84+
{line_no2, col_start2, _line_no_end, _col_end} = t1 |> Credo.Code.Token.position()
7285

7386
empty_enum? =
7487
case prev do
@@ -78,10 +91,14 @@ defmodule Credo.Check.Consistency.SpaceInParentheses.Collector do
7891
_ -> false
7992
end
8093

81-
no_space_between? = line_no == line_no2 && col_end == col_start2
82-
location = [trigger: "#{paren}", line_no: line_no, column: col_start2]
94+
if Credo.Code.Token.eol?(prev) || line_no != line_no2 do
95+
acc
96+
else
97+
no_space_between? = col_end == col_start2 || col_end > col_start2
98+
location = [trigger: "#{paren}", line_no: line_no, column: col_start2]
8399

84-
do_spaces(no_space_between?, empty_enum?, location, acc)
100+
do_spaces(no_space_between?, empty_enum?, location, acc)
101+
end
85102
end
86103

87104
defp spaces(_prev, _current, _next, acc), do: acc

lib/credo/code/token.ex

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ defmodule Credo.Code.Token do
6262
position_tuple_for_heredoc(atom_or_charlist, line_no, col_start)
6363
end
6464

65+
def position({:char, {line_no, col_start, charlist}, _number}) when is_list(charlist) do
66+
col_end = col_start + String.length(to_string(charlist))
67+
68+
{line_no, col_start, line_no, col_end}
69+
end
70+
6571
def position({:atom, {line_no, col_start, atom_or_charlist}, _atom}) do
6672
# +1 for the `:` of the atom
6773
col_end = col_start + String.length(to_string(atom_or_charlist)) + 1
@@ -84,11 +90,10 @@ defmodule Credo.Code.Token do
8490
def position({:sigil, {line_no, col_start, nil}, sigil_name, charlist, _list, _number, _binary})
8591
when is_list(charlist) do
8692
case position_tuple_for_quoted_string(charlist, line_no, col_start) do
87-
{line_no, col_start, line_no, col_end} ->
93+
{line1, col_start, line1, col_end} ->
8894
sigil_tag = String.replace("~#{sigil_name}", ~r/sigil_/, "")
8995

90-
# col_end + 1 for opening delimiter
91-
{line_no, col_start, line_no, col_end + String.length(sigil_tag) + 1}
96+
{line1, col_start, line1, col_end + String.length(sigil_tag)}
9297

9398
value ->
9499
value
@@ -158,9 +163,40 @@ defmodule Credo.Code.Token do
158163
end
159164

160165
@doc false
166+
def position_tuple_for_quoted_string([string], line_no, col_start)
167+
when is_binary(string) do
168+
# a simple string with double quotes (note the brackets in the fun head match)
169+
case String.split(string, "\n") do
170+
[string] ->
171+
# no line break
172+
col_end = col_start + String.length(string) + 2
173+
174+
{line_no, col_start, line_no, col_end}
175+
176+
[_ | _] = list ->
177+
# line breaks
178+
newlines = Enum.count(list) - 1
179+
last_line = List.last(list)
180+
181+
{line_no_end, col_end, terminator} = convert_to_col_end(line_no + newlines, 1, last_line)
182+
183+
{line_no_end, col_end} =
184+
case terminator do
185+
:eol ->
186+
# move to next line
187+
{line_no_end + 1, 1}
188+
189+
_ ->
190+
# add 1 for " (closing double quote)
191+
{line_no_end, col_end + 1}
192+
end
193+
194+
{line_no, col_start, line_no_end, col_end}
195+
end
196+
end
197+
161198
def position_tuple_for_quoted_string(atom_or_charlist, line_no, col_start)
162199
when is_list(atom_or_charlist) or is_atom(atom_or_charlist) do
163-
# add 1 for " (closing double quote)
164200
{line_no_end, col_end, terminator} = convert_to_col_end(line_no, col_start, atom_or_charlist)
165201

166202
{line_no_end, col_end} =

test/credo/check/consistency/space_in_parentheses/collector_test.exs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ defmodule Credo.Check.Consistency.SpaceInParentheses.CollectorTest do
2626
end
2727
"""
2828
@with_spaces_empty_enum """
29-
defmodule Credo.Sample2 do
30-
defmodule InlineModule do
31-
def foobar do
32-
exists = File.exists?(filename)
33-
{ result, %{} } = File.read( filename )
34-
end
29+
defmodule Credo.Sample2 do
30+
defmodule InlineModule do
31+
def foobar do
32+
exists = File.exists?(filename)
33+
{ result, %{} } = File.read( filename )
34+
end
3535
36-
def barfoo do
37-
exists = File.exists?(filename)
38-
{ result, [] } = File.read( filename )
39-
end
36+
def barfoo do
37+
exists = File.exists?(filename)
38+
{ result, [] } = File.read( filename )
4039
end
4140
end
41+
end
4242
"""
4343

4444
@heredoc_example """
@@ -51,27 +51,31 @@ defmodule Credo.Check.Consistency.SpaceInParentheses.CollectorTest do
5151
\"\"\"
5252
"""
5353

54-
test "it should report correct frequencies" do
54+
test "it should report correct frequencies for without_spaces" do
5555
without_spaces =
5656
@without_spaces
5757
|> to_source_file()
5858
|> Collector.collect_matches([])
5959

60-
assert %{without_space: 2, without_space_allow_empty_enums: 2} == without_spaces
60+
assert %{without_space: 6, without_space_allow_empty_enums: 6} == without_spaces
61+
end
6162

63+
test "it should report correct frequencies for with_spaces" do
6264
with_spaces =
6365
@with_spaces
6466
|> to_source_file()
6567
|> Collector.collect_matches([])
6668

67-
assert %{with_space: 1} == with_spaces
69+
assert %{with_space: 4} == with_spaces
70+
end
6871

72+
test "it should report correct frequencies for empty enums" do
6973
empty_enum =
7074
@with_spaces_empty_enum
7175
|> to_source_file()
7276
|> Collector.collect_matches([])
7377

74-
assert %{with_space: 2, without_space: 4, without_space_allow_empty_enums: 2} == empty_enum
78+
assert %{with_space: 8, without_space: 6, without_space_allow_empty_enums: 4} == empty_enum
7579
end
7680

7781
test "it should NOT report heredocs containing sigil chars" do

test/credo/check/consistency/space_in_parentheses_test.exs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,34 @@ defmodule Credo.Check.Consistency.SpaceInParenthesesTest do
118118
|> refute_issues()
119119
end
120120

121+
test "it should NOT report with config on empty params" do
122+
[
123+
@with_spaces_empty_params1,
124+
@with_spaces_empty_params2
125+
]
126+
|> to_source_files()
127+
|> run_check(@described_check, allow_empty_enums: true)
128+
|> refute_issues()
129+
end
130+
131+
test "it should NOT report with without_spaces" do
132+
[
133+
~S'''
134+
defmodule Credo.Test.IntegrationTest do
135+
def run(argv) do
136+
if System.get_env("DEBUG") do
137+
{suffix, remainder} = Enum.split_while(remainder, &(&1 != ?\n))
138+
end
139+
end
140+
end
141+
142+
'''
143+
]
144+
|> to_source_files()
145+
|> run_check(@described_check)
146+
|> refute_issues()
147+
end
148+
121149
#
122150
# cases raising issues
123151
#
@@ -144,7 +172,7 @@ defmodule Credo.Check.Consistency.SpaceInParenthesesTest do
144172
end)
145173
end
146174

147-
test "it should trigger error with no config on empty map" do
175+
test "it should report with no config on empty map" do
148176
[
149177
@with_spaces_empty_params1
150178
]
@@ -156,25 +184,15 @@ defmodule Credo.Check.Consistency.SpaceInParenthesesTest do
156184
end)
157185
end
158186

159-
test "it should trigger error with no config on empty array" do
187+
test "it should report with no config on empty array" do
160188
[
161189
@with_spaces_empty_params2
162190
]
163191
|> to_source_files()
164192
|> run_check(@described_check)
165193
|> assert_issue(fn issue ->
166194
assert 4 == issue.line_no
167-
assert "[" == issue.trigger
195+
assert "[]" == issue.trigger
168196
end)
169197
end
170-
171-
test "it should not trigger error with config on empty params" do
172-
[
173-
@with_spaces_empty_params1,
174-
@with_spaces_empty_params2
175-
]
176-
|> to_source_files()
177-
|> run_check(@described_check, allow_empty_enums: true)
178-
|> refute_issues()
179-
end
180198
end

test/credo/check/design/tag_helper_test.exs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmodule Credo.Check.Design.TagHelperTest do
1515
end
1616
"""
1717
|> to_source_file
18-
|> TagHelper.tags(:TODO, true)
18+
|> TagHelper.do_tags(:TODO, true)
1919

2020
assert [] == tags
2121
end
@@ -32,7 +32,7 @@ defmodule Credo.Check.Design.TagHelperTest do
3232
end
3333
"""
3434
|> to_source_file
35-
|> TagHelper.tags(:TODO, true)
35+
|> TagHelper.do_tags(:TODO, true)
3636

3737
expected = [
3838
{
@@ -57,7 +57,7 @@ defmodule Credo.Check.Design.TagHelperTest do
5757
end
5858
"""
5959
|> to_source_file
60-
|> TagHelper.tags(:TODO, true)
60+
|> TagHelper.do_tags(:TODO, true)
6161

6262
expected = [
6363
{
@@ -83,7 +83,7 @@ defmodule Credo.Check.Design.TagHelperTest do
8383
end
8484
"""
8585
|> to_source_file
86-
|> TagHelper.tags(:TODO, true)
86+
|> TagHelper.do_tags(:TODO, true)
8787

8888
expected = [
8989
{
@@ -117,7 +117,7 @@ defmodule Credo.Check.Design.TagHelperTest do
117117
end
118118
"""
119119
|> to_source_file
120-
|> TagHelper.tags(:TODO, true)
120+
|> TagHelper.do_tags(:TODO, true)
121121

122122
assert 3 == Enum.count(tags)
123123
end

0 commit comments

Comments
 (0)