Skip to main content

argyph_graph/resolve/
typescript.rs

1use camino::Utf8Path;
2
3use super::{ImportResolver, ModuleTarget};
4use crate::edge::Confidence;
5
6pub struct TypeScriptResolver;
7
8impl ImportResolver for TypeScriptResolver {
9    fn resolve_import(
10        &self,
11        source_file: &Utf8Path,
12        module_path: &[String],
13        _raw: &str,
14    ) -> Option<ModuleTarget> {
15        if module_path.is_empty() {
16            return None;
17        }
18
19        let first = &module_path[0];
20        if first != "." && first != ".." {
21            return None;
22        }
23
24        let source_dir = source_file.parent()?;
25        let joined = module_path.join("/");
26        let resolved = source_dir.join(&joined);
27
28        let candidate = format!("{resolved}.ts");
29        Some(ModuleTarget {
30            file_path: super::normalize_path(&candidate),
31            confidence: Confidence::Heuristic,
32        })
33    }
34}
35
36#[cfg(test)]
37#[allow(clippy::unwrap_used, clippy::expect_used)]
38mod tests {
39    use super::*;
40    use camino::Utf8Path;
41
42    #[test]
43    fn resolve_relative_import() {
44        let resolver = TypeScriptResolver;
45        let path: Vec<String> = [".", "math"].iter().map(|s| s.to_string()).collect();
46        let tgt = resolver
47            .resolve_import(
48                Utf8Path::new("src/components/App.ts"),
49                &path,
50                "import { add } from './math'",
51            )
52            .expect("should resolve ./math");
53
54        assert!(
55            tgt.file_path.contains("components/math"),
56            "got {}",
57            tgt.file_path
58        );
59    }
60
61    #[test]
62    fn resolve_parent_import() {
63        let resolver = TypeScriptResolver;
64        let path: Vec<String> = ["..", "types"].iter().map(|s| s.to_string()).collect();
65        let tgt = resolver
66            .resolve_import(
67                Utf8Path::new("src/deep/nested/file.ts"),
68                &path,
69                "import { User } from '../types'",
70            )
71            .expect("should resolve ../types");
72
73        assert!(
74            tgt.file_path.contains("src/deep/types"),
75            "got {}",
76            tgt.file_path
77        );
78    }
79
80    #[test]
81    fn bare_specifier_returns_none() {
82        let resolver = TypeScriptResolver;
83        let path: Vec<String> = ["react"].iter().map(|s| s.to_string()).collect();
84        assert!(resolver
85            .resolve_import(
86                Utf8Path::new("src/index.ts"),
87                &path,
88                "import { useState } from 'react'",
89            )
90            .is_none());
91    }
92}