From 2ded9a1f99366b7676a99c7d095c4fe43410f97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Wed, 24 Jan 2024 10:11:20 +0100 Subject: [PATCH] Add support for rendering erlang see tags (#287) * add support for rendering erlang see tags fix rendering of links in erlang docs * format --- lib/elixir_sense/core/applications.ex | 15 ++ lib/elixir_sense/core/erlang_html.ex | 245 +++++++++++++++++--- lib/elixir_sense/core/metadata.ex | 4 +- lib/elixir_sense/core/normalized/code.ex | 50 ++-- lib/elixir_sense/providers/docs.ex | 3 +- test/elixir_sense/core/erlang_html_test.exs | 240 ++++++++++++++++++- test/elixir_sense/docs_test.exs | 27 ++- test/elixir_sense/signature_test.exs | 2 +- test/elixir_sense/suggestions_test.exs | 2 +- 9 files changed, 526 insertions(+), 62 deletions(-) diff --git a/lib/elixir_sense/core/applications.ex b/lib/elixir_sense/core/applications.ex index 636aa224..566b5026 100644 --- a/lib/elixir_sense/core/applications.ex +++ b/lib/elixir_sense/core/applications.ex @@ -51,4 +51,19 @@ defmodule ElixirSense.Core.Applications do # for performance. :ets.match(:ac_tab, {{:loaded, :"$1"}, :_}) end + + def get_application(module) do + Enum.find_value(:application.loaded_applications(), fn {app, _, _} -> + modules = + case :application.get_key(app, :modules) do + {:ok, modules} -> + if module in modules do + app + end + + :undefined -> + nil + end + end) + end end diff --git a/lib/elixir_sense/core/erlang_html.ex b/lib/elixir_sense/core/erlang_html.ex index 9276a2a7..0da511d0 100644 --- a/lib/elixir_sense/core/erlang_html.ex +++ b/lib/elixir_sense/core/erlang_html.ex @@ -35,88 +35,99 @@ defmodule ElixirSense.Core.ErlangHtml do Document AST is defined in http://erlang.org/doc/apps/erl_docgen/doc_storage.html """ - @spec to_markdown(chunk_element()) :: String.t() - def to_markdown(ast), do: to_markdown(ast, [], :normal) + @spec to_markdown(chunk_element(), module(), atom()) :: String.t() + def to_markdown(ast, module, app), do: to_markdown(ast, [], :normal, module, app) - def to_markdown(binary, _parents, sanitize_mode) when is_binary(binary) do + def to_markdown(binary, _parents, sanitize_mode, _module, _app) when is_binary(binary) do sanitize(binary, sanitize_mode) end - def to_markdown(list, parents, sanitize_mode) when is_list(list) do - Enum.map_join(list, "", &to_markdown(&1, parents, sanitize_mode)) + def to_markdown(list, parents, sanitize_mode, module, app) when is_list(list) do + Enum.map_join(list, "", &to_markdown(&1, parents, sanitize_mode, module, app)) end - def to_markdown({:br, _attrs, _inner}, parents, _sanitize_mode) do + def to_markdown({:br, _attrs, _inner}, parents, _sanitize_mode, _module, _app) do " \n" <> build_prefix(parents) end - def to_markdown({:p, _attrs, inner}, parents, sanitize_mode) do + def to_markdown({:p, _attrs, inner}, parents, sanitize_mode, module, app) do prefix = build_prefix(parents) - to_markdown(inner, parents, sanitize_mode) <> "\n" <> prefix <> "\n" <> prefix + to_markdown(inner, parents, sanitize_mode, module, app) <> "\n" <> prefix <> "\n" <> prefix end - def to_markdown({tag, _attrs, inner}, parents, sanitize_mode) when tag in [:em, :i] do - "*" <> to_markdown(inner, parents, sanitize_mode) <> "*" + def to_markdown({tag, _attrs, inner}, parents, sanitize_mode, module, app) + when tag in [:em, :i] do + "*" <> to_markdown(inner, parents, sanitize_mode, module, app) <> "*" end - def to_markdown({tag, _attrs, inner}, parents, sanitize_mode) when tag in [:strong, :b] do - "**" <> to_markdown(inner, parents, sanitize_mode) <> "**" + def to_markdown({tag, _attrs, inner}, parents, sanitize_mode, module, app) + when tag in [:strong, :b] do + "**" <> to_markdown(inner, parents, sanitize_mode, module, app) <> "**" end - def to_markdown({:ul, _attrs, inner}, parents, sanitize_mode) do + def to_markdown({:ul, _attrs, inner}, parents, sanitize_mode, module, app) do prefix = build_prefix(parents) items = inner |> Enum.map_join("", fn {:li, _attrs, li_inner} -> - "- #{to_markdown(li_inner, [:li, :ul | parents], sanitize_mode)}\n" <> prefix + "- #{to_markdown(li_inner, [:li, :ul | parents], sanitize_mode, module, app)}\n" <> prefix end) items <> "\n" <> prefix end - def to_markdown({:ol, _attrs, inner}, parents, sanitize_mode) do + def to_markdown({:ol, _attrs, inner}, parents, sanitize_mode, module, app) do prefix = build_prefix(parents) items = inner |> Enum.with_index(1) |> Enum.map_join("", fn {{:li, _attrs, li_inner}, index} -> - "#{index}. #{to_markdown(li_inner, [:li, :ol | parents], sanitize_mode)}\n" <> prefix + "#{index}. #{to_markdown(li_inner, [:li, :ol | parents], sanitize_mode, module, app)}\n" <> + prefix end) items <> "\n" <> prefix end - def to_markdown({:dl, _attrs, inner}, parents, sanitize_mode) do + def to_markdown({:dl, _attrs, inner}, parents, sanitize_mode, module, app) do prefix = build_prefix(parents) - to_markdown(inner, parents, sanitize_mode) <> "\n" <> prefix + to_markdown(inner, parents, sanitize_mode, module, app) <> "\n" <> prefix end - def to_markdown({:dt, _attrs, inner}, parents, sanitize_mode) do - "**" <> to_markdown(inner, parents, sanitize_mode) <> ":** " + def to_markdown({:dt, _attrs, inner}, parents, sanitize_mode, module, app) do + "**" <> to_markdown(inner, parents, sanitize_mode, module, app) <> ":** " end - def to_markdown({:dd, _attrs, inner}, parents, sanitize_mode) do + def to_markdown({:dd, _attrs, inner}, parents, sanitize_mode, module, app) do prefix = build_prefix(parents) - to_markdown(inner, parents, sanitize_mode) <> " \n" <> prefix + to_markdown(inner, parents, sanitize_mode, module, app) <> " \n" <> prefix end - def to_markdown({:pre, _attrs1, [{:code, _attrs2, inner}]}, parents, _sanitize_mode) do + def to_markdown( + {:pre, _attrs1, [{:code, _attrs2, inner}]}, + parents, + _sanitize_mode, + module, + app + ) do prefix = build_prefix(parents) # TODO should we fence it as erlang? - "```\n" <> prefix <> to_markdown(inner, parents, :none) <> "\n" <> prefix <> "```\n" <> prefix + "```\n" <> + prefix <> + to_markdown(inner, parents, :none, module, app) <> "\n" <> prefix <> "```\n" <> prefix end for i <- 1..6, tag = :"h#{i}", prefix = for(_ <- 1..i, into: "", do: "#") do - def to_markdown({unquote(tag), _attrs, inner}, parents, sanitize_mode) do - unquote(prefix) <> " " <> to_markdown(inner, parents, sanitize_mode) <> "\n\n" + def to_markdown({unquote(tag), _attrs, inner}, parents, sanitize_mode, module, app) do + unquote(prefix) <> " " <> to_markdown(inner, parents, sanitize_mode, module, app) <> "\n\n" end end - def to_markdown({:div, attrs, inner}, parents, sanitize_mode) do + def to_markdown({:div, attrs, inner}, parents, sanitize_mode, module, app) do class = attrs[:class] prefix = build_prefix(parents) maybe_class = if class != nil, do: String.upcase(class) <> ": \n" <> prefix, else: "" @@ -131,20 +142,184 @@ defmodule ElixirSense.Core.ErlangHtml do "\n" <> prefix <> maybe_class <> - to_markdown(inner, parents, sanitize_mode) <> + to_markdown(inner, parents, sanitize_mode, module, app) <> "\n" <> prefix <> "\n" <> prefix <> "---" <> "\n" <> prefix <> "\n" <> prefix end - def to_markdown({:code, _attrs, inner}, parents, _sanitize_mode) do - "`" <> to_markdown(inner, parents, :backtick) <> "`" + def to_markdown({:code, _attrs, inner}, parents, _sanitize_mode, module, app) do + "`" <> to_markdown(inner, parents, :backtick, module, app) <> "`" end - def to_markdown({:a, _attrs, []}, _parents, _sanitize_mode) do - "" - end + def to_markdown({:a, attrs, inner}, parents, sanitize_mode, module_fallback, app_fallback) do + href = Keyword.get(attrs, :href, "") + + rel = Keyword.get(attrs, :rel) + + href = + if rel do + [base, hash] = + case String.split(href, "#", parts: 2) do + [base, hash] -> [base, hash |> String.replace(" ", "%20")] + [base] -> [base, ""] + end + + [app, rest] = + case String.split(base, ":", parts: 2) do + [app, rest] -> [app, rest] + [rest] -> ["", rest] + end + + # based on + # https://www.erlang.org/doc/apps/erl_docgen/inline_tags#%3Csee*%3E---see-tags + case Keyword.get(attrs, :rel) do + "https://erlang.org/doc/link/seemfa" -> + # we need to transform + # stdlib:gen_server#Module:handle_call/3 + # gen_server#Module:handle_call/3 + # #Module:handle_call/3 + # into + # https://www.erlang.org/doc/man/gen_server#Module:handle_call-3 + + module = + if rest != "" do + rest + else + to_string(module_fallback) + end + + "https://www.erlang.org/doc/man/#{module}#" <> String.replace(hash, "/", "-") + + "https://erlang.org/doc/link/seeerl" -> + # stdlib:string -> https://www.erlang.org/doc/man/string + # stdlib:string#oldapi -> https://www.erlang.org/doc/man/string#oldapi + + module = + if rest != "" do + rest + else + to_string(module_fallback) + end + + "https://www.erlang.org/doc/man/" <> + module <> if(hash != "", do: "#" <> hash, else: "") + + "https://erlang.org/doc/link/seetype" -> + # stdlib:gen_server#server_ref + # https://www.erlang.org/doc/man/gen_server#type-server_ref + + module = + if rest != "" do + rest + else + to_string(module_fallback) + end + + "https://www.erlang.org/doc/man/#{module}#type-#{hash}" + + "https://erlang.org/doc/link/seeapp" -> + # stdlib:STDLIB_app -> https://www.erlang.org/doc/man/stdlib_app + # stdlib:index -> https://www.erlang.org/doc/apps/stdlib/ + + if app == "" do + app = rest |> String.downcase() |> String.replace_suffix("_app", "") + + app = + if app != "" do + app + else + to_string(app_fallback) + end + + "https://www.erlang.org/doc/man/#{app}_app" + else + if rest == "index" do + "https://www.erlang.org/doc/apps/#{app}/" + else + "https://www.erlang.org/doc/man/#{app}_app" + end + end <> if(hash != "", do: "#" <> hash, else: "") + + "https://erlang.org/doc/link/seecom" -> + # erts:epmd -> https://www.erlang.org/doc/man/epmd + # erts:erl#remsh + # erl + # erl#async_thread_pool_size + # #environment_variables + + module = + if rest != "" do + rest + else + to_string(module_fallback) + end + + "https://www.erlang.org/doc/man/" <> + module <> if(hash != "", do: "#" <> hash, else: "") + + "https://erlang.org/doc/link/seecref" -> + # erts:erl_nif -> https://www.erlang.org/doc/man/erl_nif + # erl_driver#driver_alloc_binary + # #call + + module = + if rest != "" do + rest + else + to_string(module_fallback) + end + + "https://www.erlang.org/doc/man/" <> + module <> if(hash != "", do: "#" <> hash, else: "") + + "https://erlang.org/doc/link/seefile" -> + # kernel:config -> https://www.erlang.org/doc/man/config + # figures/perf-beamasm.svg + # diameter_dict#MESSAGE_RECORDS + # #FILE_FORMAT + # diameter_dict + + file = + if rest != "" do + rest + else + to_string(module_fallback) + end + + "https://www.erlang.org/doc/man/" <> file <> if(hash != "", do: "#" <> hash, else: "") + + "https://erlang.org/doc/link/seeguide" -> + # kernel:index -> https://www.erlang.org/doc/apps/kernel/users_guide + # kernel:logger_chapter -> https://www.erlang.org/doc/apps/kernel/logger_chapter + + # #listen + # erl_dist_protocol#dflags + # stdlib:unicode_usage#unicode_file_names + # system/design_principles:gen_server_concepts -> https://www.erlang.org/doc/design_principles/gen_server_concepts + + if String.starts_with?(app, "system/") do + page = + app + |> String.replace_prefix("system/", "") + + "https://www.erlang.org/doc/#{page}/#{if(rest == "index", do: "users_guide", else: rest)}" + else + app = + if app == "" do + to_string(app_fallback) + else + app + end + + "https://www.erlang.org/doc/apps/#{app}/#{if(rest == "index", do: "users_guide", else: rest)}" + end <> if(hash != "", do: "#" <> hash, else: "") + end + else + href + end - def to_markdown({:a, _attrs, inner}, parents, sanitize_mode) do - "[" <> to_markdown(inner, parents, sanitize_mode) <> "]" + "[" <> + to_markdown(inner, parents, sanitize_mode, module_fallback, app_fallback) <> + "](" <> href <> ")" end defp build_prefix(list), do: build_prefix(list, "") diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index a7f11e74..f3dff99c 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -374,13 +374,15 @@ defmodule ElixirSense.Core.Metadata do meta = %{implementing: behaviour} spec = Introspection.get_spec_as_string(nil, f, a, kind, meta) + app = ElixirSense.Core.Applications.get_application(behaviour) case docs[{f, a}] do nil -> {spec, "", meta} {_signatures, docs, callback_meta, mime_type} -> - {spec, docs |> NormalizedCode.extract_docs(mime_type), callback_meta |> Map.merge(meta)} + {spec, docs |> NormalizedCode.extract_docs(mime_type, behaviour, app), + callback_meta |> Map.merge(meta)} end end diff --git a/lib/elixir_sense/core/normalized/code.ex b/lib/elixir_sense/core/normalized/code.ex index 3a3cb366..2f36961a 100644 --- a/lib/elixir_sense/core/normalized/code.ex +++ b/lib/elixir_sense/core/normalized/code.ex @@ -26,25 +26,27 @@ defmodule ElixirSense.Core.Normalized.Code do case fetch_docs(module) do {:docs_v1, moduledoc_anno, _language, mime_type, moduledoc, metadata, docs} when mime_type in @supported_mime_types -> + app = ElixirSense.Core.Applications.get_application(module) + case category do :moduledoc -> - moduledoc_en = extract_docs(moduledoc, mime_type) + moduledoc_en = extract_docs(moduledoc, mime_type, module, app) {max(:erl_anno.line(moduledoc_anno), 1), moduledoc_en, maybe_mark_as_hidden(metadata, moduledoc_en)} :docs -> - get_fun_docs(module, docs, mime_type) + get_fun_docs(module, app, docs, mime_type) :callback_docs -> for {{kind, _name, _arity}, _anno, _signatures, _docs, _metadata} = entry when kind in [:callback, :macrocallback] <- docs do - map_doc_entry(entry, mime_type) + map_doc_entry(entry, mime_type, module, app) end :type_docs -> for {{:type, _name, _arity}, _anno, _signatures, _docs, _metadata} = entry <- docs do - map_doc_entry(entry, mime_type) + map_doc_entry(entry, mime_type, module, app) end end @@ -53,8 +55,13 @@ defmodule ElixirSense.Core.Normalized.Code do end end - defp map_doc_entry({{kind, name, arity}, anno, signatures, docs, metadata}, mime_type) do - docs_en = extract_docs(docs, mime_type) + defp map_doc_entry( + {{kind, name, arity}, anno, signatures, docs, metadata}, + mime_type, + module, + app + ) do + docs_en = extract_docs(docs, mime_type, module, app) # TODO check if we can get column here line = :erl_anno.line(anno) @@ -78,18 +85,23 @@ defmodule ElixirSense.Core.Normalized.Code do end end - @spec extract_docs(%{required(String.t()) => String.t()} | :hidden | :none, String.t()) :: + @spec extract_docs( + %{required(String.t()) => String.t()} | :hidden | :none, + String.t(), + module(), + atom() + ) :: String.t() | false | nil - def extract_docs(%{"en" => docs_en}, "text/markdown"), do: docs_en + def extract_docs(%{"en" => docs_en}, "text/markdown", _module, _app), do: docs_en - def extract_docs(%{"en" => docs_en}, "application/erlang+html") do - ErlangHtml.to_markdown(docs_en) + def extract_docs(%{"en" => docs_en}, "application/erlang+html", module, app) do + ErlangHtml.to_markdown(docs_en, module, app) end - def extract_docs(:hidden, _), do: false - def extract_docs(_, _), do: nil + def extract_docs(:hidden, _, _, _), do: false + def extract_docs(_, _, _, _), do: nil - defp get_fun_docs(module, docs, mime_type) do + defp get_fun_docs(module, app, docs, mime_type) do docs_from_module = Enum.filter( docs, @@ -139,7 +151,11 @@ defmodule ElixirSense.Core.Normalized.Code do end {{kind, name, arity}, anno, signatures, docs, metadata} - |> map_doc_entry(mime_type) + |> map_doc_entry( + mime_type, + Map.get(metadata, :implementing, module), + Map.get(metadata, :implementing_module_app, app) + ) end ) end @@ -161,6 +177,8 @@ defmodule ElixirSense.Core.Normalized.Code do end def callback_documentation(module) do + app = ElixirSense.Core.Applications.get_application(module) + case Code.fetch_docs(module) do {:docs_v1, _moduledoc_anno, _language, mime_type, _moduledoc, _metadata, docs} when mime_type in @supported_mime_types -> @@ -174,7 +192,9 @@ defmodule ElixirSense.Core.Normalized.Code do ) |> Stream.map(fn {{_kind, name, arity}, _anno, signatures, docs, metadata} -> {{name, arity}, - {signatures, docs, metadata |> Map.put(:implementing, module), mime_type}} + {signatures, docs, + metadata |> Map.put(:implementing, module) |> Map.put(:implementing_module_app, app), + mime_type}} end) _ -> diff --git a/lib/elixir_sense/providers/docs.ex b/lib/elixir_sense/providers/docs.ex index bcc97d27..58eed82c 100644 --- a/lib/elixir_sense/providers/docs.ex +++ b/lib/elixir_sense/providers/docs.ex @@ -308,7 +308,8 @@ defmodule ElixirSense.Providers.Docs do } {_, docs, callback_meta, mime_type} -> - docs = docs |> NormalizedCode.extract_docs(mime_type) + app = ElixirSense.Core.Applications.get_application(behaviour) + docs = docs |> NormalizedCode.extract_docs(mime_type, behaviour, app) # as of OTP 25 erlang callback doc entry does not have signature in meta # pass meta with implementing flag to trigger looking for specs in behaviour module # assume there is a typespec for behaviour module diff --git a/test/elixir_sense/core/erlang_html_test.exs b/test/elixir_sense/core/erlang_html_test.exs index 6061a433..be742e5f 100644 --- a/test/elixir_sense/core/erlang_html_test.exs +++ b/test/elixir_sense/core/erlang_html_test.exs @@ -1,6 +1,5 @@ defmodule ElixirSense.Core.ErlangHtmlTest do use ExUnit.Case, async: true - import ElixirSense.Core.ErlangHtml @tag requires_source: true test "integration" do @@ -53,6 +52,10 @@ defmodule ElixirSense.Core.ErlangHtmlTest do do: ElixirSense.Core.Normalized.Code.get_docs(m, t) end + defp to_markdown(ast) do + ElixirSense.Core.ErlangHtml.to_markdown(ast, :my_mod, :my_app) + end + test "binary" do ast = "binary" @@ -301,14 +304,245 @@ defmodule ElixirSense.Core.ErlangHtmlTest do ] assert """ - [some link] + [some link](asd) """ == to_markdown(ast) end test "empty link" do ast = {:a, [href: "asd"], []} - assert "" == to_markdown(ast) + assert "[](asd)" == to_markdown(ast) + end + + describe "seemfa" do + test "full" do + ast = + {:a, + [ + href: "stdlib:gen_server#Module:handle_call/3", + rel: "https://erlang.org/doc/link/seemfa" + ], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/gen_server#Module:handle_call-3)" == + to_markdown(ast) + end + + test "no app" do + ast = + {:a, [href: "gen_server#Module:handle_call/3", rel: "https://erlang.org/doc/link/seemfa"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/gen_server#Module:handle_call-3)" == + to_markdown(ast) + end + + test "no app no module" do + ast = + {:a, [href: "#Module:handle_call/3", rel: "https://erlang.org/doc/link/seemfa"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/my_mod#Module:handle_call-3)" == + to_markdown(ast) + end + end + + describe "seeerl" do + test "full" do + ast = + {:a, [href: "stdlib:string#oldapi", rel: "https://erlang.org/doc/link/seeerl"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/string#oldapi)" == to_markdown(ast) + end + + test "no app" do + ast = {:a, [href: "init", rel: "https://erlang.org/doc/link/seeerl"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/init)" == to_markdown(ast) + end + + test "no app no module" do + ast = {:a, [href: "#some", rel: "https://erlang.org/doc/link/seeerl"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/my_mod#some)" == to_markdown(ast) + end + end + + describe "seetype" do + test "full" do + ast = + {:a, [href: "stdlib:gen_server#server_ref", rel: "https://erlang.org/doc/link/seetype"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/gen_server#type-server_ref)" == + to_markdown(ast) + end + + test "no app" do + ast = + {:a, [href: "gen_server#server_ref", rel: "https://erlang.org/doc/link/seetype"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/gen_server#type-server_ref)" == + to_markdown(ast) + end + + test "no app no module" do + ast = {:a, [href: "#server_ref", rel: "https://erlang.org/doc/link/seetype"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/my_mod#type-server_ref)" == + to_markdown(ast) + end + end + + describe "seeapp" do + test "full index" do + ast = {:a, [href: "stdlib:index", rel: "https://erlang.org/doc/link/seeapp"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/apps/stdlib/)" == to_markdown(ast) + end + + test "full app capitalized" do + ast = + {:a, [href: "stdlib:STDLIB_app#some", rel: "https://erlang.org/doc/link/seeapp"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/stdlib_app#some)" == to_markdown(ast) + end + + test "no app" do + ast = + {:a, [href: "os_mon_app#some", rel: "https://erlang.org/doc/link/seeapp"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/os_mon_app#some)" == to_markdown(ast) + end + + test "only hash" do + ast = {:a, [href: "#some", rel: "https://erlang.org/doc/link/seeapp"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/my_app_app#some)" == to_markdown(ast) + end + end + + describe "seecom" do + test "full" do + ast = + {:a, [href: "stdlib:string#oldapi", rel: "https://erlang.org/doc/link/seecom"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/string#oldapi)" == to_markdown(ast) + end + + test "no app" do + ast = {:a, [href: "init", rel: "https://erlang.org/doc/link/seecom"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/init)" == to_markdown(ast) + end + + test "no app no module" do + ast = {:a, [href: "#some", rel: "https://erlang.org/doc/link/seecom"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/my_mod#some)" == to_markdown(ast) + end + end + + describe "seecref" do + test "full" do + ast = + {:a, [href: "stdlib:string#oldapi", rel: "https://erlang.org/doc/link/seecref"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/string#oldapi)" == to_markdown(ast) + end + + test "no app" do + ast = {:a, [href: "init", rel: "https://erlang.org/doc/link/seecref"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/init)" == to_markdown(ast) + end + + test "no app no module" do + ast = {:a, [href: "#some", rel: "https://erlang.org/doc/link/seecref"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/my_mod#some)" == to_markdown(ast) + end + end + + describe "seefile" do + test "full" do + ast = + {:a, [href: "stdlib:string#oldapi", rel: "https://erlang.org/doc/link/seefile"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/string#oldapi)" == to_markdown(ast) + end + + test "no app" do + ast = + {:a, [href: "figures/perf-beamasm.svg", rel: "https://erlang.org/doc/link/seefile"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/figures/perf-beamasm.svg)" == + to_markdown(ast) + end + + test "no app no module" do + ast = {:a, [href: "#some", rel: "https://erlang.org/doc/link/seefile"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/man/my_mod#some)" == to_markdown(ast) + end + end + + describe "seeguide" do + test "system" do + ast = + {:a, + [ + href: "system/design_principles:gen_server_concepts", + rel: "https://erlang.org/doc/link/seeguide" + ], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/design_principles/gen_server_concepts)" == + to_markdown(ast) + end + + test "system index" do + ast = + {:a, + [href: "system/design_principles:index", rel: "https://erlang.org/doc/link/seeguide"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/design_principles/users_guide)" == + to_markdown(ast) + end + + test "full guide" do + ast = + {:a, [href: "stdlib:string#old api", rel: "https://erlang.org/doc/link/seeguide"], + ["some link"]} + + assert "[some link](https://www.erlang.org/doc/apps/stdlib/string#old%20api)" == + to_markdown(ast) + end + + test "full index" do + ast = + {:a, [href: "stdlib:index", rel: "https://erlang.org/doc/link/seeguide"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/apps/stdlib/users_guide)" == to_markdown(ast) + end + + test "no app" do + ast = {:a, [href: "guide", rel: "https://erlang.org/doc/link/seeguide"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/apps/my_app/guide)" == to_markdown(ast) + end + + test "no app no module" do + ast = {:a, [href: "#some", rel: "https://erlang.org/doc/link/seeguide"], ["some link"]} + + assert "[some link](https://www.erlang.org/doc/apps/my_app/#some)" == to_markdown(ast) + end end test "div element" do diff --git a/test/elixir_sense/docs_test.exs b/test/elixir_sense/docs_test.exs index 7bcff8e9..1cf90570 100644 --- a/test/elixir_sense/docs_test.exs +++ b/test/elixir_sense/docs_test.exs @@ -644,6 +644,7 @@ defmodule ElixirSense.DocsTest do kind: :function, metadata: %{ implementing: ElixirSenseExample.BehaviourWithMeta, + implementing_module_app: :elixir_sense, since: "1.2.3" }, module: MyLocalModule, @@ -682,6 +683,7 @@ defmodule ElixirSense.DocsTest do kind: :function, metadata: %{ implementing: ElixirSenseExample.BehaviourWithMeta, + implementing_module_app: :elixir_sense, since: "1.2.3" }, module: MyLocalModule, @@ -756,7 +758,11 @@ defmodule ElixirSense.DocsTest do arity: 1, function: :bar, module: MyLocalModule, - metadata: %{since: "1.2.3", implementing: ElixirSenseExample.BehaviourWithMeta}, + metadata: %{ + since: "1.2.3", + implementing: ElixirSenseExample.BehaviourWithMeta, + implementing_module_app: :elixir_sense + }, specs: ["@macrocallback bar(integer()) :: Macro.t()"], docs: "Docs for bar", kind: :macro @@ -1198,7 +1204,10 @@ defmodule ElixirSense.DocsTest do function: :foo, arity: 0, module: ElixirSenseExample.ExampleBehaviourWithDocCallbackNoImpl, - metadata: %{implementing: ElixirSenseExample.ExampleBehaviourWithDoc}, + metadata: %{ + implementing: ElixirSenseExample.ExampleBehaviourWithDoc, + implementing_module_app: :elixir_sense + }, specs: ["@callback foo() :: :ok"], docs: "Docs for foo", kind: :function @@ -1223,7 +1232,11 @@ defmodule ElixirSense.DocsTest do arity: 1, module: ElixirSenseExample.ExampleBehaviourWithDocCallbackImpl, specs: ["@callback baz(integer()) :: :ok"], - metadata: %{implementing: ElixirSenseExample.ExampleBehaviourWithDoc, hidden: true}, + metadata: %{ + implementing: ElixirSenseExample.ExampleBehaviourWithDoc, + hidden: true, + implementing_module_app: :elixir_sense + }, docs: "Docs for baz", kind: :function } @@ -1248,6 +1261,7 @@ defmodule ElixirSense.DocsTest do module: ElixirSenseExample.ExampleBehaviourWithNoDocCallbackImpl, metadata: %{ implementing: ElixirSenseExample.ExampleBehaviourWithNoDoc, + implementing_module_app: :elixir_sense, hidden: true }, specs: ["@callback foo() :: :ok"], @@ -1273,7 +1287,10 @@ defmodule ElixirSense.DocsTest do arity: 1, function: :bar, module: ElixirSenseExample.ExampleBehaviourWithDocCallbackNoImpl, - metadata: %{implementing: ElixirSenseExample.ExampleBehaviourWithDoc}, + metadata: %{ + implementing: ElixirSenseExample.ExampleBehaviourWithDoc, + implementing_module_app: :elixir_sense + }, specs: ["@macrocallback bar(integer()) :: Macro.t()"], docs: "Docs for bar", kind: :macro @@ -1295,7 +1312,7 @@ defmodule ElixirSense.DocsTest do function: :init, module: :file_server, specs: ["@callback init(args :: term())" <> _], - metadata: %{implementing: :gen_server}, + metadata: %{implementing: :gen_server, implementing_module_app: :stdlib}, kind: :function } = doc diff --git a/test/elixir_sense/signature_test.exs b/test/elixir_sense/signature_test.exs index b1282021..ba00c0fb 100644 --- a/test/elixir_sense/signature_test.exs +++ b/test/elixir_sense/signature_test.exs @@ -240,7 +240,7 @@ defmodule ElixirSense.SignatureTest do } = ElixirSense.signature(code, 2, 32) if ExUnitConfig.erlang_eep48_supported() do - assert "Supported time unit representations:" <> _ = summary + assert summary =~ "Supported time unit representations:" end end diff --git a/test/elixir_sense/suggestions_test.exs b/test/elixir_sense/suggestions_test.exs index 5f523704..5e059d77 100644 --- a/test/elixir_sense/suggestions_test.exs +++ b/test/elixir_sense/suggestions_test.exs @@ -3969,7 +3969,7 @@ defmodule ElixirSense.SuggestionsTest do ] = suggestions if ExUnitConfig.erlang_eep48_supported() do - assert "Supported time unit representations:" <> _ = summary + assert summary =~ "Supported time unit representations:" end end