use deno_ast::ModuleSpecifier;
use deno_doc::html::pages::SymbolPage;
use deno_doc::html::*;
use deno_doc::DocNode;
use deno_doc::DocParser;
use deno_doc::DocParserOptions;
use deno_graph::source::LoadFuture;
use deno_graph::source::LoadOptions;
use deno_graph::source::LoadResponse;
use deno_graph::source::Loader;
use deno_graph::BuildOptions;
use deno_graph::CapturingModuleAnalyzer;
use deno_graph::GraphKind;
use deno_graph::ModuleGraph;
use futures::future;
use indexmap::IndexMap;
use std::fs;
use std::rc::Rc;
struct SourceFileLoader {}
impl Loader for SourceFileLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: LoadOptions,
) -> LoadFuture {
let result = if specifier.scheme() == "file" {
let path = specifier.to_file_path().unwrap();
fs::read(path)
.map(|content| {
Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
content: content.into(),
})
})
.map_err(|err| err.into())
} else {
Ok(None)
};
Box::pin(future::ready(result))
}
}
struct EmptyResolver {}
impl HrefResolver for EmptyResolver {
fn resolve_path(
&self,
current: UrlResolveKind,
target: UrlResolveKind,
) -> String {
href_path_resolve(current, target)
}
fn resolve_global_symbol(&self, _symbol: &[String]) -> Option<String> {
None
}
fn resolve_import_href(
&self,
_symbol: &[String],
_src: &str,
) -> Option<String> {
None
}
fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option<String> {
current_resolve
.get_file()
.map(|current_file| current_file.path.to_string())
}
fn resolve_source(&self, _location: &deno_doc::Location) -> Option<String> {
None
}
}
async fn get_files(subpath: &str) -> IndexMap<ModuleSpecifier, Vec<DocNode>> {
let files = fs::read_dir(
std::env::current_dir()
.unwrap()
.join("tests")
.join("testdata")
.join(subpath),
)
.unwrap();
let source_files: Vec<ModuleSpecifier> = files
.into_iter()
.map(|entry| {
let entry = entry.unwrap();
ModuleSpecifier::from_file_path(entry.path()).unwrap()
})
.collect();
let loader = SourceFileLoader {};
let analyzer = CapturingModuleAnalyzer::default();
let mut graph = ModuleGraph::new(GraphKind::TypesOnly);
graph
.build(
source_files.clone(),
&loader,
BuildOptions {
module_analyzer: &analyzer,
..Default::default()
},
)
.await;
let parser = DocParser::new(
&graph,
&analyzer,
DocParserOptions {
diagnostics: false,
private: false,
},
)
.unwrap();
let mut source_files = source_files.clone();
source_files.sort();
let mut doc_nodes_by_url = IndexMap::with_capacity(source_files.len());
for source_file in source_files {
let nodes = parser.parse_with_reexports(&source_file).unwrap();
doc_nodes_by_url.insert(source_file, nodes);
}
doc_nodes_by_url
}
#[tokio::test]
async fn html_doc_files() {
let files = generate(
GenerateOptions {
package_name: None,
main_entrypoint: None,
href_resolver: Rc::new(EmptyResolver {}),
usage_composer: None,
rewrite_map: None,
composable_output: false,
},
get_files("single").await,
)
.unwrap();
let mut file_names = files.keys().collect::<Vec<_>>();
file_names.sort();
assert_eq!(
file_names,
[
"./all_symbols.html",
"./index.html",
"./~/Bar.html",
"./~/Bar.prototype.html",
"./~/Foo.html",
"./~/Foo.prototype.html",
"./~/Foobar.html",
"./~/Foobar.prototype.html",
"fuse.js",
"page.css",
"script.js",
"search.js",
"search_index.js",
"styles.css",
]
);
#[cfg(all(not(feature = "syntect"), not(feature = "tree-sitter")))]
{
insta::assert_snapshot!(files.get("./all_symbols.html").unwrap());
insta::assert_snapshot!(files.get("./index.html").unwrap());
insta::assert_snapshot!(files.get("./~/Bar.html").unwrap());
insta::assert_snapshot!(files.get("./~/Bar.prototype.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foo.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foo.prototype.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foobar.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foobar.prototype.html").unwrap());
insta::assert_snapshot!(files.get("fuse.js").unwrap());
insta::assert_snapshot!(files.get("page.css").unwrap());
insta::assert_snapshot!(files.get("script.js").unwrap());
insta::assert_snapshot!(files.get("search.js").unwrap());
insta::assert_snapshot!(files.get("search_index.js").unwrap());
}
}
#[tokio::test]
async fn html_doc_files_rewrite() {
let multiple_dir = std::env::current_dir()
.unwrap()
.join("tests")
.join("testdata")
.join("multiple");
let mut rewrite_map = IndexMap::new();
rewrite_map.insert(
ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(),
".".to_string(),
);
rewrite_map.insert(
ModuleSpecifier::from_file_path(multiple_dir.join("b.ts")).unwrap(),
"foo".to_string(),
);
let files = generate(
GenerateOptions {
package_name: None,
main_entrypoint: None,
href_resolver: Rc::new(EmptyResolver {}),
usage_composer: None,
rewrite_map: Some(rewrite_map),
composable_output: false,
},
get_files("multiple").await,
)
.unwrap();
let mut file_names = files.keys().collect::<Vec<_>>();
file_names.sort();
assert_eq!(
file_names,
[
"./all_symbols.html",
"./index.html",
"./~/A.html",
"./~/A.prototype.html",
"./~/B.html",
"./~/B.prototype.html",
"./~/Bar.html",
"./~/Bar.prototype.html",
"./~/Baz.foo.html",
"./~/Baz.html",
"./~/Foo.bar.html",
"./~/Foo.html",
"./~/Foo.prototype.\"><img src=x onerror=alert(1)>.html",
"./~/Foo.prototype.foo.html",
"./~/Foo.prototype.html",
"./~/Foobar.html",
"./~/Foobar.prototype.html",
"./~/Hello.html",
"./~/Hello.world.html",
"./~/index.html",
"./~/qaz.html",
"foo/~/default.html",
"foo/~/index.html",
"foo/~/x.html",
"fuse.js",
"page.css",
"script.js",
"search.js",
"search_index.js",
"styles.css"
]
);
#[cfg(all(not(feature = "syntect"), not(feature = "tree-sitter")))]
{
insta::assert_snapshot!(files.get("./all_symbols.html").unwrap());
insta::assert_snapshot!(files.get("./index.html").unwrap());
insta::assert_snapshot!(files.get("./~/Bar.html").unwrap());
insta::assert_snapshot!(files.get("./~/Bar.prototype.html").unwrap());
insta::assert_snapshot!(files.get("./~/Baz.html").unwrap());
insta::assert_snapshot!(files.get("./~/Baz.foo.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foo.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foo.bar.html").unwrap());
insta::assert_snapshot!(files
.get("./~/Foo.prototype.\"><img src=x onerror=alert(1)>.html")
.unwrap());
insta::assert_snapshot!(files.get("./~/Foo.prototype.foo.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foo.prototype.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foobar.html").unwrap());
insta::assert_snapshot!(files.get("./~/Foobar.prototype.html").unwrap());
insta::assert_snapshot!(files.get("./~/Hello.html").unwrap());
insta::assert_snapshot!(files.get("./~/Hello.world.html").unwrap());
insta::assert_snapshot!(files.get("./~/index.html").unwrap());
insta::assert_snapshot!(files.get("foo/~/index.html").unwrap());
insta::assert_snapshot!(files.get("foo/~/x.html").unwrap());
insta::assert_snapshot!(files.get("fuse.js").unwrap());
insta::assert_snapshot!(files.get("page.css").unwrap());
insta::assert_snapshot!(files.get("script.js").unwrap());
insta::assert_snapshot!(files.get("search.js").unwrap());
insta::assert_snapshot!(files.get("search_index.js").unwrap());
}
}
#[tokio::test]
async fn symbol_group() {
let multiple_dir = std::env::current_dir()
.unwrap()
.join("tests")
.join("testdata")
.join("multiple");
let doc_nodes_by_url = get_files("multiple").await;
let mut rewrite_map = IndexMap::new();
rewrite_map.insert(
ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(),
".".to_string(),
);
rewrite_map.insert(
ModuleSpecifier::from_file_path(multiple_dir.join("b.ts")).unwrap(),
"foo".to_string(),
);
let ctx = GenerateCtx::new(
GenerateOptions {
package_name: None,
main_entrypoint: Some(
ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(),
),
href_resolver: Rc::new(EmptyResolver {}),
usage_composer: None,
rewrite_map: Some(rewrite_map),
composable_output: false,
},
None,
Default::default(),
doc_nodes_by_url,
)
.unwrap();
let mut files = vec![];
{
for (short_path, doc_nodes) in &ctx.doc_nodes {
let symbol_pages =
generate_symbol_pages_for_module(&ctx, short_path, doc_nodes);
files.extend(symbol_pages.into_iter().map(
|symbol_page| match symbol_page {
SymbolPage::Symbol {
breadcrumbs_ctx,
symbol_group_ctx,
toc_ctx,
} => {
let root = ctx.href_resolver.resolve_path(
UrlResolveKind::Symbol {
file: short_path,
symbol: &symbol_group_ctx.name,
},
UrlResolveKind::Root,
);
let html_head_ctx = pages::HtmlHeadCtx::new(
&root,
&symbol_group_ctx.name,
ctx.package_name.as_ref(),
Some(short_path),
);
Some(pages::SymbolPageCtx {
html_head_ctx,
symbol_group_ctx,
breadcrumbs_ctx,
toc_ctx,
})
}
SymbolPage::Redirect { .. } => None,
},
));
}
}
#[cfg(all(not(feature = "syntect"), not(feature = "tree-sitter")))]
insta::assert_json_snapshot!(files);
}
#[tokio::test]
async fn symbol_search() {
let multiple_dir = std::env::current_dir()
.unwrap()
.join("tests")
.join("testdata")
.join("multiple");
let doc_nodes_by_url = get_files("multiple").await;
let mut rewrite_map = IndexMap::new();
rewrite_map.insert(
ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(),
".".to_string(),
);
rewrite_map.insert(
ModuleSpecifier::from_file_path(multiple_dir.join("b.ts")).unwrap(),
"foo".to_string(),
);
let ctx = GenerateCtx::new(
GenerateOptions {
package_name: None,
main_entrypoint: Some(
ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(),
),
href_resolver: Rc::new(EmptyResolver {}),
usage_composer: None,
rewrite_map: Some(rewrite_map),
composable_output: false,
},
None,
Default::default(),
doc_nodes_by_url,
)
.unwrap();
let search_index = generate_search_index(&ctx);
insta::assert_json_snapshot!(search_index);
}
#[tokio::test]
async fn module_doc() {
let multiple_dir = std::env::current_dir()
.unwrap()
.join("tests")
.join("testdata")
.join("multiple");
let doc_nodes_by_url = get_files("multiple").await;
let mut rewrite_map = IndexMap::new();
rewrite_map.insert(
ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(),
".".to_string(),
);
rewrite_map.insert(
ModuleSpecifier::from_file_path(multiple_dir.join("b.ts")).unwrap(),
"foo".to_string(),
);
let ctx = GenerateCtx::new(
GenerateOptions {
package_name: None,
main_entrypoint: Some(
ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(),
),
href_resolver: Rc::new(EmptyResolver {}),
usage_composer: None,
rewrite_map: Some(rewrite_map),
composable_output: false,
},
None,
FileMode::Single,
doc_nodes_by_url,
)
.unwrap();
let mut module_docs = vec![];
for (short_path, doc_nodes) in &ctx.doc_nodes {
let render_ctx =
RenderContext::new(&ctx, doc_nodes, UrlResolveKind::File(short_path));
let module_doc = jsdoc::ModuleDocCtx::new(&render_ctx, short_path);
module_docs.push(module_doc);
}
#[cfg(all(not(feature = "syntect"), not(feature = "tree-sitter")))]
insta::assert_json_snapshot!(module_docs); }