diff --git a/elixir_sense/README.md b/elixir_sense/README.md index e8db68a..be0601e 100644 --- a/elixir_sense/README.md +++ b/elixir_sense/README.md @@ -120,6 +120,17 @@ For coverage: mix coveralls ``` -## WIP +## Credits -Credits, License, ... +- This project probably wouldn't even exist without all the work done by Samuel Tonini and all contributors from [alchemist-server](https://github.com/tonini/alchemist-server). +- The Expand feature was inspired by the [mex](https://github.com/mrluc/mex) tool by Luc Fueston. There's also a very nice post where he describes the whole process of [Building A Macro-Expansion Helper for IEx](http://blog.maketogether.com/building-a-macro-expansion-helper/). + +## License (The MIT License) + +Copyright (c) 2017 Marlus Saraiva + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/elixir_sense/lib/alchemist/helpers/complete.ex b/elixir_sense/lib/alchemist/helpers/complete.ex index 92e29db..e7f5279 100644 --- a/elixir_sense/lib/alchemist/helpers/complete.ex +++ b/elixir_sense/lib/alchemist/helpers/complete.ex @@ -18,17 +18,17 @@ defmodule Alchemist.Helpers.Complete do def run(exp) do code = case is_bitstring(exp) do - true -> exp |> String.to_char_list + true -> exp |> String.to_charlist _ -> exp end - {status, result, list } = expand(code |> Enum.reverse) + {status, result, list} = expand(code |> Enum.reverse) - case { status, result, list } do - { :no, _, _ } -> '' - { :yes, [], _ } -> List.insert_at(list, 0, %{type: :hint, value: "#{exp}"}) - { :yes, _, [] } -> run(code ++ result) - { :yes, _, _ } -> List.insert_at(run(code ++ result), 1, Enum.at(list, 0)) + case {status, result, list} do + {:no, _, _} -> '' + {:yes, [], _} -> List.insert_at(list, 0, %{type: :hint, value: "#{exp}"}) + {:yes, _, []} -> run(code ++ result) + {:yes, _, _} -> List.insert_at(run(code ++ result), 1, Enum.at(list, 0)) # end end @@ -40,9 +40,9 @@ defmodule Alchemist.Helpers.Complete do !({f, a} in @builtin_functions) and (function_exported?(mod, f, a) or macro_exported?(mod, f, a)) end accept_function = fn - (mod, mod, _, _, _ ) -> true + (mod, mod, _, _, _) -> true (_ , _ , _, _, :undefined) -> false - (_ , mod, f, a, _ ) -> exported?.(mod, f, a) + (_ , mod, f, a, _) -> exported?.(mod, f, a) end for module <- modules, module != Elixir do @@ -73,9 +73,9 @@ defmodule Alchemist.Helpers.Complete do expand_import("") end - def expand([h|t]=expr) do + def expand([h|t] = expr) do cond do - h === ?. and t != []-> + h === ?. and t != [] -> expand_dot(reduce(t)) h === ?: -> expand_erlang_modules() @@ -131,7 +131,7 @@ defmodule Alchemist.Helpers.Complete do end defp yes(hint, entries) do - {:yes, String.to_char_list(hint), entries} + {:yes, String.to_charlist(hint), entries} end defp no do @@ -151,14 +151,14 @@ defmodule Alchemist.Helpers.Complete do end end - defp format_expansion([first|_]=entries, hint) do + defp format_expansion([first|_] = entries, hint) do binary = Enum.map(entries, &(&1.name)) length = byte_size(hint) prefix = :binary.longest_common_prefix(binary) if prefix in [0, length] do yes("", Enum.flat_map(entries, &to_entries/1)) else - yes(:binary.part(first.name, prefix, length-prefix), []) + yes(:binary.part(first.name, prefix, length - prefix), []) end end @@ -171,8 +171,9 @@ defmodule Alchemist.Helpers.Complete do # Elixir.fun defp expand_call({:__aliases__, _, list}, hint) do - expand_alias(list) - |> normalize_module + list + |> expand_alias() + |> normalize_module() |> expand_require(hint) end @@ -210,8 +211,9 @@ defmodule Alchemist.Helpers.Complete do end defp expand_elixir_modules(list, hint) do - expand_alias(list) - |> normalize_module + list + |> expand_alias() + |> normalize_module() |> expand_elixir_modules(hint, []) end @@ -236,8 +238,9 @@ defmodule Alchemist.Helpers.Complete do end end - defp env_aliases() do - Application.get_env(:"alchemist.el", :aliases) + defp env_aliases do + :"alchemist.el" + |> Application.get_env(:aliases) |> format_aliases end @@ -263,7 +266,8 @@ defmodule Alchemist.Helpers.Complete do mod_as_atom = mod |> String.to_atom desc = Introspection.get_module_docs_summary(mod_as_atom) subtype = Introspection.get_module_subtype(mod_as_atom) - %{kind: :module, type: :elixir, name: Enum.at(parts, depth-1), desc: desc, subtype: subtype} + %{kind: :module, type: :elixir, name: Enum.at(parts, depth - 1), + desc: desc, subtype: subtype} end |> Enum.uniq_by(fn %{name: name} -> name end) end @@ -279,7 +283,8 @@ defmodule Alchemist.Helpers.Complete do end defp match_modules(hint, root) do - get_modules(root) + root + |> get_modules() |> :lists.usort() |> Enum.drop_while(& not starts_with?(&1, hint)) |> Enum.take_while(& starts_with?(&1, hint)) @@ -321,7 +326,8 @@ defmodule Alchemist.Helpers.Complete do list = Enum.reduce falist, [], fn {f, a, func_kind, doc, spec}, acc -> case :lists.keyfind(f, 1, acc) do - {f, aa, func_kind, docs, specs} -> :lists.keyreplace(f, 1, acc, {f, [a|aa], func_kind, [doc|docs], [spec|specs]}) + {f, aa, func_kind, docs, specs} -> + :lists.keyreplace(f, 1, acc, {f, [a|aa], func_kind, [doc|docs], [spec|specs]}) false -> [{f, [a], func_kind, [doc], [spec]}|acc] end end @@ -329,7 +335,8 @@ defmodule Alchemist.Helpers.Complete do for {fun, arities, func_kind, docs, specs} <- list, name = Atom.to_string(fun), starts_with?(name, hint) do - %{kind: :function, name: name, arities: arities, module: mod, func_kind: func_kind, docs: docs, specs: specs} + %{kind: :function, name: name, arities: arities, module: mod, + func_kind: func_kind, docs: docs, specs: specs} end |> :lists.sort() _otherwise -> [] @@ -341,15 +348,19 @@ defmodule Alchemist.Helpers.Complete do funs = if docs = Code.get_docs(mod, :docs) do specs = Introspection.get_module_specs(mod) for {{f, a}, _line, func_kind, _sign, doc} = func_doc <- docs, doc != false do - spec = Map.get(specs, {f,a}, "") + spec = Map.get(specs, {f, a}, "") {f, a, func_kind, func_doc, spec} end else - macros = mod.__info__(:macros) |> Enum.map(fn {f, a} -> {f, a, :macro, nil, nil} end) - functions = mod.__info__(:functions) |> Enum.map(fn {f, a} -> {f, a, :function, nil, nil} end) + macros = :macros + |> mod.__info__() + |> Enum.map(fn {f, a} -> {f, a, :macro, nil, nil} end) + functions = :functions + |> mod.__info__() + |> Enum.map(fn {f, a} -> {f, a, :function, nil, nil} end) macros ++ functions end - funs ++ (@builtin_functions |> Enum.map(fn {f,a} -> {f, a, :function, nil, nil } end)) + funs ++ (@builtin_functions |> Enum.map(fn {f, a} -> {f, a, :function, nil, nil} end)) else for {f, a} <- mod.module_info(:exports) do case f |> Atom.to_string do @@ -386,7 +397,8 @@ defmodule Alchemist.Helpers.Complete do :defmacro -> "macro" _ -> "function" end - mod_name = mod |> Introspection.module_to_string + mod_name = mod + |> Introspection.module_to_string %{type: kind, name: name, arity: a, args: fun_args, origin: mod_name, summary: desc, spec: spec} end end diff --git a/elixir_sense/lib/alchemist/helpers/module_info.ex b/elixir_sense/lib/alchemist/helpers/module_info.ex index 8d1c492..7de310f 100644 --- a/elixir_sense/lib/alchemist/helpers/module_info.ex +++ b/elixir_sense/lib/alchemist/helpers/module_info.ex @@ -16,7 +16,8 @@ defmodule Alchemist.Helpers.ModuleInfo do def expand_alias([name | rest] = list, aliases) do module = Module.concat(Elixir, name) - Enum.find_value(aliases, list, fn {alias, mod} -> + aliases + |> Enum.find_value(list, fn {alias, mod} -> if alias === module do case Atom.to_string(mod) do "Elixir." <> mod -> @@ -25,7 +26,8 @@ defmodule Alchemist.Helpers.ModuleInfo do mod end end - end) |> normalize_module + end) + |> normalize_module end def get_functions(module, hint) do @@ -39,8 +41,9 @@ defmodule Alchemist.Helpers.ModuleInfo do false -> [{f, [a]}|acc] end end - - do_get_functions(list, hint) |> :lists.sort() + list + |> do_get_functions(hint) + |> :lists.sort() end def has_function?(module, function) do @@ -58,7 +61,10 @@ defmodule Alchemist.Helpers.ModuleInfo do defp get_module_funs(module) do case Code.ensure_loaded(module) do {:module, _} -> - (module.module_info(:functions) |> filter_module_funs) ++ module.__info__(:macros) + (:functions + |> module.module_info() + |> filter_module_funs) + ++ module.__info__(:macros) _otherwise -> [] end diff --git a/elixir_sense/lib/elixir_sense.ex b/elixir_sense/lib/elixir_sense.ex index e753767..e9921f7 100644 --- a/elixir_sense/lib/elixir_sense.ex +++ b/elixir_sense/lib/elixir_sense.ex @@ -63,7 +63,7 @@ defmodule ElixirSense do ...> ''' iex> {path, line} = ElixirSense.definition(code, 3, 11) iex> "#{Path.basename(path)}:#{to_string(line)}" - "enum.ex:2523" + "enum.ex:2576" """ @spec definition(String.t, pos_integer, pos_integer) :: Definition.location def definition(code, line, column) do @@ -87,10 +87,10 @@ defmodule ElixirSense do [":application", ":application_controller", ":application_master", ":application_starter"] iex> ElixirSense.all_modules() |> Enum.take(-4) - ["Version.Parser", "Version.Parser.DSL", "Version.Requirement", "WithClauseError"] + ["Version.InvalidVersionError", "Version.Parser", "Version.Requirement", "WithClauseError"] """ - def all_modules() do + def all_modules do Introspection.all_modules() |> Enum.map(&Atom.to_string(&1)) |> Enum.map(fn x -> if String.downcase(x) == x do ":" <> x else x end end) diff --git a/elixir_sense/lib/elixir_sense/core/ast.ex b/elixir_sense/lib/elixir_sense/core/ast.ex index ee22721..a026e88 100644 --- a/elixir_sense/lib/elixir_sense/core/ast.ex +++ b/elixir_sense/lib/elixir_sense/core/ast.ex @@ -1,41 +1,35 @@ defmodule ElixirSense.Core.Ast do + @moduledoc """ + Abstract Syntax Tree support + """ alias ElixirSense.Core.Introspection @empty_env_info %{requires: [], imports: [], behaviours: []} - @partials [:def, :defp, :defmodule, :@, :defmacro, :defmacrop, :defoverridable, :__ENV__, :__CALLER__, :raise, :if, :unless, :in] + @partials [:def, :defp, :defmodule, :@, :defmacro, :defmacrop, :defoverridable, + :__ENV__, :__CALLER__, :raise, :if, :unless, :in] - @max_expand_count 30000 + @max_expand_count 30_000 def extract_use_info(use_ast, module) do - try do - env = Map.put(__ENV__, :module, module) - {expanded_ast, _requires} = Macro.prewalk(use_ast, {env, 1}, &do_expand/2) - {_ast, env_info} = Macro.prewalk(expanded_ast, @empty_env_info, &pre_walk_expanded/2) - env_info - rescue - _e -> - # DEBUG - # IO.puts(:stderr, "Expanding #{Macro.to_string(use_ast)} failed.") - # IO.puts(:stderr, Exception.message(e) <> "\n" <> Exception.format_stacktrace(System.stacktrace)) - @empty_env_info - catch - {:expand_error, _} -> - IO.puts(:stderr, "Info: ignoring recursive macro") - @empty_env_info - end + env = Map.merge(__ENV__, %{module: module, function: nil}) + {expanded_ast, _requires} = Macro.prewalk(use_ast, {env, 1}, &do_expand/2) + {_ast, env_info} = Macro.prewalk(expanded_ast, @empty_env_info, &pre_walk_expanded/2) + env_info + catch + {:expand_error, _} -> + IO.puts(:stderr, "Info: ignoring recursive macro") + @empty_env_info end def expand_partial(ast, env) do - try do - {expanded_ast, _} = Macro.prewalk(ast, {env, 1}, &do_expand_partial/2) - expanded_ast - rescue - _e -> ast - catch - e -> e - end + {expanded_ast, _} = Macro.prewalk(ast, {env, 1}, &do_expand_partial/2) + expanded_ast + rescue + _e -> ast + catch + e -> e end def expand_all(ast, env) do @@ -105,8 +99,13 @@ defmodule ElixirSense.Core.Ast do if count > @max_expand_count do throw {:expand_error, "Cannot expand recursive macro"} end - expanded_ast = Macro.expand(ast, env) - {expanded_ast, {env, count+1}} + try do + expanded_ast = Macro.expand(ast, env) + {expanded_ast, {env, count + 1}} + rescue + _e -> + {ast, {env, count + 1}} + end end defp pre_walk_expanded({:__block__, _, _} = ast, acc) do diff --git a/elixir_sense/lib/elixir_sense/core/introspection.ex b/elixir_sense/lib/elixir_sense/core/introspection.ex index f3f64cf..ae4af0e 100644 --- a/elixir_sense/lib/elixir_sense/core/introspection.ex +++ b/elixir_sense/lib/elixir_sense/core/introspection.ex @@ -1,5 +1,4 @@ defmodule ElixirSense.Core.Introspection do - @moduledoc """ A collection of functions to introspect/format docs, specs, types and callbacks. @@ -22,7 +21,7 @@ defmodule ElixirSense.Core.Introspection do :gen_event => GenEvent } - def all_modules() do + def all_modules do ModuleInfo.all_applications_modules() end @@ -52,7 +51,9 @@ defmodule ElixirSense.Core.Introspection do nil -> nil docs -> for {{f, arity}, _, _, args, text} <- docs, f == fun do - fun_args_text = Enum.map_join(args, ", ", &format_doc_arg(&1)) |> String.replace("\\\\", "\\\\\\\\") + fun_args_text = args + |> Enum.map_join(", ", &format_doc_arg(&1)) + |> String.replace("\\\\", "\\\\\\\\") mod_str = module_to_string(mod) fun_str = Atom.to_string(fun) "> #{mod_str}.#{fun_str}(#{fun_args_text})\n\n#{get_spec_text(mod, fun, arity)}#{text}" @@ -96,7 +97,8 @@ defmodule ElixirSense.Core.Introspection do #{doc} """ - end |> Enum.join("\n\n____\n\n") + end + |> Enum.join("\n\n____\n\n") end def get_callbacks_with_docs(mod) when is_atom(mod) do @@ -115,7 +117,8 @@ defmodule ElixirSense.Core.Introspection do {callbacks, docs} -> Enum.map docs, fn {{fun, arity}, _, :macrocallback, doc} -> - get_callback_with_doc(fun, :macrocallback, doc, {:"MACRO-#{fun}", arity + 1}, callbacks) + fun + |> get_callback_with_doc(:macrocallback, doc, {:"MACRO-#{fun}", arity + 1}, callbacks) |> Map.put(:arity, arity) {{fun, arity}, _, kind, doc} -> get_callback_with_doc(fun, kind, doc, {fun, arity}, callbacks) @@ -124,7 +127,9 @@ defmodule ElixirSense.Core.Introspection do end def get_types_with_docs(module) when is_atom(module) do - get_types(module) |> Enum.map(fn {_, {t, _, _args}} = type -> + module + |> get_types() + |> Enum.map(fn {_, {t, _, _args}} = type -> %{type: format_type(type), doc: get_type_doc(module, t)} end) end @@ -191,7 +196,8 @@ defmodule ElixirSense.Core.Introspection do end def define_callback?(mod, fun, arity) do - Kernel.Typespec.beam_callbacks(mod) + mod + |> Kernel.Typespec.beam_callbacks() |> Enum.any?(fn {{f, a}, _} -> {f, a} == {fun, arity} end) end @@ -249,7 +255,9 @@ defmodule ElixirSense.Core.Introspection do {_, [spec | _]} = List.keyfind(callbacks, key, 0) {_f, arity} = key - spec_ast = Typespec.spec_to_ast(name, spec) |> Macro.prewalk(&drop_macro_env/1) + spec_ast = name + |> Typespec.spec_to_ast(spec) + |> Macro.prewalk(&drop_macro_env/1) signature = get_typespec_signature(spec_ast, arity) definition = format_spec_ast(spec_ast) @@ -328,23 +336,30 @@ defmodule ElixirSense.Core.Introspection do {ast, index} end defp next_snippet(ast, index) do - {"${#{index}:#{spec_ast_to_string(ast)}}$", index+1} + {"${#{index}:#{spec_ast_to_string(ast)}}$", index + 1} end def param_to_var({{:=, _, [_lhs, {name, _, _} = rhs]}, arg_index}) when is_atom(name) do - to_var(rhs, arg_index + 1) |> Macro.to_string + rhs + |> to_var(arg_index + 1) + |> Macro.to_string end def param_to_var({{:=, _, [{name, _, _} = lhs, _rhs]}, arg_index}) when is_atom(name) do - to_var(lhs, arg_index + 1) |> Macro.to_string + lhs + |> to_var(arg_index + 1) + |> Macro.to_string end def param_to_var({{:\\, _, _} = ast, _}) do - ast |> Macro.to_string + ast + |> Macro.to_string end def param_to_var({ast, arg_index}) do - to_var(ast, arg_index + 1) |> Macro.to_string + ast + |> to_var(arg_index + 1) + |> Macro.to_string end defp to_var({:{}, _, _}, _), @@ -376,19 +391,27 @@ defmodule ElixirSense.Core.Introspection do end def get_module_subtype(module) do - has_func = fn f,a -> Code.ensure_loaded?(module) && Kernel.function_exported?(module,f,a) end + has_func = fn f, a -> Code.ensure_loaded?(module) && Kernel.function_exported?(module, f, a) end cond do has_func.(:__protocol__, 1) -> :protocol has_func.(:__impl__, 1) -> :implementation - has_func.(:__struct__, 0) -> if Map.get(module.__struct__, :__exception__), do: :exception, else: :struct + has_func.(:__struct__, 0) -> + if Map.get(module.__struct__, :__exception__) do + :exception + else + :struct + end true -> nil end end - def extract_fun_args_and_desc({ { _fun, _ }, _line, _kind, args, doc }) do - args = Enum.map_join(args, ",", &format_doc_arg(&1)) |> String.replace(~r/\s+/, " ") + def extract_fun_args_and_desc({{_fun, _}, _line, _kind, args, doc}) do + formatted_args = + args + |> Enum.map_join(",", &format_doc_arg(&1)) + |> String.replace(~r/\s+/, " ") desc = extract_summary_from_docs(doc) - {args, desc} + {formatted_args, desc} end def extract_fun_args_and_desc(nil) do @@ -400,7 +423,7 @@ defmodule ElixirSense.Core.Introspection do nil -> %{} specs -> for {_kind, {{f, a}, _spec}} = spec <- specs, into: %{} do - {{f,a}, spec_to_string(spec)} + {{f, a}, spec_to_string(spec)} end end end @@ -433,17 +456,21 @@ defmodule ElixirSense.Core.Introspection do {:ok, quoted} when is_atom(quoted) -> {quoted, nil} {:ok, quoted} -> - case Macro.decompose_call(quoted) do - {{:__aliases__, _, mod_parts}, fun, _args} -> - {Module.concat(mod_parts), fun} - {:__aliases__, mod_parts} -> - {Module.concat(mod_parts), nil} - {mod, func, []} when is_atom(mod) and is_atom(func) -> - {mod, func} - {func, []} when is_atom(func) -> - {nil, func} - _ -> {nil, nil} - end + split_mod_quoted_fun_call(quoted) + end + end + + def split_mod_quoted_fun_call(quoted) do + case Macro.decompose_call(quoted) do + {{:__aliases__, _, mod_parts}, fun, _args} -> + {Module.concat(mod_parts), fun} + {:__aliases__, mod_parts} -> + {Module.concat(mod_parts), nil} + {mod, func, []} when is_atom(mod) and is_atom(func) -> + {mod, func} + {func, []} when is_atom(func) -> + {nil, func} + _ -> {nil, nil} end end @@ -451,25 +478,25 @@ defmodule ElixirSense.Core.Introspection do docs = Code.get_docs(module, :docs) || [] specs = get_module_specs(module) for {{f, a}, _line, func_kind, _sign, doc} = func_doc <- docs, doc != false, into: %{} do - spec = Map.get(specs, {f,a}, "") + spec = Map.get(specs, {f, a}, "") {fun_args, desc} = extract_fun_args_and_desc(func_doc) {{f, a}, {func_kind, fun_args, desc, spec}} end end def get_callback_ast(module, callback, arity) do - {{name, _}, [spec | _]} = - Kernel.Typespec.beam_callbacks(module) + {{name, _}, [spec | _]} = module + |> Kernel.Typespec.beam_callbacks() |> Enum.find(fn {{f, a}, _} -> {f, a} == {callback, arity} end) Kernel.Typespec.spec_to_ast(name, spec) end - defp format_doc_arg({ :\\, _, [left, right] }) do + defp format_doc_arg({:\\, _, [left, right]}) do format_doc_arg(left) <> " \\\\ " <> Macro.to_string(right) end - defp format_doc_arg({ var, _, _ }) do + defp format_doc_arg({var, _, _}) do Atom.to_string(var) end diff --git a/elixir_sense/lib/elixir_sense/core/metadata.ex b/elixir_sense/lib/elixir_sense/core/metadata.ex index ebfd921..852affb 100644 --- a/elixir_sense/lib/elixir_sense/core/metadata.ex +++ b/elixir_sense/lib/elixir_sense/core/metadata.ex @@ -1,4 +1,7 @@ defmodule ElixirSense.Core.Metadata do + @moduledoc """ + Core Metadata + """ alias ElixirSense.Core.State alias ElixirSense.Core.Introspection @@ -31,12 +34,15 @@ defmodule ElixirSense.Core.Metadata do def get_function_params(%__MODULE__{} = metadata, module, function) do params = - get_function_info(metadata, module, function) + metadata + |> get_function_info(module, function) |> Map.get(:params) |> Enum.reverse Enum.map(params, fn param -> - Macro.to_string(param) |> String.slice(1..-2) + param + |> Macro.to_string() + |> String.slice(1..-2) end) end @@ -44,7 +50,8 @@ defmodule ElixirSense.Core.Metadata do docs = code_docs || Code.get_docs(module, :docs) || [] params_list = - get_function_info(metadata, module, function) + metadata + |> get_function_info(module, function) |> Map.get(:params) |> Enum.reverse @@ -57,7 +64,7 @@ defmodule ElixirSense.Core.Metadata do {Introspection.extract_summary_from_docs(text), Introspection.get_spec(module, function, arity)} end) %{name: Atom.to_string(function), - params: Enum.with_index(params) |> Enum.map(&Introspection.param_to_var/1), + params: params |> Enum.with_index() |> Enum.map(&Introspection.param_to_var/1), documentation: doc, spec: spec } diff --git a/elixir_sense/lib/elixir_sense/core/parser.ex b/elixir_sense/lib/elixir_sense/core/parser.ex index 3b8a337..a7f1169 100644 --- a/elixir_sense/lib/elixir_sense/core/parser.ex +++ b/elixir_sense/lib/elixir_sense/core/parser.ex @@ -1,4 +1,7 @@ defmodule ElixirSense.Core.Parser do + @moduledoc """ + Core Parser + """ alias ElixirSense.Core.MetadataBuilder alias ElixirSense.Core.Metadata @@ -93,7 +96,7 @@ defmodule ElixirSense.Core.Parser do # IO.puts :stderr, "REPLACING LINE: #{line}" source |> String.split(["\n", "\r\n"]) - |> List.replace_at(line-1, "(__atom_elixir_marker_#{line}__())") + |> List.replace_at(line - 1, "(__atom_elixir_marker_#{line}__())") |> Enum.join("\n") end diff --git a/elixir_sense/lib/elixir_sense/core/source.ex b/elixir_sense/lib/elixir_sense/core/source.ex index 493f2eb..8ff7f0c 100644 --- a/elixir_sense/lib/elixir_sense/core/source.ex +++ b/elixir_sense/lib/elixir_sense/core/source.ex @@ -1,11 +1,14 @@ defmodule ElixirSense.Core.Source do + @moduledoc """ + Source parsing + """ @empty_graphemes [" ", "\n", "\r\n"] @stop_graphemes ~w/{ } ( ) [ ] < > + - * & ^ , ; ~ % = " ' \\ \/ $ ! ?`#/ ++ @empty_graphemes def prefix(code, line, col) do - line = code |> String.split("\n") |> Enum.at(line-1) - line_str = line |> String.slice(0, col-1) + line = code |> String.split("\n") |> Enum.at(line - 1) + line_str = line |> String.slice(0, col - 1) case Regex.run(~r/[\w0-9\._!\?\:@]+$/, line_str) do nil -> "" [prefix] -> prefix @@ -94,7 +97,7 @@ defmodule ElixirSense.Core.Source do def which_func(prefix) do tokens = - case prefix |> String.to_char_list |> :elixir_tokenizer.tokenize(1, []) do + case prefix |> String.to_charlist |> :elixir_tokenizer.tokenize(1, []) do {:ok, _, _, tokens} -> tokens |> Enum.reverse {:error, {_line, _error_prefix, _token}, _rest, sofar} -> @@ -104,8 +107,8 @@ defmodule ElixirSense.Core.Source do # IO.inspect(:stderr, {:rest, rest}, []) sofar end - - result = scan(tokens, %{npar: 0, count: 0, count2: 0, candidate: [], pos: nil, pipe_before: false }) + pattern = %{npar: 0, count: 0, count2: 0, candidate: [], pos: nil, pipe_before: false} + result = scan(tokens, pattern) %{candidate: candidate, npar: npar, pipe_before: pipe_before, pos: pos} = result %{ @@ -154,7 +157,8 @@ defmodule ElixirSense.Core.Source do scan(tokens, %{state | candidate: [value|state.candidate], pos: update_pos(pos, state.pos)}) end defp scan([{:aliases, pos, [value]}|tokens], %{count: 1} = state) do - scan(tokens, %{state | candidate: [Module.concat([value])|state.candidate], pos: update_pos(pos, state.pos)}) + updated_pos = update_pos(pos, state.pos) + scan(tokens, %{state | candidate: [Module.concat([value])|state.candidate], pos: updated_pos}) end defp scan([{:atom, pos, value}|tokens], %{count: 1} = state) do scan(tokens, %{state | candidate: [value|state.candidate], pos: update_pos(pos, state.pos)}) diff --git a/elixir_sense/lib/elixir_sense/core/state.ex b/elixir_sense/lib/elixir_sense/core/state.ex index 5fb136f..02382ef 100644 --- a/elixir_sense/lib/elixir_sense/core/state.ex +++ b/elixir_sense/lib/elixir_sense/core/state.ex @@ -1,4 +1,7 @@ defmodule ElixirSense.Core.State do + @moduledoc """ + Core State + """ defstruct [ namespace: [:Elixir], @@ -17,6 +20,7 @@ defmodule ElixirSense.Core.State do ] defmodule Env do + @moduledoc false defstruct imports: [], requires: [], aliases: [], module: nil, vars: [], attributes: [], behaviours: [], scope: nil end @@ -52,10 +56,11 @@ defmodule ElixirSense.Core.State do end def get_current_scope_name(state) do - case hd(state.scopes) do + scope = case hd(state.scopes) do {fun, _} -> fun mod -> mod - end |> Atom.to_string + end + scope |> Atom.to_string() end def add_mod_fun_to_line(state, {module, fun, arity}, line, params) do @@ -65,7 +70,8 @@ defmodule ElixirSense.Core.State do new_params = [params|current_params] new_lines = [line|current_lines] - %{state | mods_funs_to_lines: Map.put(state.mods_funs_to_lines, {module, fun, arity}, %{lines: new_lines, params: new_params})} + mods_funs_to_lines = Map.put(state.mods_funs_to_lines, {module, fun, arity}, %{lines: new_lines, params: new_params}) + %{state | mods_funs_to_lines: mods_funs_to_lines} end def new_namespace(state, module) do @@ -224,8 +230,9 @@ defmodule ElixirSense.Core.State do else [attribute|attributes_from_scope] end - - %{state | attributes: [attributes_from_scope|other_attributes], scope_attributes: [attributes_from_scope|tl(state.scope_attributes)]} + attributes = [attributes_from_scope|other_attributes] + scope_attributes = [attributes_from_scope|tl(state.scope_attributes)] + %{state | attributes: attributes, scope_attributes: scope_attributes} end def add_behaviour(state, module) do diff --git a/elixir_sense/lib/elixir_sense/providers/docs.ex b/elixir_sense/lib/elixir_sense/providers/docs.ex index 8e9a892..7e32df7 100644 --- a/elixir_sense/lib/elixir_sense/providers/docs.ex +++ b/elixir_sense/lib/elixir_sense/providers/docs.ex @@ -1,5 +1,7 @@ defmodule ElixirSense.Providers.Docs do - + @moduledoc """ + Doc Provider + """ alias ElixirSense.Core.Introspection @spec all(String.t, [module], [{module, module}], module) :: {actual_mod_fun :: String.t, docs :: Introspection.docs} diff --git a/elixir_sense/lib/elixir_sense/providers/expand.ex b/elixir_sense/lib/elixir_sense/providers/expand.ex index 93111e9..4f26a79 100644 --- a/elixir_sense/lib/elixir_sense/providers/expand.ex +++ b/elixir_sense/lib/elixir_sense/providers/expand.ex @@ -28,10 +28,10 @@ defmodule ElixirSense.Providers.Expand do try do {_, expr} = code |> Code.string_to_quoted %{ - expand_once: Macro.expand_once(expr, env) |> Macro.to_string, - expand: Macro.expand(expr, env) |> Macro.to_string, - expand_partial: Ast.expand_partial(expr, env) |> Macro.to_string, - expand_all: Ast.expand_all(expr, env) |> Macro.to_string, + expand_once: expr |> Macro.expand_once(env) |> Macro.to_string, + expand: expr |> Macro.expand(env) |> Macro.to_string, + expand_partial: expr |> Ast.expand_partial(env) |> Macro.to_string, + expand_all: expr |> Ast.expand_all(env) |> Macro.to_string, } rescue e -> diff --git a/elixir_sense/lib/elixir_sense/providers/signature.ex b/elixir_sense/lib/elixir_sense/providers/signature.ex index 5da1f64..e046348 100644 --- a/elixir_sense/lib/elixir_sense/providers/signature.ex +++ b/elixir_sense/lib/elixir_sense/providers/signature.ex @@ -28,10 +28,11 @@ defmodule ElixirSense.Providers.Signature do defp find_signatures({mod, fun}, metadata) do docs = Code.get_docs(mod, :docs) - case Metadata.get_function_signatures(metadata, mod, fun, docs) do + signatures = case Metadata.get_function_signatures(metadata, mod, fun, docs) do [] -> Introspection.get_signatures(mod, fun, docs) signatures -> signatures - end |> Enum.uniq_by(fn sig -> sig.params end) + end + signatures |> Enum.uniq_by(fn sig -> sig.params end) end end diff --git a/elixir_sense/lib/elixir_sense/server.ex b/elixir_sense/lib/elixir_sense/server.ex index 057e44b..13c65bc 100644 --- a/elixir_sense/lib/elixir_sense/server.ex +++ b/elixir_sense/lib/elixir_sense/server.ex @@ -1,15 +1,20 @@ defmodule ElixirSense.Server do + @moduledoc """ + Server entry point and coordinator + """ + + alias ElixirSense.Server.TCPServer def start(args) do [socket_type, port, env] = validate_args(args) IO.puts(:stderr, "Initializing ElixirSense server for environment \"#{env}\" (Elixir version #{System.version})") IO.puts(:stderr, "Working directory is \"#{Path.expand(".")}\"") - ElixirSense.Server.TCPServer.start([socket_type: socket_type, port: port, env: env]) + TCPServer.start([socket_type: socket_type, port: port, env: env]) loop() end defp validate_args(["unix", _port, env] = args) do - {version, _} = :erlang.system_info(:otp_release) |> :string.to_integer + {version, _} = :otp_release |> :erlang.system_info() |> :string.to_integer() if version < 19 do IO.puts(:stderr, "Warning: Erlang version < 19. Cannot use Unix domain sockets. Using tcp/ip instead.") ["tcpip", "0", env] @@ -21,7 +26,7 @@ defmodule ElixirSense.Server do args end - defp loop() do + defp loop do case IO.gets("") do :eof -> IO.puts(:stderr, "Stopping ElixirSense server") diff --git a/elixir_sense/lib/elixir_sense/server/context_loader.ex b/elixir_sense/lib/elixir_sense/server/context_loader.ex index 1428e1b..f98f48d 100644 --- a/elixir_sense/lib/elixir_sense/server/context_loader.ex +++ b/elixir_sense/lib/elixir_sense/server/context_loader.ex @@ -1,4 +1,7 @@ defmodule ElixirSense.Server.ContextLoader do + @moduledoc """ + Server Context Loader + """ use GenServer @minimal_reload_time 2000 @@ -54,9 +57,9 @@ defmodule ElixirSense.Server.ContextLoader do end) end - defp all_loaded() do + defp all_loaded do preload_modules([Inspect, :base64, :crypto]) - for {m,_} <- :code.all_loaded, do: m + for {m, _} <- :code.all_loaded, do: m end defp load_paths(env, cwd) do diff --git a/elixir_sense/lib/elixir_sense/server/request_handler.ex b/elixir_sense/lib/elixir_sense/server/request_handler.ex index e1b52b3..2156ee6 100644 --- a/elixir_sense/lib/elixir_sense/server/request_handler.ex +++ b/elixir_sense/lib/elixir_sense/server/request_handler.ex @@ -43,7 +43,14 @@ defmodule ElixirSense.Server.RequestHandler do end def handle_request("set_context", %{"env" => env, "cwd" => cwd}) do - ContextLoader.set_context(env, cwd) |> Tuple.to_list + env |> ContextLoader.set_context(cwd) |> Tuple.to_list() + end + + def handle_request("version", %{}) do + %{ + elixir: System.version, + otp: System.otp_release + } end def handle_request(request, payload) do diff --git a/elixir_sense/lib/elixir_sense/server/tcp_server.ex b/elixir_sense/lib/elixir_sense/server/tcp_server.ex index 63b2462..da953dd 100644 --- a/elixir_sense/lib/elixir_sense/server/tcp_server.ex +++ b/elixir_sense/lib/elixir_sense/server/tcp_server.ex @@ -1,4 +1,7 @@ defmodule ElixirSense.Server.TCPServer do + @moduledoc """ + TCP Server connection endpoint + """ use Bitwise alias ElixirSense.Server.{RequestHandler, ContextLoader} @@ -42,13 +45,15 @@ defmodule ElixirSense.Server.TCPServer do defp format_output("tcpip", host, port, auth_token) do "ok:#{host}:#{port}:#{auth_token}" end + defp format_output("unix", host, file, _auth_token) do "ok:#{host}:#{file}" end defp listen_options("tcpip", port) do - {String.to_integer(port), @default_listen_options ++ [ip: {127,0,0,1}]} + {String.to_integer(port), @default_listen_options ++ [ip: {127, 0, 0, 1}]} end + defp listen_options("unix", _port) do {0, @default_listen_options ++ [ifaddr: {:local, socket_file()}]} end @@ -80,31 +85,43 @@ defmodule ElixirSense.Server.TCPServer do end defp process_request(data, auth_token) do + with \ + {:ok, decoded_data} <- decode_request_data(data), + {:ok, result} <- dispatch_request(decoded_data, auth_token) + do + :erlang.term_to_binary(result) + else + {:invalid_request, message} -> + IO.puts(:stderr, "Server Error: #{message}") + :erlang.term_to_binary(%{request_id: nil, payload: nil, error: message}) + {:error, request_id, exception} -> + IO.puts(:stderr, "Server Error: \n" <> Exception.message(exception) <> "\n" <> Exception.format_stacktrace(System.stacktrace)) + :erlang.term_to_binary(%{request_id: request_id, payload: nil, error: Exception.message(exception)}) + end + end + + defp dispatch_request(%{ + "request_id" => request_id, + "auth_token" => req_token, + "request" => request, + "payload" => payload}, auth_token) do try do - data - |> :erlang.binary_to_term() - |> dispatch_request(auth_token) - |> :erlang.term_to_binary() + result = + if secure_compare(auth_token, req_token) do + ContextLoader.reload() + payload = RequestHandler.handle_request(request, payload) + %{request_id: request_id, payload: payload, error: nil} + else + %{request_id: request_id, payload: nil, error: "unauthorized"} + end + {:ok, result} rescue - e -> - IO.puts(:stderr, "Server Error: \n" <> Exception.message(e) <> "\n" <> Exception.format_stacktrace(System.stacktrace)) - :erlang.term_to_binary(%{request_id: nil, payload: nil, error: Exception.message(e)}) - catch - e -> - error = "Uncaught value #{inspect(e)}" - IO.puts(:stderr, "Server Error: #{error}\n" <> Exception.format_stacktrace(System.stacktrace)) - :erlang.term_to_binary(%{request_id: nil, payload: nil, error: error}) + e -> {:error, request_id, e} end end - defp dispatch_request(%{ "request_id" => request_id, "auth_token" => req_token, "request" => request, "payload" => payload }, auth_token) do - if secure_compare(auth_token, req_token) do - ContextLoader.reload() - payload = RequestHandler.handle_request(request, payload) - %{request_id: request_id, payload: payload, error: nil } - else - %{request_id: request_id, payload: nil, error: "unauthorized" } - end + defp dispatch_request(_, _) do + {:invalid_request, "Invalid request"} end defp send_response(data, socket) do @@ -116,17 +133,28 @@ defmodule ElixirSense.Server.TCPServer do String.to_charlist("/tmp/elixir-sense-#{sock_id}.sock") end + defp decode_request_data(data) do + try do + {:ok, :erlang.binary_to_term(data)} + rescue + _e -> + {:error, "Cannot decode request data. :erlang.binary_to_term/1 failed"} + end + end + # Adapted from https://github.com/plackemacher/secure_compare/blob/master/lib/secure_compare.ex defp secure_compare(nil, nil), do: true defp secure_compare(a, b) when is_nil(a) or is_nil(b), do: false defp secure_compare(a, b) when byte_size(a) != byte_size(b), do: false defp secure_compare(a, b) when is_binary(a) and is_binary(b) do - a_list = String.to_char_list(a) - b_list = String.to_char_list(b) + a_list = String.to_charlist(a) + b_list = String.to_charlist(b) secure_compare(a_list, b_list) end defp secure_compare(a, b) when is_list(a) and is_list(b) do - res = Enum.zip(a, b) |> Enum.reduce(0, fn({a_byte, b_byte}, acc) -> + res = a + |> Enum.zip(b) + |> Enum.reduce(0, fn({a_byte, b_byte}, acc) -> acc ||| bxor(a_byte, b_byte) end) res == 0 diff --git a/elixir_sense/mix.exs b/elixir_sense/mix.exs index 74ca9cb..24e007f 100644 --- a/elixir_sense/mix.exs +++ b/elixir_sense/mix.exs @@ -1,10 +1,11 @@ defmodule ElixirSense.Mixfile do + @moduledoc false use Mix.Project def project do [app: :elixir_sense, - version: "0.1.0", - elixir: "~> 1.2", + version: "1.0.0", + elixir: "~> 1.5", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, test_coverage: [tool: ExCoveralls], @@ -13,7 +14,9 @@ defmodule ElixirSense.Mixfile do flags: ["-Wunmatched_returns", "-Werror_handling", "-Wrace_conditions", "-Wunderspecs", "-Wno_match"] ], deps: deps(), - docs: docs() + docs: docs(), + description: description(), + package: package(), ] end @@ -24,6 +27,7 @@ defmodule ElixirSense.Mixfile do defp deps do [{:excoveralls, "~> 0.6", only: :test}, {:dialyxir, "~> 0.4", only: [:dev]}, + {:credo, "~> 0.8.4", only: [:dev]}, {:ex_doc, "~> 0.14", only: [:dev]}] end @@ -31,4 +35,18 @@ defmodule ElixirSense.Mixfile do [main: "ElixirSense"] end + defp description do + """ + An API/Server for Elixir projects that provides context-aware information + for code completion, documentation, go/jump to definition, signature info + and more. + """ + end + + defp package do + [maintainers: ["Marlus Saraiva (@msaraiva)"], + licenses: ["MIT"], + links: %{"GitHub" => "https://github.com/msaraiva/elixir_sense"}] + end + end diff --git a/elixir_sense/mix.lock b/elixir_sense/mix.lock index 5e77e3f..1354392 100644 --- a/elixir_sense/mix.lock +++ b/elixir_sense/mix.lock @@ -1,4 +1,6 @@ -%{"certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], []}, +%{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"}, + "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], []}, + "credo": {:hex, :credo, "0.8.4", "4e50acac058cf6292d6066e5b0d03da5e1483702e1ccde39abba385c9f03ead4", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.4.3", "a4daeebd0107de10d3bbae2ccb6b8905e69544db1ed5fe9148ad27cd4cb2c0cd", [:mix], []}, "earmark": {:hex, :earmark, "1.1.0", "8c2bf85d725050a92042bc1edf362621004d43ca6241c756f39612084e95487f", [:mix], []}, "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}, diff --git a/elixir_sense/test/elixir_sense/docs_test.exs b/elixir_sense/test/elixir_sense/docs_test.exs index 1e14ebd..7d6f7f2 100644 --- a/elixir_sense/test/elixir_sense/docs_test.exs +++ b/elixir_sense/test/elixir_sense/docs_test.exs @@ -150,7 +150,7 @@ defmodule ElixirSense.DocsTest do ### Specs - `@spec create_file(Path.t, iodata, Keyword.t) :: any` + `@spec create_file(Path.t, iodata, keyword) :: any` Creates a file with the given contents. If the file already exists, asks for user confirmation. @@ -158,6 +158,15 @@ defmodule ElixirSense.DocsTest do ## Options * `:force` - forces installation without a shell prompt. + + ## Examples + + iex> Mix.Generator.create_file \".gitignore\", \"_build + deps + \" + * creating .gitignore + :ok + """ end @@ -177,6 +186,8 @@ defmodule ElixirSense.DocsTest do Defines a macro with the given name and body. + Check `def/2` for rules on naming and default arguments. + ## Examples defmodule MyLogic do diff --git a/elixir_sense/test/elixir_sense/eval_test.exs b/elixir_sense/test/elixir_sense/eval_test.exs index 49f10aa..2d4ff3e 100644 --- a/elixir_sense/test/elixir_sense/eval_test.exs +++ b/elixir_sense/test/elixir_sense/eval_test.exs @@ -74,7 +74,7 @@ defmodule ElixirSense.Evaltest do def(stop(_state)) do :ok end - defoverridable(stop: 1) + defoverridable(Application) ) ) """ |> String.trim @@ -84,8 +84,12 @@ defmodule ElixirSense.Evaltest do ( require(Application) ( - Module.put_attribute(MyModule, :behaviour, Application, nil, nil) - Module.put_attribute(MyModule, :doc, {0, false}, [{MyModule, :__MODULE__, 0, [file: "lib/elixir_sense/providers/expand.ex", line: 0]}], nil) + @behaviour(Application) + @doc(false) + def(stop(_state)) do + :ok + end + Module.make_overridable(MyModule, Application) """ |> String.trim else assert result.expand_all =~ """ diff --git a/elixir_sense/test/elixir_sense/providers/suggestion_test.exs b/elixir_sense/test/elixir_sense/providers/suggestion_test.exs index 68d6eff..11560a7 100644 --- a/elixir_sense/test/elixir_sense/providers/suggestion_test.exs +++ b/elixir_sense/test/elixir_sense/providers/suggestion_test.exs @@ -10,24 +10,14 @@ defmodule ElixirSense.Providers.SuggestionTest do end test "find definition of functions from Kernel" do - assert [ - %{type: :hint, value: "List."}, - %{name: "List", subtype: nil, summary: "" <> _, type: :module}, - %{name: "Chars", subtype: :protocol, summary: "The List.Chars protocol" <> _, type: :module}, - %{args: "", arity: 1, name: "__info__", origin: "List", spec: nil, summary: "", type: "function"}, - %{args: "list", arity: 1, name: "first", origin: "List", spec: "@spec first([elem]) :: nil | elem when elem: var", summary: "Returns the first " <> _, type: "function"}, - %{args: "list", arity: 1, name: "last", origin: "List", spec: "@spec last([elem]) :: nil | elem when elem: var", summary: "Returns the last element " <> _, type: "function"}, - %{args: "charlist", arity: 1, name: "to_atom", origin: "List", spec: "@spec to_atom(charlist) :: atom", summary: "Converts a charlist to an atom.", type: "function"}, - %{args: "charlist", arity: 1, name: "to_existing_atom", origin: "List", spec: "@spec to_existing_atom(charlist) :: atom", summary: "Converts a charlist" <> _, type: "function"}, - %{args: "charlist", arity: 1, name: "to_float", origin: "List", spec: "@spec to_float(charlist) :: float", summary: "Returns the float " <> _, type: "function"}, - %{args: "list", arity: 1, name: "to_string", origin: "List", spec: "@spec to_string(:unicode.charlist) :: String.t", summary: "Converts a list " <> _, type: "function"}, - %{args: "list", arity: 1, name: "to_tuple", origin: "List", spec: "@spec to_tuple(list) :: tuple", summary: "Converts a list to a tuple.", type: "function"}, - %{args: "list", arity: 1, name: "wrap", origin: "List", spec: "@spec wrap(list | any) :: list", summary: "Wraps the " <> _, type: "function"}, - %{args: "list_of_lists", arity: 1, name: "zip", origin: "List", spec: "@spec zip([list]) :: [tuple]", summary: "Zips corresponding " <> _, type: "function"}, - %{args: "", arity: 1, name: "module_info", origin: "List", spec: nil, summary: "", type: "function"}, - %{args: "", arity: 0, name: "module_info", origin: "List", spec: nil, summary: "", type: "function"}, - %{args: "list,item", arity: 2, name: "delete", origin: "List", spec: "@spec delete(list, any) :: list", summary: "Deletes the given " <> _, type: "function"} - | _] = Suggestion.find("List", [], [], [], [], [], SomeModule) + result = Suggestion.find("List", [], [], [], [], [], SomeModule) |> Enum.take(16) + assert result |> Enum.at(0) == %{type: :hint, value: "List."} + assert result |> Enum.at(1) == %{name: "List", subtype: nil, summary: "Functions that work on (linked) lists.", type: :module} + assert result |> Enum.at(3) == %{args: "", arity: 1, name: "__info__", origin: "List", spec: nil, summary: "", type: "function"} + assert result |> Enum.at(4) == %{args: "list", arity: 1, name: "first", origin: "List", spec: "@spec first([elem]) :: nil | elem when elem: var", summary: "Returns the first element in `list` or `nil` if `list` is empty.", type: "function"} + assert result |> Enum.at(5) == %{args: "list", arity: 1, name: "last", origin: "List", spec: "@spec last([elem]) :: nil | elem when elem: var", summary: "Returns the last element in `list` or `nil` if `list` is empty.", type: "function"} + assert result |> Enum.at(13) == %{args: "", arity: 1, name: "module_info", origin: "List", spec: nil, summary: "", type: "function"} + assert result |> Enum.at(15) == %{args: "list,item", arity: 2, name: "delete", origin: "List", spec: "@spec delete(list, any) :: list", summary: "Deletes the given `item` from the `list`. Returns a new list without\nthe item.", type: "function"} end test "return completion candidates for 'Str'" do diff --git a/elixir_sense/test/elixir_sense/signature_test.exs b/elixir_sense/test/elixir_sense/signature_test.exs index 689a18f..876990b 100644 --- a/elixir_sense/test/elixir_sense/signature_test.exs +++ b/elixir_sense/test/elixir_sense/signature_test.exs @@ -129,13 +129,13 @@ defmodule ElixirSense.SignatureTest do name: "inspect", params: ["item", "opts \\\\ []"], documentation: "Inspects and writes the given `item` to the device.", - spec: "@spec inspect(item, Keyword.t) :: item when item: var" + spec: "@spec inspect(item, keyword) :: item when item: var" }, %{ name: "inspect", params: ["device", "item", "opts"], documentation: "Inspects `item` according to the given options using the IO `device`.", - spec: "@spec inspect(device, item, Keyword.t) :: item when item: var" + spec: "@spec inspect(device, item, keyword) :: item when item: var" } ] } diff --git a/elixir_sense/test/elixir_sense/suggestions_test.exs b/elixir_sense/test/elixir_sense/suggestions_test.exs index 390a360..67e5599 100644 --- a/elixir_sense/test/elixir_sense/suggestions_test.exs +++ b/elixir_sense/test/elixir_sense/suggestions_test.exs @@ -27,7 +27,7 @@ defmodule ElixirSense.SuggestionsTest do arity: 2, origin: "Kernel.SpecialForms", spec: "", type: "macro", args: "module,opts", name: "require", - summary: "Requires a given module to be compiled and loaded." + summary: "Requires a module in order to use its macros." } end diff --git a/elixir_sense/test/server_test.exs b/elixir_sense/test/server_test.exs index 43f55c5..98895b5 100644 --- a/elixir_sense/test/server_test.exs +++ b/elixir_sense/test/server_test.exs @@ -44,7 +44,7 @@ defmodule ElixirSense.ServerTest do "column" => 6 } } - assert send_request(socket, request) =~ "enum.ex:2523" + assert send_request(socket, request) =~ "enum.ex:2576" end test "signature request", %{socket: socket, auth_token: auth_token} do