summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-04-07 19:47:06 +0200
committerGitHub <noreply@github.com>2020-04-07 19:47:06 +0200
commit86fd0c66a645a3dd262e57e330bb7fbe4663e468 (patch)
treecedd0b8e4b759de8f675467ebf7771a1eea0f525
parent51f5276e8cbce89f396458e754d8f31c11fbf1ec (diff)
feat(doc): handle basic reexports (#4625)
-rw-r--r--cli/doc/module.rs39
-rw-r--r--cli/doc/node.rs29
-rw-r--r--cli/doc/parser.rs287
-rw-r--r--cli/doc/tests.rs220
-rw-r--r--cli/lib.rs42
5 files changed, 488 insertions, 129 deletions
diff --git a/cli/doc/module.rs b/cli/doc/module.rs
index e1d629ccf..ba62902dc 100644
--- a/cli/doc/module.rs
+++ b/cli/doc/module.rs
@@ -147,42 +147,3 @@ pub fn get_doc_node_for_export_decl(
}
}
}
-
-#[allow(unused)]
-pub fn get_doc_nodes_for_named_export(
- doc_parser: &DocParser,
- named_export: &swc_ecma_ast::NamedExport,
-) -> Vec<DocNode> {
- let file_name = named_export.src.as_ref().expect("").value.to_string();
- // TODO: resolve specifier
- let source_code =
- std::fs::read_to_string(&file_name).expect("Failed to read file");
- let doc_nodes = doc_parser
- .parse(file_name, source_code)
- .expect("Failed to print docs");
- let reexports: Vec<String> = named_export
- .specifiers
- .iter()
- .map(|export_specifier| {
- use crate::swc_ecma_ast::ExportSpecifier::*;
-
- match export_specifier {
- Named(named_export_specifier) => {
- Some(named_export_specifier.orig.sym.to_string())
- }
- // TODO:
- Namespace(_) => None,
- Default(_) => None,
- }
- })
- .filter(|s| s.is_some())
- .map(|s| s.unwrap())
- .collect();
-
- let reexports_docs: Vec<DocNode> = doc_nodes
- .into_iter()
- .filter(|doc_node| reexports.contains(&doc_node.name))
- .collect();
-
- reexports_docs
-}
diff --git a/cli/doc/node.rs b/cli/doc/node.rs
index e1e83ad0d..1be3ba7b1 100644
--- a/cli/doc/node.rs
+++ b/cli/doc/node.rs
@@ -48,6 +48,35 @@ impl Into<Location> for swc_common::Loc {
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
+pub enum ReexportKind {
+ /// export * from "./path/to/module.js";
+ All,
+ /// export * as someNamespace from "./path/to/module.js";
+ Namespace(String),
+ /// export default from "./path/to/module.js";
+ Default,
+ /// (identifier, optional alias)
+ /// export { foo } from "./path/to/module.js";
+ /// export { foo as bar } from "./path/to/module.js";
+ Named(String, Option<String>),
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct Reexport {
+ pub kind: ReexportKind,
+ pub src: String,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ModuleDoc {
+ pub exports: Vec<DocNode>,
+ pub reexports: Vec<Reexport>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
pub struct DocNode {
pub kind: DocNodeKind,
pub name: String,
diff --git a/cli/doc/parser.rs b/cli/doc/parser.rs
index 7a8fee56f..7c67f8d09 100644
--- a/cli/doc/parser.rs
+++ b/cli/doc/parser.rs
@@ -1,4 +1,5 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use crate::op_error::OpError;
use crate::swc_common;
use crate::swc_common::comments::CommentKind;
use crate::swc_common::comments::Comments;
@@ -22,34 +23,87 @@ use crate::swc_ecma_parser::Session;
use crate::swc_ecma_parser::SourceFileInput;
use crate::swc_ecma_parser::Syntax;
use crate::swc_ecma_parser::TsConfig;
+
+use deno_core::ErrBox;
+use deno_core::ModuleSpecifier;
+use futures::Future;
use regex::Regex;
+use std::collections::HashMap;
+use std::error::Error;
+use std::fmt;
+use std::pin::Pin;
use std::sync::Arc;
use std::sync::RwLock;
+use super::namespace::NamespaceDef;
+use super::node;
+use super::node::ModuleDoc;
use super::DocNode;
use super::DocNodeKind;
use super::Location;
-pub type SwcDiagnostics = Vec<Diagnostic>;
+#[derive(Clone, Debug)]
+pub struct SwcDiagnosticBuffer {
+ pub diagnostics: Vec<Diagnostic>,
+}
+
+impl Error for SwcDiagnosticBuffer {}
+
+impl fmt::Display for SwcDiagnosticBuffer {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let msg = self
+ .diagnostics
+ .iter()
+ .map(|d| d.message())
+ .collect::<Vec<String>>()
+ .join(",");
+
+ f.pad(&msg)
+ }
+}
+
+#[derive(Clone)]
+pub struct SwcErrorBuffer(Arc<RwLock<SwcDiagnosticBuffer>>);
-#[derive(Clone, Default)]
-pub struct BufferedError(Arc<RwLock<SwcDiagnostics>>);
+impl SwcErrorBuffer {
+ pub fn default() -> Self {
+ Self(Arc::new(RwLock::new(SwcDiagnosticBuffer {
+ diagnostics: vec![],
+ })))
+ }
+}
-impl Emitter for BufferedError {
+impl Emitter for SwcErrorBuffer {
fn emit(&mut self, db: &DiagnosticBuilder) {
- self.0.write().unwrap().push((**db).clone());
+ self.0.write().unwrap().diagnostics.push((**db).clone());
}
}
-impl From<BufferedError> for Vec<Diagnostic> {
- fn from(buf: BufferedError) -> Self {
+impl From<SwcErrorBuffer> for SwcDiagnosticBuffer {
+ fn from(buf: SwcErrorBuffer) -> Self {
let s = buf.0.read().unwrap();
s.clone()
}
}
+pub trait DocFileLoader {
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &str,
+ ) -> Result<ModuleSpecifier, OpError> {
+ ModuleSpecifier::resolve_import(specifier, referrer).map_err(OpError::from)
+ }
+
+ fn load_source_code(
+ &self,
+ specifier: &str,
+ ) -> Pin<Box<dyn Future<Output = Result<String, OpError>>>>;
+}
+
pub struct DocParser {
- pub buffered_error: BufferedError,
+ pub loader: Box<dyn DocFileLoader>,
+ pub buffered_error: SwcErrorBuffer,
pub source_map: Arc<SourceMap>,
pub handler: Handler,
pub comments: Comments,
@@ -57,8 +111,8 @@ pub struct DocParser {
}
impl DocParser {
- pub fn default() -> Self {
- let buffered_error = BufferedError::default();
+ pub fn new(loader: Box<dyn DocFileLoader>) -> Self {
+ let buffered_error = SwcErrorBuffer::default();
let handler = Handler::with_emitter_and_flags(
Box::new(buffered_error.clone()),
@@ -70,6 +124,7 @@ impl DocParser {
);
DocParser {
+ loader,
buffered_error,
source_map: Arc::new(SourceMap::default()),
handler,
@@ -78,15 +133,16 @@ impl DocParser {
}
}
- pub fn parse(
+ fn parse_module(
&self,
- file_name: String,
- source_code: String,
- ) -> Result<Vec<DocNode>, SwcDiagnostics> {
+ file_name: &str,
+ source_code: &str,
+ ) -> Result<ModuleDoc, SwcDiagnosticBuffer> {
swc_common::GLOBALS.set(&self.globals, || {
- let swc_source_file = self
- .source_map
- .new_source_file(FileName::Custom(file_name), source_code);
+ let swc_source_file = self.source_map.new_source_file(
+ FileName::Custom(file_name.to_string()),
+ source_code.to_string(),
+ );
let buffered_err = self.buffered_error.clone();
let session = Session {
@@ -112,15 +168,130 @@ impl DocParser {
.parse_module()
.map_err(move |mut err: DiagnosticBuilder| {
err.cancel();
- SwcDiagnostics::from(buffered_err)
+ SwcDiagnosticBuffer::from(buffered_err)
})?;
- let doc_entries = self.get_doc_nodes_for_module_body(module.body);
- Ok(doc_entries)
+ let doc_entries = self.get_doc_nodes_for_module_body(module.body.clone());
+ let reexports = self.get_reexports_for_module_body(module.body);
+ let module_doc = ModuleDoc {
+ exports: doc_entries,
+ reexports,
+ };
+ Ok(module_doc)
})
}
- pub fn get_doc_nodes_for_module_decl(
+ pub async fn parse(&self, file_name: &str) -> Result<Vec<DocNode>, ErrBox> {
+ let source_code = self.loader.load_source_code(file_name).await?;
+
+ let module_doc = self.parse_module(file_name, &source_code)?;
+ Ok(module_doc.exports)
+ }
+
+ async fn flatten_reexports(
+ &self,
+ reexports: &[node::Reexport],
+ referrer: &str,
+ ) -> Result<Vec<DocNode>, ErrBox> {
+ let mut by_src: HashMap<String, Vec<node::Reexport>> = HashMap::new();
+
+ let mut processed_reexports: Vec<DocNode> = vec![];
+
+ for reexport in reexports {
+ if by_src.get(&reexport.src).is_none() {
+ by_src.insert(reexport.src.to_string(), vec![]);
+ }
+
+ let bucket = by_src.get_mut(&reexport.src).unwrap();
+ bucket.push(reexport.clone());
+ }
+
+ for specifier in by_src.keys() {
+ let resolved_specifier = self.loader.resolve(specifier, referrer)?;
+ let doc_nodes = self.parse(&resolved_specifier.to_string()).await?;
+ let reexports_for_specifier = by_src.get(specifier).unwrap();
+
+ for reexport in reexports_for_specifier {
+ match &reexport.kind {
+ node::ReexportKind::All => {
+ processed_reexports.extend(doc_nodes.clone())
+ }
+ node::ReexportKind::Namespace(ns_name) => {
+ let ns_def = NamespaceDef {
+ elements: doc_nodes.clone(),
+ };
+ let ns_doc_node = DocNode {
+ kind: DocNodeKind::Namespace,
+ name: ns_name.to_string(),
+ location: Location {
+ filename: specifier.to_string(),
+ line: 1,
+ col: 0,
+ },
+ js_doc: None,
+ namespace_def: Some(ns_def),
+ enum_def: None,
+ type_alias_def: None,
+ interface_def: None,
+ variable_def: None,
+ function_def: None,
+ class_def: None,
+ };
+ processed_reexports.push(ns_doc_node);
+ }
+ node::ReexportKind::Named(ident, maybe_alias) => {
+ // Try to find reexport.
+ // NOTE: the reexport might actually be reexport from another
+ // module; for now we're skipping nested reexports.
+ let maybe_doc_node =
+ doc_nodes.iter().find(|node| &node.name == ident);
+
+ if let Some(doc_node) = maybe_doc_node {
+ let doc_node = doc_node.clone();
+ let doc_node = if let Some(alias) = maybe_alias {
+ DocNode {
+ name: alias.to_string(),
+ ..doc_node
+ }
+ } else {
+ doc_node
+ };
+
+ processed_reexports.push(doc_node);
+ }
+ }
+ node::ReexportKind::Default => {
+ // TODO: handle default export from child module
+ }
+ }
+ }
+ }
+
+ Ok(processed_reexports)
+ }
+
+ pub async fn parse_with_reexports(
+ &self,
+ file_name: &str,
+ ) -> Result<Vec<DocNode>, ErrBox> {
+ let source_code = self.loader.load_source_code(file_name).await?;
+
+ let module_doc = self.parse_module(file_name, &source_code)?;
+
+ let flattened_docs = if !module_doc.reexports.is_empty() {
+ let mut flattenned_reexports = self
+ .flatten_reexports(&module_doc.reexports, file_name)
+ .await?;
+ flattenned_reexports.extend(module_doc.exports);
+ flattenned_reexports
+ } else {
+ module_doc.exports
+ };
+
+ Ok(flattened_docs)
+ }
+
+ pub fn get_doc_nodes_for_module_exports(
&self,
module_decl: &ModuleDecl,
) -> Vec<DocNode> {
@@ -131,16 +302,6 @@ impl DocParser {
export_decl,
)]
}
- ModuleDecl::ExportNamed(_named_export) => {
- vec![]
- // TODO(bartlomieju):
- // super::module::get_doc_nodes_for_named_export(self, named_export)
- }
- ModuleDecl::ExportDefaultDecl(_) => vec![],
- ModuleDecl::ExportDefaultExpr(_) => vec![],
- ModuleDecl::ExportAll(_) => vec![],
- ModuleDecl::TsExportAssignment(_) => vec![],
- ModuleDecl::TsNamespaceExport(_) => vec![],
_ => vec![],
}
}
@@ -316,6 +477,67 @@ impl DocParser {
}
}
+ pub fn get_reexports_for_module_body(
+ &self,
+ module_body: Vec<swc_ecma_ast::ModuleItem>,
+ ) -> Vec<node::Reexport> {
+ use swc_ecma_ast::ExportSpecifier::*;
+
+ let mut reexports: Vec<node::Reexport> = vec![];
+
+ for node in module_body.iter() {
+ if let swc_ecma_ast::ModuleItem::ModuleDecl(module_decl) = node {
+ let r = match module_decl {
+ ModuleDecl::ExportNamed(named_export) => {
+ if let Some(src) = &named_export.src {
+ let src_str = src.value.to_string();
+ named_export
+ .specifiers
+ .iter()
+ .map(|export_specifier| match export_specifier {
+ Namespace(ns_export) => node::Reexport {
+ kind: node::ReexportKind::Namespace(
+ ns_export.name.sym.to_string(),
+ ),
+ src: src_str.to_string(),
+ },
+ Default(_) => node::Reexport {
+ kind: node::ReexportKind::Default,
+ src: src_str.to_string(),
+ },
+ Named(named_export) => {
+ let ident = named_export.orig.sym.to_string();
+ let maybe_alias =
+ named_export.exported.as_ref().map(|e| e.sym.to_string());
+ let kind = node::ReexportKind::Named(ident, maybe_alias);
+ node::Reexport {
+ kind,
+ src: src_str.to_string(),
+ }
+ }
+ })
+ .collect::<Vec<node::Reexport>>()
+ } else {
+ vec![]
+ }
+ }
+ ModuleDecl::ExportAll(export_all) => {
+ let reexport = node::Reexport {
+ kind: node::ReexportKind::All,
+ src: export_all.src.value.to_string(),
+ };
+ vec![reexport]
+ }
+ _ => vec![],
+ };
+
+ reexports.extend(r);
+ }
+ }
+
+ reexports
+ }
+
pub fn get_doc_nodes_for_module_body(
&self,
module_body: Vec<swc_ecma_ast::ModuleItem>,
@@ -324,7 +546,8 @@ impl DocParser {
for node in module_body.iter() {
match node {
swc_ecma_ast::ModuleItem::ModuleDecl(module_decl) => {
- doc_entries.extend(self.get_doc_nodes_for_module_decl(module_decl));
+ doc_entries
+ .extend(self.get_doc_nodes_for_module_exports(module_decl));
}
swc_ecma_ast::ModuleItem::Stmt(stmt) => {
if let Some(doc_node) = self.get_doc_node_for_stmt(stmt) {
diff --git a/cli/doc/tests.rs b/cli/doc/tests.rs
index 9432ba095..337f32466 100644
--- a/cli/doc/tests.rs
+++ b/cli/doc/tests.rs
@@ -4,8 +4,47 @@ use crate::colors;
use serde_json;
use serde_json::json;
-#[test]
-fn export_fn() {
+use super::parser::DocFileLoader;
+use crate::op_error::OpError;
+use std::collections::HashMap;
+
+use futures::Future;
+use futures::FutureExt;
+use std::pin::Pin;
+
+pub struct TestLoader {
+ files: HashMap<String, String>,
+}
+
+impl TestLoader {
+ pub fn new(files_vec: Vec<(String, String)>) -> Box<Self> {
+ let mut files = HashMap::new();
+
+ for file_tuple in files_vec {
+ files.insert(file_tuple.0, file_tuple.1);
+ }
+
+ Box::new(Self { files })
+ }
+}
+
+impl DocFileLoader for TestLoader {
+ fn load_source_code(
+ &self,
+ specifier: &str,
+ ) -> Pin<Box<dyn Future<Output = Result<String, OpError>>>> {
+ eprintln!("specifier {:#?}", specifier);
+ let res = match self.files.get(specifier) {
+ Some(source_code) => Ok(source_code.to_string()),
+ None => Err(OpError::other("not found".to_string())),
+ };
+
+ async move { res }.boxed_local()
+ }
+}
+
+#[tokio::test]
+async fn export_fn() {
let source_code = r#"/**
* Hello there, this is a multiline JSdoc.
*
@@ -17,9 +56,9 @@ export function foo(a: string, b: number): void {
console.log("Hello world");
}
"#;
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
let expected_json = json!({
@@ -68,13 +107,13 @@ export function foo(a: string, b: number): void {
);
}
-#[test]
-fn export_const() {
+#[tokio::test]
+async fn export_const() {
let source_code =
"/** Something about fizzBuzz */\nexport const fizzBuzz = \"fizzBuzz\";\n";
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
let expected_json = json!({
@@ -100,8 +139,8 @@ fn export_const() {
);
}
-#[test]
-fn export_class() {
+#[tokio::test]
+async fn export_class() {
let source_code = r#"
/** Class doc */
export class Foobar extends Fizz implements Buzz, Aldrin {
@@ -124,9 +163,9 @@ export class Foobar extends Fizz implements Buzz, Aldrin {
}
}
"#;
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let expected_json = json!({
"kind": "class",
@@ -314,8 +353,8 @@ export class Foobar extends Fizz implements Buzz, Aldrin {
);
}
-#[test]
-fn export_interface() {
+#[tokio::test]
+async fn export_interface() {
let source_code = r#"
/**
* Interface js doc
@@ -325,9 +364,9 @@ export interface Reader {
read(buf: Uint8Array, something: unknown): Promise<number>
}
"#;
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
let expected_json = json!({
@@ -399,15 +438,15 @@ export interface Reader {
);
}
-#[test]
-fn export_type_alias() {
+#[tokio::test]
+async fn export_type_alias() {
let source_code = r#"
/** Array holding numbers */
export type NumberArray = Array<number>;
"#;
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
let expected_json = json!({
@@ -445,8 +484,8 @@ export type NumberArray = Array<number>;
);
}
-#[test]
-fn export_enum() {
+#[tokio::test]
+async fn export_enum() {
let source_code = r#"
/**
* Some enum for good measure
@@ -457,9 +496,9 @@ export enum Hello {
Buzz = "buzz",
}
"#;
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
let expected_json = json!({
@@ -498,8 +537,8 @@ export enum Hello {
);
}
-#[test]
-fn export_namespace() {
+#[tokio::test]
+async fn export_namespace() {
let source_code = r#"
/** Namespace JSdoc */
export namespace RootNs {
@@ -515,9 +554,9 @@ export namespace RootNs {
}
}
"#;
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
let expected_json = json!({
@@ -593,8 +632,8 @@ export namespace RootNs {
);
}
-#[test]
-fn declare_namespace() {
+#[tokio::test]
+async fn declare_namespace() {
let source_code = r#"
/** Namespace JSdoc */
declare namespace RootNs {
@@ -610,9 +649,9 @@ declare namespace RootNs {
}
}
"#;
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
let expected_json = json!({
@@ -687,16 +726,16 @@ declare namespace RootNs {
.contains("namespace RootNs")
);
}
-#[test]
-fn optional_return_type() {
+#[tokio::test]
+async fn optional_return_type() {
let source_code = r#"
export function foo(a: number) {
return a;
}
"#;
- let entries = DocParser::default()
- .parse("test.ts".to_string(), source_code.to_string())
- .unwrap();
+ let loader =
+ TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]);
+ let entries = DocParser::new(loader).parse("test.ts").await.unwrap();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
let expected_json = json!({
@@ -732,3 +771,94 @@ fn optional_return_type() {
.contains("function foo(a: number)")
);
}
+
+#[tokio::test]
+async fn reexports() {
+ let nested_reexport_source_code = r#"
+/**
+ * JSDoc for bar
+ */
+export const bar = "bar";
+"#;
+ let reexport_source_code = r#"
+import { bar } from "./nested_reexport.ts";
+
+/**
+ * JSDoc for const
+ */
+export const foo = "foo";
+"#;
+ let test_source_code = r#"
+export { foo as fooConst } from "./reexport.ts";
+
+/** JSDoc for function */
+export function fooFn(a: number) {
+ return a;
+}
+"#;
+ let loader = TestLoader::new(vec![
+ ("file:///test.ts".to_string(), test_source_code.to_string()),
+ (
+ "file:///reexport.ts".to_string(),
+ reexport_source_code.to_string(),
+ ),
+ (
+ "file:///nested_reexport.ts".to_string(),
+ nested_reexport_source_code.to_string(),
+ ),
+ ]);
+ let entries = DocParser::new(loader)
+ .parse_with_reexports("file:///test.ts")
+ .await
+ .unwrap();
+ assert_eq!(entries.len(), 2);
+
+ let expected_json = json!([
+ {
+ "kind": "variable",
+ "name": "fooConst",
+ "location": {
+ "filename": "file:///reexport.ts",
+ "line": 7,
+ "col": 0
+ },
+ "jsDoc": "JSDoc for const",
+ "variableDef": {
+ "tsType": null,
+ "kind": "const"
+ }
+ },
+ {
+ "kind": "function",
+ "name": "fooFn",
+ "location": {
+ "filename": "file:///test.ts",
+ "line": 5,
+ "col": 0
+ },
+ "jsDoc": "JSDoc for function",
+ "functionDef": {
+ "params": [
+ {
+ "name": "a",
+ "tsType": {
+ "keyword": "number",
+ "kind": "keyword",
+ "repr": "number",
+ },
+ }
+ ],
+ "returnType": null,
+ "isAsync": false,
+ "isGenerator": false
+ }
+ }
+ ]);
+ let actual = serde_json::to_value(entries.clone()).unwrap();
+ assert_eq!(actual, expected_json);
+
+ assert!(
+ colors::strip_ansi_codes(super::printer::format(entries).as_str())
+ .contains("function fooFn(a: number)")
+ );
+}
diff --git a/cli/lib.rs b/cli/lib.rs
index 9ccc8d022..a87846c11 100644
--- a/cli/lib.rs
+++ b/cli/lib.rs
@@ -66,9 +66,12 @@ pub use dprint_plugin_typescript::swc_ecma_ast;
pub use dprint_plugin_typescript::swc_ecma_parser;
use crate::compilers::TargetLib;
+use crate::doc::parser::DocFileLoader;
use crate::file_fetcher::SourceFile;
+use crate::file_fetcher::SourceFileFetcher;
use crate::global_state::GlobalState;
use crate::msg::MediaType;
+use crate::op_error::OpError;
use crate::ops::io::get_stdio;
use crate::state::DebugType;
use crate::state::State;
@@ -79,12 +82,14 @@ use deno_core::ModuleSpecifier;
use flags::DenoSubcommand;
use flags::Flags;
use futures::future::FutureExt;
+use futures::Future;
use log::Level;
use log::Metadata;
use log::Record;
use std::env;
use std::io::Write;
use std::path::PathBuf;
+use std::pin::Pin;
use upgrade::upgrade_command;
use url::Url;
@@ -371,24 +376,35 @@ async fn doc_command(
let global_state = GlobalState::new(flags.clone())?;
let module_specifier =
ModuleSpecifier::resolve_url_or_path(&source_file).unwrap();
- let source_file = global_state
- .file_fetcher
- .fetch_source_file(&module_specifier, None)
- .await?;
- let source_code = String::from_utf8(source_file.source_code)?;
- let doc_parser = doc::DocParser::default();
- let parse_result =
- doc_parser.parse(module_specifier.to_string(), source_code);
+ impl DocFileLoader for SourceFileFetcher {
+ fn load_source_code(
+ &self,
+ specifier: &str,
+ ) -> Pin<Box<dyn Future<Output = Result<String, OpError>>>> {
+ let specifier =
+ ModuleSpecifier::resolve_url_or_path(specifier).expect("Bad specifier");
+ let fetcher = self.clone();
+
+ async move {
+ let source_file = fetcher.fetch_source_file(&specifier, None).await?;
+ String::from_utf8(source_file.source_code)
+ .map_err(|_| OpError::other("failed to parse".to_string()))
+ }
+ .boxed_local()
+ }
+ }
+
+ let loader = Box::new(global_state.file_fetcher.clone());
+ let doc_parser = doc::DocParser::new(loader);
+ let parse_result = doc_parser
+ .parse_with_reexports(&module_specifier.to_string())
+ .await;
let doc_nodes = match parse_result {
Ok(nodes) => nodes,
Err(e) => {
- eprintln!("Failed to parse documentation:");
- for diagnostic in e {
- eprintln!("{}", diagnostic.message());
- }
-
+ eprintln!("{}", e);
std::process::exit(1);
}
};