Skip to content

Commit 789205e

Browse files
Generate relative links for same-package documentation
1 parent 13d274c commit 789205e

5 files changed

+146
-12
lines changed

compiler-core/src/docs/printer.rs

+52
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,58 @@ impl Printer<'_> {
460460
self.title(name)
461461
} else if package == self.package && module == self.module {
462462
self.link(eco_format!("#{name}"), self.title(name), None)
463+
} else if package == self.package {
464+
// If we are linking to the current package, we might be viewing the
465+
// documentation locally and so we need to generate a relative link.
466+
467+
let mut module_path = module.split('/').peekable();
468+
let mut current_module = self.module.split('/');
469+
470+
// The documentation page for the final segment of the module is just
471+
// an html file by itself, so it doesn't form part of the path and doesn't
472+
// need to be backtracked using `..`.
473+
let module_name = module_path.next_back().unwrap_or(module);
474+
_ = current_module.next_back();
475+
476+
// The two modules might have some sharer part of the path, which we
477+
// don't need to traverse back through. However, if the two modules are
478+
// something like `gleam/a/wibble/wobble` and `gleam/b/wibble/wobble`,
479+
// the `wibble` folders are two different folders despite being at the
480+
// same position with the same name.
481+
let mut encountered_different_path = false;
482+
let mut path = Vec::new();
483+
484+
// Calculate how far backwards in the directory tree we need to walk
485+
for segment in current_module {
486+
// If this is still part of the shared path, we can just skip it:
487+
// no need to go back and forth through the same directory in the
488+
// path!
489+
if !encountered_different_path && module_path.peek() == Some(&segment) {
490+
_ = module_path.next();
491+
} else {
492+
encountered_different_path = true;
493+
path.push("..");
494+
}
495+
}
496+
497+
// Once we have walked backwards, we walk forwards again to the correct
498+
// page.
499+
path.extend(module_path);
500+
path.push(module_name);
501+
502+
let qualified_name = docvec![
503+
self.variable(EcoString::from(module_name)),
504+
".",
505+
self.title(name)
506+
];
507+
508+
let title = eco_format!("{module}.{{type {name}}}");
509+
510+
self.link(
511+
eco_format!("{path}.html#{name}", path = path.join("/")),
512+
qualified_name,
513+
Some(title),
514+
)
463515
} else {
464516
let module_name = module.split('/').next_back().unwrap_or(module);
465517
let qualified_name = docvec![

compiler-core/src/docs/snapshots/gleam_core__docs__tests__link_to_type_in_different_module.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ pub fn make_dict() -> dict.Dict(a, b) { todo }
1616
---- VALUES
1717

1818
--- make_dict
19-
<pre><code>pub fn make_dict() -> <a href="https://hexdocs.pm/thepackage/gleam/dict.html#Dict" title="gleam/dict.{type Dict}">dict.Dict</a>(a, b)</code></pre>
19+
<pre><code>pub fn make_dict() -> <a href="gleam/dict.html#Dict" title="gleam/dict.{type Dict}">dict.Dict</a>(a, b)</code></pre>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
source: compiler-core/src/docs/tests.rs
3+
expression: output
4+
---
5+
---- SOURCE CODE
6+
-- gleam/dict.gleam
7+
pub type Dict(a, b)
8+
9+
-- gleam/dynamic/decode.gleam
10+
11+
import gleam/dict
12+
13+
pub fn decode_dict() -> dict.Dict(a, b) { todo }
14+
15+
16+
---- VALUES
17+
18+
--- decode_dict
19+
<pre><code>pub fn decode_dict() -> <a href="../dict.html#Dict" title="gleam/dict.{type Dict}">dict.Dict</a>(a, b)</code></pre>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
source: compiler-core/src/docs/tests.rs
3+
expression: output
4+
---
5+
---- SOURCE CODE
6+
-- gleam/dynamic.gleam
7+
pub type Dynamic
8+
9+
-- gleam/dynamic/decode.gleam
10+
11+
import gleam/dynamic
12+
13+
pub type Dynamic = dynamic.Dynamic
14+
15+
16+
---- TYPES
17+
18+
--- Dynamic
19+
<pre><code>pub type Dynamic =
20+
<a href="../dynamic.html#Dynamic" title="gleam/dynamic.{type Dynamic}">dynamic.Dynamic</a></code></pre>

compiler-core/src/docs/tests.rs

+54-11
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,20 @@ pub fn compile(config: PackageConfig, modules: Vec<(&str, &str)>) -> EcoString {
133133
}
134134

135135
fn compile_documentation(
136-
main_module: &str,
136+
module_name: &str,
137+
module_src: &str,
137138
modules: Vec<(&str, &str, &str)>,
138139
options: PrintOptions,
139140
) -> EcoString {
140-
let module = type_::tests::compile_module("main", main_module, None, modules.clone())
141+
let module = type_::tests::compile_module(module_name, module_src, None, modules.clone())
141142
.expect("Module should compile successfully");
142143

143144
let mut config = PackageConfig::default();
144145
config.name = "thepackage".into();
145146
let paths = ProjectPaths::new("/".into());
146147
let build_module = build::Module {
147148
name: "main".into(),
148-
code: main_module.into(),
149+
code: module_src.into(),
149150
mtime: SystemTime::now(),
150151
input_path: "/".into(),
151152
origin: Origin::Src,
@@ -168,7 +169,14 @@ fn compile_documentation(
168169
let types = module
169170
.definitions
170171
.iter()
171-
.filter_map(|statement| printer.type_definition(&source_links, statement))
172+
.filter_map(
173+
|statement: &crate::ast::Definition<
174+
std::sync::Arc<type_::Type>,
175+
crate::ast::TypedExpr,
176+
EcoString,
177+
EcoString,
178+
>| printer.type_definition(&source_links, statement),
179+
)
172180
.sorted()
173181
.collect_vec();
174182

@@ -185,8 +193,10 @@ fn compile_documentation(
185193
for (_package, name, src) in modules {
186194
output.push_str(&format!("-- {name}.gleam\n{src}\n\n"));
187195
}
188-
output.push_str("-- main.gleam\n");
189-
output.push_str(main_module);
196+
output.push_str("-- ");
197+
output.push_str(module_name);
198+
output.push_str(".gleam\n");
199+
output.push_str(module_src);
190200

191201
if !types.is_empty() {
192202
output.push_str("\n\n---- TYPES");
@@ -241,27 +251,32 @@ macro_rules! assert_documentation {
241251
};
242252

243253
($src:literal, $options:expr $(,)?) => {
244-
let output = compile_documentation($src, Vec::new(), $options);
254+
let output = compile_documentation("main", $src, Vec::new(), $options);
245255
insta::assert_snapshot!(output);
246256
};
247257

248258
($(($name:expr, $module_src:literal)),+, $src:literal $(,)?) => {
249-
let output = compile_documentation($src, vec![$(("thepackage", $name, $module_src)),*], PrintOptions::all());
259+
let output = compile_documentation("main", $src, vec![$(("thepackage", $name, $module_src)),*], PrintOptions::all());
250260
insta::assert_snapshot!(output);
251261
};
252262

253263
($(($name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => {
254-
let output = compile_documentation($src, vec![$(("thepackage", $name, $module_src)),*], $options);
264+
let output = compile_documentation("main", $src, vec![$(("thepackage", $name, $module_src)),*], $options);
265+
insta::assert_snapshot!(output);
266+
};
267+
268+
($(($name:expr, $module_src:literal)),+, $main_module:literal, $src:literal, $options:expr $(,)?) => {
269+
let output = compile_documentation($main_module, $src, vec![$(("thepackage", $name, $module_src)),*], $options);
255270
insta::assert_snapshot!(output);
256271
};
257272

258273
($(($package:expr, $name:expr, $module_src:literal)),+, $src:literal $(,)?) => {
259-
let output = compile_documentation($src, vec![$(($package, $name, $module_src)),*], PrintOptions::all());
274+
let output = compile_documentation("main", $src, vec![$(($package, $name, $module_src)),*], PrintOptions::all());
260275
insta::assert_snapshot!(output);
261276
};
262277

263278
($(($package:expr, $name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => {
264-
let output = compile_documentation($src, vec![$(($package, $name, $module_src)),*], $options);
279+
let output = compile_documentation("main", $src, vec![$(($package, $name, $module_src)),*], $options);
265280
insta::assert_snapshot!(output);
266281
};
267282
}
@@ -834,6 +849,34 @@ pub fn make_dict() -> dict.Dict(a, b) { todo }
834849
);
835850
}
836851

852+
#[test]
853+
fn link_to_type_in_different_module_from_nested_module() {
854+
assert_documentation!(
855+
("gleam/dict", "pub type Dict(a, b)"),
856+
"gleam/dynamic/decode",
857+
"
858+
import gleam/dict
859+
860+
pub fn decode_dict() -> dict.Dict(a, b) { todo }
861+
",
862+
ONLY_LINKS
863+
);
864+
}
865+
866+
#[test]
867+
fn link_to_type_in_different_module_from_nested_module_with_shared_path() {
868+
assert_documentation!(
869+
("gleam/dynamic", "pub type Dynamic"),
870+
"gleam/dynamic/decode",
871+
"
872+
import gleam/dynamic
873+
874+
pub type Dynamic = dynamic.Dynamic
875+
",
876+
ONLY_LINKS
877+
);
878+
}
879+
837880
// https://github.com/gleam-lang/gleam/issues/3461
838881
#[test]
839882
fn link_to_type_in_different_package() {

0 commit comments

Comments
 (0)