gleam-lang / gleam

⭐️ A friendly language for building type-safe, scalable systems!
https://gleam.run
Apache License 2.0
16.73k stars 699 forks source link

Extend the doc comment syntax to allow linking to other gleam code #2869

Open george-grec opened 3 months ago

george-grec commented 3 months ago

Sometimes you want to refer to another specific piece of code when documenting something. You can currently just write the name of the code segment in ticks and the reader can search for that manually but it would be much more useful if there was a standard way to link somewhere and have it be a clickable link in an IDE or on the generated docs page.

Possible inspirations:

Personally, I would love to able to link to functions, types, variants or record accessors.

Example Take the function fn from_uri(uri: Uri) from the gleam_http package which creates a request from a Uri that you can pass to an http client. Now there is also the function fn to(url: String) that does the exact same thing but you can just give it a String which will be parsed into a Uri and passed on to from_uri(uri: Uri). A user of this API might wonder what http method the returned request might have. If the documentation for from_uri already specifies that it defaults to GET then you could write the documentation for fn to(url: String) this way:

/// Construct a request from a URL string. The returned [request.Request] will have the same defaults
/// as [request.from_uri()] as that is used under the hood.
///
pub fn to(url: String) -> Result(Request(String), Nil)

When hovering over to() somewhere in your code or when looking at the hexdocs of that function the discoverability would then be much better if you could instantly jump to the referenced function from_uri() to read up on it.

lpil commented 3 months ago

Good idea! Thank you. We'll need to decide the precise syntax. I think it would be nice if the syntax could be valid markdown too. For example:

/// Construct a request from a URL string. The returned `gleam/http/request.Request` will have the same defaults
/// as `gleam/http/request.from_uri` as that is used under the hood.
///
pub fn to(url: String) -> Result(Request(String), Nil)

One tricky thing is when a type and a value have the same name. Which should we link to? I guess the syntax would need to be able specify one or the other? Could get annoying

george-grec commented 3 months ago

@lpil Good points!

I think it would be nice if the syntax could be valid markdown too

Strictly speaking using brackets around some text is not invalid markdown but I see where you are coming from. Still, I think it is fine if the code links would just look like regular text with visible brackets in a regular markdown editor. We can expect most gleam code to be open in editors with gleam support where that should not be an issue. Then we could keep backticks as they are for just providing code examples without also being links which I would suggest is a common use case. You could also argue that the functionality that we want (linking to code) is closer to markdown links which also utilize brackets.

One tricky thing is when a type and a value have the same name. Which should we link to?

I am not sure what you mean by "value" in this context but if you mean something like this:

pub type Foo {
  Foo
}

then [request.Foo] should link to the type and [request.Foo.Foo] should link to the variant / constructor.

In any case, I think it would be best to always require the name of the module for every code link. However, if the type is already imported I don't think a full module path (like gleam/http/request.Request from your example) is required. For example:

/// see [request.from_uri] // Good
/// see [from_uri] // Bad, even if the function is defined in the same module
///
pub fn to(url: String) -> Result(Request(String), Nil)

An exception should be made when referencing an argument in the function that is being documented.

/// returns `Nil` if [url] is invalid // Still good, because url is an argument for this function
///
pub fn to(url: String) -> Result(Request(String), Nil)

You will notice that I used backticks instead of a link for Nil as it is not something that can be linked to ;)

Would this cover all ambiguities?

lpil commented 3 months ago

I would want it to ideally be markdown formatting that still makes sense when there's regular markdown rendering or no rendering, so possibly backticks. We can look at what other tools do.

Value constructors are not namespaced by their type so I'm not a huge fan of having them be so in this context. We try and make everything as consistent or explicit as possible.

How would it know that "request" means the "gleam/http/request" module?

george-grec commented 3 months ago

How would it know that "request" means the "gleam/http/request" module?

By being aware of the imports in the current file. Of course, if you reference something from a module that is not imported you would still need to use the full path. That is at least how it works in KDoc.

lpil commented 3 months ago

How do we avoid links breaking when imports are changed? To we have to parse the comments and check them on each compile run?

george-grec commented 3 months ago

I would want it to ideally be markdown formatting that still makes sense when there's regular markdown rendering or no rendering, so possibly backticks. We can look at what other tools do.

Sure, I am biased because the tools I use use brackets for this but I'd be happy if other people could make the case for other options so we explore all possibilities.


Value constructors are not namespaced by their type so I'm not a huge fan of having them be so in this context. We try and make everything as consistent or explicit as possible.

Here is an idea off the top of my head:

// in file example.gleam

pub type Foo {
  Foo
}

/// see `example.{type Foo}` references the type and /// see `example.Foo` references the variant


How do we avoid links breaking when imports are changed? To we have to parse the comments and check them on each compile run?

When writing Kotlin in IntelliJ (and Android Studio I guess) the refactoring tools change references in documentation comments too. If you reference something that is not recognized, the code link in the comment section is highlighted like an error in the editor. To me that sounds like a feature for the LSP and not the compiler but I am not familiar how those things work under the hood - I am just a user of those features.

lpil commented 3 months ago

Given the LSP is an optional extra rather than a required tool for writing Gleam any feature needs to work just as well when not using it. We could have the compiler or build tool do something to cover all users, but not the language server.