changed
CHANGELOG.md
|
@@ -2,8 +2,42 @@
|
2
2
|
|
3
3
|
**Note** Styler's only public API is its usage as a formatter plugin. While you're welcome to play with its internals,
|
4
4
|
they can and will change without that change being reflected in Styler's semantic version.
|
5
|
+
|
5
6
|
## main
|
6
7
|
|
8
|
+ ## 1.3.3
|
9
|
+
|
10
|
+ ### Improvements
|
11
|
+
|
12
|
+ - `with do: body` and variations with no arrows in the head will be rewritten to just `body`
|
13
|
+ - `# styler:sort` will sort arbitrary ast nodes within a `do end` block:
|
14
|
+
|
15
|
+ Given:
|
16
|
+ # styler:sort
|
17
|
+ my_macro "some arg" do
|
18
|
+ another_macro :q
|
19
|
+ another_macro :w
|
20
|
+ another_macro :e
|
21
|
+ another_macro :r
|
22
|
+ another_macro :t
|
23
|
+ another_macro :y
|
24
|
+ end
|
25
|
+
|
26
|
+ We get
|
27
|
+ # styler:sort
|
28
|
+ my_macro "some arg" do
|
29
|
+ another_macro :e
|
30
|
+ another_macro :q
|
31
|
+ another_macro :r
|
32
|
+ another_macro :t
|
33
|
+ another_macro :w
|
34
|
+ another_macro :y
|
35
|
+ end
|
36
|
+
|
37
|
+ ### Fixes
|
38
|
+
|
39
|
+ - fix a bug in comment-movement when multiple `# styler:sort` directives are added to a file at the same time
|
40
|
+
|
7
41
|
## 1.3.2
|
8
42
|
|
9
43
|
### Improvements
|
changed
hex_metadata.config
|
@@ -1,6 +1,6 @@
|
1
1
|
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/adobe/elixir-styler">>}]}.
|
2
2
|
{<<"name">>,<<"styler">>}.
|
3
|
- {<<"version">>,<<"1.3.2">>}.
|
3
|
+ {<<"version">>,<<"1.3.3">>}.
|
4
4
|
{<<"description">>,
|
5
5
|
<<"A code-style enforcer that will just FIFY instead of complaining">>}.
|
6
6
|
{<<"elixir">>,<<"~> 1.15">>}.
|
changed
lib/style/blocks.ex
|
@@ -75,103 +75,32 @@ defmodule Styler.Style.Blocks do
|
75
75
|
{:cont, Zipper.replace(zipper, {:if, m, children}), ctx}
|
76
76
|
end
|
77
77
|
|
78
|
+ def run({{:with, _, [[{{:__block__, _, [:do]}, body} | _]]}, _} = zipper, ctx) do
|
79
|
+ {:cont, Zipper.replace(zipper, body), ctx}
|
80
|
+ end
|
81
|
+
|
78
82
|
# Credo.Check.Refactor.WithClauses
|
79
|
- def run({{:with, with_meta, children}, _} = zipper, ctx) when is_list(children) do
|
80
|
- # a std lib `with` block will have at least one left arrow and a `do` body. anything else we skip ¯\_(ツ)_/¯
|
81
|
- arrow_or_match? = &(left_arrow?(&1) || match?({:=, _, _}, &1))
|
83
|
+ def run({{:with, _, children}, _} = zipper, ctx) when is_list(children) do
|
84
|
+ do_block? = Enum.any?(children, &Style.do_block?/1)
|
85
|
+ arrow_or_match? = Enum.any?(children, &(left_arrow?(&1) || match?({:=, _, _}, &1)))
|
82
86
|
|
83
|
- if Enum.any?(children, arrow_or_match?) and Enum.any?(children, &Style.do_block?/1) do
|
84
|
- {preroll, children} =
|
85
|
- children
|
86
|
- |> Enum.map(fn
|
87
|
- # `_ <- rhs` => `rhs`
|
88
|
- {:<-, _, [{:_, _, _}, rhs]} -> rhs
|
89
|
- # `lhs <- rhs` => `lhs = rhs`
|
90
|
- {:<-, m, [{atom, _, nil} = lhs, rhs]} when is_atom(atom) -> {:=, m, [lhs, rhs]}
|
91
|
- child -> child
|
92
|
- end)
|
93
|
- |> Enum.split_while(&(not left_arrow?(&1)))
|
87
|
+ cond do
|
88
|
+ # we can style this!
|
89
|
+ do_block? and arrow_or_match? ->
|
90
|
+ style_with_statement(zipper, ctx)
|
94
91
|
|
95
|
- # after rewriting `x <- y()` to `x = y()` there are no more arrows.
|
96
|
- # this never should've been a with statement at all! we can just replace it with assignments
|
97
|
- if Enum.empty?(children) do
|
98
|
- {:cont, replace_with_statement(zipper, preroll), ctx}
|
99
|
- else
|
100
|
- [[{{_, do_meta, _} = do_block, do_body} | elses] | reversed_clauses] = Enum.reverse(children)
|
101
|
- {postroll, reversed_clauses} = Enum.split_while(reversed_clauses, &(not left_arrow?(&1)))
|
102
|
- [{:<-, final_clause_meta, [lhs, rhs]} = _final_clause | rest] = reversed_clauses
|
92
|
+ # `with (head_statements) do: x (else ...)`
|
93
|
+ do_block? ->
|
94
|
+ # head statements can be the empty list, if it matters
|
95
|
+ {head_statements, [[{{:__block__, _, [:do]}, body} | _]]} = Enum.split_while(children, &(not Style.do_block?(&1)))
|
96
|
+ [first | rest] = head_statements ++ [body]
|
97
|
+ # replace this `with` statement with its headers + body
|
98
|
+ zipper = zipper |> Zipper.replace(first) |> Zipper.insert_siblings(rest)
|
99
|
+ {:cont, zipper, ctx}
|
103
100
|
|
104
|
- # drop singleton identity else clauses like `else foo -> foo end`
|
105
|
- elses =
|
106
|
- case elses do
|
107
|
- [{{_, _, [:else]}, [{:->, _, [[left], right]}]}] -> if nodes_equivalent?(left, right), do: [], else: elses
|
108
|
- _ -> elses
|
109
|
- end
|
110
|
-
|
111
|
- {reversed_clauses, do_body} =
|
112
|
- cond do
|
113
|
- # Put the postroll into the body
|
114
|
- Enum.any?(postroll) ->
|
115
|
- {node, do_body_meta, do_children} = do_body
|
116
|
- do_children = if node == :__block__, do: do_children, else: [do_body]
|
117
|
- do_body = {:__block__, Keyword.take(do_body_meta, [:line]), Enum.reverse(postroll, do_children)}
|
118
|
- {reversed_clauses, do_body}
|
119
|
-
|
120
|
- # Credo.Check.Refactor.RedundantWithClauseResult
|
121
|
- Enum.empty?(elses) and nodes_equivalent?(lhs, do_body) ->
|
122
|
- {rest, rhs}
|
123
|
-
|
124
|
- # no change
|
125
|
- true ->
|
126
|
- {reversed_clauses, do_body}
|
127
|
- end
|
128
|
-
|
129
|
- do_line = do_meta[:line]
|
130
|
- final_clause_line = final_clause_meta[:line]
|
131
|
-
|
132
|
- do_line =
|
133
|
- cond do
|
134
|
- do_meta[:format] == :keyword && final_clause_line + 1 >= do_line -> do_line
|
135
|
- do_meta[:format] == :keyword -> final_clause_line + 1
|
136
|
- true -> final_clause_line
|
137
|
- end
|
138
|
-
|
139
|
- do_block = Macro.update_meta(do_block, &Keyword.put(&1, :line, do_line))
|
140
|
- # disable keyword `, do:` since there will be multiple statements in the body
|
141
|
- with_meta =
|
142
|
- if Enum.any?(postroll),
|
143
|
- do: Keyword.merge(with_meta, do: [line: with_meta[:line]], end: [line: Style.max_line(children) + 1]),
|
144
|
- else: with_meta
|
145
|
-
|
146
|
- with_children = Enum.reverse(reversed_clauses, [[{do_block, do_body} | elses]])
|
147
|
- zipper = Zipper.replace(zipper, {:with, with_meta, with_children})
|
148
|
-
|
149
|
- cond do
|
150
|
- # oops! RedundantWithClauseResult removed the final arrow in this. no more need for a with statement!
|
151
|
- Enum.empty?(reversed_clauses) ->
|
152
|
- {:cont, replace_with_statement(zipper, preroll ++ with_children), ctx}
|
153
|
-
|
154
|
- # recurse if the # of `<-` have changed (this `with` could now be eligible for a `case` rewrite)
|
155
|
- Enum.any?(preroll) ->
|
156
|
- # put the preroll before the with statement in either a block we create or the existing parent block
|
157
|
- zipper
|
158
|
- |> Style.find_nearest_block()
|
159
|
- |> Zipper.prepend_siblings(preroll)
|
160
|
- |> run(ctx)
|
161
|
-
|
162
|
- # the # of `<-` canged, so we should have another look at this with statement
|
163
|
- Enum.any?(postroll) ->
|
164
|
- run(zipper, ctx)
|
165
|
-
|
166
|
- true ->
|
167
|
- # of clauess didn't change, so don't reecurse or we'll loop FOREEEVEERR
|
168
|
- {:cont, zipper, ctx}
|
169
|
- end
|
170
|
- end
|
171
|
- else
|
172
|
- # maybe this isn't a with statement - could be a function named `with`
|
173
|
- # or it's just a with statement with no arrows, but that's too saddening to imagine
|
174
|
- {:cont, zipper, ctx}
|
101
|
+ # maybe this isn't a with statement - could be a function named `with` or something.
|
102
|
+ true ->
|
103
|
+ {:cont, zipper, ctx}
|
175
104
|
end
|
176
105
|
end
|
177
106
|
|
|
@@ -217,6 +146,97 @@ defmodule Styler.Style.Blocks do
|
217
146
|
|
218
147
|
def run(zipper, ctx), do: {:cont, zipper, ctx}
|
219
148
|
|
149
|
+ # with statements can do _a lot_, so this beast of a function likewise does a lot.
|
150
|
+ defp style_with_statement({{:with, with_meta, children}, _} = zipper, ctx) do
|
151
|
+ {preroll, children} =
|
152
|
+ children
|
153
|
+ |> Enum.map(fn
|
154
|
+ # `_ <- rhs` => `rhs`
|
155
|
+ {:<-, _, [{:_, _, _}, rhs]} -> rhs
|
156
|
+ # `lhs <- rhs` => `lhs = rhs`
|
157
|
+ {:<-, m, [{atom, _, nil} = lhs, rhs]} when is_atom(atom) -> {:=, m, [lhs, rhs]}
|
158
|
+ child -> child
|
159
|
+ end)
|
160
|
+ |> Enum.split_while(&(not left_arrow?(&1)))
|
161
|
+
|
162
|
+ # after rewriting `x <- y()` to `x = y()` there are no more arrows.
|
163
|
+ # this never should've been a with statement at all! we can just replace it with assignments
|
164
|
+ if Enum.empty?(children) do
|
165
|
+ {:cont, replace_with_statement(zipper, preroll), ctx}
|
166
|
+ else
|
167
|
+ [[{{_, do_meta, _} = do_block, do_body} | elses] | reversed_clauses] = Enum.reverse(children)
|
168
|
+ {postroll, reversed_clauses} = Enum.split_while(reversed_clauses, &(not left_arrow?(&1)))
|
169
|
+ [{:<-, final_clause_meta, [lhs, rhs]} = _final_clause | rest] = reversed_clauses
|
170
|
+
|
171
|
+ # drop singleton identity else clauses like `else foo -> foo end`
|
172
|
+ elses =
|
173
|
+ case elses do
|
174
|
+ [{{_, _, [:else]}, [{:->, _, [[left], right]}]}] -> if nodes_equivalent?(left, right), do: [], else: elses
|
175
|
+ _ -> elses
|
176
|
+ end
|
177
|
+
|
178
|
+ {reversed_clauses, do_body} =
|
179
|
+ cond do
|
180
|
+ # Put the postroll into the body
|
181
|
+ Enum.any?(postroll) ->
|
182
|
+ {node, do_body_meta, do_children} = do_body
|
183
|
+ do_children = if node == :__block__, do: do_children, else: [do_body]
|
184
|
+ do_body = {:__block__, Keyword.take(do_body_meta, [:line]), Enum.reverse(postroll, do_children)}
|
185
|
+ {reversed_clauses, do_body}
|
186
|
+
|
187
|
+ # Credo.Check.Refactor.RedundantWithClauseResult
|
188
|
+ Enum.empty?(elses) and nodes_equivalent?(lhs, do_body) ->
|
189
|
+ {rest, rhs}
|
190
|
+
|
191
|
+ # no change
|
192
|
+ true ->
|
193
|
+ {reversed_clauses, do_body}
|
194
|
+ end
|
195
|
+
|
196
|
+ do_line = do_meta[:line]
|
197
|
+ final_clause_line = final_clause_meta[:line]
|
198
|
+
|
199
|
+ do_line =
|
200
|
+ cond do
|
201
|
+ do_meta[:format] == :keyword && final_clause_line + 1 >= do_line -> do_line
|
202
|
+ do_meta[:format] == :keyword -> final_clause_line + 1
|
203
|
+ true -> final_clause_line
|
204
|
+ end
|
205
|
+
|
206
|
+ do_block = Macro.update_meta(do_block, &Keyword.put(&1, :line, do_line))
|
207
|
+ # disable keyword `, do:` since there will be multiple statements in the body
|
208
|
+ with_meta =
|
209
|
+ if Enum.any?(postroll),
|
210
|
+ do: Keyword.merge(with_meta, do: [line: with_meta[:line]], end: [line: Style.max_line(children) + 1]),
|
211
|
+ else: with_meta
|
212
|
+
|
213
|
+ with_children = Enum.reverse(reversed_clauses, [[{do_block, do_body} | elses]])
|
214
|
+ zipper = Zipper.replace(zipper, {:with, with_meta, with_children})
|
215
|
+
|
216
|
+ cond do
|
217
|
+ # oops! RedundantWithClauseResult removed the final arrow in this. no more need for a with statement!
|
218
|
+ Enum.empty?(reversed_clauses) ->
|
219
|
+ {:cont, replace_with_statement(zipper, preroll ++ with_children), ctx}
|
220
|
+
|
221
|
+ # recurse if the # of `<-` have changed (this `with` could now be eligible for a `case` rewrite)
|
222
|
+ Enum.any?(preroll) ->
|
223
|
+ # put the preroll before the with statement in either a block we create or the existing parent block
|
224
|
+ zipper
|
225
|
+ |> Style.find_nearest_block()
|
226
|
+ |> Zipper.prepend_siblings(preroll)
|
227
|
+ |> run(ctx)
|
228
|
+
|
229
|
+ # the # of `<-` canged, so we should have another look at this with statement
|
230
|
+ Enum.any?(postroll) ->
|
231
|
+ run(zipper, ctx)
|
232
|
+
|
233
|
+ true ->
|
234
|
+ # of clauess didn't change, so don't reecurse or we'll loop FOREEEVEERR
|
235
|
+ {:cont, zipper, ctx}
|
236
|
+ end
|
237
|
+ end
|
238
|
+ end
|
239
|
+
|
220
240
|
# `with a <- b(), c <- d(), do: :ok, else: (_ -> :error)`
|
221
241
|
# =>
|
222
242
|
# `a = b(); c = d(); :ok`
|
changed
lib/style/comment_directives.ex
|
@@ -33,8 +33,7 @@ defmodule Styler.Style.CommentDirectives do
|
33
33
|
end)
|
34
34
|
|
35
35
|
if found do
|
36
|
- {node, _} = found
|
37
|
- {sorted, comments} = sort(node, ctx.comments)
|
36
|
+ {sorted, comments} = found |> Zipper.node() |> sort(comments)
|
38
37
|
{Zipper.replace(found, sorted), comments}
|
39
38
|
else
|
40
39
|
{zipper, comments}
|
|
@@ -107,5 +106,23 @@ defmodule Styler.Style.CommentDirectives do
|
107
106
|
{{key, value}, comments}
|
108
107
|
end
|
109
108
|
|
109
|
+ # sorts arbitrary ast nodes within a `do end` list
|
110
|
+ defp sort({f, m, args} = node, comments) do
|
111
|
+ if m[:do] && m[:end] && match?([{{:__block__, _, [:do]}, {:__block__, _, _}}], List.last(args)) do
|
112
|
+ {[{{:__block__, m1, [:do]}, {:__block__, m2, nodes}}], args} = List.pop_at(args, -1)
|
113
|
+
|
114
|
+ {nodes, comments} =
|
115
|
+ nodes
|
116
|
+ |> Enum.sort_by(&Macro.to_string/1)
|
117
|
+ |> Style.order_line_meta_and_comments(comments, m[:line])
|
118
|
+
|
119
|
+ args = List.insert_at(args, -1, [{{:__block__, m1, [:do]}, {:__block__, m2, nodes}}])
|
120
|
+
|
121
|
+ {{f, m, args}, comments}
|
122
|
+ else
|
123
|
+ {node, comments}
|
124
|
+ end
|
125
|
+ end
|
126
|
+
|
110
127
|
defp sort(x, comments), do: {x, comments}
|
111
128
|
end
|
changed
lib/zipper.ex
|
@@ -172,18 +172,18 @@ defmodule Styler.Zipper do
|
172
172
|
top level.
|
173
173
|
"""
|
174
174
|
@spec insert_left(zipper, tree) :: zipper
|
175
|
- def insert_left({_, nil}, _), do: raise(ArgumentError, message: "Can't insert siblings at the top level.")
|
176
|
- def insert_left({tree, meta}, child), do: {tree, %{meta | l: [child | meta.l]}}
|
175
|
+ def insert_left(zipper, child), do: prepend_siblings(zipper, [child])
|
177
176
|
|
178
177
|
@doc """
|
179
178
|
Inserts many siblings to the left.
|
179
|
+ If the node is at the top of the tree, builds a new root `:__block__` while maintaining focus on the current node.
|
180
180
|
|
181
181
|
Equivalent to
|
182
182
|
|
183
183
|
Enum.reduce(siblings, zipper, &Zipper.insert_left(&2, &1))
|
184
184
|
"""
|
185
185
|
@spec prepend_siblings(zipper, [tree]) :: zipper
|
186
|
- def prepend_siblings({_, nil}, _), do: raise(ArgumentError, message: "Can't insert siblings at the top level.")
|
186
|
+ def prepend_siblings({node, nil}, siblings), do: {:__block__, [], siblings ++ [node]} |> zip() |> down() |> rightmost()
|
187
187
|
def prepend_siblings({tree, meta}, siblings), do: {tree, %{meta | l: Enum.reverse(siblings, meta.l)}}
|
188
188
|
|
189
189
|
@doc """
|
|
@@ -192,18 +192,18 @@ defmodule Styler.Zipper do
|
192
192
|
top level.
|
193
193
|
"""
|
194
194
|
@spec insert_right(zipper, tree) :: zipper
|
195
|
- def insert_right({_, nil}, _), do: raise(ArgumentError, message: "Can't insert siblings at the top level.")
|
196
|
- def insert_right({tree, meta}, child), do: {tree, %{meta | r: [child | meta.r]}}
|
195
|
+ def insert_right(zipper, child), do: insert_siblings(zipper, [child])
|
197
196
|
|
198
197
|
@doc """
|
199
198
|
Inserts many siblings to the right.
|
199
|
+ If the node is at the top of the tree, builds a new root `:__block__` while maintaining focus on the current node.
|
200
200
|
|
201
201
|
Equivalent to
|
202
202
|
|
203
203
|
Enum.reduce(siblings, zipper, &Zipper.insert_right(&2, &1))
|
204
204
|
"""
|
205
205
|
@spec insert_siblings(zipper, [tree]) :: zipper
|
206
|
- def insert_siblings({_, nil}, _), do: raise(ArgumentError, message: "Can't insert siblings at the top level.")
|
206
|
+ def insert_siblings({node, nil}, siblings), do: {:__block__, [], [node | siblings]} |> zip() |> down()
|
207
207
|
def insert_siblings({tree, meta}, siblings), do: {tree, %{meta | r: siblings ++ meta.r}}
|
208
208
|
|
209
209
|
@doc """
|
changed
mix.exs
|
@@ -12,7 +12,7 @@ defmodule Styler.MixProject do
|
12
12
|
use Mix.Project
|
13
13
|
|
14
14
|
# Don't forget to bump the README when doing non-patch version changes
|
15
|
- @version "1.3.2"
|
15
|
+ @version "1.3.3"
|
16
16
|
@url "https://github.com/adobe/elixir-styler"
|
17
17
|
|
18
18
|
def project do
|