@@ -17,10 +17,9 @@ defmodule Expert.CodeIntelligence.Completion do
1717
1818 require Logger
1919
20+ @ build_env Mix . env ( )
2021 @ expert_deps Enum . map ( [ :expert | Mix.Project . deps_apps ( ) ] , & Atom . to_string / 1 )
2122
22- @ expert_dep_modules Enum . map ( @ expert_deps , & Macro . camelize / 1 )
23-
2423 def trigger_characters do
2524 [ "." , "@" , "&" , "%" , "^" , ":" , "!" , "-" , "~" ]
2625 end
@@ -166,9 +165,10 @@ defmodule Expert.CodeIntelligence.Completion do
166165 % CompletionContext { } = context
167166 ) do
168167 debug_local_completions ( local_completions )
168+ project_apps = Engine.Api . project_apps ( project )
169169
170170 for result <- local_completions ,
171- displayable? ( project , result ) ,
171+ displayable? ( project , project_apps , result ) ,
172172 applies_to_context? ( project , result , context ) ,
173173 applies_to_env? ( env , result ) ,
174174 % CompletionItem { } = item <- to_completion_item ( result , env ) do
@@ -206,7 +206,7 @@ defmodule Expert.CodeIntelligence.Completion do
206206 |> List . wrap ( )
207207 end
208208
209- defp displayable? ( % Project { } = project , result ) do
209+ defp displayable? ( % Project { } = project , project_apps , result ) do
210210 suggested_module =
211211 case result do
212212 % _ { full_name: full_name } when is_binary ( full_name ) -> full_name
@@ -223,16 +223,65 @@ defmodule Expert.CodeIntelligence.Completion do
223223 true
224224
225225 true ->
226- Enum . reduce_while ( @ expert_dep_modules , true , fn module , _ ->
227- if String . starts_with? ( suggested_module , module ) do
228- { :halt , false }
229- else
230- { :cont , true }
231- end
232- end )
226+ project_module? ( project , project_apps , suggested_module , result )
227+ end
228+ end
229+
230+ defp project_module? ( _ , _ , "" , _ ) , do: true
231+
232+ # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
233+ defp project_module? ( % Project { } = project , project_apps , suggested_module , result ) do
234+ module = module_string_to_atom ( suggested_module )
235+ module_app = Application . get_application ( module )
236+ project_app = Application . get_application ( project . project_module )
237+
238+ metadata = Map . get ( result , :metadata )
239+
240+ result_app = metadata [ :app ]
241+
242+ cond do
243+ module_app in project_apps ->
244+ true
245+
246+ # This is useful for some struct field completions, where
247+ # a suggested module is not always part of the result struct,
248+ # but the application is.
249+ # If no application is set though, it's usually part of a result
250+ # that is not part of any application yet.
251+ result_app in project_apps or is_nil ( metadata ) ->
252+ true
253+
254+ not is_nil ( module_app ) and module_app == project_app ->
255+ true
256+
257+ is_nil ( module_app ) and not is_nil ( project . project_module ) and
258+ module == project . project_module ->
259+ true
260+
261+ true ->
262+ # The following cases happen on test cases, due to the application
263+ # controller not always recognizing project fixture modules as part
264+ # of any application.
265+ test_env? ( ) and is_nil ( module_app ) and is_nil ( project . project_module )
233266 end
234267 end
235268
269+ # Because the build env is fixed at compile time, dialyzer knows that
270+ # in :dev and :prod environments, this function will always return false,
271+ # so it produces a warning.
272+ @ dialyzer { :nowarn_function , test_env?: 0 }
273+ defp test_env? , do: @ build_env == :test
274+
275+ defp module_string_to_atom ( "" ) , do: nil
276+
277+ defp module_string_to_atom ( module_string ) do
278+ Forge.Ast.Module . to_atom ( module_string )
279+ rescue
280+ _e in ArgumentError ->
281+ # Return nil if we can't safely convert the module string to an atom
282+ nil
283+ end
284+
236285 defp applies_to_env? ( % Env { } = env , % struct_module { } = result ) do
237286 cond do
238287 Env . in_context? ( env , :struct_reference ) ->
0 commit comments