1use std::path::Path;
2
3use compact_str::CompactString;
4use tree_sitter::Tree;
5
6use crate::imports::js;
7use crate::symbols::extract_symbols_from_tree;
8use crate::{ImportKind, ImportSpec, ParserPool, RawImport, Symbol};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct FileAnalysis {
13 pub path: CompactString,
15 pub content_hash: u64,
17 pub language: Option<CompactString>,
19 pub symbols: Vec<Symbol>,
21 pub imports: Vec<RawImport>,
23 pub has_opaque_imports: bool,
25}
26
27#[derive(Clone, Copy)]
28enum ImportExtractor {
29 Query(fn(&str) -> ImportKind),
30 Custom(fn(&str, &Tree) -> Vec<RawImport>),
31}
32
33#[must_use]
39pub fn analyze_source(
40 source: &str,
41 path: &str,
42 content_hash: u64,
43 pool: &mut ParserPool<'_>,
44) -> FileAnalysis {
45 let mut analysis = FileAnalysis {
46 path: CompactString::from(path),
47 content_hash,
48 language: None,
49 symbols: Vec::new(),
50 imports: Vec::new(),
51 has_opaque_imports: false,
52 };
53
54 let Some(id) = pool.registry().for_path(Path::new(path)) else {
55 return analysis;
56 };
57 let Some(spec) = pool.registry().get(id) else {
58 return analysis;
59 };
60 analysis.language = Some(spec.name.clone());
61
62 if u32::try_from(source.len()).is_err() {
63 return analysis;
64 }
65
66 let import_extractor = spec.imports.as_ref().map(|imports| match imports {
67 ImportSpec::Query { kind_map, .. } => ImportExtractor::Query(*kind_map),
68 ImportSpec::Custom(extract) => ImportExtractor::Custom(*extract),
69 });
70
71 let tree = {
72 let Some(parser) = pool.parser(id) else {
73 return analysis;
74 };
75 let Some(tree) = parser.parse(source, None) else {
76 return analysis;
77 };
78 tree
79 };
80
81 analysis.symbols = extract_symbols_from_tree(source, &tree, id, pool);
82 match import_extractor {
83 Some(ImportExtractor::Query(kind_map)) => {
84 if let Some(query) = pool.imports_query(id) {
85 let extracted = js::extract(source, &tree, query, kind_map);
86 analysis.imports = extracted.imports;
87 analysis.has_opaque_imports = extracted.opaque_count != 0;
88 }
89 }
90 Some(ImportExtractor::Custom(extract)) => {
91 analysis.imports = extract(source, &tree);
92 }
93 None => {}
94 }
95
96 analysis
97}