use crate::js_doc::JsDoc;
use crate::namespace::NamespaceDef;
use crate::node;
use crate::node::DeclarationKind;
use crate::node::DocNode;
use crate::node::ModuleDoc;
use crate::swc_util::get_location;
use crate::swc_util::js_doc_for_range;
use crate::swc_util::module_export_name_value;
use crate::swc_util::module_js_doc_for_source;
use crate::ts_type::LiteralPropertyDef;
use crate::ts_type::TsTypeDef;
use crate::ts_type::TsTypeDefKind;
use crate::ts_type::TsTypeLiteralDef;
use crate::variable::VariableDef;
use crate::DocNodeKind;
use crate::ImportDef;
use crate::Location;
use crate::ReexportKind;
use deno_ast::swc::ast::Decl;
use deno_ast::swc::ast::DefaultDecl;
use deno_ast::swc::ast::ExportSpecifier;
use deno_ast::swc::ast::Expr;
use deno_ast::swc::ast::ImportSpecifier;
use deno_ast::swc::ast::ModuleDecl;
use deno_ast::swc::ast::ModuleItem;
use deno_ast::swc::ast::Stmt;
use deno_ast::swc::ast::VarDeclKind;
use deno_ast::MediaType;
use deno_ast::ParsedSource;
use deno_ast::SourceRangedForSpanned;
use deno_graph::CapturingModuleParser;
use deno_graph::ModuleGraph;
use deno_graph::ModuleKind;
use deno_graph::ModuleParser;
use deno_graph::ModuleSpecifier;
use deno_graph::Resolved;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum DocError {
Resolve(String),
#[allow(dead_code)]
Io(std::io::Error),
Parse(deno_ast::Diagnostic),
}
impl Error for DocError {}
impl fmt::Display for DocError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let m = match self {
Self::Resolve(s) => s.to_string(),
Self::Io(err) => err.to_string(),
Self::Parse(err) => err.to_string(),
};
f.pad(&m)
}
}
impl From<deno_ast::Diagnostic> for DocError {
fn from(error: deno_ast::Diagnostic) -> DocError {
DocError::Parse(error)
}
}
#[derive(Debug, Clone)]
enum ImportKind {
Namespace(String),
Named(String, Option<String>),
}
#[derive(Debug, Clone)]
struct Import {
src: String,
kind: ImportKind,
}
pub struct DocParser<'a> {
pub graph: ModuleGraph,
pub private: bool,
pub parser: CapturingModuleParser<'a>,
}
impl<'a> DocParser<'a> {
pub fn new(
graph: ModuleGraph,
private: bool,
parser: CapturingModuleParser<'a>,
) -> Self {
DocParser {
graph,
private,
parser,
}
}
pub fn parse_module(
&self,
specifier: &ModuleSpecifier,
) -> Result<ModuleDoc, DocError> {
let parsed_source = self.get_parsed_source(specifier)?;
let module = parsed_source.module();
let mut definitions =
self.get_doc_nodes_for_module_body(&parsed_source, module.body.clone());
let import_doc_entries = self.get_doc_nodes_for_module_imports(
&parsed_source,
module.body.clone(),
specifier,
)?;
definitions.extend(import_doc_entries);
let reexports = self.get_reexports_for_module_body(&module.body);
let module_doc = ModuleDoc {
definitions,
reexports,
};
Ok(module_doc)
}
fn get_parsed_source(
&self,
specifier: &ModuleSpecifier,
) -> Result<ParsedSource, DocError> {
let specifier = self.graph.resolve(specifier);
let module = self.graph.get(&specifier).ok_or_else(|| {
DocError::Resolve(format!(
"Could not find module in graph: {}",
specifier
))
})?;
let source = module.maybe_source.clone().ok_or_else(|| {
DocError::Resolve(format!(
"Could not find module source in graph: {}",
specifier
))
})?;
self
.parser
.parse_module(&specifier, source, module.media_type)
.map_err(DocError::Parse)
}
#[cfg(feature = "rust")]
pub fn parse(
&self,
specifier: &ModuleSpecifier,
) -> Result<Vec<DocNode>, DocError> {
self.parse_module(specifier).map(|md| md.definitions)
}
fn flatten_reexports(
&self,
reexports: &[node::Reexport],
referrer: &ModuleSpecifier,
) -> Result<Vec<DocNode>, DocError> {
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
.graph
.resolve_dependency(specifier, referrer, true)
.ok_or_else(|| DocError::Resolve(specifier.clone()))?;
let doc_nodes = self.parse_with_reexports(resolved_specifier)?;
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 mut js_doc = JsDoc::default();
for doc_node in &doc_nodes {
if matches!(doc_node.kind, DocNodeKind::ModuleDoc) {
js_doc = doc_node.js_doc.clone();
}
}
let ns_def = NamespaceDef {
elements: doc_nodes
.iter()
.filter(|dn| !matches!(dn.kind, DocNodeKind::ModuleDoc))
.cloned()
.collect(),
};
let ns_doc_node = DocNode::namespace(
ns_name.to_string(),
Location {
filename: specifier.to_string(),
line: 1,
col: 0,
},
DeclarationKind::Export,
js_doc,
ns_def,
);
processed_reexports.push(ns_doc_node);
}
node::ReexportKind::Named(ident, maybe_alias) => {
let doc_nodes = doc_nodes
.iter()
.filter(|node| &node.name == ident)
.collect::<Vec<_>>();
for doc_node in doc_nodes {
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);
}
}
}
}
}
Ok(processed_reexports)
}
pub fn parse_with_reexports(
&self,
specifier: &ModuleSpecifier,
) -> Result<Vec<DocNode>, DocError> {
let module = self
.graph
.try_get(specifier)
.map_err(|err| DocError::Resolve(err.to_string()))?
.ok_or_else(|| {
DocError::Resolve(format!(
"Unable to load specifier: \"{}\"",
specifier
))
})?;
let module = if let Some((_, Resolved::Ok { specifier, .. })) =
&module.maybe_types_dependency
{
self
.graph
.try_get(specifier)
.map_err(|err| DocError::Resolve(err.to_string()))?
.ok_or_else(|| {
DocError::Resolve(format!(
"Unable to load specifier: \"{}\"",
specifier
))
})?
} else {
module
};
if module.kind == ModuleKind::Asserted {
if module.media_type == MediaType::Json {
if let Some(source) = &module.maybe_source {
if let Ok(value) = serde_json::from_str(source) {
return Ok(vec![parse_json_module(&module.specifier, &value)]);
}
}
}
return Ok(Vec::new());
}
let module_doc = self.parse_module(&module.specifier)?;
let flattened_docs = if !module_doc.reexports.is_empty() {
let mut flattened_reexports =
self.flatten_reexports(&module_doc.reexports, &module.specifier)?;
flattened_reexports.extend(module_doc.definitions);
flattened_reexports
} else {
module_doc.definitions
};
Ok(flattened_docs)
}
fn get_doc_nodes_for_module_imports(
&self,
parsed_source: &ParsedSource,
module_body: Vec<deno_ast::swc::ast::ModuleItem>,
referrer: &ModuleSpecifier,
) -> Result<Vec<DocNode>, DocError> {
let mut imports = vec![];
for node in module_body.iter() {
if let deno_ast::swc::ast::ModuleItem::ModuleDecl(ModuleDecl::Import(
import_decl,
)) = node
{
let js_doc = js_doc_for_range(parsed_source, &import_decl.range());
let location = get_location(parsed_source, import_decl.start());
for specifier in &import_decl.specifiers {
use deno_ast::swc::ast::ImportSpecifier::*;
let (name, maybe_imported_name, src) = match specifier {
Named(named_specifier) => (
named_specifier.local.sym.to_string(),
named_specifier
.imported
.as_ref()
.map(module_export_name_value)
.or_else(|| Some(named_specifier.local.sym.to_string())),
import_decl.src.value.to_string(),
),
Default(default_specifier) => (
default_specifier.local.sym.to_string(),
Some("default".to_string()),
import_decl.src.value.to_string(),
),
Namespace(namespace_specifier) => (
namespace_specifier.local.sym.to_string(),
None,
import_decl.src.value.to_string(),
),
};
let resolved_specifier = self
.graph
.resolve_dependency(&src, referrer, true)
.ok_or_else(|| DocError::Resolve(src.clone()))?;
let import_def = ImportDef {
src: resolved_specifier.to_string(),
imported: maybe_imported_name,
};
let doc_node =
DocNode::import(name, location.clone(), js_doc.clone(), import_def);
imports.push(doc_node);
}
}
}
Ok(imports)
}
pub fn get_doc_nodes_for_module_exports(
&self,
parsed_source: &ParsedSource,
module_decl: &ModuleDecl,
previous_doc_nodes: Vec<&DocNode>,
) -> Vec<DocNode> {
match module_decl {
ModuleDecl::ExportDecl(export_decl) => {
super::module::get_doc_node_for_export_decl(
self,
parsed_source,
export_decl,
previous_doc_nodes,
)
}
ModuleDecl::ExportDefaultDecl(export_default_decl) => {
let js_doc =
js_doc_for_range(parsed_source, &export_default_decl.range());
let location = get_location(parsed_source, export_default_decl.start());
let name = "default".to_string();
let doc_node = match &export_default_decl.decl {
DefaultDecl::Class(class_expr) => {
let (class_def, decorator_js_doc) =
crate::class::class_to_class_def(
parsed_source,
&class_expr.class,
);
let js_doc = if js_doc.is_empty() {
decorator_js_doc
} else {
js_doc
};
DocNode::class(
name,
location,
DeclarationKind::Export,
js_doc,
class_def,
)
}
DefaultDecl::Fn(fn_expr) => {
let function_def = crate::function::function_to_function_def(
parsed_source,
&fn_expr.function,
);
DocNode::function(
name,
location,
DeclarationKind::Export,
js_doc,
function_def,
)
}
DefaultDecl::TsInterfaceDecl(interface_decl) => {
let (_, interface_def) =
crate::interface::get_doc_for_ts_interface_decl(
parsed_source,
interface_decl,
);
DocNode::interface(
name,
location,
DeclarationKind::Export,
js_doc,
interface_def,
)
}
};
vec![doc_node]
}
_ => vec![],
}
}
pub fn get_doc_node_for_decl(
&self,
parsed_source: &ParsedSource,
decl: &Decl,
previous_nodes: Vec<&DocNode>,
) -> Option<Vec<DocNode>> {
match decl {
Decl::Class(class_decl) => {
let (name, class_def, _) =
super::class::get_doc_for_class_decl(parsed_source, class_decl);
let js_doc = js_doc_for_range(parsed_source, &class_decl.class.range());
let location = get_location(parsed_source, class_decl.class.start());
Some(vec![DocNode::class(
name,
location,
DeclarationKind::Declare,
js_doc,
class_def,
)])
}
Decl::Fn(fn_decl) => {
let (name, function_def) =
super::function::get_doc_for_fn_decl(parsed_source, fn_decl);
let js_doc = js_doc_for_range(parsed_source, &fn_decl.function.range());
let location = get_location(parsed_source, fn_decl.function.start());
Some(vec![DocNode::function(
name,
location,
DeclarationKind::Declare,
js_doc,
function_def,
)])
}
Decl::Var(var_decl) => Some(
super::variable::get_doc_for_var_decl(
parsed_source,
var_decl,
previous_nodes,
)
.into_iter()
.map(|(name, var_def, _)| {
let js_doc = js_doc_for_range(parsed_source, &var_decl.range());
let location = get_location(parsed_source, var_decl.start());
DocNode::variable(
name,
location,
DeclarationKind::Declare,
js_doc,
var_def,
)
})
.collect(),
),
Decl::TsInterface(ts_interface_decl) => {
let (name, interface_def) =
super::interface::get_doc_for_ts_interface_decl(
parsed_source,
ts_interface_decl,
);
let js_doc =
js_doc_for_range(parsed_source, &ts_interface_decl.range());
let location = get_location(parsed_source, ts_interface_decl.start());
Some(vec![DocNode::interface(
name,
location,
DeclarationKind::Declare,
js_doc,
interface_def,
)])
}
Decl::TsTypeAlias(ts_type_alias) => {
let (name, type_alias_def) =
super::type_alias::get_doc_for_ts_type_alias_decl(
parsed_source,
ts_type_alias,
);
let js_doc = js_doc_for_range(parsed_source, &ts_type_alias.range());
let location = get_location(parsed_source, ts_type_alias.start());
Some(vec![DocNode::type_alias(
name,
location,
DeclarationKind::Declare,
js_doc,
type_alias_def,
)])
}
Decl::TsEnum(ts_enum) => {
let (name, enum_def) =
super::r#enum::get_doc_for_ts_enum_decl(parsed_source, ts_enum);
let js_doc = js_doc_for_range(parsed_source, &ts_enum.range());
let location = get_location(parsed_source, ts_enum.start());
Some(vec![DocNode::r#enum(
name,
location,
DeclarationKind::Declare,
js_doc,
enum_def,
)])
}
Decl::TsModule(ts_module) => {
let (name, namespace_def) = super::namespace::get_doc_for_ts_module(
self,
parsed_source,
ts_module,
);
let js_doc = js_doc_for_range(parsed_source, &ts_module.range());
let location = get_location(parsed_source, ts_module.start());
Some(vec![DocNode::namespace(
name,
location,
DeclarationKind::Declare,
js_doc,
namespace_def,
)])
}
}
}
fn get_imports_for_module_body(
&self,
module_body: &[deno_ast::swc::ast::ModuleItem],
) -> HashMap<String, Import> {
let mut imports = HashMap::new();
for node in module_body.iter() {
if let ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) = node {
for specifier in &import_decl.specifiers {
let import = match specifier {
ImportSpecifier::Named(named_specifier) => Import {
kind: ImportKind::Named(
named_specifier.local.sym.to_string(),
named_specifier
.imported
.as_ref()
.map(module_export_name_value),
),
src: import_decl.src.value.to_string(),
},
ImportSpecifier::Default(default_specifier) => Import {
kind: ImportKind::Named(
default_specifier.local.sym.to_string(),
Some("default".to_string()),
),
src: import_decl.src.value.to_string(),
},
ImportSpecifier::Namespace(namespace_specifier) => Import {
kind: ImportKind::Namespace(
namespace_specifier.local.sym.to_string(),
),
src: import_decl.src.value.to_string(),
},
};
let name = match import.kind.clone() {
ImportKind::Named(name, _) | ImportKind::Namespace(name) => name,
};
imports.insert(name, import);
}
}
}
imports
}
pub fn get_reexports_for_module_body(
&self,
module_body: &[deno_ast::swc::ast::ModuleItem],
) -> Vec<node::Reexport> {
let imports = self.get_imports_for_module_body(module_body);
let mut reexports: Vec<node::Reexport> = vec![];
if self.private {
reexports.extend(imports.values().cloned().map(|import| node::Reexport {
src: import.src,
kind: match import.kind {
ImportKind::Named(orig, exported) => {
ReexportKind::Named(orig, exported)
}
ImportKind::Namespace(name) => ReexportKind::Namespace(name),
},
}))
}
for node in module_body.iter() {
if let deno_ast::swc::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 {
ExportSpecifier::Namespace(ns_export) => node::Reexport {
kind: node::ReexportKind::Namespace(
module_export_name_value(&ns_export.name),
),
src: src_str.to_string(),
},
ExportSpecifier::Default(specifier) => node::Reexport {
kind: node::ReexportKind::Named(
"default".to_string(),
Some(specifier.exported.sym.to_string()),
),
src: src_str.to_string(),
},
ExportSpecifier::Named(named_export) => {
let export_name =
module_export_name_value(&named_export.orig);
let maybe_alias = named_export
.exported
.as_ref()
.map(module_export_name_value);
let kind =
node::ReexportKind::Named(export_name, maybe_alias);
node::Reexport {
kind,
src: src_str.to_string(),
}
}
})
.collect::<Vec<node::Reexport>>()
} else {
named_export
.specifiers
.iter()
.filter_map(|specifier| {
if let ExportSpecifier::Named(specifier) = specifier {
if let Some(import) =
imports.get(&module_export_name_value(&specifier.orig))
{
if self.private && specifier.exported.is_none() {
return None;
}
let name = module_export_name_value(
specifier.exported.as_ref().unwrap_or(&specifier.orig),
);
Some(node::Reexport {
src: import.src.clone(),
kind: match &import.kind {
ImportKind::Named(orig, maybe_export) => {
ReexportKind::Named(
maybe_export
.clone()
.unwrap_or_else(|| orig.clone()),
Some(name),
)
}
ImportKind::Namespace(_) => {
ReexportKind::Namespace(name)
}
},
})
} else {
None
}
} else {
None
}
})
.collect()
}
}
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
}
fn get_symbols_for_module_body(
&self,
parsed_source: &ParsedSource,
module_body: &[deno_ast::swc::ast::ModuleItem],
) -> HashMap<String, DocNode> {
let mut symbols = HashMap::new();
for node in module_body.iter() {
let doc_nodes = match node {
ModuleItem::Stmt(Stmt::Decl(decl)) => self.get_doc_node_for_decl(
parsed_source,
decl,
symbols.values().collect(),
),
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
Some(super::module::get_doc_node_for_export_decl(
self,
parsed_source,
export_decl,
symbols.values().collect(),
))
}
_ => None,
};
if let Some(doc_nodes) = doc_nodes {
for doc_node in doc_nodes {
symbols.insert(doc_node.name.clone(), doc_node);
}
}
}
symbols
}
pub fn get_doc_nodes_for_module_body(
&self,
parsed_source: &ParsedSource,
module_body: Vec<deno_ast::swc::ast::ModuleItem>,
) -> Vec<DocNode> {
let symbols = self.get_symbols_for_module_body(parsed_source, &module_body);
let mut doc_entries: Vec<DocNode> = Vec::new();
let mut ambient_entries: Vec<DocNode> = Vec::new();
let mut is_ambient = true;
if let Some((js_doc, range)) = module_js_doc_for_source(parsed_source) {
let doc_node =
DocNode::module_doc(get_location(parsed_source, range.start), js_doc);
doc_entries.push(doc_node);
}
for node in module_body.iter() {
match node {
ModuleItem::Stmt(stmt) => {
if let Stmt::Decl(decl) = stmt {
if let Some(doc_nodes) = self.get_doc_node_for_decl(
parsed_source,
decl,
doc_entries.iter().collect(),
) {
let is_declared = self.get_declare_for_decl(decl);
for mut doc_node in doc_nodes {
if self.private {
doc_node.declaration_kind = if is_declared {
DeclarationKind::Declare
} else {
DeclarationKind::Private
};
doc_entries.push(doc_node);
} else if is_declared {
doc_node.declaration_kind = DeclarationKind::Declare;
ambient_entries.push(doc_node)
}
}
}
}
}
ModuleItem::ModuleDecl(module_decl) => {
is_ambient = false;
doc_entries.extend(self.get_doc_nodes_for_module_exports(
parsed_source,
module_decl,
symbols.values().collect(),
));
match module_decl {
ModuleDecl::ExportNamed(export_named) => {
for specifier in &export_named.specifiers {
match specifier {
ExportSpecifier::Named(named_specifier) => {
let symbol =
module_export_name_value(&named_specifier.orig);
if let Some(doc_node) = symbols.get(&symbol) {
let mut doc_node = doc_node.clone();
if let Some(exported) = &named_specifier.exported {
doc_node.name = module_export_name_value(exported)
}
doc_node.declaration_kind = DeclarationKind::Export;
doc_entries.push(doc_node)
}
}
ExportSpecifier::Default(_default_specifier) => {}
ExportSpecifier::Namespace(_namespace_specifier) => {}
}
}
}
ModuleDecl::ExportDefaultExpr(export_expr) => {
if let Expr::Ident(ident) = export_expr.expr.as_ref() {
if let Some(doc_node) = symbols.get(&ident.sym.to_string()) {
doc_entries.push(DocNode {
name: String::from("default"),
declaration_kind: DeclarationKind::Export,
..doc_node.clone()
});
}
} else {
let js_doc =
js_doc_for_range(parsed_source, &export_expr.range());
let location = get_location(parsed_source, export_expr.start());
doc_entries.push(DocNode::variable(
String::from("default"),
location,
DeclarationKind::Export,
js_doc,
super::variable::VariableDef {
kind: deno_ast::swc::ast::VarDeclKind::Var,
ts_type: super::ts_type::infer_ts_type_from_expr(
parsed_source,
export_expr.expr.as_ref(),
true,
),
},
));
}
}
_ => {}
}
}
}
}
if is_ambient {
doc_entries.extend(ambient_entries);
}
doc_entries
}
fn get_declare_for_decl(&self, decl: &Decl) -> bool {
match decl {
Decl::Class(class_decl) => class_decl.declare,
Decl::Fn(fn_decl) => fn_decl.declare,
Decl::TsEnum(ts_enum_decl) => ts_enum_decl.declare,
Decl::TsInterface(ts_interface_decl) => ts_interface_decl.declare,
Decl::TsModule(ts_module_decl) => ts_module_decl.declare,
Decl::TsTypeAlias(ts_type_alias_decl) => ts_type_alias_decl.declare,
Decl::Var(var_decl) => var_decl.declare,
}
}
}
fn parse_json_module(
specifier: &ModuleSpecifier,
value: &serde_json::Value,
) -> DocNode {
DocNode {
kind: DocNodeKind::Variable,
name: "default".to_string(),
location: Location {
filename: specifier.to_string(),
col: 0,
line: 1,
},
declaration_kind: DeclarationKind::Export,
variable_def: Some(VariableDef {
kind: VarDeclKind::Var,
ts_type: Some(parse_json_module_type(value)),
}),
..Default::default()
}
}
fn parse_json_module_type(value: &serde_json::Value) -> TsTypeDef {
match value {
serde_json::Value::Null => TsTypeDef::keyword("null"),
serde_json::Value::Bool(value) => TsTypeDef::bool_value(*value),
serde_json::Value::String(value) => {
TsTypeDef::string_value(value.to_string())
}
serde_json::Value::Number(value) => match value.as_f64() {
Some(value) => TsTypeDef::number_value(value),
None => TsTypeDef::keyword("number"),
},
serde_json::Value::Array(_) => TsTypeDef {
repr: "unknown[]".to_string(),
kind: Some(TsTypeDefKind::Array),
array: Some(Box::new(TsTypeDef::keyword("unknown"))),
..Default::default()
},
serde_json::Value::Object(obj) => TsTypeDef {
repr: "".to_string(),
kind: Some(TsTypeDefKind::TypeLiteral),
type_literal: Some(TsTypeLiteralDef {
properties: obj
.iter()
.map(|(key, value)| LiteralPropertyDef {
name: key.to_string(),
ts_type: Some(parse_json_module_type(value)),
params: Vec::new(),
readonly: false,
computed: false,
optional: false,
type_params: Vec::new(),
})
.collect(),
..Default::default()
}),
..Default::default()
},
}
}