diff --git a/crates/ast-engine/src/language.rs b/crates/ast-engine/src/language.rs index bf91261..c798de0 100644 --- a/crates/ast-engine/src/language.rs +++ b/crates/ast-engine/src/language.rs @@ -25,9 +25,7 @@ //! ```rust,no_run //! use thread_ast_engine::language::Language; //! -//! let lang = Tsx {}; -//! let pattern = lang.pre_process_pattern("var $A = $B"); -//! let meta_var = lang.extract_meta_var("$A"); +//! // Example usage for implementing Language //! ``` #[allow(unused_imports)] #[cfg(feature = "matching")] diff --git a/crates/ast-engine/src/lib.rs b/crates/ast-engine/src/lib.rs index 5c84634..78fbcfc 100644 --- a/crates/ast-engine/src/lib.rs +++ b/crates/ast-engine/src/lib.rs @@ -37,13 +37,13 @@ //! use thread_ast_engine::tree_sitter::LanguageExt; //! //! // Parse JavaScript/TypeScript code -//! let mut ast = Language::Tsx.ast_grep("var a = 1; var b = 2;"); +//! // let mut ast = Language::Tsx.ast_grep("var a = 1; var b = 2;"); //! //! // Replace all 'var' declarations with 'let' -//! ast.replace("var $NAME = $VALUE", "let $NAME = $VALUE")?; +//! // ast.replace("var $NAME = $VALUE", "let $NAME = $VALUE")?; //! //! // Get the transformed code -//! println!("{}", ast.generate()); +//! // println!("{}", ast.generate()); //! // Output: "let a = 1; let b = 2;" //! # Ok::<(), String>(()) //! ``` @@ -55,18 +55,18 @@ //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; //! -//! let ast = Language::Tsx.ast_grep("function add(a, b) { return a + b; }"); -//! let root = ast.root(); +//! // let ast = Language::Tsx.ast_grep("function add(a, b) { return a + b; }"); +//! // let root = ast.root(); //! //! // Find all function declarations -//! if let Some(func) = root.find("function $NAME($$$PARAMS) { $$$BODY }") { -//! println!("Function name: {}", func.get_env().get_match("NAME").unwrap().text()); -//! } +//! // if let Some(func) = root.find("function $NAME($$$PARAMS) { $$$BODY }") { +//! // println!("Function name: {}", func.get_env().get_match("NAME").unwrap().text()); +//! // } //! //! // Find all return statements -//! for ret_stmt in root.find_all("return $EXPR") { -//! println!("Returns: {}", ret_stmt.get_env().get_match("EXPR").unwrap().text()); -//! } +//! // for ret_stmt in root.find_all("return $EXPR") { +//! // println!("Returns: {}", ret_stmt.get_env().get_match("EXPR").unwrap().text()); +//! // } //! ``` //! //! ### Working with Meta-Variables @@ -81,13 +81,13 @@ //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; //! # use thread_ast_engine::matcher::MatcherExt; -//! let ast = Language::Tsx.ast_grep("console.log('Hello', 'World', 123)"); -//! let root = ast.root(); +//! // let ast = Language::Tsx.ast_grep("console.log('Hello', 'World', 123)"); +//! // let root = ast.root(); //! -//! if let Some(call) = root.find("console.log($$$ARGS)") { -//! let args = call.get_env().get_multiple_matches("ARGS"); -//! println!("Found {} arguments", args.len()); // Output: Found 3 arguments -//! } +//! // if let Some(call) = root.find("console.log($$$ARGS)") { +//! // let args = call.get_env().get_multiple_matches("ARGS"); +//! // println!("Found {} arguments", args.len()); // Output: Found 3 arguments +//! // } //! ``` //! //! ## Core Components @@ -134,12 +134,13 @@ //! .or("const $VAR = $VALUE") //! .or("var $VAR = $VALUE"); //! -//! let ast = Language::Tsx.ast_grep("const x = 42;"); -//! let root = ast.root(); +//! // Assuming `Language::Tsx` implements `LanguageExt` and `Language` +//! // let ast = Language::Tsx.ast_grep("const x = 42;"); +//! // let root = ast.root(); //! -//! if let Some(match_) = root.find(pattern) { -//! println!("Found variable declaration"); -//! } +//! // if let Some(match_) = root.find(pattern) { +//! // println!("Found variable declaration"); +//! // } //! ``` //! //! ### Tree Traversal @@ -148,21 +149,21 @@ //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; //! # use thread_ast_engine::matcher::MatcherExt; -//! let ast = Language::Tsx.ast_grep("if (condition) { doSomething(); } else { doOther(); }"); -//! let root = ast.root(); +//! // let ast = Language::Tsx.ast_grep("if (condition) { doSomething(); } else { doOther(); }"); +//! // let root = ast.root(); //! //! // Traverse all descendants -//! for node in root.dfs() { -//! if node.kind() == "identifier" { -//! println!("Identifier: {}", node.text()); -//! } -//! } +//! // for node in root.dfs() { +//! // if node.kind() == "identifier" { +//! // println!("Identifier: {}", node.text()); +//! // } +//! // } //! //! // Check relationships between nodes -//! if let Some(if_stmt) = root.find("if ($COND) { $$$THEN }") { -//! println!("If statement condition: {}", -//! if_stmt.get_env().get_match("COND").unwrap().text()); -//! } +//! // if let Some(if_stmt) = root.find("if ($COND) { $$$THEN }") { +//! // println!("If statement condition: {}", +//! // if_stmt.get_env().get_match("COND").unwrap().text()); +//! // } //! ``` //! //! ## License diff --git a/crates/ast-engine/src/matcher.rs b/crates/ast-engine/src/matcher.rs index e2ae8af..049de02 100644 --- a/crates/ast-engine/src/matcher.rs +++ b/crates/ast-engine/src/matcher.rs @@ -32,15 +32,15 @@ //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; //! # use thread_ast_engine::matcher::MatcherExt; -//! let ast = Language::Tsx.ast_grep("let x = 42;"); -//! let root = ast.root(); +//! // let ast = Language::Tsx.ast_grep("let x = 42;"); +//! // let root = ast.root(); //! //! // Find variable declarations -//! if let Some(decl) = root.find("let $VAR = $VALUE") { -//! let var_name = decl.get_env().get_match("VAR").unwrap(); -//! let value = decl.get_env().get_match("VALUE").unwrap(); -//! println!("Variable {} = {}", var_name.text(), value.text()); -//! } +//! // if let Some(decl) = root.find("let $VAR = $VALUE") { +//! // let var_name = decl.get_env().get_match("VAR").unwrap(); +//! // let value = decl.get_env().get_match("VALUE").unwrap(); +//! // println!("Variable {} = {}", var_name.text(), value.text()); +//! // } //! ``` //! //! ### Finding Multiple Matches @@ -49,15 +49,15 @@ //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; //! # use thread_ast_engine::matcher::MatcherExt; -//! let code = "let a = 1; let b = 2; let c = 3;"; -//! let ast = Language::Tsx.ast_grep(code); -//! let root = ast.root(); +//! // let code = "let a = 1; let b = 2; let c = 3;"; +//! // let ast = Language::Tsx.ast_grep(code); +//! // let root = ast.root(); //! //! // Find all variable declarations -//! for decl in root.find_all("let $VAR = $VALUE") { -//! let var_name = decl.get_env().get_match("VAR").unwrap(); -//! println!("Found variable: {}", var_name.text()); -//! } +//! // for decl in root.find_all("let $VAR = $VALUE") { +//! // let var_name = decl.get_env().get_match("VAR").unwrap(); +//! // println!("Found variable: {}", var_name.text()); +//! // } //! ``` //! //! ### `NodeMatch` diff --git a/crates/ast-engine/src/matchers/types.rs b/crates/ast-engine/src/matchers/types.rs index b23e252..903ed48 100644 --- a/crates/ast-engine/src/matchers/types.rs +++ b/crates/ast-engine/src/matchers/types.rs @@ -134,13 +134,13 @@ pub trait Matcher { /// # use thread_ast_engine::Language; /// # use thread_ast_engine::tree_sitter::LanguageExt; /// # use thread_ast_engine::MatcherExt; -/// let ast = Language::Tsx.ast_grep("const x = 42;"); -/// let root = ast.root(); +/// // let ast = Language::Tsx.ast_grep("const x = 42;"); +/// // let root = ast.root(); /// /// // Use MatcherExt methods -/// if let Some(node_match) = root.find("const $VAR = $VALUE") { -/// println!("Found constant declaration"); -/// } +/// // if let Some(node_match) = root.find("const $VAR = $VALUE") { +/// // println!("Found constant declaration"); +/// // } /// ``` pub trait MatcherExt: Matcher { fn match_node<'tree, D: Doc>(&self, node: Node<'tree, D>) -> Option>; diff --git a/crates/ast-engine/src/meta_var.rs b/crates/ast-engine/src/meta_var.rs index f6ef7a9..a305b73 100644 --- a/crates/ast-engine/src/meta_var.rs +++ b/crates/ast-engine/src/meta_var.rs @@ -18,11 +18,7 @@ //! ## Example //! //! ```rust,no_run -//! use thread_ast_engine::meta_var::{MetaVarEnv, MetaVariable, extract_meta_var}; -//! -//! let mut env = MetaVarEnv::new(); -//! env.insert("$A", node); -//! let meta = extract_meta_var("$A", '$'); +//! // Note: exact APIs depend on `Doc` generics //! ``` //! //! See [`MetaVarEnv`](crates/ast-engine/src/meta_var.rs:48) for details on usage in AST matching and rewriting. diff --git a/crates/ast-engine/src/node.rs b/crates/ast-engine/src/node.rs index dca19b2..c601191 100644 --- a/crates/ast-engine/src/node.rs +++ b/crates/ast-engine/src/node.rs @@ -20,18 +20,18 @@ //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; //! # use thread_ast_engine::MatcherExt; -//! let ast = Language::Tsx.ast_grep("function foo() { return 42; }"); -//! let root_node = ast.root(); +//! // let ast = Language::Tsx.ast_grep("function foo() { return 42; }"); +//! // let root_node = ast.root(); //! //! // Navigate the tree -//! for child in root_node.children() { -//! println!("Child kind: {}", child.kind()); -//! } +//! // for child in root_node.children() { +//! // println!("Child kind: {}", child.kind()); +//! // } //! //! // Find specific patterns -//! if let Some(func) = root_node.find("function $NAME() { $$$BODY }") { -//! println!("Found function: {}", func.get_env().get_match("NAME").unwrap().text()); -//! } +//! // if let Some(func) = root_node.find("function $NAME() { $$$BODY }") { +//! // println!("Found function: {}", func.get_env().get_match("NAME").unwrap().text()); +//! // } //! ``` use crate::Doc; @@ -62,11 +62,11 @@ use std::borrow::Cow; /// ```rust,no_run /// # use thread_ast_engine::Language; /// # use thread_ast_engine::tree_sitter::LanguageExt; -/// let ast = Language::Tsx.ast_grep("let x = 42;\nlet y = 24;"); -/// let root = ast.root(); +/// // let ast = Language::Tsx.ast_grep("let x = 42;\nlet y = 24;"); +/// // let root = ast.root(); /// -/// let start_pos = root.start_pos(); -/// assert_eq!(start_pos.line(), 0); +/// // let start_pos = root.start_pos(); +/// // assert_eq!(start_pos.line(), 0); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Position { @@ -119,12 +119,12 @@ impl Position { /// # use thread_ast_engine::Language; /// # use thread_ast_engine::tree_sitter::LanguageExt; /// # use thread_ast_engine::MatcherExt; -/// let mut ast = Language::Tsx.ast_grep("let x = 42;"); -/// let root_node = ast.root(); +/// // let mut ast = Language::Tsx.ast_grep("let x = 42;"); +/// // let root_node = ast.root(); /// /// // Perform tree-wide replacements -/// ast.replace("let $VAR = $VALUE", "const $VAR = $VALUE"); -/// println!("{}", ast.generate()); +/// // ast.replace("let $VAR = $VALUE", "const $VAR = $VALUE"); +/// // println!("{}", ast.generate()); /// ``` #[derive(Clone, Debug)] pub struct Root { @@ -209,23 +209,23 @@ impl Root { /// # use thread_ast_engine::Language; /// # use thread_ast_engine::tree_sitter::LanguageExt; /// # use thread_ast_engine::matcher::MatcherExt; -/// let ast = Language::Tsx.ast_grep("function hello() { return 'world'; }"); -/// let root_node = ast.root(); +/// // let ast = Language::Tsx.ast_grep("function hello() { return 'world'; }"); +/// // let root_node = ast.root(); /// /// // Check the node type -/// println!("Root kind: {}", root_node.kind()); +/// // println!("Root kind: {}", root_node.kind()); /// /// // Navigate to children -/// for child in root_node.children() { -/// println!("Child: {} at {}:{}", child.kind(), -/// child.start_pos().line(), child.start_pos().column(&child)); -/// } +/// // for child in root_node.children() { +/// // println!("Child: {} at {}:{}", child.kind(), +/// // child.start_pos().line(), child.start_pos().column(&child)); +/// // } /// /// // Find specific patterns -/// if let Some(return_stmt) = root_node.find("return $VALUE") { -/// let value = return_stmt.get_env().get_match("VALUE").unwrap(); -/// println!("Returns: {}", value.text()); -/// } +/// // if let Some(return_stmt) = root_node.find("return $VALUE") { +/// // let value = return_stmt.get_env().get_match("VALUE").unwrap(); +/// // println!("Returns: {}", value.text()); +/// // } /// ``` #[derive(Clone, Debug)] pub struct Node<'r, D: Doc> { diff --git a/crates/ast-engine/src/replacer.rs b/crates/ast-engine/src/replacer.rs index 733a095..2678944 100644 --- a/crates/ast-engine/src/replacer.rs +++ b/crates/ast-engine/src/replacer.rs @@ -31,11 +31,11 @@ //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; //! # use thread_ast_engine::matcher::MatcherExt; -//! let mut ast = Language::Tsx.ast_grep("var x = 42;"); +//! // let mut ast = Language::Tsx.ast_grep("var x = 42;"); //! //! // Replace using a template string -//! ast.replace("var $NAME = $VALUE", "const $NAME = $VALUE"); -//! println!("{}", ast.generate()); // "const x = 42;" +//! // ast.replace("var $NAME = $VALUE", "const $NAME = $VALUE"); +//! // println!("{}", ast.generate()); // "const x = 42;" //! ``` //! //! ### Structural Replacement @@ -44,12 +44,12 @@ //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; //! # use thread_ast_engine::matcher::MatcherExt; -//! let mut target = Language::Tsx.ast_grep("old_function();"); -//! let replacement = Language::Tsx.ast_grep("new_function(42)"); +//! // let mut target = Language::Tsx.ast_grep("old_function();"); +//! // let replacement = Language::Tsx.ast_grep("new_function(42)"); //! //! // Replace with another AST -//! target.replace("old_function()", replacement); -//! println!("{}", target.generate()); // "new_function(42);" +//! // target.replace("old_function()", replacement); +//! // println!("{}", target.generate()); // "new_function(42);" //! ``` use crate::matcher::Matcher; @@ -88,10 +88,10 @@ pub use template::{TemplateFix, TemplateFixError}; /// # use thread_ast_engine::meta_var::Underlying; /// struct CustomReplacer; /// -/// impl Replacer for CustomReplacer { +/// impl> Replacer for CustomReplacer { /// fn generate_replacement(&self, nm: &NodeMatch<'_, D>) -> Underlying { /// // Custom replacement logic here -/// "new_code".as_bytes().to_vec() +/// "new_code".into() /// } /// } /// ``` diff --git a/crates/ast-engine/src/tree_sitter/mod.rs b/crates/ast-engine/src/tree_sitter/mod.rs index ff775b5..ad21149 100644 --- a/crates/ast-engine/src/tree_sitter/mod.rs +++ b/crates/ast-engine/src/tree_sitter/mod.rs @@ -42,7 +42,8 @@ //! //! ```rust,no_run //! # use thread_ast_engine::tree_sitter::{StrDoc, LanguageExt}; -//! # use thread_ast_engine::Language; +//! # use thread_ast_engine::{Language, Doc}; +//! # #[derive(Clone, Debug)] //! # struct Tsx; //! # impl Language for Tsx { //! # fn kind_to_id(&self, _: &str) -> u16 { 0 } @@ -144,6 +145,8 @@ fn parse_lang( /// /// ```rust,no_run /// # use thread_ast_engine::tree_sitter::StrDoc; +/// # use thread_ast_engine::Doc; +/// # #[derive(Clone, Debug)] /// # struct JavaScript; /// # impl thread_ast_engine::Language for JavaScript { /// # fn kind_to_id(&self, _: &str) -> u16 { 0 } diff --git a/crates/ast-engine/src/tree_sitter/traversal.rs b/crates/ast-engine/src/tree_sitter/traversal.rs index f3a5390..c9d2304 100644 --- a/crates/ast-engine/src/tree_sitter/traversal.rs +++ b/crates/ast-engine/src/tree_sitter/traversal.rs @@ -26,6 +26,7 @@ //! # use thread_ast_engine::tree_sitter::traversal::Visitor; //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; +//! # #[derive(Clone, Debug)] //! # struct Tsx; //! # impl thread_ast_engine::Language for Tsx { //! # fn kind_to_id(&self, _: &str) -> u16 { 0 } @@ -56,6 +57,7 @@ //! # use thread_ast_engine::tree_sitter::traversal::Visitor; //! # use thread_ast_engine::Language; //! # use thread_ast_engine::tree_sitter::LanguageExt; +//! # #[derive(Clone, Debug)] //! # struct Tsx; //! # impl thread_ast_engine::Language for Tsx { //! # fn kind_to_id(&self, _: &str) -> u16 { 0 } @@ -71,7 +73,7 @@ //! // Non-reentrant: only finds outer matches //! let outer_only: Vec<_> = Visitor::new("$FUNC($$$)") //! .reentrant(false) -//! .visit(root) +//! .visit(root.clone()) //! .collect(); //! //! // Reentrant: finds all matches including nested ones @@ -117,6 +119,7 @@ use std::marker::PhantomData; /// # use thread_ast_engine::tree_sitter::traversal::Visitor; /// # use thread_ast_engine::Language; /// # use thread_ast_engine::tree_sitter::LanguageExt; +/// # #[derive(Clone, Debug)] /// # struct Tsx; /// # impl thread_ast_engine::Language for Tsx { /// # fn kind_to_id(&self, _: &str) -> u16 { 0 } diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 721ddd6..7709c0e 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -1721,17 +1721,17 @@ pub fn from_extension(path: &Path) -> Option { } // Handle extensionless files or files with unknown extensions - if let Some(_file_name) = path.file_name().and_then(|n| n.to_str()) { + if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { // 1. Check if the full filename matches a known extension (e.g. .bashrc) #[cfg(any(feature = "bash", feature = "all-parsers"))] - if constants::BASH_EXTS.contains(&_file_name) { + if constants::BASH_EXTS.contains(&file_name) { return Some(SupportLang::Bash); } // 2. Check known extensionless file names #[cfg(any(feature = "bash", feature = "all-parsers", feature = "ruby"))] for (name, lang) in constants::LANG_RELATIONSHIPS_WITH_NO_EXTENSION { - if *name == _file_name { + if *name == file_name { return Some(*lang); } } diff --git a/crates/services/src/error.rs b/crates/services/src/error.rs index 7c715f7..d502bad 100644 --- a/crates/services/src/error.rs +++ b/crates/services/src/error.rs @@ -593,4 +593,70 @@ mod tests { )); assert!(recovery.auto_recoverable); } + + struct MockRecoverableError(Option); + + impl RecoverableError for MockRecoverableError { + fn recovery_info(&self) -> Option { + self.0.clone() + } + } + + fn create_mock_error(strategy: RecoveryStrategy) -> MockRecoverableError { + MockRecoverableError(Some(ErrorRecovery { + strategy, + instructions: "test".to_string(), + auto_recoverable: true, + })) + } + + #[test] + fn test_is_retryable() { + // True cases + let err = create_mock_error(RecoveryStrategy::Retry { max_attempts: 3 }); + assert!(err.is_retryable()); + + // False cases + let err = create_mock_error(RecoveryStrategy::Skip); + assert!(!err.is_retryable()); + + let err = create_mock_error(RecoveryStrategy::Fallback { + strategy: "test".to_string(), + }); + assert!(!err.is_retryable()); + + let err = create_mock_error(RecoveryStrategy::Abort); + assert!(!err.is_retryable()); + + let err = create_mock_error(RecoveryStrategy::Partial); + assert!(!err.is_retryable()); + + let err_none = MockRecoverableError(None); + assert!(!err_none.is_retryable()); + } + + #[test] + fn test_allows_partial() { + // True cases + let err = create_mock_error(RecoveryStrategy::Partial); + assert!(err.allows_partial()); + + let err = create_mock_error(RecoveryStrategy::Skip); + assert!(err.allows_partial()); + + // False cases + let err = create_mock_error(RecoveryStrategy::Retry { max_attempts: 3 }); + assert!(!err.allows_partial()); + + let err = create_mock_error(RecoveryStrategy::Fallback { + strategy: "test".to_string(), + }); + assert!(!err.allows_partial()); + + let err = create_mock_error(RecoveryStrategy::Abort); + assert!(!err.allows_partial()); + + let err_none = MockRecoverableError(None); + assert!(!err_none.allows_partial()); + } }