@@ -165,7 +165,12 @@ defmodule Igniter.Project.Module do
165165 path
166166 end
167167
168- Igniter . create_new_file ( igniter , location , contents )
168+ igniter
169+ |> Igniter . create_new_file ( location , contents )
170+ |> put_module_index (
171+ module_name ,
172+ Igniter.Util.BackwardsCompat . relative_to_cwd ( location , force: true )
173+ )
169174 end
170175
171176 @ doc "Checks if a module is defined somewhere in the project. The returned igniter should not be discarded."
@@ -291,7 +296,37 @@ defmodule Igniter.Project.Module do
291296 @ spec find_module ( Igniter . t ( ) , module ( ) ) ::
292297 { :ok , { Igniter . t ( ) , Rewrite.Source . t ( ) , Zipper . t ( ) } } | { :error , Igniter . t ( ) }
293298 def find_module ( igniter , module_name ) do
294- check_first =
299+ with { :miss , igniter } <- check_module_index ( igniter , module_name ) ,
300+ { :miss , igniter } <- try_compiled_source ( igniter , module_name ) ,
301+ { :miss , igniter } <- try_conventional_path ( igniter , module_name ) ,
302+ { :miss , igniter , searched } <- try_filename_match ( igniter , module_name ) ,
303+ { :miss , igniter , searched } <- try_directory_search ( igniter , module_name , searched ) ,
304+ { :miss , igniter } <- try_full_scan ( igniter , module_name , searched ) do
305+ { :error , igniter }
306+ else
307+ { :ok , { igniter , source , zipper } } ->
308+ { :ok , { put_module_index ( igniter , module_name , source . path ) , source , zipper } }
309+ end
310+ end
311+
312+ defp check_module_index ( igniter , module_name ) do
313+ case get_in ( igniter . assigns , [ :private , :module_index , module_name ] ) do
314+ path when is_binary ( path ) ->
315+ case check_file_for_module ( igniter , path , module_name ) do
316+ { :ok , { igniter , source , zipper } } ->
317+ { :ok , { log_find_module_strategy ( igniter , module_name , :module_index ) , source , zipper } }
318+
319+ _ ->
320+ { :miss , igniter }
321+ end
322+
323+ _ ->
324+ { :miss , igniter }
325+ end
326+ end
327+
328+ defp try_compiled_source ( igniter , module_name ) do
329+ path =
295330 with true <- Code . ensure_loaded? ( module_name ) ,
296331 source when not is_nil ( source ) <- module_name . module_info ( ) [ :compile ] [ :source ] ,
297332 path =
@@ -302,38 +337,226 @@ defmodule Igniter.Project.Module do
302337 _ -> nil
303338 end
304339
305- with check_first when not is_nil ( check_first ) <- check_first ,
306- igniter <- Igniter . include_existing_file ( igniter , check_first ) ,
307- { :ok , source } <- Rewrite . source ( igniter . rewrite , check_first ) ,
308- { :ok , zipper } <-
309- source
340+ case path do
341+ nil ->
342+ { :miss , igniter }
343+
344+ path ->
345+ case check_file_for_module ( igniter , path , module_name ) do
346+ { :ok , { igniter , source , zipper } } ->
347+ { :ok ,
348+ { log_find_module_strategy ( igniter , module_name , :compiled_source ) , source , zipper } }
349+
350+ _ ->
351+ { :miss , igniter }
352+ end
353+ end
354+ end
355+
356+ defp try_conventional_path ( igniter , module_name ) do
357+ paths = conventional_paths ( igniter , module_name )
358+
359+ Enum . reduce_while ( paths , { :miss , igniter } , fn path , { :miss , igniter } ->
360+ case check_file_for_module ( igniter , path , module_name ) do
361+ { :ok , { igniter , source , zipper } } ->
362+ { :halt ,
363+ { :ok ,
364+ { log_find_module_strategy ( igniter , module_name , :conventional_path ) , source , zipper } } }
365+
366+ { :miss , igniter } ->
367+ { :cont , { :miss , igniter } }
368+ end
369+ end )
370+ end
371+
372+ defp try_filename_match ( igniter , module_name ) do
373+ last_segment =
374+ module_name
375+ |> Module . split ( )
376+ |> List . last ( )
377+ |> to_string ( )
378+ |> Macro . underscore ( )
379+
380+ source_folders = Igniter.Project.IgniterConfig . get ( igniter , :source_folders )
381+
382+ igniter =
383+ Enum . reduce ( source_folders , igniter , fn source_folder , igniter ->
384+ Igniter . include_glob ( igniter , Path . join ( source_folder , "**/#{ last_segment } .{ex,exs}" ) )
385+ end )
386+
387+ igniter = Igniter . include_glob ( igniter , "test/**/#{ last_segment } .{ex,exs}" )
388+
389+ igniter . rewrite
390+ |> Enum . filter ( fn source ->
391+ match? ( % Rewrite.Source { filetype: % Rewrite.Source.Ex { } } , source ) and
392+ Path . basename ( source . path , Path . extname ( source . path ) ) == last_segment
393+ end )
394+ |> search_sources_for_module ( igniter , module_name )
395+ |> case do
396+ { :ok , { igniter , source , zipper } } ->
397+ { :ok , { log_find_module_strategy ( igniter , module_name , :filename_match ) , source , zipper } }
398+
399+ miss ->
400+ miss
401+ end
402+ end
403+
404+ defp try_directory_search ( igniter , module_name , searched ) do
405+ segments =
406+ module_name
407+ |> Module . split ( )
408+ |> Enum . map ( fn s -> s |> to_string ( ) |> Macro . underscore ( ) end )
409+ |> Enum . reverse ( )
410+ |> Enum . drop ( 1 )
411+
412+ source_folders = Igniter.Project.IgniterConfig . get ( igniter , :source_folders )
413+
414+ Enum . reduce_while ( segments , { :miss , igniter , searched } , fn segment ,
415+ { :miss , igniter , searched } ->
416+ igniter =
417+ Enum . reduce ( source_folders , igniter , fn source_folder , igniter ->
418+ Igniter . include_glob (
419+ igniter ,
420+ Path . join ( source_folder , "**/#{ segment } /**/*.{ex,exs}" )
421+ )
422+ end )
423+
424+ igniter . rewrite
425+ |> Enum . filter ( fn source ->
426+ match? ( % Rewrite.Source { filetype: % Rewrite.Source.Ex { } } , source ) and
427+ not MapSet . member? ( searched , source . path ) and
428+ String . contains? ( source . path , "/#{ segment } /" )
429+ end )
430+ |> search_sources_for_module ( igniter , module_name , searched )
431+ |> case do
432+ { :ok , { igniter , source , zipper } } ->
433+ { :halt ,
434+ { :ok ,
435+ { log_find_module_strategy ( igniter , module_name , :directory_search ) , source , zipper } } }
436+
437+ { :miss , igniter , searched } ->
438+ { :cont , { :miss , igniter , searched } }
439+ end
440+ end )
441+ end
442+
443+ defp try_full_scan ( igniter , module_name , searched ) do
444+ igniter = Igniter . include_all_elixir_files ( igniter )
445+
446+ igniter
447+ |> Map . get ( :rewrite )
448+ |> Enum . filter ( fn source ->
449+ match? ( % Rewrite.Source { filetype: % Rewrite.Source.Ex { } } , source ) and
450+ not MapSet . member? ( searched , source . path )
451+ end )
452+ |> Task . async_stream (
453+ fn source ->
454+ { source
455+ |> Rewrite.Source . get ( :quoted )
456+ |> Zipper . zip ( )
457+ |> Igniter.Code.Module . move_to_defmodule ( module_name ) , source }
458+ end ,
459+ timeout: :infinity
460+ )
461+ |> Enum . find_value ( fn
462+ { :ok , { { :ok , zipper } , source } } ->
463+ { :ok , { igniter , source , zipper } }
464+
465+ _other ->
466+ false
467+ end )
468+ |> case do
469+ { :ok , { igniter , source , zipper } } ->
470+ { :ok , { log_find_module_strategy ( igniter , module_name , :full_scan ) , source , zipper } }
471+
472+ nil ->
473+ { :miss , igniter }
474+ end
475+ end
476+
477+ defp check_file_for_module ( igniter , path , module_name ) do
478+ igniter = Igniter . include_existing_file ( igniter , path )
479+
480+ case Rewrite . source ( igniter . rewrite , path ) do
481+ { :ok , source } ->
482+ case source
483+ |> Rewrite.Source . get ( :quoted )
484+ |> Zipper . zip ( )
485+ |> Igniter.Code.Module . move_to_defmodule ( module_name ) do
486+ { :ok , zipper } -> { :ok , { igniter , source , zipper } }
487+ _ -> { :miss , igniter }
488+ end
489+
490+ _ ->
491+ { :miss , igniter }
492+ end
493+ end
494+
495+ defp search_sources_for_module ( sources , igniter , module_name , searched \\ MapSet . new ( ) ) do
496+ Enum . reduce_while ( sources , { :miss , igniter , searched } , fn source ,
497+ { :miss , igniter , searched } ->
498+ searched = MapSet . put ( searched , source . path )
499+
500+ case source
310501 |> Rewrite.Source . get ( :quoted )
311502 |> Zipper . zip ( )
312503 |> Igniter.Code.Module . move_to_defmodule ( module_name ) do
313- { :ok , { igniter , source , zipper } }
314- else
315- _ ->
316- igniter = Igniter . include_all_elixir_files ( igniter )
504+ { :ok , zipper } ->
505+ { :halt , { :ok , { igniter , source , zipper } } }
317506
318- igniter
319- |> Map . get ( :rewrite )
320- |> Enum . filter ( & match? ( % Rewrite.Source { filetype: % Rewrite.Source.Ex { } } , & 1 ) )
321- |> Task . async_stream (
322- fn source ->
323- { source
324- |> Rewrite.Source . get ( :quoted )
325- |> Zipper . zip ( )
326- |> Igniter.Code.Module . move_to_defmodule ( module_name ) , source }
327- end ,
328- timeout: :infinity
329- )
330- |> Enum . find_value ( { :error , igniter } , fn
331- { :ok , { { :ok , zipper } , source } } ->
332- { :ok , { igniter , source , zipper } }
333-
334- _other ->
335- false
336- end )
507+ _ ->
508+ { :cont , { :miss , igniter , searched } }
509+ end
510+ end )
511+ end
512+
513+ defp conventional_paths ( igniter , module_name ) do
514+ segments =
515+ case Module . split ( module_name ) do
516+ [ "Mix" , "Tasks" | rest ] ->
517+ suffix =
518+ rest
519+ |> Enum . map ( & to_string / 1 )
520+ |> Enum . map_join ( "." , & Macro . underscore / 1 )
521+
522+ [ "mix" , "tasks" , suffix ]
523+
524+ _other ->
525+ module_name
526+ |> Module . split ( )
527+ |> Enum . map ( & to_string / 1 )
528+ |> Enum . map ( & Macro . underscore / 1 )
529+ end
530+
531+ last = List . last ( segments )
532+ leading = :lists . droplast ( segments )
533+
534+ source_folders = Igniter.Project.IgniterConfig . get ( igniter , :source_folders )
535+
536+ standard = for sf <- source_folders , do: Path . join ( [ sf | leading ] ++ [ "#{ last } .ex" ] )
537+
538+ inside_folder =
539+ for sf <- source_folders , do: Path . join ( [ sf | leading ] ++ [ last , "#{ last } .ex" ] )
540+
541+ standard ++ inside_folder
542+ end
543+
544+ defp put_module_index ( igniter , module_name , path ) do
545+ private = igniter . assigns [ :private ] || % { }
546+ module_index = Map . get ( private , :module_index , % { } )
547+ module_index = Map . put ( module_index , module_name , path )
548+ private = Map . put ( private , :module_index , module_index )
549+ % { igniter | assigns: Map . put ( igniter . assigns , :private , private ) }
550+ end
551+
552+ defp log_find_module_strategy ( igniter , module_name , strategy ) do
553+ if igniter . assigns [ :test_mode? ] do
554+ private = igniter . assigns [ :private ] || % { }
555+ log = Map . get ( private , :find_module_log , [ ] )
556+ private = Map . put ( private , :find_module_log , log ++ [ { module_name , strategy } ] )
557+ % { igniter | assigns: Map . put ( igniter . assigns , :private , private ) }
558+ else
559+ igniter
337560 end
338561 end
339562
0 commit comments