use deno_ast::ModuleItemRef;
use deno_ast::SourceRange;
use deno_ast::SourceRanged;
use deno_ast::SourceRangedForSpanned;
use deno_ast::swc::ast::ClassDecl;
use deno_ast::swc::ast::Decl;
use deno_ast::swc::ast::DefaultDecl;
use deno_ast::swc::ast::ExportDecl;
use deno_ast::swc::ast::ExportDefaultDecl;
use deno_ast::swc::ast::ExportDefaultExpr;
use deno_ast::swc::ast::FnDecl;
use deno_ast::swc::ast::Ident;
use deno_ast::swc::ast::ModuleDecl;
use deno_ast::swc::ast::TsEnumDecl;
use deno_ast::swc::ast::TsInterfaceDecl;
use deno_ast::swc::ast::TsModuleDecl;
use deno_ast::swc::ast::TsModuleName;
use deno_ast::swc::ast::TsTypeAliasDecl;
use deno_ast::swc::ast::VarDecl;
use deno_ast::swc::ast::VarDeclKind;
use deno_ast::swc::ast::VarDeclarator;
use deno_graph::Module;
use deno_graph::ModuleGraph;
use deno_graph::ModuleSpecifier;
use deno_graph::ast::EsParser;
use deno_graph::symbols::EsModuleInfo;
use deno_graph::symbols::ExpandoPropertyRef;
use deno_graph::symbols::ExportDeclRef;
use deno_graph::symbols::ModuleInfoRef;
use deno_graph::symbols::Symbol as GraphSymbol;
use deno_graph::symbols::SymbolDecl;
use deno_graph::symbols::SymbolNodeRef;
use deno_graph::symbols::UniqueSymbolId;
use indexmap::IndexMap;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashSet;
use std::error::Error;
use std::fmt;
use std::rc::Rc;
use std::sync::Arc;
use crate::Location;
use crate::diagnostics::DiagnosticsCollector;
use crate::diagnostics::DocDiagnostic;
use crate::js_doc::JsDoc;
use crate::node::DeclarationDef;
use crate::node::DeclarationKind;
use crate::node::Document;
use crate::node::Import;
use crate::node::NamespaceDef;
use crate::node::ReferenceDef;
use crate::ts_type::PropertyDef;
use crate::ts_type::TsTypeDef;
use crate::ts_type::TsTypeDefKind;
use crate::ts_type::TsTypeLiteralDef;
use crate::ts_type::infer_simple_ts_type_from_init;
use crate::util::graph::resolve_deno_graph_module;
use crate::util::swc::get_location;
use crate::util::swc::get_text_info_location;
use crate::util::swc::js_doc_for_range;
use crate::util::swc::module_export_name_value;
use crate::util::swc::module_js_doc_for_source;
use crate::util::symbol::get_module_info;
use crate::variable::VariableDef;
use crate::visibility::SymbolVisibility;
use crate::{Declaration, Symbol};
#[derive(Debug)]
pub enum DocError {
Resolve(String),
#[allow(dead_code)]
Io(std::io::Error),
Parse(deno_ast::ParseDiagnostic),
}
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::ParseDiagnostic> for DocError {
fn from(error: deno_ast::ParseDiagnostic) -> DocError {
DocError::Parse(error)
}
}
pub type ParseOutput = IndexMap<ModuleSpecifier, Document>;
#[derive(Default, Clone)]
pub struct DocParserOptions {
pub diagnostics: bool,
pub private: bool,
}
pub struct DocParser<'a> {
graph: &'a ModuleGraph,
private: bool,
root_symbol: Rc<deno_graph::symbols::RootSymbol<'a>>,
visibility: SymbolVisibility,
specifiers: &'a [ModuleSpecifier],
diagnostics: Option<RefCell<DiagnosticsCollector<'a>>>,
}
impl<'a> DocParser<'a> {
pub fn new(
graph: &'a ModuleGraph,
parser: &'a dyn EsParser,
specifiers: &'a [ModuleSpecifier],
options: DocParserOptions,
) -> Result<Self, anyhow::Error> {
let root_symbol =
Rc::new(deno_graph::symbols::RootSymbol::new(graph, parser));
let visibility = SymbolVisibility::build(graph, &root_symbol)?;
let diagnostics = if options.diagnostics {
Some(RefCell::new(DiagnosticsCollector::new(root_symbol.clone())))
} else {
None
};
Ok(DocParser {
graph,
private: options.private,
root_symbol,
visibility,
specifiers,
diagnostics,
})
}
pub fn take_diagnostics(&self) -> Vec<DocDiagnostic> {
if let Some(diagnostics) = &self.diagnostics {
diagnostics.borrow_mut().take_diagnostics()
} else {
debug_assert!(
self.diagnostics.is_some(),
"diagnostics were not enabled, but they were taken"
);
Vec::new()
}
}
fn get_module_info(
&self,
specifier: &ModuleSpecifier,
) -> Result<ModuleInfoRef<'_>, DocError> {
get_module_info(&self.root_symbol, specifier)
}
pub fn parse(&self) -> Result<ParseOutput, DocError> {
let mut symbols_by_url = self
.specifiers
.iter()
.map(|specifier| {
Ok((
specifier.clone(),
self.parse_with_reexports_inner(specifier, HashSet::new())?,
))
})
.collect::<Result<IndexMap<_, _>, DocError>>()?;
let mut all_locations = symbols_by_url
.values()
.flat_map(|doc| doc.symbols.iter())
.flat_map(|symbol| {
fn walk_arc_symbols(symbol: &[Arc<Symbol>]) -> Vec<Location> {
symbol
.iter()
.flat_map(|symbol| {
symbol.declarations.iter().flat_map(|decl| {
if let Some(namespace) = decl.namespace_def() {
walk_arc_symbols(&namespace.elements)
} else {
vec![decl.location.clone()]
}
})
})
.collect()
}
symbol
.declarations
.iter()
.flat_map(|decl| {
if let Some(namespace) = decl.namespace_def() {
walk_arc_symbols(&namespace.elements)
} else {
vec![decl.location.clone()]
}
})
.collect::<Vec<_>>()
})
.collect::<HashSet<_>>();
for (specifier, document) in symbols_by_url.iter_mut() {
self.resolve_references_for_nodes(
specifier,
&mut document.symbols,
&[],
&mut all_locations,
)?;
}
for (_, document) in &symbols_by_url {
self.collect_diagnostics_for_symbols(&document.symbols);
}
Ok(symbols_by_url)
}
fn resolve_references_for_nodes(
&self,
specifier: &ModuleSpecifier,
symbols: &mut Vec<Arc<Symbol>>,
name_path: &[String],
all_locations: &mut HashSet<Location>,
) -> Result<(), DocError> {
let mut i = 0;
'outer: while i < symbols.len() {
let name = symbols[i].name.to_string();
for decl in &mut Arc::make_mut(&mut symbols[i]).declarations {
match &mut decl.def {
DeclarationDef::Namespace(namespace_def) => {
let mut new_name_path = Vec::with_capacity(name_path.len() + 1);
new_name_path.extend_from_slice(name_path);
new_name_path.push(name.clone());
self.resolve_references_for_nodes(
specifier,
&mut namespace_def.elements,
&new_name_path,
all_locations,
)?;
}
DeclarationDef::Reference(reference_def) => {
let mut new_name_path = Vec::with_capacity(name_path.len() + 1);
new_name_path.extend_from_slice(name_path);
new_name_path.push(name.clone());
if !all_locations.contains(&reference_def.target)
&& let Some(new_symbol) = self.resolve_dangling_reference(
specifier,
reference_def,
new_name_path,
false,
)?
{
if let Some(new_symbol) = new_symbol {
symbols[i] = Arc::new(new_symbol);
all_locations.extend(symbols.iter().flat_map(|symbol| {
symbol.declarations.iter().map(|decl| decl.location.clone())
}));
} else {
symbols.remove(i);
}
continue 'outer;
}
}
_ => {}
}
}
i += 1;
}
Ok(())
}
fn resolve_dangling_reference(
&self,
specifier: &ModuleSpecifier,
reference_def: &ReferenceDef,
mut name_path: Vec<String>,
star: bool,
) -> Result<Option<Option<Symbol>>, DocError> {
let module = resolve_deno_graph_module(self.graph, specifier)?;
match module {
Module::Js(_) | Module::Json(_) | Module::Wasm(_) => {
let module_info = self.get_module_info(module.specifier())?;
let exports = module_info.exports(&self.root_symbol);
if name_path.is_empty() {
return Ok(Some(None));
}
let root_name = name_path.remove(0);
let Some((mut export_name, export)) = exports
.resolved
.iter()
.find(|(name, _)| &&root_name == name)
else {
return Ok(Some(None));
};
let export = export.as_resolved_export();
let mut export_symbol = export.module.symbol(export.symbol_id).unwrap();
let mut definitions = self
.root_symbol
.go_to_definitions(export.module, export_symbol)
.collect::<Vec<_>>();
if let Some(first_def) = definitions.first() {
use deno_graph::symbols::DefinitionKind;
match first_def.kind {
DefinitionKind::ExportStar(file_dep) => {
debug_assert_eq!(definitions.len(), 1);
let specifier = self.resolve_dependency(
&file_dep.specifier,
first_def.module.specifier(),
)?;
return self.resolve_dangling_reference(
specifier,
reference_def,
name_path,
true,
);
}
DefinitionKind::Definition => {
for (i, name_path_item) in name_path.iter().enumerate() {
let Some((name, id)) = export_symbol
.exports()
.iter()
.find(|(name, _)| &name_path_item == name)
else {
let definitions = self
.root_symbol
.go_to_definitions(export.module, export_symbol)
.collect::<Vec<_>>();
return self.resolve_dangling_reference(
definitions.first().unwrap().module.specifier(),
reference_def,
if i > 1 {
name_path[i - 1..].to_vec()
} else {
let mut out = vec![root_name];
out.extend_from_slice(&name_path);
out
},
false,
);
};
export_name = name;
export_symbol = export.module.symbol(*id).unwrap();
}
definitions = self
.root_symbol
.go_to_definitions(export.module, export_symbol)
.collect::<Vec<_>>();
}
}
}
let original_range = &export_symbol.decls().first().unwrap().range;
if let Some(first_def) = definitions.first() {
use deno_graph::symbols::DefinitionKind;
match first_def.kind {
DefinitionKind::ExportStar(_) => {}
DefinitionKind::Definition => {
if (star
|| first_def.module.specifier() != module_info.specifier())
&& definitions
.iter()
.any(|def| definition_location(def) == reference_def.target)
{
let mut declarations = vec![];
for definition in &definitions {
let decl = definition.symbol_decl;
let mut decls = self.decls_for_maybe_node(
export_name,
definition.module,
definition.symbol,
decl.maybe_node(),
module_info.esm(),
first_def.module.specifier(),
Some(decl),
Some(original_range),
);
for d in &mut decls {
d.declaration_kind = DeclarationKind::Export;
}
declarations.extend(decls);
}
if !declarations.is_empty() {
return Ok(Some(Some(Symbol {
name: export_name.as_str().into(),
is_default: export_name == "default",
declarations,
})));
}
}
}
}
}
debug_assert!(false, "should not reach here");
Ok(Some(None))
}
Module::Npm(_) | Module::Node(_) | Module::External(_) => Ok(None),
}
}
fn collect_diagnostics_for_symbols(&self, nodes: &[Arc<Symbol>]) {
if let Some(diagnostics) = &self.diagnostics {
let mut diagnostics = diagnostics.borrow_mut();
diagnostics.analyze_doc_nodes(nodes);
}
}
fn parse_with_reexports_inner(
&self,
specifier: &ModuleSpecifier,
mut visited: HashSet<ModuleSpecifier>,
) -> Result<Document, DocError> {
if !visited.insert(specifier.clone()) {
return Ok(Default::default()); }
let module = resolve_deno_graph_module(self.graph, specifier)?;
match module {
Module::Js(_) | Module::Json(_) | Module::Wasm(_) => {
let module_info = self.get_module_info(module.specifier())?;
let mut document = self
.get_symbols_for_module_info(module_info)?
.unwrap_or_default();
let exports = module_info.exports(&self.root_symbol);
for (export_name, export) in exports.resolved {
let export = export.as_resolved_export();
let export_symbol = export.module.symbol(export.symbol_id).unwrap();
let definitions = self
.root_symbol
.go_to_definitions(export.module, export_symbol)
.collect::<Vec<_>>();
if let Some(first_def) = definitions.first() {
use deno_graph::symbols::DefinitionKind;
match first_def.kind {
DefinitionKind::ExportStar(file_dep) => {
debug_assert_eq!(definitions.len(), 1);
let def_location = definition_location(first_def);
let specifier = self.resolve_dependency(
&file_dep.specifier,
first_def.module.specifier(),
)?;
let doc_nodes = self
.parse_with_reexports_inner(specifier, visited.clone())?;
let js_doc = (|| {
if let ModuleInfoRef::Esm(esm) = export.module {
for item in esm.source().program_ref().body() {
if let ModuleItemRef::ModuleDecl(decl) = item {
let decl_range = decl.range();
if decl_range.contains(first_def.range())
&& let Some(js_doc) =
js_doc_for_range(esm, &decl_range)
{
if !js_doc.is_empty() {
return js_doc;
}
break;
}
}
}
}
doc_nodes.module_doc.clone()
})();
let ns_def = NamespaceDef {
elements: doc_nodes
.symbols
.into_iter()
.map(|mut node| {
let sym = Arc::make_mut(&mut node);
let decl = &mut sym.declarations[0];
let target = decl.location.clone();
decl.def =
DeclarationDef::Reference(ReferenceDef { target });
decl.location = def_location.clone();
node
})
.collect(),
};
let ns_symbol = Symbol::namespace(
export_name.into_boxed_str(),
false,
def_location,
DeclarationKind::Export,
js_doc,
ns_def,
);
document.symbols.push(Arc::new(ns_symbol));
}
DefinitionKind::Definition => {}
}
}
}
Ok(document)
}
Module::Npm(_) | Module::Node(_) | Module::External(_) => {
Ok(Default::default())
}
}
}
fn get_imports_for_module_info(
&self,
module_info: &EsModuleInfo,
) -> Result<Vec<Import>, DocError> {
let parsed_source = module_info.source();
let referrer = module_info.specifier();
let mut imports = vec![];
for node in parsed_source.program_ref().body() {
if let ModuleItemRef::ModuleDecl(ModuleDecl::Import(import_decl)) = node
&& let Some(js_doc) =
js_doc_for_range(module_info, &import_decl.range())
{
for specifier in &import_decl.specifiers {
use deno_ast::swc::ast::ImportSpecifier::*;
let (imported_name, original_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,
),
Default(default_specifier) => (
default_specifier.local.sym.to_string(),
Some("default".to_string()),
&import_decl.src.value,
),
Namespace(namespace_specifier) => (
namespace_specifier.local.sym.to_string(),
None,
&import_decl.src.value,
),
};
let resolved_specifier =
self.resolve_dependency(&src.to_string_lossy(), referrer)?;
imports.push(Import {
imported_name: imported_name.into_boxed_str(),
js_doc: js_doc.clone(),
src: resolved_specifier.to_string(),
original_name,
});
}
}
}
Ok(imports)
}
fn get_doc_for_expando_property(
&self,
module_info: &EsModuleInfo,
expando_property: ExpandoPropertyRef,
) -> Option<Declaration> {
let location =
get_location(module_info, expando_property.prop_name_range().start());
let js_doc =
js_doc_for_range(module_info, &expando_property.inner().range())?;
let init = expando_property.assignment();
Some(decl_from_expr(module_info, init, location, js_doc))
}
fn get_doc_for_var_declarator_ident(
&self,
module_info: &EsModuleInfo,
var_decl: &VarDecl,
var_declarator: &VarDeclarator,
ident: &Ident,
full_range: &SourceRange,
) -> Option<Declaration> {
let full_range = if ident.start() != var_declarator.start() {
Cow::Owned(ident.range())
} else {
Cow::Borrowed(full_range)
};
let js_doc = js_doc_for_range(module_info, &full_range)?;
if let Some(init) = &var_declarator.init
&& matches!(
&**init,
deno_ast::swc::ast::Expr::Class(_)
| deno_ast::swc::ast::Expr::Fn(_)
| deno_ast::swc::ast::Expr::Arrow(_)
)
{
let location = get_location(module_info, ident.start());
return Some(decl_from_expr(module_info, init, location, js_doc));
}
super::variable::get_docs_for_var_declarator(
module_info,
var_decl,
var_declarator,
)
.into_iter()
.find(|(name, _)| name.as_str() == &*ident.sym)
.map(|(_name, var_def)| {
let location = get_location(module_info, ident.start());
Declaration::variable(location, DeclarationKind::Declare, js_doc, var_def)
})
}
fn get_doc_for_class_decl(
&self,
module_info: &EsModuleInfo,
class_decl: &ClassDecl,
full_range: &SourceRange,
) -> Option<Declaration> {
let jsdoc_range = match class_decl.class.decorators.first() {
Some(decorator) if decorator.start() < full_range.start => {
Cow::Owned(SourceRange::new(decorator.start(), full_range.end))
}
_ => Cow::Borrowed(full_range),
};
let js_doc = js_doc_for_range(module_info, &jsdoc_range)?;
let (class_def, _) =
super::class::get_doc_for_class_decl(module_info, class_decl);
let location = get_location(module_info, full_range.start);
Some(Declaration::class(
location,
DeclarationKind::Declare,
js_doc,
class_def,
))
}
fn get_decl_for_fn_decl(
&self,
module_info: &EsModuleInfo,
fn_decl: &FnDecl,
full_range: &SourceRange,
) -> Option<Declaration> {
let js_doc = js_doc_for_range(module_info, full_range)?;
let function_def =
super::function::get_doc_for_fn_decl(module_info, fn_decl);
let location = get_location(module_info, full_range.start);
Some(Declaration::function(
location,
DeclarationKind::Declare,
js_doc,
function_def,
))
}
fn get_decl_for_interface_decl(
&self,
module_info: &EsModuleInfo,
ts_interface_decl: &TsInterfaceDecl,
full_range: &SourceRange,
) -> Option<Declaration> {
let js_doc = js_doc_for_range(module_info, full_range)?;
let interface_def = super::interface::get_doc_for_ts_interface_decl(
module_info,
ts_interface_decl,
None,
);
let location = get_location(module_info, full_range.start);
Some(Declaration::interface(
location,
DeclarationKind::Declare,
js_doc,
interface_def,
))
}
fn get_decl_for_type_alias(
&self,
module_info: &EsModuleInfo,
ts_type_alias: &TsTypeAliasDecl,
full_range: &SourceRange,
) -> Option<Declaration> {
let js_doc = js_doc_for_range(module_info, full_range)?;
let type_alias_def = super::type_alias::get_doc_for_ts_type_alias_decl(
module_info,
ts_type_alias,
);
let location = get_location(module_info, full_range.start);
Some(Declaration::type_alias(
location,
DeclarationKind::Declare,
js_doc,
type_alias_def,
))
}
fn get_decl_for_enum(
&self,
module_info: &EsModuleInfo,
ts_enum: &TsEnumDecl,
full_range: &SourceRange,
) -> Option<Declaration> {
let js_doc = js_doc_for_range(module_info, full_range)?;
let enum_def =
super::r#enum::get_doc_for_ts_enum_decl(module_info, ts_enum);
let location = get_location(module_info, full_range.start);
Some(Declaration::r#enum(
location,
DeclarationKind::Declare,
js_doc,
enum_def,
))
}
fn get_decl_for_ts_namespace(
&self,
module_info: &EsModuleInfo,
ns_symbol: &GraphSymbol,
ts_module: &TsModuleDecl,
full_range: &SourceRange,
) -> Option<Declaration> {
fn symbol_in_ancestors(
id: UniqueSymbolId,
parent: &GraphSymbol,
module_info: &EsModuleInfo,
) -> bool {
let mut current_symbol = Some(parent);
while let Some(symbol) = current_symbol.take() {
if symbol.unique_id() == id {
return true;
}
current_symbol = symbol.parent_id().and_then(|s| module_info.symbol(s));
}
false
}
let first_ns_decl = ns_symbol
.decls()
.iter()
.filter_map(|d| {
d.maybe_node().and_then(|n| match n {
SymbolNodeRef::ExportDecl(
ExportDecl {
decl: Decl::TsModule(n),
..
},
_,
) => Some(&**n),
SymbolNodeRef::TsNamespace(n) => Some(n),
_ => None,
})
})
.next()
.unwrap();
if first_ns_decl.range() != ts_module.range() {
return None; }
let _namespace_name = match &ts_module.id {
TsModuleName::Ident(ident) => ident.sym.to_string(),
TsModuleName::Str(str_) => str_.value.to_string_lossy().into_owned(),
};
let mut elements = Vec::new();
let mut handled_symbols = HashSet::new();
for (export_name, export_symbol_id) in ns_symbol.exports() {
handled_symbols.insert(UniqueSymbolId::new(
module_info.module_id(),
*export_symbol_id,
));
let export_symbol = module_info.symbol(*export_symbol_id).unwrap();
let definitions = self
.root_symbol
.go_to_definitions(ModuleInfoRef::Esm(module_info), export_symbol);
let original_range = &export_symbol.decls().first().unwrap().range;
let mut declarations = vec![];
for definition in definitions {
let definition_id = definition.symbol.unique_id();
if symbol_in_ancestors(definition_id, ns_symbol, module_info) {
continue;
}
handled_symbols.insert(definition_id);
let mut decls = self.decls_for_maybe_node(
export_name,
definition.module,
definition.symbol,
definition.symbol_decl.maybe_node(),
Some(module_info),
module_info.specifier(),
Some(definition.symbol_decl),
Some(original_range),
);
for decl in &mut decls {
decl.declaration_kind = DeclarationKind::Export;
}
declarations.extend(decls);
}
if !declarations.is_empty() {
elements.push(Arc::new(Symbol {
name: export_name.as_str().into(),
is_default: export_name == "default",
declarations,
}));
}
}
let is_ambient = elements.is_empty() && !module_has_import(module_info);
for child_id in ns_symbol.child_ids() {
let unique_id = UniqueSymbolId::new(module_info.module_id(), child_id);
if !handled_symbols.insert(unique_id) {
continue; }
if is_ambient
|| self.private
|| self.visibility.has_non_exported_public(&unique_id)
{
let child_symbol = module_info.symbol(child_id).unwrap();
if let Some(symbol) = self.get_private_symbol_for_ast_symbol(
ModuleInfoRef::Esm(module_info),
child_symbol,
) {
elements.push(Arc::new(symbol));
}
}
}
let js_doc = js_doc_for_range(module_info, full_range)?;
let location = get_location(module_info, full_range.start);
Some(Declaration::namespace(
location,
DeclarationKind::Declare,
js_doc,
NamespaceDef { elements },
))
}
fn get_private_symbol_for_ast_symbol(
&self,
module_info: ModuleInfoRef,
child_symbol: &GraphSymbol,
) -> Option<Symbol> {
debug_assert!(
self
.visibility
.get_root_exported_deps(&child_symbol.unique_id())
.is_none()
);
let mut declarations = Vec::with_capacity(child_symbol.decls().len());
let mut name: Option<Box<str>> = None;
for decl in child_symbol.decls() {
let mut maybe_decls = self.decls_for_maybe_node(
"",
module_info,
child_symbol,
decl.maybe_node(),
module_info.esm(),
module_info.specifier(),
None,
None,
);
let is_declared = decl
.maybe_node()
.map(|node| self.get_declare_for_symbol_node(node))
.unwrap_or(false);
let declaration_kind = if is_declared {
DeclarationKind::Declare
} else {
DeclarationKind::Private
};
for doc in &mut maybe_decls {
doc.declaration_kind = declaration_kind;
}
if name.is_none() {
name = child_symbol
.decls()
.first()
.and_then(|d| d.maybe_node())
.and_then(|n| {
use deno_graph::symbols::SymbolNodeRef;
match n {
SymbolNodeRef::ClassDecl(n) => Some(n.ident.sym.to_string()),
SymbolNodeRef::FnDecl(n) => Some(n.ident.sym.to_string()),
SymbolNodeRef::TsEnum(n) => Some(n.id.sym.to_string()),
SymbolNodeRef::TsInterface(n) => Some(n.id.sym.to_string()),
SymbolNodeRef::TsTypeAlias(n) => Some(n.id.sym.to_string()),
SymbolNodeRef::TsNamespace(n) => match &n.id {
TsModuleName::Ident(ident) => Some(ident.sym.to_string()),
TsModuleName::Str(s) => {
Some(s.value.to_string_lossy().into_owned())
}
},
SymbolNodeRef::Var(_, _, ident) => Some(ident.sym.to_string()),
SymbolNodeRef::ExportDefaultDecl(_) => {
Some("default".to_string())
}
SymbolNodeRef::ExportDefaultExpr(_) => {
Some("default".to_string())
}
_ => None,
}
})
.map(|s| s.into_boxed_str());
}
declarations.extend(maybe_decls);
}
if declarations.is_empty() {
return None;
}
let name = name.unwrap_or_else(|| "".into());
let is_default = &*name == "default";
Some(Symbol {
name,
is_default,
declarations,
})
}
fn get_doc_for_export_default_decl(
&self,
module_info: &EsModuleInfo,
export_default_decl: &ExportDefaultDecl,
) -> Option<Declaration> {
let js_doc = js_doc_for_range(module_info, &export_default_decl.range())?;
let location = get_location(module_info, export_default_decl.start());
let doc_node = match &export_default_decl.decl {
DefaultDecl::Class(class_expr) => {
let default_name = class_expr
.ident
.as_ref()
.map(|ident| ident.sym.to_string().into_boxed_str());
let (class_def, decorator_js_doc) = crate::class::class_to_class_def(
module_info,
&class_expr.class,
default_name,
);
let js_doc = if js_doc.is_empty() {
decorator_js_doc
} else {
js_doc
};
Declaration::class(location, DeclarationKind::Export, js_doc, class_def)
}
DefaultDecl::Fn(fn_expr) => {
let default_name =
fn_expr.ident.as_ref().map(|ident| ident.sym.to_string());
let function_def = crate::function::function_to_function_def(
module_info,
&fn_expr.function,
default_name,
);
Declaration::function(
location,
DeclarationKind::Export,
js_doc,
function_def,
)
}
DefaultDecl::TsInterfaceDecl(interface_decl) => {
let default_name = interface_decl.id.sym.to_string();
let interface_def = crate::interface::get_doc_for_ts_interface_decl(
module_info,
interface_decl,
Some(default_name),
);
Declaration::interface(
location,
DeclarationKind::Export,
js_doc,
interface_def,
)
}
};
Some(doc_node)
}
fn get_decl_for_export_default_expr(
&self,
module_info: &EsModuleInfo,
export_expr: &ExportDefaultExpr,
) -> Option<Declaration> {
let js_doc = js_doc_for_range(module_info, &export_expr.range())?;
let location = get_location(module_info, export_expr.start());
if let deno_ast::swc::ast::Expr::Arrow(arrow_expr) = &*export_expr.expr {
let function_def =
crate::function::arrow_to_function_def(module_info, arrow_expr);
Some(Declaration::function(
location,
DeclarationKind::Export,
js_doc,
function_def,
))
} else {
Some(Declaration::variable(
location,
DeclarationKind::Export,
js_doc,
VariableDef {
kind: VarDeclKind::Var,
ts_type: super::ts_type::infer_ts_type_from_expr(
module_info,
export_expr.expr.as_ref(),
true,
),
},
))
}
}
fn get_decl_for_reference(
&self,
reference_module_info: &EsModuleInfo,
parsed_module_info: &EsModuleInfo,
reference_range: &SourceRange,
target_range: &SourceRange,
) -> Option<Declaration> {
if let Some(js_doc) =
js_doc_for_range(reference_module_info, &reference_range.range())
{
let location =
get_location(reference_module_info, reference_range.start());
let target = get_location(parsed_module_info, target_range.start());
Some(Declaration::reference(
location,
js_doc,
ReferenceDef { target },
))
} else {
None
}
}
fn get_symbols_for_module_info(
&self,
module_info: ModuleInfoRef,
) -> Result<Option<Document>, DocError> {
match module_info {
ModuleInfoRef::Json(module) => Ok(parse_json_module_symbol(
module.specifier(),
module.text_info().text_str(),
)),
ModuleInfoRef::Esm(module_info) => {
if let Some(mut document) =
self.get_document_for_module_info_body(module_info)
{
document.imports = self.get_imports_for_module_info(module_info)?;
Ok(Some(document))
} else {
Ok(None)
}
}
}
}
fn get_document_for_module_info_body(
&self,
module_info: &EsModuleInfo,
) -> Option<Document> {
let mut symbols = Vec::new();
let module_doc =
if let Some(module_js_doc) = module_js_doc_for_source(module_info) {
if let Some((js_doc, _range)) = module_js_doc {
js_doc
} else {
return None;
}
} else {
Default::default()
};
let mut handled_symbols = HashSet::new();
let exports = module_info.exports(&self.root_symbol);
for (export_name, export) in &exports.resolved {
let export = export.as_resolved_export();
handled_symbols.insert(UniqueSymbolId::new(
export.module.module_id(),
export.symbol_id,
));
let export_symbol = export.module.symbol(export.symbol_id).unwrap();
let definitions = self
.root_symbol
.go_to_definitions(export.module, export_symbol);
let original_range = &export_symbol.decls().first().unwrap().range;
let mut declarations = vec![];
for definition in definitions {
handled_symbols.insert(definition.symbol.unique_id());
let maybe_docs = self.decls_for_maybe_node(
export_name,
definition.module,
definition.symbol,
definition.symbol_decl.maybe_node(),
export.module.esm(),
export.module.specifier(),
Some(definition.symbol_decl),
Some(original_range),
);
for mut decl in maybe_docs {
decl.declaration_kind = DeclarationKind::Export;
declarations.push(decl);
}
}
if !declarations.is_empty() {
symbols.push(Arc::new(Symbol {
name: export_name.as_str().into(),
is_default: export_name == "default",
declarations,
}));
}
}
let is_ambient =
exports.resolved.is_empty() && !module_has_import(module_info);
for child_id in module_info.module_symbol().child_ids() {
let unique_id = UniqueSymbolId::new(module_info.module_id(), child_id);
if !handled_symbols.insert(unique_id) {
continue; }
if is_ambient
|| self.private
|| self.visibility.has_non_exported_public(&unique_id)
{
let child_symbol = module_info.symbol(child_id).unwrap();
if let Some(symbol) = self.get_private_symbol_for_ast_symbol(
ModuleInfoRef::Esm(module_info),
child_symbol,
) {
symbols.push(Arc::new(symbol));
}
}
}
Some(Document {
module_doc,
imports: vec![],
symbols,
})
}
#[allow(clippy::too_many_arguments)]
fn decls_for_maybe_node(
&self,
export_name: &str,
module_info: ModuleInfoRef,
symbol: &GraphSymbol,
maybe_node: Option<SymbolNodeRef<'_>>,
original_module_source: Option<&EsModuleInfo>,
original_specifier: &ModuleSpecifier,
decl: Option<&SymbolDecl>,
original_range: Option<&SourceRange>,
) -> Vec<Declaration> {
let mut decls = Vec::with_capacity(2);
match module_info {
ModuleInfoRef::Json(module_info) => {
if let Some(document) = parse_json_module_symbol(
module_info.specifier(),
module_info.text_info().text_str(),
) {
for symbol in document.symbols {
decls.extend(symbol.declarations.clone());
}
}
return decls;
}
ModuleInfoRef::Esm(module_info) => {
let maybe_doc = if let Some(node) = maybe_node {
if module_info.specifier() == original_specifier {
self.get_decl_for_symbol_node_ref(module_info, symbol, node)
} else if let Some(decl) = decl {
self.get_decl_for_reference(
original_module_source.unwrap(),
module_info,
original_range.unwrap(),
&decl.range,
)
} else {
None
}
} else {
None
};
if let Some(doc) = maybe_doc {
decls.push(doc);
self.check_private_type_in_public_diagnostic(
ModuleInfoRef::Esm(module_info),
symbol,
);
if let Some(node) = maybe_node
&& node.is_function()
{
if let Some(expando_namespace) = self
.maybe_expando_property_namespace_doc(
export_name,
&decls[0],
ModuleInfoRef::Esm(module_info),
symbol,
)
{
decls.push(expando_namespace);
}
}
}
}
}
decls
}
fn maybe_expando_property_namespace_doc(
&self,
func_name: &str,
_func_doc: &Declaration,
module_info: ModuleInfoRef,
symbol: &GraphSymbol,
) -> Option<Declaration> {
let expando_properties = symbol.exports().iter().flat_map(|(name, id)| {
let symbol = module_info.symbol(*id).unwrap();
symbol
.decls()
.iter()
.filter_map(move |n| match n.maybe_node() {
Some(SymbolNodeRef::ExpandoProperty(n)) => Some((name, n)),
_ => None,
})
});
let elements = expando_properties
.flat_map(|(name, n)| {
self
.decls_for_maybe_node(
name,
module_info,
symbol,
Some(SymbolNodeRef::ExpandoProperty(n)),
module_info.esm(),
module_info.specifier(),
None,
None,
)
.into_iter()
.map(|mut decl| {
decl.declaration_kind = DeclarationKind::Declare;
Arc::new(Symbol {
name: name.as_str().into(),
is_default: false,
declarations: vec![decl],
})
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
if elements.is_empty() {
return None;
}
Some(Declaration::namespace(
elements[0].declarations[0].location.clone(),
DeclarationKind::Declare,
JsDoc {
doc: Some(
format!("Additional properties on the `{}` function.", func_name)
.into_boxed_str(),
),
tags: Box::new([]),
},
NamespaceDef { elements },
))
}
fn check_private_type_in_public_diagnostic(
&self,
doc_module_info: ModuleInfoRef<'_>,
doc_symbol: &GraphSymbol,
) {
let Some(diagnostics) = &self.diagnostics else {
return;
};
if doc_module_info.specifier().scheme() != "file" {
return; }
let doc_symbol_id = doc_symbol.unique_id();
let Some(deps_by_member) =
self.visibility.get_root_exported_deps(&doc_symbol_id)
else {
return;
};
if deps_by_member.is_empty() {
return; }
let mut diagnostics = diagnostics.borrow_mut();
for decl_with_deps in deps_by_member.iter() {
if decl_with_deps.had_ignorable_tag {
continue; }
let decl_symbol = doc_module_info
.symbol(decl_with_deps.symbol_id.symbol_id)
.unwrap();
let Some(decl_name) =
doc_module_info.fully_qualified_symbol_name(decl_symbol)
else {
continue;
};
for dep in &decl_with_deps.deps {
let dep_module =
self.root_symbol.module_from_id(dep.module_id).unwrap();
let dep_symbol = dep_module.symbol(dep.symbol_id).unwrap();
diagnostics.add_private_type_in_public(
doc_module_info,
&decl_name,
decl_with_deps.decl_range,
doc_symbol_id,
dep_module,
dep_symbol,
);
}
}
}
fn get_decl_for_symbol_node_ref(
&self,
module_info: &EsModuleInfo,
symbol: &GraphSymbol,
node: SymbolNodeRef<'_>,
) -> Option<Declaration> {
match node {
SymbolNodeRef::ClassDecl(n) => {
self.get_doc_for_class_decl(module_info, n, &n.class.range())
}
SymbolNodeRef::ExpandoProperty(n) => {
self.get_doc_for_expando_property(module_info, n)
}
SymbolNodeRef::ExportDefaultDecl(n) => {
self.get_doc_for_export_default_decl(module_info, n)
}
SymbolNodeRef::ExportDefaultExpr(n) => {
self.get_decl_for_export_default_expr(module_info, n)
}
SymbolNodeRef::FnDecl(n) => {
self.get_decl_for_fn_decl(module_info, n, &n.function.range())
}
SymbolNodeRef::TsEnum(n) => {
self.get_decl_for_enum(module_info, n, &n.range())
}
SymbolNodeRef::TsInterface(n) => {
self.get_decl_for_interface_decl(module_info, n, &n.range())
}
SymbolNodeRef::TsNamespace(n) => {
self.get_decl_for_ts_namespace(module_info, symbol, n, &n.range())
}
SymbolNodeRef::TsTypeAlias(n) => {
self.get_decl_for_type_alias(module_info, n, &n.range())
}
SymbolNodeRef::Var(parent_decl, n, ident) => self
.get_doc_for_var_declarator_ident(
module_info,
parent_decl,
n,
ident,
&parent_decl.range(),
),
SymbolNodeRef::UsingVar(_, _, _) => {
None
}
SymbolNodeRef::ExportDecl(export_decl, inner) => match inner {
ExportDeclRef::Class(n) => {
self.get_doc_for_class_decl(module_info, n, &export_decl.range())
}
ExportDeclRef::Fn(n) => {
self.get_decl_for_fn_decl(module_info, n, &export_decl.range())
}
ExportDeclRef::TsEnum(n) => {
self.get_decl_for_enum(module_info, n, &export_decl.range())
}
ExportDeclRef::TsModule(n) => self.get_decl_for_ts_namespace(
module_info,
symbol,
n,
&export_decl.range(),
),
ExportDeclRef::TsInterface(n) => {
self.get_decl_for_interface_decl(module_info, n, &export_decl.range())
}
ExportDeclRef::TsTypeAlias(n) => {
self.get_decl_for_type_alias(module_info, n, &export_decl.range())
}
ExportDeclRef::Var(var_decl, var_declarator, ident) => self
.get_doc_for_var_declarator_ident(
module_info,
var_decl,
var_declarator,
ident,
&export_decl.range(),
),
},
SymbolNodeRef::Module(_)
| SymbolNodeRef::AutoAccessor(_)
| SymbolNodeRef::ClassMethod(_)
| SymbolNodeRef::ClassParamProp(_)
| SymbolNodeRef::ClassProp(_)
| SymbolNodeRef::Constructor(_)
| SymbolNodeRef::TsIndexSignature(_)
| SymbolNodeRef::TsCallSignatureDecl(_)
| SymbolNodeRef::TsConstructSignatureDecl(_)
| SymbolNodeRef::TsPropertySignature(_)
| SymbolNodeRef::TsGetterSignature(_)
| SymbolNodeRef::TsSetterSignature(_)
| SymbolNodeRef::TsMethodSignature(_) => {
None
}
}
}
fn get_declare_for_symbol_node(&self, node: SymbolNodeRef) -> bool {
match node {
SymbolNodeRef::ClassDecl(n) => n.declare,
SymbolNodeRef::ExportDecl(n, _) => self.get_declare_for_ast_decl(&n.decl),
SymbolNodeRef::ExportDefaultDecl(_) => false,
SymbolNodeRef::ExportDefaultExpr(_) => false,
SymbolNodeRef::FnDecl(n) => n.declare,
SymbolNodeRef::TsEnum(n) => n.declare,
SymbolNodeRef::TsInterface(n) => n.declare,
SymbolNodeRef::TsNamespace(n) => n.declare,
SymbolNodeRef::TsTypeAlias(n) => n.declare,
SymbolNodeRef::Var(n, _, _) => n.declare,
SymbolNodeRef::UsingVar(_, _, _) => false,
SymbolNodeRef::Module(_)
| SymbolNodeRef::AutoAccessor(_)
| SymbolNodeRef::ClassMethod(_)
| SymbolNodeRef::ClassProp(_)
| SymbolNodeRef::ClassParamProp(_)
| SymbolNodeRef::ExpandoProperty(_)
| SymbolNodeRef::Constructor(_)
| SymbolNodeRef::TsIndexSignature(_)
| SymbolNodeRef::TsCallSignatureDecl(_)
| SymbolNodeRef::TsConstructSignatureDecl(_)
| SymbolNodeRef::TsPropertySignature(_)
| SymbolNodeRef::TsGetterSignature(_)
| SymbolNodeRef::TsSetterSignature(_)
| SymbolNodeRef::TsMethodSignature(_) => {
debug_assert!(false, "should not reach here");
false
}
}
}
fn get_declare_for_ast_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,
Decl::Using(_) => false,
}
}
fn resolve_dependency(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
) -> Result<&ModuleSpecifier, DocError> {
self
.graph
.resolve_dependency(specifier, referrer, true)
.ok_or_else(|| {
DocError::Resolve(format!(
"Failed resolving '{}' from '{}'.",
specifier, referrer
))
})
}
}
fn decl_from_expr(
module_info: &EsModuleInfo,
expr: &deno_ast::swc::ast::Expr,
location: Location,
js_doc: JsDoc,
) -> Declaration {
match expr {
deno_ast::swc::ast::Expr::Class(class_expr) => {
let def_name = class_expr
.ident
.as_ref()
.map(|id| id.sym.to_string().into_boxed_str());
let (class_def, decorator_js_doc) = crate::class::class_to_class_def(
module_info,
&class_expr.class,
def_name,
);
let js_doc = if js_doc.is_empty() {
decorator_js_doc
} else {
js_doc
};
Declaration::class(location, DeclarationKind::Declare, js_doc, class_def)
}
deno_ast::swc::ast::Expr::Fn(fn_expr) => {
let def_name = fn_expr.ident.as_ref().map(|id| id.sym.to_string());
let function_def = crate::function::function_to_function_def(
module_info,
&fn_expr.function,
def_name,
);
Declaration::function(
location,
DeclarationKind::Declare,
js_doc,
function_def,
)
}
deno_ast::swc::ast::Expr::Arrow(arrow_expr) => {
let function_def =
crate::function::arrow_to_function_def(module_info, arrow_expr);
Declaration::function(
location,
DeclarationKind::Declare,
js_doc,
function_def,
)
}
_ => {
let ts_type =
infer_simple_ts_type_from_init(module_info, Some(expr), true);
Declaration::variable(
location,
DeclarationKind::Declare,
js_doc,
VariableDef {
ts_type,
kind: VarDeclKind::Const,
},
)
}
}
}
fn parse_json_module_symbol(
specifier: &ModuleSpecifier,
source: &str,
) -> Option<Document> {
if let Ok(value) = serde_json::from_str(source) {
Some(Document {
module_doc: Default::default(),
imports: vec![],
symbols: vec![Arc::new(Symbol::variable(
"default".into(),
true,
Location {
filename: specifier.to_string().into_boxed_str(),
col: 0,
line: 0,
byte_index: 0,
},
DeclarationKind::Export,
JsDoc::default(),
VariableDef {
kind: VarDeclKind::Var,
ts_type: Some(parse_json_module_type(&value)),
},
))],
})
} else {
None
}
}
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: TsTypeDefKind::Array(Box::new(TsTypeDef::keyword("unknown"))),
},
serde_json::Value::Object(obj) => TsTypeDef {
repr: "".to_string(),
kind: TsTypeDefKind::TypeLiteral(TsTypeLiteralDef {
properties: obj
.iter()
.map(|(key, value)| PropertyDef {
name: key.to_string(),
js_doc: Default::default(),
ts_type: Some(parse_json_module_type(value)),
params: Vec::new(),
readonly: false,
computed: false,
optional: false,
type_params: Box::new([]),
location: Default::default(),
})
.collect(),
..Default::default()
}),
},
}
}
fn module_has_import(module_info: &EsModuleInfo) -> bool {
module_info.source().program_ref().body().any(|m| {
matches!(
m,
ModuleItemRef::ModuleDecl(
ModuleDecl::Import(_) | ModuleDecl::TsImportEquals(_)
)
)
})
}
fn definition_location(
definition: &deno_graph::symbols::Definition,
) -> Location {
get_text_info_location(
definition.module.specifier().as_str(),
definition.module.text_info(),
definition.range().start,
)
}