Skip to content

Conversation

eksperimental
Copy link
Contributor

This started trying to fix a bug and I had to end up revamping a big part of the Autolink module.
I couldn't set a custom link to a built-in type,

[term()](`t:term/0`)

wouldn't work, while

range()

would work.

So, long story short, custom link only work for Elixir functions and locals,
but nothing else. Now custom links can be used for anything like built-in types and erlang functions

It was extremely hard to grasp what was going on with the regexes, so I created a SystaxRule module.

Please let me know what you guys think

@eksperimental eksperimental changed the title [WIP]Autolink revamp [WIP] Autolink revamp Sep 8, 2018

def 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

def 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

mfa = re_source(:mfa, :erlang)

assert "[`example`](`:mod.example/1`)" ==
run(re({:custom_link, mfa}, :markdown), "[`example`](`:mod.example/1`)") |> hd()

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ebert I find your suggestion specially for long lines like this not useful

mfa = re_source(:mfa)

assert "[`example`](`Mod.example/1`)" ==
run(re({:custom_link, mfa}, :markdown), "[`example`](`Mod.example/1`)") |> hd()

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


test "mfa" do
assert ":my_erlang_module.is_fun!/123" ==
run(re(:mfa, :erlang), "`:my_erlang_module.is_fun!/123`") |> hd()

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


test "functions" do
assert "fun" == run(re(:f), "`fun/1`") |> hd()
assert "is_FUN_99?" == run(re(:f), "`is_FUN_99?/1`") |> hd()

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

end

test "functions" do
assert "fun" == run(re(:f), "`fun/1`") |> hd()

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

test "modules" do
refute run(re(:m), "module.fun/1")
assert "Module" == run(re(:m), "Module.fun/1") |> hd()
assert "Module.Some" == run(re(:m), "Module.Some.fun/1") |> hd()

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


test "modules" do
refute run(re(:m), "module.fun/1")
assert "Module" == run(re(:m), "Module.fun/1") |> hd()

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

@@ -1,10 +1,162 @@
defmodule ExDoc.SyntaxRule do

Choose a reason for hiding this comment

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

Modules should have a @moduledoc tag.

Copy link
Member

Choose a reason for hiding this comment

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

I am afraid testing this is pretty much testing implementation detail. Can we please inline this module inside the current autolink one and erase those tests?

@eksperimental
Copy link
Contributor Author

It also fixed this bug.

[the `:erlang.apply/2` function](other.html)

Eventhough it was announced in the docs, it didn't work.
using backticks as part of the custom link test only worked when the backtick is at the beginning and/or the end of the text, due to a limitation of how regexes work.

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

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


@spec re_source(atom, language) :: String.t()
def re_source(name, language \\ :elixir) do
re(name, language) |> Regex.source()

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

`\s* # leading backtick
(#{function_re_source}) # CAPTURE 1
\s*` # trailing backtick
(?!`)

Choose a reason for hiding this comment

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

There should be no trailing white-space at the end of a line.

"[#{text}](#{module}#{extension}##{prefix}#{enc_h(function)}/#{arity})"
case language do
:erlang ->
cond do

Choose a reason for hiding this comment

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

Cond statements should contain at least two conditions besides true.

@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 22, max is 9).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What should I do here?

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think I can fix this, it will loose readability. What shall I do about it?

@eksperimental eksperimental force-pushed the autolink_revamp branch 2 times, most recently from 8c1c380 to 71205cb Compare September 8, 2018 17:52
"[#{text}](#{elixir_docs}Kernel.SpecialForms" <>
"#{extension}##{prefix}#{enc_h(function)}/#{arity})"
@doc false
def erlang_functions(string) when is_binary(string) do
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have made these set of functions no longer accesible in the documentation, as I consider they are not longer necessary. They have been kept because all the tests rely on them.
We should improve the docs of the newly introduce function that replaces them link/4.

Copy link
Member

Choose a reason for hiding this comment

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

Those functions can be removed. We had them out to ease testing.

@type kind :: :function | :module
@type link_type :: :normal | :custom

random_string = 1..10_000_000 |> Enum.random() |> Integer.to_charlist(36)
Copy link
Member

Choose a reason for hiding this comment

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

Let's pick a random string. Having it being a different value every time we compile the code will make it hard to spot bugs.

@josevalim
Copy link
Member

Thanks @eksperimental for the great work. Unfortunately this PR is doing two things at once, refactoring the code and changing the user surface API. This makes it hard to review and exposes new contracts we are not sure we should expose. Can we please do the refactoring first and keep the user API the same? Then we can have a discussion on what becomes public and what becomes private. Thanks!

- Rename ExDoc.SyntaxRule to ExDoc.Formatter.HTML.SyntaxRule
- Fix syntax rules for normal links
- Add internal option `:preprocess?` to Autolink.link/4, which is set to `true` by default
- Fix Autolink.link_everything/2 to only preprocess and postprocess just once
- Add corner-cases section in text, including a few ones
@eksperimental
Copy link
Contributor Author

eksperimental commented Sep 10, 2018

@josevalim what API changes are you refering to? I'm confused bc above you mention that we can remove the functions locals, erlang-functions, elixir-functions, etc


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

@kernel_function_strings for {f, a} <- kernel_exports, do: "#{f}/#{a}"
@special_form_strings for {f, a} <- special_form_exports, do: "#{f}/#{a}"


Choose a reason for hiding this comment

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

There should be no more than 1 consecutive blank lines.

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.

There should be no trailing white-space at the end of a line.

@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).

@josevalim
Copy link
Member

@eksperimental we can move them but in a separate commit. The public API should stay the same while refactoring. This means we shouldn't expose the link function and the existing API should remain documented. Thanks!

"""

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

match in @kernel_function_strings ->
"[#{text}](#{elixir_docs}Kernel#{extension}##{prefix}#{enc_h(function)}/#{arity})"
: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).

@sourcelevel-bot
Copy link

Ebert has finished reviewing this Pull Request and has found:

  • 6 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/893.

@eksperimental
Copy link
Contributor Author

Closing in favor of #896

@eksperimental eksperimental deleted the autolink_revamp branch August 19, 2020 07:43
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.

2 participants