11defmodule ElixirLS.LanguageServer.Providers.CodeAction do
22 use ElixirLS.LanguageServer.Protocol
33
4- def code_actions ( uri , diagnostics ) do
4+ alias ElixirLS.LanguageServer.SourceFile
5+
6+ @ unknown_remote_function_pattern ~r/ (.*)\/ (.*) is undefined or private. .*:\n (.*)/ s
7+
8+ def code_actions ( uri , diagnostics , source_file ) do
59 actions =
610 diagnostics
7- |> Enum . map ( fn diagnostic -> actions ( uri , diagnostic ) end )
11+ |> Enum . map ( fn diagnostic -> actions ( uri , diagnostic , source_file ) end )
812 |> List . flatten ( )
913
1014 { :ok , actions }
1115 end
1216
13- defp actions ( uri , % { "message" => message } = diagnostic ) do
17+ defp actions ( uri , % { "message" => message } = diagnostic , source_file ) do
1418 [
15- { ~r/ variable "(.*)" is unused/ , & prefix_with_underscore / 2 } ,
16- { ~r/ variable "(.*)" is unused/ , & remove_variable / 2 }
19+ { ~r/ variable "(.*)" is unused/ , & prefix_with_underscore / 3 } ,
20+ { ~r/ variable "(.*)" is unused/ , & remove_variable / 3 } ,
21+ { @ unknown_remote_function_pattern , & replace_unknown_function / 3 }
1722 ]
1823 |> Enum . filter ( fn { r , _fun } -> String . match? ( message , r ) end )
19- |> Enum . map ( fn { _r , fun } -> fun . ( uri , diagnostic ) end )
24+ |> Enum . map ( fn { _r , fun } -> fun . ( uri , diagnostic , source_file ) end )
2025 end
2126
22- defp prefix_with_underscore ( uri , % { "range" => range } ) do
27+ defp prefix_with_underscore ( uri , % { "range" => range } , _source_file ) do
2328 % {
2429 "title" => "Add '_' to unused variable" ,
2530 "kind" => "quickfix" ,
@@ -42,7 +47,7 @@ defmodule ElixirLS.LanguageServer.Providers.CodeAction do
4247 }
4348 end
4449
45- defp remove_variable ( uri , % { "range" => range } ) do
50+ defp remove_variable ( uri , % { "range" => range } , _source_file ) do
4651 % {
4752 "title" => "Remove unused variable" ,
4853 "kind" => "quickfix" ,
@@ -58,4 +63,62 @@ defmodule ElixirLS.LanguageServer.Providers.CodeAction do
5863 }
5964 }
6065 end
66+
67+ defp replace_unknown_function ( uri , % { "message" => message , "range" => range } , source_file ) do
68+ [ _ , full_function_name , _function_arity , candidates_string ] =
69+ Regex . run ( @ unknown_remote_function_pattern , message )
70+
71+ function_module =
72+ full_function_name
73+ |> String . split ( "." )
74+ |> Enum . slice ( 0 .. - 2 // 1 )
75+ |> Enum . join ( "." )
76+
77+ start_line = start_line_from_range ( range )
78+
79+ source_line =
80+ source_file
81+ |> SourceFile . lines ( )
82+ |> Enum . at ( start_line )
83+
84+ candidates_string
85+ |> parse_candidates_string ( )
86+ |> Enum . map ( & ( function_module <> "." <> & 1 ) )
87+ |> Enum . reject ( & ( & 1 == full_function_name ) )
88+ |> Enum . map ( fn full_candidate_name ->
89+ % {
90+ "title" => "Replace unknown function with '#{ full_candidate_name } '" ,
91+ "kind" => "quickfix" ,
92+ "edit" => % {
93+ "changes" => % {
94+ uri => [
95+ % {
96+ "range" =>
97+ range (
98+ start_line ,
99+ 0 ,
100+ start_line ,
101+ String . length ( source_line )
102+ ) ,
103+ "newText" => String . replace ( source_line , full_function_name , full_candidate_name )
104+ }
105+ ]
106+ }
107+ }
108+ }
109+ end )
110+ end
111+
112+ defp start_line_from_range ( % { "start" => % { "line" => start_line } } ) , do: start_line
113+
114+ defp parse_candidates_string ( str ) do
115+ pattern = ~r" [ ]*\* (?<function_name>.*)/.*"
116+
117+ str
118+ |> String . split ( "\n " )
119+ |> Enum . map ( & Regex . run ( pattern , & 1 , capture: :all_names ) )
120+ |> Enum . reject ( & is_nil / 1 )
121+ |> List . flatten ( )
122+ |> Enum . uniq ( )
123+ end
61124end
0 commit comments