Skip to main content

thread_services/
types.rs

1// SPDX-FileCopyrightText: 2025 Knitli Inc. <knitli@knit.li>
2// SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
3// SPDX-License-Identifier: AGPL-3.0-or-later
4#![allow(dead_code)]
5//! # Service Layer Types - Abstraction Glue for Thread
6//!
7//! This module provides language-agnostic types that abstract over ast-grep functionality
8//! while preserving all its powerful capabilities. The service layer acts as glue between
9//! file-level ast-grep operations and codebase-level graph intelligence.
10//!
11//! ## Core Philosophy
12//!
13//! - **Preserve Power**: All ast-grep capabilities (Matcher, Replacer, Position) remain accessible
14//! - **Bridge Levels**: Connect file-level AST operations to codebase-level relational intelligence
15//! - **Enable Execution**: Abstract over different execution environments (rayon, cloud workers)
16//! - **Commercial Ready**: Clear boundaries for commercial extensions
17//!
18//! ## Key Types
19//!
20//! - [`ParsedDocument`] - Wraps ast-grep Root while enabling cross-file intelligence
21//! - [`CodeMatch`] - Extends NodeMatch with codebase-level context
22//! - [`ExecutionScope`] - Defines execution boundaries (file, module, codebase)
23//! - [`AnalysisContext`] - Carries execution and analysis context across service boundaries
24
25use std::any::Any;
26use std::path::PathBuf;
27use thread_utilities::RapidMap;
28
29// Conditionally import thread dependencies when available
30#[cfg(feature = "ast-grep-backend")]
31use thread_ast_engine::{Node, NodeMatch, Position, Root};
32
33#[cfg(feature = "ast-grep-backend")]
34pub use thread_ast_engine::source::Doc;
35
36#[cfg(feature = "ast-grep-backend")]
37use thread_ast_engine::pinned::PinnedNodeData;
38
39#[cfg(feature = "ast-grep-backend")]
40pub type PinnedNodeResult<D> = PinnedNodeData<D, Node<'static, D>>;
41
42#[cfg(not(feature = "ast-grep-backend"))]
43pub type PinnedNodeResult<D> = PinnedNodeData<D>;
44
45/// Re-export key ast-grep types when available
46#[cfg(feature = "ast-grep-backend")]
47pub use thread_ast_engine::{
48    Node as AstNode, NodeMatch as AstNodeMatch, Position as AstPosition, Root as AstRoot,
49};
50
51#[cfg(feature = "ast-grep-backend")]
52pub use thread_language::{SupportLang, SupportLangErr};
53
54#[cfg(not(feature = "ast-grep-backend"))]
55pub trait Doc = Clone + 'static;
56
57#[cfg(not(feature = "ast-grep-backend"))]
58#[derive(Debug, Clone)]
59pub struct Root<D>(pub std::marker::PhantomData<D>);
60
61#[cfg(not(feature = "ast-grep-backend"))]
62impl<D: Doc> Root<D> {
63    pub fn root<'a>(&'a self) -> Node<'a, D> {
64        Node(std::marker::PhantomData)
65    }
66
67    pub fn generate(&self) -> String {
68        String::new()
69    }
70}
71
72#[cfg(not(feature = "ast-grep-backend"))]
73#[derive(Debug, Clone)]
74pub struct Node<'a, D>(pub std::marker::PhantomData<&'a D>);
75
76#[cfg(not(feature = "ast-grep-backend"))]
77#[derive(Debug, Clone)]
78pub struct NodeMatch<'a, D>(pub std::marker::PhantomData<&'a D>);
79
80#[cfg(not(feature = "ast-grep-backend"))]
81impl<'a, D> std::ops::Deref for NodeMatch<'a, D> {
82    type Target = Node<'a, D>;
83    fn deref(&self) -> &Self::Target {
84        unsafe { &*(self as *const Self as *const Node<'a, D>) }
85    }
86}
87
88#[cfg(not(feature = "ast-grep-backend"))]
89#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
90pub struct Position {
91    pub row: usize,
92    pub column: usize,
93    pub index: usize,
94}
95
96#[cfg(not(feature = "ast-grep-backend"))]
97impl Position {
98    pub fn new(row: usize, column: usize, index: usize) -> Self {
99        Self { row, column, index }
100    }
101}
102
103#[cfg(not(feature = "ast-grep-backend"))]
104#[derive(Debug, Clone)]
105pub struct PinnedNodeData<D>(pub std::marker::PhantomData<D>);
106
107#[cfg(not(feature = "ast-grep-backend"))]
108impl<D: Doc> PinnedNodeData<D> {
109    pub fn new<F, T>(_root: &Root<D>, _f: F) -> Self
110    where
111        F: FnOnce(&Root<D>) -> T,
112    {
113        Self(std::marker::PhantomData)
114    }
115}
116
117#[cfg(not(feature = "ast-grep-backend"))]
118pub trait MatcherExt {}
119
120#[cfg(not(feature = "ast-grep-backend"))]
121impl<T> MatcherExt for T {}
122
123// SupportLang enum stub when not using ast-grep-backend
124#[cfg(not(feature = "ast-grep-backend"))]
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126pub enum SupportLang {
127    Bash,
128    C,
129    Cpp,
130    CSharp,
131    Css,
132    Go,
133    Elixir,
134    Haskell,
135    Html,
136    Java,
137    JavaScript,
138    Kotlin,
139    Lua,
140    Nix,
141    Php,
142    Python,
143    Ruby,
144    Rust,
145    Scala,
146    Swift,
147    TypeScript,
148    Tsx,
149    Yaml,
150}
151
152#[cfg(not(feature = "ast-grep-backend"))]
153impl SupportLang {
154    pub fn from_path(_path: &std::path::Path) -> Option<Self> {
155        // Simple stub implementation
156        Some(Self::Rust)
157    }
158}
159
160#[cfg(not(feature = "ast-grep-backend"))]
161#[derive(Debug, Clone)]
162pub struct SupportLangErr(pub String);
163
164#[cfg(not(feature = "ast-grep-backend"))]
165impl std::fmt::Display for SupportLangErr {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        write!(f, "{}", self.0)
168    }
169}
170
171#[cfg(not(feature = "ast-grep-backend"))]
172impl std::error::Error for SupportLangErr {}
173
174/// A parsed document that wraps ast-grep Root with additional codebase-level metadata.
175///
176/// This type preserves all ast-grep functionality while adding context needed for
177/// cross-file analysis and graph intelligence. It acts as the bridge between
178/// file-level AST operations and codebase-level relational analysis.
179#[derive(Debug)]
180pub struct ParsedDocument<D: Doc> {
181    /// The underlying ast-grep Root - preserves all ast-grep functionality
182    pub ast_root: Root<D>,
183
184    /// Source file path for this document
185    pub file_path: PathBuf,
186
187    /// Language of this document
188    pub language: SupportLang,
189
190    /// Content fingerprint for deduplication and change detection (blake3 hash)
191    pub content_fingerprint: recoco_utils::fingerprint::Fingerprint,
192
193    /// Codebase-level metadata (symbols, imports, exports, etc.)
194    pub metadata: DocumentMetadata,
195
196    /// Internal storage for ast-engine types (type-erased for abstraction)
197    pub(crate) internal: Box<dyn Any + Send + Sync>,
198}
199
200impl<D: Doc> ParsedDocument<D> {
201    /// Create a new ParsedDocument wrapping an ast-grep Root
202    pub fn new(
203        ast_root: Root<D>,
204        file_path: PathBuf,
205        language: SupportLang,
206        content_fingerprint: recoco_utils::fingerprint::Fingerprint,
207    ) -> Self {
208        Self {
209            ast_root,
210            file_path,
211            language,
212            content_fingerprint,
213            metadata: DocumentMetadata::default(),
214            internal: Box::new(()),
215        }
216    }
217
218    /// Get the root node - preserves ast-grep API
219    pub fn root(&self) -> Node<'_, D> {
220        self.ast_root.root()
221    }
222
223    /// Get the underlying ast-grep Root for full access to capabilities
224    pub fn ast_grep_root(&self) -> &Root<D> {
225        &self.ast_root
226    }
227
228    /// Get mutable access to ast-grep Root for replacements
229    pub fn ast_grep_root_mut(&mut self) -> &mut Root<D> {
230        &mut self.ast_root
231    }
232
233    /// Create a pinned version for cross-thread/FFI usage
234    pub fn pin_for_threading(&self) -> PinnedNodeResult<D> {
235        #[cfg(feature = "ast-grep-backend")]
236        return PinnedNodeData::new(self.ast_root.clone(), |r| r.root());
237
238        #[cfg(not(feature = "ast-grep-backend"))]
239        return PinnedNodeData::new(&self.ast_root, |_| ());
240    }
241
242    /// Generate the source code (preserves ast-grep replacement functionality)
243    pub fn generate(&self) -> String {
244        #[cfg(feature = "ast-grep-backend")]
245        {
246            use thread_ast_engine::source::Content;
247            let root_node = self.root();
248            let doc = root_node.get_doc();
249            let range = root_node.range();
250            let bytes = doc.get_source().get_range(range);
251            D::Source::encode_bytes(bytes).into_owned()
252        }
253        #[cfg(not(feature = "ast-grep-backend"))]
254        self.ast_root.generate()
255    }
256
257    /// Get document metadata for codebase-level analysis
258    pub fn metadata(&self) -> &DocumentMetadata {
259        &self.metadata
260    }
261
262    /// Get mutable document metadata
263    pub fn metadata_mut(&mut self) -> &mut DocumentMetadata {
264        &mut self.metadata
265    }
266}
267
268/// A pattern match that extends ast-grep NodeMatch with codebase-level context.
269///
270/// Preserves all NodeMatch functionality while adding cross-file relationship
271/// information needed for graph intelligence.
272#[derive(Debug)]
273pub struct CodeMatch<'tree, D: Doc> {
274    /// The underlying ast-grep NodeMatch - preserves all matching functionality
275    pub node_match: NodeMatch<'tree, D>,
276
277    /// Additional context for codebase-level analysis
278    pub context: MatchContext,
279
280    /// Cross-file relationships (calls, imports, inheritance, etc.)
281    pub relationships: Vec<CrossFileRelationship>,
282}
283
284impl<'tree, D: Doc> CodeMatch<'tree, D> {
285    /// Create a new CodeMatch wrapping an ast-grep NodeMatch
286    pub fn new(node_match: NodeMatch<'tree, D>) -> Self {
287        Self {
288            node_match,
289            context: MatchContext::default(),
290            relationships: Vec::new(),
291        }
292    }
293
294    /// Get the underlying NodeMatch for full ast-grep access
295    pub fn ast_node_match(&self) -> &NodeMatch<'tree, D> {
296        &self.node_match
297    }
298
299    /// Get the matched node (delegate to NodeMatch)
300    pub fn node(&self) -> &Node<'tree, D> {
301        &self.node_match
302    }
303
304    #[cfg(any(feature = "ast-grep-backend", feature = "matching"))]
305    /// Get captured meta-variables (delegate to NodeMatch)
306    pub fn get_env(&self) -> &thread_ast_engine::MetaVarEnv<'tree, D> {
307        self.node_match.get_env()
308    }
309
310    /// Add cross-file relationship information
311    pub fn add_relationship(&mut self, relationship: CrossFileRelationship) {
312        self.relationships.push(relationship);
313    }
314
315    /// Get all cross-file relationships
316    pub fn relationships(&self) -> &[CrossFileRelationship] {
317        &self.relationships
318    }
319}
320
321/// Metadata about a parsed document for codebase-level analysis
322#[derive(Debug, Default, Clone)]
323pub struct DocumentMetadata {
324    /// Symbols defined in this document (functions, classes, variables)
325    pub defined_symbols: RapidMap<String, SymbolInfo>,
326
327    /// Symbols imported from other files
328    pub imported_symbols: RapidMap<String, ImportInfo>,
329
330    /// Symbols exported by this file
331    pub exported_symbols: RapidMap<String, ExportInfo>,
332
333    /// Function calls made in this document
334    pub function_calls: Vec<CallInfo>,
335
336    /// Type definitions and usages
337    pub type_info: Vec<TypeInfo>,
338
339    /// Language-specific metadata
340    pub language_metadata: RapidMap<String, String>,
341}
342
343/// Information about a symbol definition
344#[derive(Debug, Clone)]
345pub struct SymbolInfo {
346    pub name: String,
347    pub kind: SymbolKind,
348    pub position: Position,
349    pub scope: String,
350    pub visibility: Visibility,
351}
352
353/// Information about an import
354#[derive(Debug, Clone)]
355pub struct ImportInfo {
356    pub symbol_name: String,
357    pub source_path: String,
358    pub import_kind: ImportKind,
359    pub position: Position,
360}
361
362/// Information about an export
363#[derive(Debug, Clone)]
364pub struct ExportInfo {
365    pub symbol_name: String,
366    pub export_kind: ExportKind,
367    pub position: Position,
368}
369
370/// Information about a function call
371#[derive(Debug, Clone)]
372pub struct CallInfo {
373    pub function_name: String,
374    pub position: Position,
375    pub arguments_count: usize,
376    pub is_resolved: bool,
377    pub target_file: Option<PathBuf>,
378}
379
380/// Information about type usage
381#[derive(Debug, Clone)]
382pub struct TypeInfo {
383    pub type_name: String,
384    pub position: Position,
385    pub kind: TypeKind,
386    pub generic_params: Vec<String>,
387}
388
389/// Cross-file relationships for graph intelligence
390#[derive(Debug, Clone)]
391pub struct CrossFileRelationship {
392    pub kind: RelationshipKind,
393    pub source_file: PathBuf,
394    pub target_file: PathBuf,
395    pub source_symbol: String,
396    pub target_symbol: String,
397    pub relationship_data: RapidMap<String, String>,
398}
399
400/// Context for pattern matches
401#[derive(Debug, Default, Clone)]
402pub struct MatchContext {
403    pub execution_scope: ExecutionScope,
404    pub analysis_depth: AnalysisDepth,
405    pub context_data: RapidMap<String, String>,
406}
407
408/// Execution scope for analysis operations
409#[derive(Debug, Clone, Default)]
410pub enum ExecutionScope {
411    /// Single file analysis
412    #[default]
413    File,
414    /// Module or directory level
415    Module(PathBuf),
416    /// Entire codebase
417    Codebase,
418    /// Custom scope with specific files
419    Custom(Vec<PathBuf>),
420}
421
422/// Depth of analysis to perform
423#[derive(Debug, Clone, Default)]
424pub enum AnalysisDepth {
425    /// Syntax-only analysis
426    Syntax,
427    /// Include local dependencies
428    #[default]
429    Local,
430    /// Include external dependencies
431    Deep,
432    /// Complete codebase analysis
433    Complete,
434}
435
436/// Execution context that carries state across service boundaries
437#[derive(Debug, Clone)]
438pub struct AnalysisContext {
439    /// Scope of the current analysis
440    pub scope: ExecutionScope,
441
442    /// Depth of analysis
443    pub depth: AnalysisDepth,
444
445    /// Base directory for relative path resolution
446    pub base_directory: PathBuf,
447
448    /// Include patterns for file filtering
449    pub include_patterns: Vec<String>,
450
451    /// Exclude patterns for file filtering
452    pub exclude_patterns: Vec<String>,
453
454    /// Maximum number of files to process
455    pub max_files: Option<usize>,
456
457    /// Parallel execution configuration
458    pub execution_config: ExecutionConfig,
459
460    /// Custom context data
461    pub context_data: RapidMap<String, String>,
462}
463
464impl Default for AnalysisContext {
465    fn default() -> Self {
466        Self {
467            scope: ExecutionScope::File,
468            depth: AnalysisDepth::Local,
469            base_directory: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
470            include_patterns: vec!["**/*".to_string()],
471            exclude_patterns: vec!["**/node_modules/**".to_string(), "**/target/**".to_string()],
472            max_files: None,
473            execution_config: ExecutionConfig::default(),
474            context_data: thread_utilities::get_map(),
475        }
476    }
477}
478
479/// Configuration for execution environments
480#[derive(Debug, Clone)]
481pub struct ExecutionConfig {
482    /// Parallel execution strategy
483    pub strategy: ExecutionStrategy,
484
485    /// Maximum number of concurrent operations
486    pub max_concurrency: Option<usize>,
487
488    /// Chunk size for batched operations
489    pub chunk_size: Option<usize>,
490
491    /// Timeout for individual operations
492    pub operation_timeout: Option<std::time::Duration>,
493}
494
495impl Default for ExecutionConfig {
496    fn default() -> Self {
497        Self {
498            strategy: ExecutionStrategy::Auto,
499            max_concurrency: None,
500            chunk_size: None,
501            operation_timeout: None,
502        }
503    }
504}
505
506/// Execution strategy for different environments
507#[derive(Debug, Clone, Default)]
508pub enum ExecutionStrategy {
509    /// Choose strategy automatically based on environment
510    #[default]
511    Auto,
512    /// Single-threaded execution
513    Sequential,
514    /// Rayon-based parallel execution (for CLI)
515    Rayon,
516    /// Chunked execution for cloud workers
517    Chunked,
518    /// Custom execution strategy
519    Custom(String),
520}
521
522// Enums for categorizing symbols and relationships
523
524#[derive(Debug, Clone, PartialEq)]
525pub enum SymbolKind {
526    Function,
527    Class,
528    Interface,
529    Variable,
530    Constant,
531    Type,
532    Module,
533    Namespace,
534    Enum,
535    Field,
536    Property,
537    Method,
538    Constructor,
539    Other(String),
540}
541
542#[derive(Debug, Clone, PartialEq)]
543pub enum Visibility {
544    Public,
545    Private,
546    Protected,
547    Internal,
548    Package,
549    Other(String),
550}
551
552#[derive(Debug, Clone, PartialEq)]
553pub enum ImportKind {
554    Named,
555    Default,
556    Namespace,
557    SideEffect,
558    Dynamic,
559    Other(String),
560}
561
562#[derive(Debug, Clone, PartialEq)]
563pub enum ExportKind {
564    Named,
565    Default,
566    Namespace,
567    Reexport,
568    Other(String),
569}
570
571#[derive(Debug, Clone, PartialEq)]
572pub enum TypeKind {
573    Primitive,
574    Struct,
575    Class,
576    Interface,
577    Union,
578    Enum,
579    Generic,
580    Function,
581    Array,
582    Other(String),
583}
584
585#[derive(Debug, Clone, PartialEq)]
586pub enum RelationshipKind {
587    /// Function calls another function
588    Calls,
589    /// Module imports from another module
590    Imports,
591    /// Class inherits from another class
592    Inherits,
593    /// Interface implements another interface
594    Implements,
595    /// Type uses another type
596    Uses,
597    /// Module depends on another module
598    DependsOn,
599    /// Symbol references another symbol
600    References,
601    /// Custom relationship type
602    Custom(String),
603}
604
605/// Range representing a span of text in source code
606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
607pub struct Range {
608    pub start: Position,
609    pub end: Position,
610}
611
612impl Range {
613    pub fn new(start: Position, end: Position) -> Self {
614        Self { start, end }
615    }
616
617    /// Create a range from ast-grep positions
618    pub fn from_ast_positions(start: Position, end: Position) -> Self {
619        Self { start, end }
620    }
621
622    /// Check if this range contains a position
623    pub fn contains(&self, pos: Position) -> bool {
624        pos >= self.start && pos <= self.end
625    }
626
627    /// Check if this range overlaps with another range
628    pub fn overlaps(&self, other: &Range) -> bool {
629        self.start <= other.end && other.start <= self.end
630    }
631}