@@ -22,10 +22,11 @@ defmodule PhoenixIconify.Scanner do
2222 content
2323 |> tokenize_heex ( "nofile" )
2424 |> icons_from_tokens ( )
25+ |> Enum . uniq ( )
2526 end
2627
2728 defp source_paths do
28- [ "lib/**/*.ex" , "lib/**/*.heex" ]
29+ [ "lib/**/*.ex" , "lib/**/*.heex" , "priv/**/*.heex" ]
2930 |> Enum . flat_map ( & Path . wildcard / 1 )
3031 end
3132
@@ -49,11 +50,18 @@ defmodule PhoenixIconify.Scanner do
4950
5051 defp scan_ex ( content ) do
5152 case Code . string_to_quoted ( content ) do
52- { :ok , ast } -> ast |> heex_sigil_sources ( ) |> Enum . flat_map ( & scan_heex_content / 1 )
53+ { :ok , ast } -> scan_ast ( ast )
5354 { :error , _ } -> [ ]
5455 end
5556 end
5657
58+ defp scan_ast ( ast ) do
59+ heex_icons = ast |> heex_sigil_sources ( ) |> Enum . flat_map ( & scan_heex_content / 1 )
60+ icon_function_icons = ast |> icon_function_string_literals ( )
61+
62+ heex_icons ++ icon_function_icons
63+ end
64+
5765 defp heex_sigil_sources ( ast ) do
5866 { _ast , sources } =
5967 Macro . prewalk ( ast , [ ] , fn
@@ -69,10 +77,29 @@ defmodule PhoenixIconify.Scanner do
6977 end
7078
7179 defp tokenize_heex ( content , path ) do
80+ do_tokenize_heex ( content , path )
81+ rescue
82+ Phoenix.LiveView.Tokenizer.ParseError -> tokenize_heex_lines ( content , path )
83+ end
84+
85+ defp tokenize_heex_lines ( content , path ) do
86+ content
87+ |> String . split ( "\n " )
88+ |> Enum . with_index ( 1 )
89+ |> Enum . flat_map ( fn { line , line_number } ->
90+ try do
91+ do_tokenize_heex ( line , path , line_number )
92+ rescue
93+ Phoenix.LiveView.Tokenizer.ParseError -> [ ]
94+ end
95+ end )
96+ end
97+
98+ defp do_tokenize_heex ( content , path , line \\ 1 ) do
7299 state = Tokenizer . init ( 0 , path , content , Phoenix.LiveView.HTMLEngine )
73100
74101 { tokens , _cont } =
75- Tokenizer . tokenize ( content , [ line: 1 , column: 1 ] , [ ] , { :text , :enabled } , state )
102+ Tokenizer . tokenize ( content , [ line: line , column: 1 ] , [ ] , { :text , :enabled } , state )
76103
77104 tokens
78105 end
@@ -85,28 +112,108 @@ defmodule PhoenixIconify.Scanner do
85112
86113 defp icon_from_token ( { :local_component , "icon" , attrs , _meta } ) do
87114 attrs
88- |> Enum . find_value ( & name_attr / 1 )
115+ |> Enum . find_value ( & icon_name_attr / 1 )
116+ |> List . wrap ( )
117+ end
118+
119+ defp icon_from_token ( { :local_component , _name , attrs , _meta } ) do
120+ attrs
121+ |> Enum . find_value ( & component_icon_attr / 1 )
89122 |> List . wrap ( )
90123 end
91124
92125 defp icon_from_token ( _token ) , do: [ ]
93126
94- defp name_attr ( { "name" , { :string , name , _meta } , _attr_meta } ) do
127+ defp icon_name_attr ( { "name" , { :string , name , _meta } , _attr_meta } ) do
95128 normalize_name ( name )
96129 end
97130
98- defp name_attr ( { "name" , { :expr , expr , _meta } , _attr_meta } ) do
131+ defp icon_name_attr ( { "name" , { :expr , expr , _meta } , _attr_meta } ) do
99132 case Code . string_to_quoted ( expr ) do
100133 { :ok , name } when is_binary ( name ) -> normalize_name ( name )
101134 _ -> nil
102135 end
103136 end
104137
105- defp name_attr ( _attr ) , do: nil
138+ defp icon_name_attr ( _attr ) , do: nil
139+
140+ defp component_icon_attr ( { "icon" , { :string , name , _meta } , _attr_meta } ) do
141+ normalize_name ( name )
142+ end
143+
144+ defp component_icon_attr ( { "icon" , { :expr , expr , _meta } , _attr_meta } ) do
145+ case Code . string_to_quoted ( expr ) do
146+ { :ok , name } when is_binary ( name ) -> normalize_name ( name )
147+ _ -> nil
148+ end
149+ end
150+
151+ defp component_icon_attr ( _attr ) , do: nil
152+
153+ defp icon_function_string_literals ( ast ) do
154+ { _ast , icons } =
155+ Macro . prewalk ( ast , [ ] , fn
156+ { kind , _meta , [ { name , _name_meta , args } , [ do: body ] ] } = node , icons
157+ when kind in [ :def , :defp ] and is_atom ( name ) and is_list ( args ) ->
158+ if icon_function? ( name ) do
159+ { node , string_literals ( body ) ++ icons }
160+ else
161+ { node , icons }
162+ end
163+
164+ { kind , _meta , [ { name , _name_meta , args } , body ] } = node , icons
165+ when kind in [ :def , :defp ] and is_atom ( name ) and is_list ( args ) ->
166+ if icon_function? ( name ) do
167+ { node , string_literals ( body ) ++ icons }
168+ else
169+ { node , icons }
170+ end
171+
172+ node , icons ->
173+ { node , icons }
174+ end )
175+
176+ icons
177+ |> Enum . map ( & normalize_name / 1 )
178+ |> Enum . reject ( & is_nil / 1 )
179+ end
180+
181+ defp icon_function? ( name ) do
182+ name
183+ |> Atom . to_string ( )
184+ |> String . contains? ( "icon" )
185+ end
186+
187+ defp string_literals ( ast ) do
188+ { _ast , strings } =
189+ Macro . prewalk ( ast , [ ] , fn
190+ string , strings when is_binary ( string ) -> { string , [ string | strings ] }
191+ node , strings -> { node , strings }
192+ end )
193+
194+ strings
195+ end
106196
107197 defp normalize_name ( "hero-" <> _rest = name ) , do: PhoenixIconify . normalize_name ( name )
108198
109199 defp normalize_name ( name ) do
110- if String . contains? ( name , ":" ) , do: name
200+ if valid_iconify_name? ( name ) , do: name
201+ end
202+
203+ defp valid_iconify_name? ( name ) when is_binary ( name ) do
204+ case String . split ( name , ":" , parts: 2 ) do
205+ [ prefix , icon ] -> valid_name_part? ( prefix ) and valid_name_part? ( icon )
206+ _ -> false
207+ end
208+ end
209+
210+ defp valid_iconify_name? ( _name ) , do: false
211+
212+ defp valid_name_part? ( part ) do
213+ part != "" and part |> String . to_charlist ( ) |> Enum . all? ( & valid_name_character? / 1 )
214+ end
215+
216+ defp valid_name_character? ( character ) do
217+ character in ?a .. ?z or character in ?A .. ?Z or character in ?0 .. ?9 or character in [ ?- , ?_ ]
111218 end
112219end
0 commit comments