From 385d4f77cc10fd089e75ccb4cfc469b3b8500303 Mon Sep 17 00:00:00 2001 From: kelmith Date: Wed, 16 Sep 2026 06:28:07 +0530 Subject: [PATCH] fix: stop trim_quotes and import-path quote stripping from panicking on a lone delimiter --- ast/src/lang/parse/utils.rs | 15 +++++----- ast/src/lang/queries/angular.rs | 10 +++---- ast/src/lang/queries/react_ts.rs | 10 +++---- ast/src/lang/queries/ruby.rs | 19 ++++++------ ast/src/testing/mod.rs | 2 ++ ast/src/testing/quotes.rs | 51 ++++++++++++++++++++++++++++++++ 6 files changed, 79 insertions(+), 28 deletions(-) create mode 100644 ast/src/testing/quotes.rs diff --git a/ast/src/lang/parse/utils.rs b/ast/src/lang/parse/utils.rs index 69692ffe8..e5ba2aaa6 100644 --- a/ast/src/lang/parse/utils.rs +++ b/ast/src/lang/parse/utils.rs @@ -6,14 +6,13 @@ use tree_sitter::{Node as TreeNode, QueryMatch}; pub fn trim_quotes(value: &str) -> &str { let value = value.trim(); - if value.starts_with('"') && value.ends_with('"') { - return &value[1..value.len() - 1]; - } - if value.starts_with("'") && value.ends_with("'") { - return &value[1..value.len() - 1]; - } - if value.starts_with("`") && value.ends_with("`") { - return &value[1..value.len() - 1]; + for quote in ['"', '\'', '`'] { + if let Some(inner) = value + .strip_prefix(quote) + .and_then(|v| v.strip_suffix(quote)) + { + return inner; + } } if let Some(stripped) = value.strip_prefix(':') { return stripped; diff --git a/ast/src/lang/queries/angular.rs b/ast/src/lang/queries/angular.rs index b2256e9e6..e12087895 100644 --- a/ast/src/lang/queries/angular.rs +++ b/ast/src/lang/queries/angular.rs @@ -427,11 +427,11 @@ impl Stack for Angular { path = path[1..].to_string(); } - if (path.starts_with('"') && path.ends_with('"')) - || (path.starts_with('\'') && path.ends_with('\'')) - || (path.starts_with('`') && path.ends_with('`')) - { - path = path[1..path.len() - 1].to_string(); + for quote in ['"', '\'', '`'] { + if let Some(inner) = path.strip_prefix(quote).and_then(|p| p.strip_suffix(quote)) { + path = inner.to_string(); + break; + } } path diff --git a/ast/src/lang/queries/react_ts.rs b/ast/src/lang/queries/react_ts.rs index 048327209..0bf359d65 100644 --- a/ast/src/lang/queries/react_ts.rs +++ b/ast/src/lang/queries/react_ts.rs @@ -1160,11 +1160,11 @@ impl Stack for TypeScriptReact { path = path[1..].to_string(); } - if (path.starts_with('"') && path.ends_with('"')) - || (path.starts_with('\'') && path.ends_with('\'')) - || (path.starts_with('`') && path.ends_with('`')) - { - path = path[1..path.len() - 1].to_string(); + for quote in ['"', '\'', '`'] { + if let Some(inner) = path.strip_prefix(quote).and_then(|p| p.strip_suffix(quote)) { + path = inner.to_string(); + break; + } } // FROM TYPESCRIPT: .js -> .ts replacement diff --git a/ast/src/lang/queries/ruby.rs b/ast/src/lang/queries/ruby.rs index ea77b8a52..37a6692f1 100644 --- a/ast/src/lang/queries/ruby.rs +++ b/ast/src/lang/queries/ruby.rs @@ -784,8 +784,8 @@ impl Stack for Ruby { fn resolve_import_path(&self, import_path: &str, _current_file: &str) -> String { let mut path = import_path.to_string(); - if path.starts_with("(") { - path = path[1..path.len() - 1].to_string(); + if let Some(inner) = path.strip_prefix('(').and_then(|p| p.strip_suffix(')')) { + path = inner.to_string(); } if path.contains(":") { @@ -797,20 +797,19 @@ impl Stack for Ruby { fn resolve_import_name(&self, import_name: &str) -> String { let mut name = import_name.to_string(); - if name.starts_with("(") { - name = name[1..name.len() - 1].to_string(); + if let Some(inner) = name.strip_prefix('(').and_then(|n| n.strip_suffix(')')) { + name = inner.to_string(); } if name.contains(":") { name = name.replace(":", ""); } - if name.starts_with("\"") && name.ends_with("\"") { - name = name[1..name.len() - 1].to_string(); - } - - if name.starts_with("'") && name.ends_with("'") { - name = name[1..name.len() - 1].to_string(); + for quote in ['"', '\''] { + if let Some(inner) = name.strip_prefix(quote).and_then(|n| n.strip_suffix(quote)) { + name = inner.to_string(); + break; + } } if name.starts_with("File") { diff --git a/ast/src/testing/mod.rs b/ast/src/testing/mod.rs index 13ebcd1d7..29b86bbe4 100644 --- a/ast/src/testing/mod.rs +++ b/ast/src/testing/mod.rs @@ -12,6 +12,8 @@ use annotations::run_fixture_test; pub mod builder; #[cfg(test)] pub mod registry; +#[cfg(test)] +pub mod quotes; #[cfg(test)] pub mod coverage; diff --git a/ast/src/testing/quotes.rs b/ast/src/testing/quotes.rs new file mode 100644 index 000000000..bbb915d5d --- /dev/null +++ b/ast/src/testing/quotes.rs @@ -0,0 +1,51 @@ +use crate::lang::parse::trim_quotes; +use crate::lang::queries::react_ts::TypeScriptReact; +use crate::lang::queries::ruby::Ruby; +use crate::lang::queries::Stack; + +#[test] +fn trim_quotes_strips_matching_quotes() { + assert_eq!(trim_quotes("\"abc\""), "abc"); + assert_eq!(trim_quotes("'abc'"), "abc"); + assert_eq!(trim_quotes("`abc`"), "abc"); + assert_eq!(trim_quotes("\"\""), ""); + assert_eq!(trim_quotes(":sym"), "sym"); +} + +#[test] +fn trim_quotes_leaves_unbalanced_quotes_alone() { + assert_eq!(trim_quotes("\"abc"), "\"abc"); + assert_eq!(trim_quotes("\"abc'"), "\"abc'"); +} + +#[test] +fn trim_quotes_lone_quote_does_not_panic() { + assert_eq!(trim_quotes("\""), "\""); + assert_eq!(trim_quotes("'"), "'"); + assert_eq!(trim_quotes("`"), "`"); +} + +#[test] +fn ruby_import_path_strips_matching_parens_only() { + let ruby = Ruby::default(); + assert_eq!(ruby.resolve_import_path("(foo)", ""), "foo"); + assert_eq!(ruby.resolve_import_path("(", ""), "("); + assert_eq!(ruby.resolve_import_path("(abc", ""), "(abc"); + assert_eq!(ruby.resolve_import_path("(café", ""), "(café"); +} + +#[test] +fn ruby_import_name_strips_matching_quotes_only() { + let ruby = Ruby::default(); + assert_eq!(ruby.resolve_import_name("\"foo\""), "Foo"); + assert_eq!(ruby.resolve_import_name("'foo'"), "Foo"); + assert_eq!(ruby.resolve_import_name("\""), "\""); + assert_eq!(ruby.resolve_import_name("'"), "'"); +} + +#[test] +fn react_ts_import_path_lone_quote_does_not_panic() { + let ts = TypeScriptReact::default(); + assert_eq!(ts.resolve_import_path("\"x\"", ""), "x"); + assert_eq!(ts.resolve_import_path("\"", ""), "\""); +}