diff --git a/Cargo.lock b/Cargo.lock index 65e57c3c9bc922edd5d3658b590e5627a63e1d9d..db61ba7b121773c6e6941dd940201ec0a9e82f73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9292,6 +9292,7 @@ dependencies = [ "futures 0.3.31", "gpui", "http_client", + "itertools 0.14.0", "language", "log", "lsp", diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 44b0cb58653adff27e31c6edd7fb4208e0bb4bbf..7ebafd8fdd9e2310c207d47a7d911516a498d00c 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -45,6 +45,7 @@ dap.workspace = true futures.workspace = true gpui.workspace = true http_client.workspace = true +itertools.workspace = true language.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index bc939f0b727f5ca60a851be99aaf0f3e61ee3a93..edfddd3f76e374e9ae0d1ce71cfb0b7b3c586c4d 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -5,6 +5,7 @@ use collections::HashMap; use futures::future::join_all; use gpui::{App, AppContext, AsyncApp, Task}; use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url}; +use itertools::Itertools as _; use language::{ ContextLocation, ContextProvider, File, LanguageName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain, @@ -18,7 +19,7 @@ use std::{ borrow::Cow, ffi::OsString, path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, VariableName}; use util::merge_json_value_into; @@ -511,9 +512,9 @@ fn eslint_server_binary_arguments(server_path: &Path) -> Vec { } fn replace_test_name_parameters(test_name: &str) -> String { - let pattern = regex::Regex::new(r"(%|\$)[0-9a-zA-Z]+").unwrap(); - - regex::escape(&pattern.replace_all(test_name, "(.+?)")) + static PATTERN: LazyLock = + LazyLock::new(|| regex::Regex::new(r"(\$([A-Za-z0-9_\.]+|[\#])|%[psdifjo#\$%])").unwrap()); + PATTERN.split(test_name).map(regex::escape).join("(.+?)") } pub struct TypeScriptLspAdapter { @@ -1015,7 +1016,9 @@ mod tests { use unindent::Unindent; use util::path; - use crate::typescript::{PackageJsonData, TypeScriptContextProvider}; + use crate::typescript::{ + PackageJsonData, TypeScriptContextProvider, replace_test_name_parameters, + }; #[gpui::test] async fn test_outline(cx: &mut TestAppContext) { @@ -1227,4 +1230,37 @@ mod tests { ] ); } + #[test] + fn test_escaping_name() { + let cases = [ + ("plain test name", "plain test name"), + ("test name with $param_name", "test name with (.+?)"), + ("test name with $nested.param.name", "test name with (.+?)"), + ("test name with $#", "test name with (.+?)"), + ("test name with $##", "test name with (.+?)\\#"), + ("test name with %p", "test name with (.+?)"), + ("test name with %s", "test name with (.+?)"), + ("test name with %d", "test name with (.+?)"), + ("test name with %i", "test name with (.+?)"), + ("test name with %f", "test name with (.+?)"), + ("test name with %j", "test name with (.+?)"), + ("test name with %o", "test name with (.+?)"), + ("test name with %#", "test name with (.+?)"), + ("test name with %$", "test name with (.+?)"), + ("test name with %%", "test name with (.+?)"), + ("test name with %q", "test name with %q"), + ( + "test name with regex chars .*+?^${}()|[]\\", + "test name with regex chars \\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\", + ), + ( + "test name with multiple $params and %pretty and %b and (.+?)", + "test name with multiple (.+?) and (.+?)retty and %b and \\(\\.\\+\\?\\)", + ), + ]; + + for (input, expected) in cases { + assert_eq!(replace_test_name_parameters(input), expected); + } + } }