Skip to content

Commit 751e6e4

Browse files
authored
feat(wasm/html): add callback to generate the default path resolver in resolvePath (#659)
1 parent c8b849b commit 751e6e4

File tree

9 files changed

+129
-41
lines changed

9 files changed

+129
-41
lines changed

js/mod.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,13 @@ export type UrlResolveKind =
165165
| UrlResolveKindFile
166166
| UrlResolveKindSymbol;
167167

168-
interface HrefResolver {
169-
/** Resolver for how files should link to eachother. */
170-
resolvePath?(current: UrlResolveKind, target: UrlResolveKind): string;
168+
export interface HrefResolver {
169+
/** Resolver for how files should link to each other. */
170+
resolvePath?(
171+
current: UrlResolveKind,
172+
target: UrlResolveKind,
173+
defaultResolve: () => string,
174+
): string;
171175
/** Resolver for global symbols, like the Deno namespace or other built-ins */
172176
resolveGlobalSymbol?(symbol: string[]): string | undefined;
173177
/** Resolver for symbols from non-relative imports */
@@ -212,7 +216,7 @@ export interface UsageComposer {
212216
): Map<UsageComposerEntry, string>;
213217
}
214218

215-
interface GenerateOptions {
219+
export interface GenerateOptions {
216220
/** The name of the package to use in the breadcrumbs. */
217221
packageName?: string;
218222
/** The main entrypoint if one is present. */
@@ -236,10 +240,9 @@ interface GenerateOptions {
236240
*/
237241
symbolRedirectMap?: Record<string, Record<string, string>>;
238242
/**
239-
* Map of modules, where the value is a link to where the default symbol
240-
* should redirect to.
243+
* Map of modules, where the value is what the name of the default symbol should be.
241244
*/
242-
defaultRedirectMap?: Record<string, string>;
245+
defaultSymbolMap?: Record<string, string>;
243246
/**
244247
* Hook to inject content in the `head` tag.
245248
*
@@ -300,8 +303,8 @@ const defaultUsageComposer: UsageComposer = {
300303
* @param docNodesByUrl DocNodes keyed by their absolute URL.
301304
*/
302305
export async function generateHtml(
303-
options: GenerateOptions,
304306
docNodesByUrl: Record<string, Array<DocNode>>,
307+
options: GenerateOptions,
305308
): Promise<Record<string, string>> {
306309
const {
307310
usageComposer = defaultUsageComposer,
@@ -317,7 +320,7 @@ export async function generateHtml(
317320
options.categoryDocs,
318321
options.disableSearch ?? false,
319322
options.symbolRedirectMap,
320-
options.defaultRedirectMap,
323+
options.defaultSymbolMap,
321324
options.hrefResolver?.resolvePath,
322325
options.hrefResolver?.resolveGlobalSymbol || (() => undefined),
323326
options.hrefResolver?.resolveImportHref || (() => undefined),

js/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ Deno.test({
131131
"https://deno.land/std@0.104.0/fmt/colors.ts",
132132
);
133133

134-
const files = await generateHtml({
134+
const files = await generateHtml({ ["file:///colors.ts"]: entries }, {
135135
markdownRenderer(
136136
md,
137137
_titleOnly,
@@ -143,7 +143,7 @@ Deno.test({
143143
markdownStripper(md: string) {
144144
return md;
145145
},
146-
}, { ["file:///colors.ts"]: entries });
146+
});
147147

148148
assertEquals(Object.keys(files).length, 61);
149149
},

lib/lib.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use import_map::ImportMap;
2323
use import_map::ImportMapOptions;
2424
use indexmap::IndexMap;
2525
use serde::Serialize;
26+
use std::ffi::c_void;
2627
use std::rc::Rc;
2728
use wasm_bindgen::prelude::*;
2829
use wasm_bindgen_futures::JsFuture;
@@ -37,6 +38,11 @@ macro_rules! console_warn {
3738
($($t:tt)*) => (warn(&format_args!($($t)*).to_string()))
3839
}
3940

41+
thread_local! {
42+
static CURRENT: std::cell::RefCell<*const c_void> = const { std::cell::RefCell::new(std::ptr::null()) };
43+
static TARGET: std::cell::RefCell<*const c_void> = const { std::cell::RefCell::new(std::ptr::null()) };
44+
}
45+
4046
struct JsLoader {
4147
load: js_sys::Function,
4248
}
@@ -318,13 +324,71 @@ impl deno_doc::html::HrefResolver for JsHrefResolver {
318324
if let Some(resolve_path) = &self.resolve_path {
319325
let this = JsValue::null();
320326

327+
let new_current = current.clone();
328+
let new_target = target.clone();
329+
330+
{
331+
let current_ptr =
332+
&new_current as *const UrlResolveKind as *const c_void;
333+
CURRENT.set(current_ptr);
334+
let target = &new_target as *const UrlResolveKind as *const c_void;
335+
TARGET.set(target);
336+
}
337+
338+
let default_closure = Box::new(move || {
339+
CURRENT.with(|current| {
340+
let current_ptr = *current.borrow() as *const UrlResolveKind;
341+
assert!(!current_ptr.is_null());
342+
// SAFETY: this pointer is valid until destroyed, which is done
343+
// after compose is called
344+
let current_val = unsafe { &*current_ptr };
345+
346+
let path = TARGET.with(|target| {
347+
let target_ptr = *target.borrow() as *const UrlResolveKind;
348+
assert!(!target_ptr.is_null());
349+
// SAFETY: this pointer is valid until destroyed, which is done
350+
// after compose is called
351+
let target_val = unsafe { &*target_ptr };
352+
353+
let path =
354+
deno_doc::html::href_path_resolve(*current_val, *target_val);
355+
356+
*target.borrow_mut() =
357+
target_val as *const UrlResolveKind as *const c_void;
358+
359+
path
360+
});
361+
362+
*current.borrow_mut() =
363+
current_val as *const UrlResolveKind as *const c_void;
364+
365+
path
366+
})
367+
});
368+
369+
let default_closure =
370+
Closure::wrap(Box::new(default_closure) as Box<dyn Fn() -> String>);
371+
let default_closure =
372+
JsCast::unchecked_ref::<js_sys::Function>(default_closure.as_ref());
373+
321374
let current = serde_wasm_bindgen::to_value(&current).unwrap();
322375
let target = serde_wasm_bindgen::to_value(&target).unwrap();
323376

324377
let global_symbol = resolve_path
325-
.call2(&this, &current, &target)
378+
.call3(&this, &current, &target, default_closure)
326379
.expect("resolve_path errored");
327380

381+
{
382+
let current =
383+
CURRENT.replace(std::ptr::null()) as *const UrlResolveKind;
384+
// SAFETY: take the pointer and drop it
385+
let _ = unsafe { &*current };
386+
387+
let target = TARGET.replace(std::ptr::null()) as *const UrlResolveKind;
388+
// SAFETY: take the pointer and drop it
389+
let _ = unsafe { &*target };
390+
}
391+
328392
serde_wasm_bindgen::from_value(global_symbol)
329393
.expect("resolve_path returned an invalid value")
330394
} else {

src/html/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ impl ShortPath {
493493
if self.is_main {
494494
UrlResolveKind::Root
495495
} else {
496-
UrlResolveKind::File(self)
496+
UrlResolveKind::File { file: self }
497497
}
498498
}
499499
}

src/html/pages.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ impl CategoriesPanelCtx {
113113
name: short_path.display_name().to_string(),
114114
href: ctx.ctx.resolve_path(
115115
ctx.get_current_resolve(),
116-
UrlResolveKind::File(short_path),
116+
UrlResolveKind::File { file: short_path },
117117
),
118118
active: current_path.is_some_and(|current_path| {
119119
current_path == short_path.display_name()
@@ -157,7 +157,7 @@ impl CategoriesPanelCtx {
157157
.map(|title| CategoriesPanelCategoryCtx {
158158
href: ctx.ctx.resolve_path(
159159
ctx.get_current_resolve(),
160-
UrlResolveKind::Category(&title),
160+
UrlResolveKind::Category { category: &title },
161161
),
162162
active: current_path
163163
.is_some_and(|current_path| current_path == title),
@@ -320,7 +320,7 @@ impl IndexCtx {
320320
header: SectionHeaderCtx {
321321
href: Some(render_ctx.ctx.resolve_path(
322322
render_ctx.get_current_resolve(),
323-
UrlResolveKind::Category(&title),
323+
UrlResolveKind::Category { category: &title },
324324
)),
325325
title,
326326
anchor: AnchorCtx { id: anchor },
@@ -376,8 +376,11 @@ impl IndexCtx {
376376
partitions: partition::Partitions<String>,
377377
all_doc_nodes: &[DocNodeWithContext],
378378
) -> Self {
379-
let render_ctx =
380-
RenderContext::new(ctx, all_doc_nodes, UrlResolveKind::Category(name));
379+
let render_ctx = RenderContext::new(
380+
ctx,
381+
all_doc_nodes,
382+
UrlResolveKind::Category { category: name },
383+
);
381384

382385
let sections = super::namespace::render_namespace(
383386
partitions.into_iter().map(|(title, nodes)| {
@@ -398,8 +401,10 @@ impl IndexCtx {
398401
}),
399402
);
400403

401-
let root =
402-
ctx.resolve_path(UrlResolveKind::Category(name), UrlResolveKind::Root);
404+
let root = ctx.resolve_path(
405+
UrlResolveKind::Category { category: name },
406+
UrlResolveKind::Root,
407+
);
403408

404409
let html_head_ctx = HtmlHeadCtx::new(ctx, &root, Some(name), None);
405410

@@ -515,8 +520,11 @@ pub fn generate_symbol_pages_for_module(
515520

516521
let mut generated_pages = Vec::with_capacity(name_partitions.values().len());
517522

518-
let render_ctx =
519-
RenderContext::new(ctx, module_doc_nodes, UrlResolveKind::File(short_path));
523+
let render_ctx = RenderContext::new(
524+
ctx,
525+
module_doc_nodes,
526+
UrlResolveKind::File { file: short_path },
527+
);
520528

521529
for (name, doc_nodes) in name_partitions {
522530
let (breadcrumbs_ctx, symbol_group_ctx, toc_ctx, categories_panel) =

src/html/render_context.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ impl<'ctx> RenderContext<'ctx> {
199199
},
200200
]
201201
}
202-
UrlResolveKind::Category(category) => {
202+
UrlResolveKind::Category { category } => {
203203
vec![
204204
BreadcrumbCtx {
205205
name: index_name,
@@ -217,7 +217,7 @@ impl<'ctx> RenderContext<'ctx> {
217217
},
218218
]
219219
}
220-
UrlResolveKind::File(file) => {
220+
UrlResolveKind::File { file } => {
221221
if file.is_main {
222222
vec![BreadcrumbCtx {
223223
name: index_name,
@@ -257,9 +257,10 @@ impl<'ctx> RenderContext<'ctx> {
257257
if !file.is_main {
258258
parts.push(BreadcrumbCtx {
259259
name: file.display_name().to_string(),
260-
href: self
261-
.ctx
262-
.resolve_path(self.current_resolve, UrlResolveKind::File(file)),
260+
href: self.ctx.resolve_path(
261+
self.current_resolve,
262+
UrlResolveKind::File { file },
263+
),
263264
is_symbol: false,
264265
is_first_symbol: false,
265266
});
@@ -268,7 +269,7 @@ impl<'ctx> RenderContext<'ctx> {
268269
name: category.to_string(),
269270
href: self.ctx.resolve_path(
270271
self.current_resolve,
271-
UrlResolveKind::Category(category),
272+
UrlResolveKind::Category { category },
272273
),
273274
is_symbol: false,
274275
is_first_symbol: false,
@@ -618,8 +619,11 @@ mod test {
618619
let render_ctx = RenderContext::new(&ctx, doc_nodes, UrlResolveKind::Root);
619620
assert_eq!(render_ctx.lookup_symbol_href("foo").unwrap(), "b/foo");
620621

621-
let render_ctx =
622-
RenderContext::new(&ctx, doc_nodes, UrlResolveKind::File(short_path));
622+
let render_ctx = RenderContext::new(
623+
&ctx,
624+
doc_nodes,
625+
UrlResolveKind::File { file: short_path },
626+
);
623627
assert_eq!(render_ctx.lookup_symbol_href("foo").unwrap(), "b/foo");
624628
}
625629
}

src/html/usage.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,14 @@ impl UsagesCtx {
233233
move |url: String, custom_file_identifier: Option<String>| {
234234
RENDER_CONTEXT.with(|ctx| {
235235
let render_ctx_ptr = *ctx.borrow() as *const RenderContext;
236+
assert!(!render_ctx_ptr.is_null());
236237
// SAFETY: this pointer is valid until destroyed, which is done
237238
// after compose is called
238239
let render_ctx = unsafe { &*render_ctx_ptr };
239240

240241
let usage = DOC_NODES.with(|nodes| {
241242
let (nodes_ptr, nodes_ptr_len) = *nodes.borrow();
243+
assert!(!nodes_ptr.is_null());
242244
// SAFETY: the pointers are valid until destroyed, which is done
243245
// after compose is called
244246
let doc_nodes = unsafe {

src/html/util.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,12 @@ impl NamespacedGlobalSymbols {
232232
pub enum UrlResolveKind<'a> {
233233
Root,
234234
AllSymbols,
235-
Category(&'a str),
236-
File(&'a ShortPath),
235+
Category {
236+
category: &'a str,
237+
},
238+
File {
239+
file: &'a ShortPath,
240+
},
237241
Symbol {
238242
file: &'a ShortPath,
239243
symbol: &'a str,
@@ -245,8 +249,8 @@ impl UrlResolveKind<'_> {
245249
match self {
246250
UrlResolveKind::Root => None,
247251
UrlResolveKind::AllSymbols => None,
248-
UrlResolveKind::Category(_) => None,
249-
UrlResolveKind::File(file) => Some(file),
252+
UrlResolveKind::Category { .. } => None,
253+
UrlResolveKind::File { file } => Some(file),
250254
UrlResolveKind::Symbol { file, .. } => Some(file),
251255
}
252256
}
@@ -257,7 +261,7 @@ pub fn href_path_resolve(
257261
target: UrlResolveKind,
258262
) -> String {
259263
let backs = match current {
260-
UrlResolveKind::File(file) => "../".repeat(if file.is_main {
264+
UrlResolveKind::File { file } => "../".repeat(if file.is_main {
261265
1
262266
} else {
263267
file.path.split('/').count()
@@ -269,12 +273,12 @@ pub fn href_path_resolve(
269273
}),
270274
UrlResolveKind::Root => String::new(),
271275
UrlResolveKind::AllSymbols => String::from("./"),
272-
UrlResolveKind::Category(_) => String::from("./"),
276+
UrlResolveKind::Category { .. } => String::from("./"),
273277
};
274278

275279
match target {
276280
UrlResolveKind::Root => backs,
277-
UrlResolveKind::File(target_file) if target_file.is_main => backs,
281+
UrlResolveKind::File { file: target_file } if target_file.is_main => backs,
278282
UrlResolveKind::AllSymbols => format!("{backs}./all_symbols.html"),
279283
UrlResolveKind::Symbol {
280284
file: target_file,
@@ -283,10 +287,10 @@ pub fn href_path_resolve(
283287
} => {
284288
format!("{backs}./{}/~/{target_symbol}.html", target_file.path)
285289
}
286-
UrlResolveKind::File(target_file) => {
290+
UrlResolveKind::File { file: target_file } => {
287291
format!("{backs}./{}/index.html", target_file.path)
288292
}
289-
UrlResolveKind::Category(category) => {
293+
UrlResolveKind::Category { category } => {
290294
format!("{backs}./{}.html", slugify(category))
291295
}
292296
}

tests/html_test.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,11 @@ async fn module_doc() {
511511
let mut module_docs = vec![];
512512

513513
for (short_path, doc_nodes) in &ctx.doc_nodes {
514-
let render_ctx =
515-
RenderContext::new(&ctx, doc_nodes, UrlResolveKind::File(short_path));
514+
let render_ctx = RenderContext::new(
515+
&ctx,
516+
doc_nodes,
517+
UrlResolveKind::File { file: short_path },
518+
);
516519
let module_doc = jsdoc::ModuleDocCtx::new(&render_ctx, short_path);
517520

518521
module_docs.push(module_doc);

0 commit comments

Comments
 (0)