amql_engine/resolver/
mod.rs1mod go;
7mod rust;
8mod typescript;
9
10pub use go::GoResolver;
11pub use rust::RustResolver;
12pub use typescript::TypeScriptResolver;
13
14use crate::error::AqlError;
15use crate::types::{AttrName, CodeElementName, RelativePath, TagName};
16use rustc_hash::FxHashMap;
17use serde::Serialize;
18use std::path::Path;
19
20#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
22#[cfg_attr(feature = "flow", derive(flowjs_rs::Flow))]
23#[cfg_attr(feature = "ts", ts(export))]
24#[cfg_attr(feature = "flow", flow(export))]
25#[derive(Debug, Clone, Serialize)]
26pub struct CodeElement {
27 pub tag: TagName,
29 pub name: CodeElementName,
31 #[cfg_attr(
33 feature = "ts",
34 ts(as = "std::collections::HashMap<AttrName, serde_json::Value>")
35 )]
36 #[cfg_attr(
37 feature = "flow",
38 flow(as = "std::collections::HashMap<AttrName, serde_json::Value>")
39 )]
40 pub attrs: FxHashMap<AttrName, serde_json::Value>,
41 pub children: Vec<CodeElement>,
43 pub source: SourceLocation,
45}
46
47#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
49#[cfg_attr(feature = "flow", derive(flowjs_rs::Flow))]
50#[cfg_attr(feature = "ts", ts(export))]
51#[cfg_attr(feature = "flow", flow(export))]
52#[derive(Debug, Clone, Serialize)]
53pub struct SourceLocation {
54 pub file: RelativePath,
56 pub line: usize,
58 pub column: usize,
60 pub end_line: Option<usize>,
62 pub end_column: Option<usize>,
64 pub start_byte: usize,
66 pub end_byte: usize,
68}
69
70pub trait CodeResolver: Send + Sync {
72 fn resolve(&self, file_path: &Path) -> Result<CodeElement, AqlError>;
74
75 fn extensions(&self) -> &[&str];
77
78 fn code_tags(&self) -> &[&str];
80}
81
82pub struct ResolverRegistry {
85 resolvers: Vec<Box<dyn CodeResolver>>,
86 by_ext: FxHashMap<String, usize>,
87}
88
89impl ResolverRegistry {
90 pub fn new() -> Self {
92 Self {
93 resolvers: Vec::new(),
94 by_ext: FxHashMap::default(),
95 }
96 }
97
98 pub fn with_defaults() -> Self {
100 let mut reg = Self::new();
101 reg.register(Box::new(RustResolver));
102 reg.register(Box::new(TypeScriptResolver));
103 reg.register(Box::new(GoResolver));
104 reg
105 }
106
107 pub fn register(&mut self, resolver: Box<dyn CodeResolver>) {
109 let idx = self.resolvers.len();
110 for ext in resolver.extensions() {
111 self.by_ext.insert(ext.to_string(), idx);
112 }
113 self.resolvers.push(resolver);
114 }
115
116 pub fn get_for_file(&self, file_path: &Path) -> Option<&dyn CodeResolver> {
118 let ext = file_path
119 .extension()
120 .map(|e| format!(".{}", e.to_string_lossy()))
121 .unwrap_or_default();
122
123 self.by_ext
124 .get(&ext)
125 .and_then(|&idx| self.resolvers.get(idx))
126 .map(|r| r.as_ref())
127 }
128
129 pub fn has_resolver_for(&self, file_path: &Path) -> bool {
131 self.get_for_file(file_path).is_some()
132 }
133
134 pub fn all_code_tags(&self) -> Vec<&str> {
136 let mut tags: Vec<&str> = self
137 .resolvers
138 .iter()
139 .flat_map(|r| r.code_tags().iter().copied())
140 .collect();
141 tags.sort_unstable();
142 tags.dedup();
143 tags
144 }
145}
146
147impl Default for ResolverRegistry {
148 fn default() -> Self {
149 Self::new()
150 }
151}