Skip to content

Commit

Permalink
provide docs for variables
Browse files Browse the repository at this point in the history
provide docs for variable when it shadows a function
provide docs for all module attributes
Partially addresses #164
  • Loading branch information
lukaszsamson committed Jul 17, 2023
1 parent b651396 commit 9135e21
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 9 deletions.
3 changes: 2 additions & 1 deletion lib/elixir_sense.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ defmodule ElixirSense do
nil

%{begin: begin_pos, end: end_pos, context: context} ->
{line, col} = begin_pos
metadata = Parser.parse_string(code, true, true, line)

env = Metadata.get_env(metadata, {line, column})

case Docs.all(context, env, metadata.mods_funs_to_positions, metadata.types) do
case Docs.all(context, line, col, env, metadata.mods_funs_to_positions, metadata.types) do
{actual_subject, docs} ->
%{
actual_subject: actual_subject,
Expand Down
73 changes: 68 additions & 5 deletions lib/elixir_sense/providers/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ defmodule ElixirSense.Providers.Docs do
alias ElixirSense.Core.Introspection
alias ElixirSense.Core.ReservedWords
alias ElixirSense.Core.State
alias ElixirSense.Core.State.VarInfo
alias ElixirSense.Core.SurroundContext

@spec all(any, State.Env.t(), State.mods_funs_to_positions_t(), State.types_t()) ::
@spec all(
any,
pos_integer,
pos_integer,
State.Env.t(),
State.mods_funs_to_positions_t(),
State.types_t()
) ::
{actual_mod_fun :: String.t(), docs :: Introspection.docs()} | nil
def all(
context,
line,
column,
%State.Env{
imports: imports,
requires: requires,
Expand All @@ -39,11 +49,62 @@ defmodule ElixirSense.Providers.Docs do

{:keyword, keyword} ->
docs = ReservedWords.docs(keyword)
{Atom.to_string(keyword), %{docs: docs}}

{Atom.to_string(keyword),
%{
docs: """
> #{keyword}
reserved word
#{docs}
"""
}}

{:attribute, attribute} ->
docs = BuiltinAttributes.docs(attribute)
if docs, do: {"@" <> Atom.to_string(attribute), %{docs: docs}}
docs = BuiltinAttributes.docs(attribute) || ""

{"@" <> Atom.to_string(attribute),
%{
docs: """
> @#{attribute}
module attribute
#{docs}
"""
}}

{:variable, variable} ->
var_info =
vars
|> Enum.find(fn
%VarInfo{name: name, positions: positions} ->
name == variable and {line, column} in positions
end)

if var_info != nil do
{Atom.to_string(variable),
%{
docs: """
> #{variable}
variable
"""
}}
else
mod_fun_docs(
type,
binding_env,
imports,
requires,
aliases,
module,
mods_funs,
metadata_types,
scope
)
end

_ ->
mod_fun_docs(
Expand Down Expand Up @@ -72,7 +133,6 @@ defmodule ElixirSense.Providers.Docs do
scope
) do
case Binding.expand(binding_env, type) do
# TODO
:none ->
mod_fun_docs(
{nil, name},
Expand All @@ -85,6 +145,9 @@ defmodule ElixirSense.Providers.Docs do
metadata_types,
scope
)

_ ->
nil
end
end

Expand Down
94 changes: 91 additions & 3 deletions test/elixir_sense/docs_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,8 +1028,10 @@ defmodule ElixirSense.DocsTest do

assert %{
actual_subject: "@on_load",
docs: %{docs: "A hook that will be invoked whenever the module is loaded."}
docs: %{docs: docs}
} = ElixirSense.docs(buffer, 2, 6)

assert docs =~ "A hook that will be invoked whenever the module is loaded."
end

test "retrieve unreserved module attributes documentation" do
Expand All @@ -1039,7 +1041,12 @@ defmodule ElixirSense.DocsTest do
end
"""

refute ElixirSense.docs(buffer, 2, 6)
assert %{
actual_subject: "@my_attribute",
docs: %{docs: docs}
} = ElixirSense.docs(buffer, 2, 6)

assert docs =~ "attribute"
end

test "retrieve docs on reserved words" do

Check failure on line 1052 in test/elixir_sense/docs_test.exs

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

test retrieve docs on reserved words (ElixirSense.DocsTest)

Check failure on line 1052 in test/elixir_sense/docs_test.exs

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

test retrieve docs on reserved words (ElixirSense.DocsTest)

Check failure on line 1052 in test/elixir_sense/docs_test.exs

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

test retrieve docs on reserved words (ElixirSense.DocsTest)

Check failure on line 1052 in test/elixir_sense/docs_test.exs

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

test retrieve docs on reserved words (ElixirSense.DocsTest)

Check failure on line 1052 in test/elixir_sense/docs_test.exs

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

test retrieve docs on reserved words (ElixirSense.DocsTest)

Check failure on line 1052 in test/elixir_sense/docs_test.exs

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

test retrieve docs on reserved words (ElixirSense.DocsTest)

Check failure on line 1052 in test/elixir_sense/docs_test.exs

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

test retrieve docs on reserved words (ElixirSense.DocsTest)

Check failure on line 1052 in test/elixir_sense/docs_test.exs

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

test retrieve docs on reserved words (ElixirSense.DocsTest)
Expand All @@ -1050,7 +1057,88 @@ defmodule ElixirSense.DocsTest do

assert %{
actual_subject: "do",
docs: %{docs: "do-end block control keyword"}
docs: %{docs: docs}
} = ElixirSense.docs(buffer, 1, 21)

assert docs =~ "do-end block control keyword"
end

test "retrieve docs on variables" do
buffer = """
defmodule MyModule do
def fun(my_var) do
other_var = 5
abc(my_var, other_var)
end
end
"""

assert %{
actual_subject: "my_var",
docs: %{docs: docs}
} = ElixirSense.docs(buffer, 2, 12)

assert docs =~ "variable"

assert %{
actual_subject: "other_var",
docs: %{docs: docs}
} = ElixirSense.docs(buffer, 3, 6)

assert docs =~ "variable"
end

test "variables shadow builtin functions" do
buffer = """
defmodule Vector do
@spec magnitude(Vec2.t()) :: number()
def magnitude(%Vec2{} = v), do: :math.sqrt(:math.pow(v.x, 2) + :math.pow(v.y, 2))
@spec normalize(Vec2.t()) :: Vec2.t()
def normalize(%Vec2{} = v) do
length = magnitude(v)
%{v | x: v.x / length, y: v.y / length}
end
end
"""

assert %{
actual_subject: "length",
docs: %{docs: docs}
} = ElixirSense.docs(buffer, 7, 6)

assert docs =~ "variable"

assert %{
actual_subject: "length",
docs: %{docs: docs}
} = ElixirSense.docs(buffer, 8, 21)

assert docs =~ "variable"
end

test "find local type in typespec local def elsewhere" do
buffer = """
defmodule ElixirSenseExample.Some do
@type some_local :: integer
def some_local(), do: :ok
@type user :: {some_local, integer}
def foo do
some_local
end
end
"""

assert %{actual_subject: "ElixirSenseExample.Some.some_local"} =
ElixirSense.docs(buffer, 6, 20)

# TODO assert type
assert %{actual_subject: "ElixirSenseExample.Some.some_local"} =
ElixirSense.docs(buffer, 9, 9)

# TODO assert function
end
end
25 changes: 25 additions & 0 deletions test/elixir_sense/references_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,31 @@ defmodule ElixirSense.Providers.ReferencesTest do
assert ElixirSense.references(buffer, 8, 10, trace) == expected_references
end

test "find references of a variable shadowing function", %{trace: trace} do
buffer = """
defmodule Vector do
@spec magnitude(Vec2.t()) :: number()
def magnitude(%Vec2{} = v), do: :math.sqrt(:math.pow(v.x, 2) + :math.pow(v.y, 2))
@spec normalize(Vec2.t()) :: Vec2.t()
def normalize(%Vec2{} = v) do
length = magnitude(v)
%{v | x: v.x / length, y: v.y / length}
end
end
"""

# `my_var`
expected_references = [
%{uri: nil, range: %{start: %{line: 7, column: 5}, end: %{line: 7, column: 11}}},
%{uri: nil, range: %{start: %{line: 8, column: 20}, end: %{line: 8, column: 26}}},
%{uri: nil, range: %{start: %{line: 8, column: 37}, end: %{line: 8, column: 43}}}
]

assert ElixirSense.references(buffer, 7, 6, trace) == expected_references
assert ElixirSense.references(buffer, 8, 21, trace) == expected_references
end

test "find references of attributes", %{trace: trace} do
buffer = """
defmodule MyModule do
Expand Down

0 comments on commit 9135e21

Please sign in to comment.