languages: Change syntax highlighting for JSX elements (#49881)

finico created

Syntax highlighting and its customization are very important to many
developers, including me. I've looked through a number of issues and
discussions on this topic and haven't found any active PRs. Currently,
there's no way to customize highlighting for custom JSX tags, as they
use `@type`. Since TSX has a particularly complex syntax and can often
contain types/aliases/generics/tags in a dense sequence, they all blends
into a single color and makes it difficult to "parse" by eyes.

To avoid proposing something arbitrary, I looked into how this is done
elsewhere.

- VS Code `support.class.component.tsx`
[TypeScriptReact.tmLanguage.json](https://github.com/microsoft/vscode/blob/724656efa2c26ab6e7eb2023426dcf2658dc3203/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json#L5802)

But it relies on both legacy [tmLanguage naming
conventions](https://macromates.com/manual/en/language_grammars#:~:text=rarely%20be%20used\).-,support,-%E2%80%94%20things%20provided%20by)
and the outdated assumption that React components are always classes.

- ReScript `@tag`
[rescript-zed](https://github.com/rescript-lang/rescript-zed/blob/b3930c1754ab2762938244546ea2c7fb97d01cb3/languages/rescript/highlights.scm#L277)

It's not entirely correct to just use a `@tag` - it's better to
distinguish JSX Intrinsic Elements from custom ones.

- Vue `@tag @tag.component.type.constructor`
[zed-extensions/vue](https://github.com/zed-extensions/vue/blob/2697588c5cde11375d47f53f9449af8e32600d81/languages/vue/highlights.scm#L9C21-L9C52)

- Svelte `@tag @tag.component.type.constructor`
[zed-extensions/svelte](https://github.com/zed-extensions/svelte/blob/ae381a1217d14c26cbedfaf84b0a2f5ae508f40c/languages/svelte/highlights.scm#L46C21-L46C52)

The similarity between Vue and Svelte implementations (perhaps one
borrowed from the other) didn't seem coincidental and the approach felt
appropriate.

**I decided to adopt the same one to maintain consistency for theme
creators.**

So, how it looks:

**Release (0.224.9) version**
<img width="440" height="220" alt="zed-one-release"
src="https://github.com/user-attachments/assets/6f8726c5-a17e-4387-941c-69e5c1569049"
/>

**Local version with changes** - no breaking changes for builtin themes
- uses `type` color as before and can be changed in themes separately if
needed
<img width="440" height="220" alt="zed-one-local"
src="https://github.com/user-attachments/assets/a11cc5ed-20fc-45d2-8ebd-a908503999c3"
/>

**Local version with changes and theme overrides**
<img width="440" height="220" alt="zed-one-with-overrides"
src="https://github.com/user-attachments/assets/1997bd5b-7fa4-4346-8338-b5d61e9e832b"
/>

With these changes in the config:

```jsonc
"theme_overrides": {
  "One Light": {
    "syntax": {
      // "tag.component" also matches
      "type.component": {
        "color": "#d3604fff",
      },
    },
  },
},
```

I'm pretty sure this will help many developers enjoy Zed even more.

Release Notes:

- Improved syntax highlighting for custom jsx elements in TSX and
JavaScript languages. Theme authors and users can now highlight these in
their theme/theme overrides using `tag.component.jsx`

Change summary

crates/grammars/src/javascript/highlights.scm | 18 +++++++++---------
crates/grammars/src/tsx/highlights.scm        | 18 +++++++++---------
2 files changed, 18 insertions(+), 18 deletions(-)

Detailed changes

crates/grammars/src/javascript/highlights.scm 🔗

@@ -328,26 +328,26 @@
 ; JSX elements
 (jsx_opening_element
   [
-    (identifier) @type
+    (identifier) @type @tag.component.jsx
     (member_expression
-      object: (identifier) @type
-      property: (property_identifier) @type)
+      object: (identifier) @type @tag.component.jsx
+      property: (property_identifier) @type @tag.component.jsx)
   ])
 
 (jsx_closing_element
   [
-    (identifier) @type
+    (identifier) @type @tag.component.jsx
     (member_expression
-      object: (identifier) @type
-      property: (property_identifier) @type)
+      object: (identifier) @type @tag.component.jsx
+      property: (property_identifier) @type @tag.component.jsx)
   ])
 
 (jsx_self_closing_element
   [
-    (identifier) @type
+    (identifier) @type @tag.component.jsx
     (member_expression
-      object: (identifier) @type
-      property: (property_identifier) @type)
+      object: (identifier) @type @tag.component.jsx
+      property: (property_identifier) @type @tag.component.jsx)
   ])
 
 (jsx_opening_element

crates/grammars/src/tsx/highlights.scm 🔗

@@ -389,26 +389,26 @@
 
 (jsx_opening_element
   [
-    (identifier) @type
+    (identifier) @type @tag.component.jsx
     (member_expression
-      object: (identifier) @type
-      property: (property_identifier) @type)
+      object: (identifier) @type @tag.component.jsx
+      property: (property_identifier) @type @tag.component.jsx)
   ])
 
 (jsx_closing_element
   [
-    (identifier) @type
+    (identifier) @type @tag.component.jsx
     (member_expression
-      object: (identifier) @type
-      property: (property_identifier) @type)
+      object: (identifier) @type @tag.component.jsx
+      property: (property_identifier) @type @tag.component.jsx)
   ])
 
 (jsx_self_closing_element
   [
-    (identifier) @type
+    (identifier) @type @tag.component.jsx
     (member_expression
-      object: (identifier) @type
-      property: (property_identifier) @type)
+      object: (identifier) @type @tag.component.jsx
+      property: (property_identifier) @type @tag.component.jsx)
   ])
 
 (jsx_opening_element