mirror of
https://github.com/denoland/deno.git
synced 2025-01-10 16:11:13 -05:00
feat(doc): handle basic reexports (#4625)
This commit is contained in:
parent
51f5276e8c
commit
86fd0c66a6
5 changed files with 489 additions and 130 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -46,6 +46,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 {
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct BufferedError(Arc<RwLock<SwcDiagnostics>>);
|
||||
impl Error for SwcDiagnosticBuffer {}
|
||||
|
||||
impl Emitter for BufferedError {
|
||||
fn emit(&mut self, db: &DiagnosticBuilder) {
|
||||
self.0.write().unwrap().push((**db).clone());
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BufferedError> for Vec<Diagnostic> {
|
||||
fn from(buf: BufferedError) -> Self {
|
||||
#[derive(Clone)]
|
||||
pub struct SwcErrorBuffer(Arc<RwLock<SwcDiagnosticBuffer>>);
|
||||
|
||||
impl SwcErrorBuffer {
|
||||
pub fn default() -> Self {
|
||||
Self(Arc::new(RwLock::new(SwcDiagnosticBuffer {
|
||||
diagnostics: vec![],
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for SwcErrorBuffer {
|
||||
fn emit(&mut self, db: &DiagnosticBuilder) {
|
||||
self.0.write().unwrap().diagnostics.push((**db).clone());
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
220
cli/doc/tests.rs
220
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)")
|
||||
);
|
||||
}
|
||||
|
|
42
cli/lib.rs
42
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);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue