From e476383ca8ba5bd369b68b89907ab1ebb158a4ad Mon Sep 17 00:00:00 2001 From: Ibrahim Khan <2005ibrahimkhan@gmail.com> Date: Thu, 30 Jul 2026 14:16:10 -0700 Subject: [PATCH] grammars: Highlight class instantiations in JavaScript, TypeScript, and TSX (#61890) ## Why JavaScript, TypeScript, and TSX class declarations were captured as `type.class`, but the same class names in `new` expressions were captured as the generic `type`. Themes that define a distinct style for `type.class` therefore rendered declarations and instantiations inconsistently. ## What Update the three highlight queries so constructor identifiers in `new` expressions use `type.class`. Themes without a dedicated `type.class` style continue to fall back to their existing `type` style. Add a regression test that loads the real JavaScript, TypeScript, and TSX grammars with distinct colors for `type` and `type.class`, then verifies that both the declaration and instantiation receive the class-specific highlight. ## Testing - `cargo test -p languages test_class_instantiation_highlighting` - `cargo test -p languages` - `cargo fmt --all -- --check` - `./script/clippy -p languages` ## References Closes #55943 Release Notes: - Fixed inconsistent class instantiation highlighting in JavaScript, TypeScript, and TSX. --- crates/grammars/src/javascript/highlights.scm | 2 +- crates/grammars/src/tsx/highlights.scm | 2 +- crates/grammars/src/typescript/highlights.scm | 2 +- crates/languages/src/typescript.rs | 32 ++++++++++++++++++- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/grammars/src/javascript/highlights.scm b/crates/grammars/src/javascript/highlights.scm index 336fcdd36a..5835e71061 100644 --- a/crates/grammars/src/javascript/highlights.scm +++ b/crates/grammars/src/javascript/highlights.scm @@ -30,7 +30,7 @@ ] @function.method)) (new_expression - constructor: (identifier) @type) + constructor: (identifier) @type.class) (nested_type_identifier module: (identifier) @type) diff --git a/crates/grammars/src/tsx/highlights.scm b/crates/grammars/src/tsx/highlights.scm index 3dd9038782..899737b6a6 100644 --- a/crates/grammars/src/tsx/highlights.scm +++ b/crates/grammars/src/tsx/highlights.scm @@ -30,7 +30,7 @@ ] @function.method)) (new_expression - constructor: (identifier) @type) + constructor: (identifier) @type.class) (nested_type_identifier module: (identifier) @type) diff --git a/crates/grammars/src/typescript/highlights.scm b/crates/grammars/src/typescript/highlights.scm index c3ecf17946..58e2679448 100644 --- a/crates/grammars/src/typescript/highlights.scm +++ b/crates/grammars/src/typescript/highlights.scm @@ -123,7 +123,7 @@ ] @function.method)) (new_expression - constructor: (identifier) @type) + constructor: (identifier) @type.class) (nested_type_identifier module: (identifier) @type) diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 0da1b63590..806fa372d4 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -908,10 +908,12 @@ async fn get_cached_ts_server_binary( mod tests { use std::path::Path; - use gpui::{AppContext as _, BackgroundExecutor, TestAppContext}; + use gpui::{AppContext as _, BackgroundExecutor, Hsla, TestAppContext}; use project::FakeFs; + use rope::Rope; use serde_json::json; use task::TaskTemplates; + use theme::SyntaxTheme; use unindent::Unindent; use util::{path, rel_path::rel_path}; @@ -919,6 +921,34 @@ mod tests { PackageJsonData, TypeScriptContextProvider, replace_test_name_parameters, }; + #[test] + fn test_class_instantiation_highlighting() { + let source = Rope::from("class Dog {}\nconst dog = new Dog();"); + let theme = SyntaxTheme::new_test([("type", Hsla::blue()), ("type.class", Hsla::green())]); + + for language in [ + crate::language( + "typescript", + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), + ), + crate::language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()), + crate::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into()), + ] { + language.set_theme(&theme); + let class_highlight = language + .grammar() + .and_then(|grammar| grammar.highlight_id_for_name("type.class")) + .expect("type.class highlight should be defined"); + + assert_eq!( + language.highlight_text(&source, 0..source.len()), + vec![(6..9, class_highlight), (29..32, class_highlight)], + "{} class instantiations should use the type.class highlight", + language.name() + ); + } + } + #[gpui::test] async fn test_outline(cx: &mut TestAppContext) { for language in [