Skip to content

Conversation

eksperimental
Copy link
Contributor

Round 1 is here: [WIP] Autolink revamp #893

This PR doesn't change the API as requested by @josevalim in #893 (comment)


defp re(:f, :erlang) do
~r{
# TODO: revise the erlang rules for function names

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO found

defp re(:m, :erlang) do
~r{
: # prefix
# TODO: revise the erlang rules for module names

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO found

"""

refute Autolink.preprocess(string) === string
assert Autolink.preprocess(string) |> Autolink.postprocess() === string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a function call when a pipeline is only one function long

@doc false
# The heart of the autolinking logic
@spec replacement(String.t(), language, kind, String.t(), keyword) :: String.t()
def replacement(string, language, kind, match, text \\ nil, options) do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function is too complex (CC is 21, max is 9).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this function public too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry I forgot to make it private

end

:function ->
cond do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function body is nested too deep (max depth is 2, was 3).

# with a token
@doc false
@spec link(String.t(), language, kind, keyword) :: String.t()
def link(string, language, kind, options) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this function public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot to make it private

|> link_process(:preprocess, options[:preprocess?])
|> link(language, kind, :custom, options)
|> link(language, kind, :normal, options)
|> link_process(:postprocess, options[:preprocess?])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong option here. It seems we do not have tests for this then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what is wrong here?

end

term
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove debugging code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

end

@doc false
# Replaces all backticks inside the text of custom links with @backtick_token.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we do pre-processing before? Why do we need it now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't. But it fixes the bug of linking inside the custom text. Before it was only working if either the opening backtick is touching the opening bracket, or the closing backtick is touching the closing bracket.

This wouldn't pass before IIRC.

      assert Autolink.erlang_functions("[the `:erlang.apply/2` function](other.html)") ==
               "[the `:erlang.apply/2` function](other.html)"

So this behaviour was inconsistent. I think it worked for local and Elixir functions and it didn't work for the rest

module_id: module_id,
locals: locals
})
|> Map.to_list()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that we are converting structured list into unstructured one. My suggestion is to keep using a map internally everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

case link_type do
:normal ->
fn all, match ->
replacement(all, language, kind, match, options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should merge replace_fun and replacement such that replace_fun already returns a function considering the link_type, language and kind. :)

Copy link
Contributor Author

@eksperimental eksperimental Oct 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but after splitting replacement into two functions clauses I cannot see how to merge it with replace_fun

I ended up having something like this

  defp replace_fun(language, kind, :normal, options) do
    fn all, match ->
      replacement(all, language, kind, match, options)
    end
  end

  defp replace_fun(language, kind, :custom, options) do
    fn all, text, match ->
      replacement(all, language, kind, match, text, options)
    end
  end

  # The heart of the autolinking logic
  defp replacement(string, language, kind, match, text \\ nil, options)

  defp replacement(string, :erlang, kind, match, text, options) do
    lib_dirs = Map.get(options, :lib_dirs, default_lib_dirs(:erlang))

    pmfa = {_prefix, module, function, arity} = split_function(match)
    text = text || default_text(:erlang, kind, match, pmfa)

    if doc = module_docs(:erlang, module, lib_dirs) do
      case kind do
        :module ->
          "[#{text}](#{doc}#{module}.html)"

        :function ->
          "[#{text}](#{doc}#{module}.html##{function}-#{arity})"
      end
    else
      string
    end
  end

  defp replacement(string, :elixir, kind, match, text, options) do
    aliases = Map.get(options, :aliases, [])
    docs_refs = Map.get(options, :docs_refs, [])
    extension = Map.get(options, :extension, ".html")
    lib_dirs = Map.get(options, :lib_dirs, default_lib_dirs(:elixir))
    locals = Map.get(options, :locals, [])
    module_id = Map.get(options, :module_id, nil)
    modules_refs = Map.get(options, :modules_refs, [])

    pmfa = {prefix, module, function, arity} = split_function(match)
    text = text || default_text(:elixir, kind, match, pmfa)

    elixir_docs = get_elixir_docs(aliases, lib_dirs)

    case kind do
      :module ->
        cond do
          match == module_id ->
            "[`#{match}`](#{match}#{extension}#content)"

          match in modules_refs ->
            "[`#{match}`](#{match}#{extension})"

          doc = module_docs(:elixir, match, lib_dirs) ->
            "[`#{match}`](#{doc}#{match}.html)"

          true ->
            string
        end

      :function ->
        cond do
          match in locals ->
            "[#{text}](##{prefix}#{enc_h(function)}/#{arity})"

          match in docs_refs ->
            "[#{text}](#{module}#{extension}##{prefix}#{enc_h(function)}/#{arity})"

          match in @basic_type_strings ->
            "[#{text}](#{elixir_docs}#{@basic_types_page})"

          match in @built_in_type_strings ->
            "[#{text}](#{elixir_docs}#{@built_in_types_page})"

          match in @kernel_function_strings ->
            "[#{text}](#{elixir_docs}Kernel#{extension}##{prefix}#{enc_h(function)}/#{arity})"

          match in @special_form_strings ->
            "[#{text}](#{elixir_docs}Kernel.SpecialForms#{extension}##{prefix}#{enc_h(function)}/#{
              arity
            })"

          doc = module_docs(:elixir, module, lib_dirs) ->
            "[#{text}](#{doc}#{module}.html##{prefix}#{enc_h(function)}/#{arity})"

          true ->
            string
        end
    end

@josevalim
Copy link
Member

josevalim commented Sep 21, 2018

Thanks @eksperimental! I have reviewed the code and now I can see with more clarity the improvements it brings. However, I still believe we have some extra work to do. In particular, I think the replacement function should be split and be concerned with at least erlang or elixir, instead of a large function that worries about all scenarios. ❤️ 💚 💙 💛 💜


@doc """
Helper function for autolinking elixir modules.
defp replacement(string, :elixir, kind, match, text, options) do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function is too complex (CC is 16, max is 9).

@sourcelevel-bot
Copy link

Ebert has finished reviewing this Pull Request and has found:

  • 3 possible new issues (including those that may have been commented here).

You can see more details about this review at https://ebertapp.io/github/elixir-lang/ex_doc/pulls/896.

@eksperimental
Copy link
Contributor Author

@josevalim thank you for reviewing my code. I have implemented all the changes you suggested, except here and here

@josevalim josevalim merged commit 3319b05 into elixir-lang:master Oct 6, 2018
@josevalim
Copy link
Member

❤️ 💚 💙 💛 💜

@josevalim
Copy link
Member

@eksperimental I have applied the replace_fun suggestion in this commit.

Note I have also removed preprocess/postprocess, because they are testing implementation details and not behaviour. Namely, we don't care how we handle the backticks, as long as we handle it correctly, and that's what we need to test.

I believe now we can do the second part of your PR which is to rewrite the tests. Basically, right now we are testing elixir_functions, locals and so on, but the code does not actually call any of those functions. I think we need to change all of the Autolink tests to go exclusively through project_doc. We may need to make project_doc/4 public though to test it.

@eksperimental
Copy link
Contributor Author

The tests for the preprocess/postprocess were there because I found bugs in my previous code.
Maybe I can conver those tests that were removed to use project_doc/4 to make sure we are dealing with backtick replacement correctly.

I will go through your commit whenever I getg some free time and get back if I have any comments.
thank you

@wojtekmach
Copy link
Member

I just noticed that besides refactoring, we've introduced here a new feature: auto linking Erlang modules. It can introduce false positives, see: :file:

screenshot 2018-10-28 at 00 37 03

probably not a deal breaker but can be a bit jarring.

@eksperimental
Copy link
Contributor Author

@wojtekmach great find!
I think we should revert it, or at least partially revert it, as it is going to be a big annoyance.
By partially I mean, we should remove this features from normal links and allow it only when called explicitly in custom links. WDYGT?

@josevalim
Copy link
Member

josevalim commented Oct 28, 2018 via email

@eksperimental
Copy link
Contributor Author

ok. let's here from others and I will provide a PR as soon as possible

@josevalim
Copy link
Member

@eksperimental sweet. what do you have in mind? instead of a for comprehensions that builds @regexes, should we just list all combinations explicitly? Also, I think we should not support normal nor custom links for erlang modules. That would be a bit inconsistent.

@josevalim
Copy link
Member

Pushed in 75d32db.

@wojtekmach
Copy link
Member

Great, thanks everyone!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants