From 01d337b880adf66cc75e2b561347170ebc8325e2 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Fri, 14 Feb 2020 19:11:35 +0100 Subject: [PATCH 1/2] Improvements to Code Lens Fixed code lens not showing for functions with @doc attribute Fixed code lens not showing for macros, guards, delegates --- .../dialyzer/success_typings.ex | 5 +++-- .../lib/language_server/providers/code_lens.ex | 13 ++++++++++--- .../providers/workspace_symbols.ex | 11 ++--------- .../lib/language_server/server.ex | 4 +++- .../lib/language_server/source_file.ex | 18 +++++++++++++++++- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/apps/language_server/lib/language_server/dialyzer/success_typings.ex b/apps/language_server/lib/language_server/dialyzer/success_typings.ex index e3227d8d5..81e4825c2 100644 --- a/apps/language_server/lib/language_server/dialyzer/success_typings.ex +++ b/apps/language_server/lib/language_server/dialyzer/success_typings.ex @@ -12,9 +12,10 @@ defmodule ElixirLS.LanguageServer.Dialyzer.SuccessTypings do file in files, {{^mod, fun, arity} = mfa, success_typing} <- success_typings(plt, mod), :dialyzer_plt.lookup_contract(plt, mfa) == :none, - line = SourceFile.function_line(mod, fun, arity), + {stripped_fun, stripped_arity} = SourceFile.strip_macro_prefix({fun, arity}), + line = SourceFile.function_line(mod, stripped_fun, stripped_arity), is_integer(line), - do: {file, line, mfa, success_typing} + do: {file, line, {mod, stripped_fun, stripped_arity}, success_typing, stripped_fun != fun} end defp source(module) do diff --git a/apps/language_server/lib/language_server/providers/code_lens.ex b/apps/language_server/lib/language_server/providers/code_lens.ex index 056ae8754..667264565 100644 --- a/apps/language_server/lib/language_server/providers/code_lens.ex +++ b/apps/language_server/lib/language_server/providers/code_lens.ex @@ -16,7 +16,7 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do import ElixirLS.LanguageServer.Protocol defmodule ContractTranslator do - def translate_contract(fun, contract) do + def translate_contract(fun, contract, is_macro) do {[%ExSpec{specs: [spec]} | _], _} = "-spec foo#{contract}." |> Parse.string() @@ -29,6 +29,7 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do spec |> Macro.postwalk(&tweak_specs/1) + |> drop_macro_env(is_macro) |> Macro.to_string() |> String.replace("()", "") |> Code.format_string!(line_length: :infinity) @@ -97,13 +98,19 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do defp tweak_specs(node) do node end + + defp drop_macro_env(ast, false), do: ast + + defp drop_macro_env({:"::", [], [{:foo, [], [_env | rest]}, res]}, true) do + {:"::", [], [{:foo, [], rest}, res]} + end end def code_lens(uri, text) do resp = - for {_, line, {mod, fun, arity}, contract} <- Server.suggest_contracts(uri), + for {_, line, {mod, fun, arity}, contract, is_macro} <- Server.suggest_contracts(uri), SourceFile.function_def_on_line?(text, line, fun), - spec = ContractTranslator.translate_contract(fun, contract) do + spec = ContractTranslator.translate_contract(fun, contract, is_macro) do %{ "range" => range(line - 1, 0, line - 1, 0), "command" => %{ diff --git a/apps/language_server/lib/language_server/providers/workspace_symbols.ex b/apps/language_server/lib/language_server/providers/workspace_symbols.ex index bf4c5e927..018793136 100644 --- a/apps/language_server/lib/language_server/providers/workspace_symbols.ex +++ b/apps/language_server/lib/language_server/providers/workspace_symbols.ex @@ -379,7 +379,7 @@ defmodule ElixirLS.LanguageServer.Providers.WorkspaceSymbols do |> do_process_chunked(fn chunk -> for {module, path} <- chunk, {function, arity} <- module.module_info(:exports) do - {function, arity} = strip_macro_prefix({function, arity}) + {function, arity} = SourceFile.strip_macro_prefix({function, arity}) line = find_function_line(module, function, arity, path) build_result(:functions, {module, function, arity}, path, line) @@ -414,7 +414,7 @@ defmodule ElixirLS.LanguageServer.Providers.WorkspaceSymbols do # TODO: Don't call into here directly {{callback, arity}, [{:type, line, _, _}]} <- ElixirSense.Core.Normalized.Typespec.get_callbacks(module) do - {callback, arity} = strip_macro_prefix({callback, arity}) + {callback, arity} = SourceFile.strip_macro_prefix({callback, arity}) build_result(:callbacks, {module, callback, arity}, path, line) end @@ -525,11 +525,4 @@ defmodule ElixirLS.LanguageServer.Providers.WorkspaceSymbols do end: %{line: line, character: 0} } end - - defp strip_macro_prefix({function, arity}) do - case Atom.to_string(function) do - "MACRO-" <> rest -> {String.to_atom(rest), arity - 1} - _other -> {function, arity} - end - end end diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 74de0d062..e1755e82b 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -584,7 +584,9 @@ defmodule ElixirLS.LanguageServer.Server do for {from, uri} <- not_dirty do contracts = - Enum.filter(contracts, fn {file, _, _, _} -> SourceFile.path_from_uri(uri) == file end) + Enum.filter(contracts, fn {file, _, _, _, _} -> + SourceFile.path_from_uri(uri) == file + end) GenServer.reply(from, contracts) end diff --git a/apps/language_server/lib/language_server/source_file.ex b/apps/language_server/lib/language_server/source_file.ex index d2e6e9844..b8a23b8b0 100644 --- a/apps/language_server/lib/language_server/source_file.ex +++ b/apps/language_server/lib/language_server/source_file.ex @@ -144,7 +144,23 @@ defmodule ElixirLS.LanguageServer.SourceFile do false line_text -> - Regex.match?(Regex.compile!("^\s*def\s+#{Regex.escape(to_string(fun))}"), line_text) + # when function line is taken from docs the line points to `@doc` attribute + # or first `def`/`defp`/`defmacro`/`defmacrop`/`defquard`/`defguardp`/`defdelegate` clause line if no `@doc` attribute + Regex.match?( + Regex.compile!( + "^\s*def((macro)|(guard)|(delegate))?p?\s+#{Regex.escape(to_string(fun))}" + ), + line_text + ) or + Regex.match?(Regex.compile!("^\s*@doc"), line_text) + end + end + + @spec strip_macro_prefix({atom, non_neg_integer}) :: {atom, non_neg_integer} + def strip_macro_prefix({function, arity}) do + case Atom.to_string(function) do + "MACRO-" <> rest -> {String.to_atom(rest), arity - 1} + _other -> {function, arity} end end end From a93d0f858476438434c1b51e04f5ad7e86738560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Sat, 15 Feb 2020 16:04:54 +0100 Subject: [PATCH 2/2] Update apps/language_server/lib/language_server/source_file.ex Co-Authored-By: Jason Axelson --- apps/language_server/lib/language_server/source_file.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/language_server/lib/language_server/source_file.ex b/apps/language_server/lib/language_server/source_file.ex index b8a23b8b0..7578556c3 100644 --- a/apps/language_server/lib/language_server/source_file.ex +++ b/apps/language_server/lib/language_server/source_file.ex @@ -145,7 +145,7 @@ defmodule ElixirLS.LanguageServer.SourceFile do line_text -> # when function line is taken from docs the line points to `@doc` attribute - # or first `def`/`defp`/`defmacro`/`defmacrop`/`defquard`/`defguardp`/`defdelegate` clause line if no `@doc` attribute + # or first `def`/`defp`/`defmacro`/`defmacrop`/`defguard`/`defguardp`/`defdelegate` clause line if no `@doc` attribute Regex.match?( Regex.compile!( "^\s*def((macro)|(guard)|(delegate))?p?\s+#{Regex.escape(to_string(fun))}"