@@ -60,8 +60,7 @@ defmodule Styler.Style.ModuleDirectives do
6060 require: [ ] ,
6161 nondirectives: [ ] ,
6262 alias_env: % { } ,
63- attrs: MapSet . new ( ) ,
64- attr_lifts: [ ]
63+ attrs: MapSet . new ( )
6564 }
6665
6766 # module directives typically doesn't do anything until it sees a module (typical .ex file) or a directive (like a snippet)
@@ -73,7 +72,7 @@ defmodule Styler.Style.ModuleDirectives do
7372 def run ( zipper , % { __MODULE__ => true } = ctx ) , do: do_run ( zipper , ctx )
7473
7574 def run ( { node , nil } = zipper , ctx ) do
76- if interesting_zipper = Zipper . find ( zipper , & interesting? / 1 ) do
75+ if interesting_zipper = Zipper . find ( zipper , & match? ( { x , _ , _ } when x in [ :defmodule , :@ | @ directives ] , & 1 ) ) do
7776 do_run ( interesting_zipper , Map . put ( ctx , __MODULE__ , true ) )
7877 else
7978 # there's no defmodules or aliasy things - see if we can do some alias lifting?
@@ -103,9 +102,6 @@ defmodule Styler.Style.ModuleDirectives do
103102 end
104103 end
105104
106- defp interesting? ( { x , _ , _ } ) when x in [ :defmodule , :@ | @ directives ] , do: true
107- defp interesting? ( _ ) , do: false
108-
109105 defp do_run ( { { :defmodule , _ , children } , _ } = zipper , ctx ) do
110106 [ name , [ { { :__block__ , do_meta , [ :do ] } , _body } ] ] = children
111107
@@ -198,28 +194,38 @@ defmodule Styler.Style.ModuleDirectives do
198194 # a dynamic module name, like `defmodule my_variable do ... end`
199195 defp moduledoc ( _ ) , do: nil
200196
201- defp lift_module_attrs ( { node , _ , _ } = ast , % { attrs: attrs } = acc ) do
197+ # pops module attributes this directive depends on out of `nondirectives`,
198+ # returning their definitions in a list alongside the updated accumulator
199+ #
200+ # naively grouping module attributes in nondirectives can break a common pattern where an attribute is referenced by
201+ # `use` or `@moduledoc` clauses, which then get moved above the attributes they reference.
202+ # this function finds and returns those dependencies, allowing the caller to keep them above the dependent directive
203+ defp split_depended_attributes ( { _ , _ , _ } = directive , % { attrs: attrs , nondirectives: nondirectives } = acc ) do
202204 if MapSet . size ( attrs ) == 0 do
203- { ast , acc }
205+ { [ ] , acc }
204206 else
205- use? = node == :use
206-
207- Macro . prewalk ( ast , acc , fn
208- { :@ , m , [ { attr , _ , _ } = var ] } = ast , acc ->
207+ directive
208+ |> Macro . prewalk ( { [ ] , acc } , fn
209+ { :@ , _ , [ { attr , _ , _ } ] } = ast , { prepends , acc } ->
209210 if attr in attrs do
210- replacement =
211- if use? ,
212- do: { :unquote , [ closing: [ line: m [ :line ] ] , line: m [ :line ] ] , [ var ] } ,
213- else: var
211+ { definitions , nondirectives } = Enum . split_with ( nondirectives , & match? ( { :@ , _ , [ { ^ attr , _ , _ } ] } , & 1 ) )
212+ acc = % { acc | nondirectives: nondirectives }
214213
215- { replacement , % { acc | attr_lifts: [ attr | acc . attr_lifts ] } }
214+ recursed =
215+ Enum . reduce ( definitions , { definitions ++ prepends , acc } , fn ast , { prepends , acc } ->
216+ { definitions , acc } = split_depended_attributes ( ast , acc )
217+ { prepends ++ definitions , acc }
218+ end )
219+
220+ { ast , recursed }
216221 else
217- { ast , acc }
222+ { ast , { prepends , acc } }
218223 end
219224
220225 ast , acc ->
221226 { ast , acc }
222227 end )
228+ |> elem ( 1 )
223229 end
224230 end
225231
@@ -230,21 +236,22 @@ defmodule Styler.Style.ModuleDirectives do
230236 |> Enum . reduce ( @ env , fn
231237 { :@ , _ , [ { attr_directive , _ , _ } ] } = ast , acc when attr_directive in @ attr_directives ->
232238 # attr_directives are moved above aliases, so we need to expand them
233- { ast , acc } = acc . alias_env |> AliasEnv . expand_ast ( ast ) |> lift_module_attrs ( acc )
234- % { acc | attr_directive => [ ast | acc [ attr_directive ] ] }
239+ ast = AliasEnv . expand_ast ( acc . alias_env , ast )
240+ { prepends , acc } = split_depended_attributes ( ast , acc )
241+ % { acc | attr_directive => [ ast | prepends ++ acc [ attr_directive ] ] }
235242
236243 { :@ , _ , [ { attr , _ , _ } ] } = ast , acc ->
237244 % { acc | nondirectives: [ ast | acc . nondirectives ] , attrs: MapSet . put ( acc . attrs , attr ) }
238245
239246 { directive , _ , _ } = ast , acc when directive in @ directives ->
240- { ast , acc } = lift_module_attrs ( ast , acc )
247+ { prepends , acc } = split_depended_attributes ( ast , acc )
241248 ast = expand ( ast )
242- # import and used get hoisted above aliases, so need to expand them
249+ # import and use get hoisted above aliases, so need to expand them
243250 ast = if directive in ~w( import use) a , do: AliasEnv . expand_ast ( acc . alias_env , ast ) , else: ast
244251 alias_env = if directive == :alias , do: AliasEnv . define ( acc . alias_env , ast ) , else: acc . alias_env
245252
246253 # the reverse accounts for `expand` putting things in reading order, whereas we're accumulating in reverse
247- % { acc | directive => Enum . reverse ( ast , acc [ directive ] ) , alias_env: alias_env }
254+ % { acc | directive => Enum . reverse ( ast , prepends ++ acc [ directive ] ) , alias_env: alias_env }
248255
249256 ast , acc ->
250257 % { acc | nondirectives: [ ast | acc . nondirectives ] }
@@ -261,38 +268,6 @@ defmodule Styler.Style.ModuleDirectives do
261268 |> lift_aliases ( )
262269 |> apply_aliases ( )
263270
264- # Not happy with it, but this does the work to move module attribute assignments above the module or quote or whatever
265- # Given that it'll only be run once and not again, i'm okay with it being inefficient
266- { acc , parent } =
267- if Enum . any? ( acc . attr_lifts ) do
268- lifts = acc . attr_lifts
269-
270- nondirectives =
271- Enum . map ( acc . nondirectives , fn
272- { :@ , m , [ { attr , am , _ } ] } = ast -> if attr in lifts , do: { :@ , m , [ { attr , am , [ { attr , am , nil } ] } ] } , else: ast
273- ast -> ast
274- end )
275-
276- assignments =
277- Enum . flat_map ( acc . nondirectives , fn
278- { :@ , m , [ { attr , am , [ val ] } ] } -> if attr in lifts , do: [ { := , m , [ { attr , am , nil } , val ] } ] , else: [ ]
279- _ -> [ ]
280- end )
281-
282- { past , _ } = parent
283-
284- parent =
285- parent
286- |> Zipper . up ( )
287- |> Style . find_nearest_block ( )
288- |> Zipper . prepend_siblings ( assignments )
289- |> Zipper . find ( & ( & 1 == past ) )
290-
291- { % { acc | nondirectives: nondirectives } , parent }
292- else
293- { acc , parent }
294- end
295-
296271 nondirectives = acc . nondirectives
297272
298273 directives =
0 commit comments