diff --git a/src/lib.rs b/src/lib.rs index 4b86c7d..5e02c57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,6 +110,7 @@ pub fn build_dependency_graph( Box::new(PackageDepsParser), Box::new(types::index::IndexParser), Box::new(types::js::JsParser), + Box::new(types::mdx::MdxParser), Box::new(types::html::HtmlParser), ]; let workers = workers.unwrap_or_else(|| num_cpus::get()); diff --git a/src/types/mdx.rs b/src/types/mdx.rs new file mode 100644 index 0000000..57816a5 --- /dev/null +++ b/src/types/mdx.rs @@ -0,0 +1,133 @@ +use regex::Regex; +use std::path::Path; +use vfs::VfsPath; + +use crate::types::js::{ + JS_EXTENSIONS, is_node_builtin, resolve_alias_import, resolve_relative_import, +}; +use crate::types::{Context, Edge, Parser}; +use crate::{EdgeType, LogLevel, Node, NodeKind}; + +pub struct MdxParser; + +impl Parser for MdxParser { + fn name(&self) -> &'static str { + "mdx" + } + + fn can_parse(&self, path: &VfsPath) -> bool { + Path::new(path.as_str()) + .extension() + .and_then(|s| s.to_str()) + == Some("mdx") + } + + fn parse(&self, path: &VfsPath, ctx: &Context) -> anyhow::Result> { + let src = match path.read_to_string() { + Ok(s) => s, + Err(e) => { + ctx.logger.log( + LogLevel::Error, + &format!("failed to read {}: {e}", path.as_str()), + ); + return Ok(Vec::new()); + } + }; + let root_str = ctx.root.as_str().trim_end_matches('/'); + let rel = path + .as_str() + .strip_prefix(root_str) + .unwrap_or(path.as_str()) + .trim_start_matches('/'); + let from_node = Node { + name: rel.to_string(), + kind: NodeKind::File, + }; + let mut edges = Vec::new(); + let re = Regex::new(r#"^\s*import\s+(?:[^'\"]*?from\s+)?['\"]([^'\"]+)['\"]"#).unwrap(); + let dir = path.parent(); + for cap in re.captures_iter(&src) { + let spec = cap[1].to_string(); + let (target_str, kind) = if spec.starts_with('.') { + if let Some(target) = resolve_relative_import(&dir, &spec) { + let rel = target + .as_str() + .strip_prefix(root_str) + .unwrap_or(target.as_str()) + .trim_start_matches('/') + .to_string(); + let ext = Path::new(target.as_str()) + .extension() + .and_then(|s| s.to_str()) + .unwrap_or(""); + let kind = if JS_EXTENSIONS.contains(&ext) { + NodeKind::File + } else { + NodeKind::Asset + }; + (rel, kind) + } else { + continue; + } + } else if let Some(target) = resolve_alias_import(ctx.aliases, &spec) { + let rel = target + .as_str() + .strip_prefix(root_str) + .unwrap_or(target.as_str()) + .trim_start_matches('/') + .to_string(); + let ext = Path::new(target.as_str()) + .extension() + .and_then(|s| s.to_str()) + .unwrap_or(""); + let kind = if JS_EXTENSIONS.contains(&ext) { + NodeKind::File + } else { + NodeKind::Asset + }; + (rel, kind) + } else if is_node_builtin(&spec) { + (spec.clone(), NodeKind::Builtin) + } else { + (spec.clone(), NodeKind::External) + }; + let to_node = Node { + name: target_str.clone(), + kind: kind.clone(), + }; + edges.push(Edge { + from: from_node.clone(), + to: to_node, + kind: EdgeType::Regular, + }); + } + Ok(edges) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_util::TestFS; + + #[test] + fn test_mdx_parser_basic() { + let fs = TestFS::new([ + ("index.mdx", "import Foo from './foo.js'\n\n# Hello"), + ("foo.js", ""), + ]); + let root = fs.root(); + let logger = crate::EmptyLogger; + let walk = crate::WalkBuilder::new(&root).build(); + let graph = crate::build_dependency_graph(&walk, None, &logger).unwrap(); + let mdx_idx = graph + .node_indices() + .find(|i| graph[*i].name == "index.mdx" && graph[*i].kind == NodeKind::File) + .unwrap(); + let foo_idx = graph + .node_indices() + .find(|i| graph[*i].name == "foo.js" && graph[*i].kind == NodeKind::File) + .unwrap(); + assert!(graph.find_edge(mdx_idx, foo_idx).is_some()); + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 3f3a6a9..73d111f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -2,7 +2,7 @@ use petgraph::graph::{DiGraph, NodeIndex}; use std::collections::HashMap; use vfs::VfsPath; -use crate::{Logger, Node, NodeKind, EdgeType}; +use crate::{EdgeType, Logger, Node, NodeKind}; #[derive(Debug)] pub struct GraphCtx { @@ -30,8 +30,9 @@ pub trait Parser: Send + Sync { } pub mod html; -pub mod js; pub mod index; +pub mod js; +pub mod mdx; pub mod monorepo; pub mod package_json; pub mod package_util;