languages: Add injections for string and tagged template literals for JS/TS(X) (#44180)

Vitaly Slobodin created

Hi! This pull request adds language injections for string and tagged
template literals for JS/TS(X).
This is similar to what [this
extension](https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates)
provides for VSCode. This PR is inspired by this tweet
https://x.com/leaverou/status/1996306611208388953?s=46&t=foDQRPR8oIl1buTJ4kZoSQ

I've added injections queries for the following languages: HTML, CSS,
GraphQL and SQL.
This works for:

- String literals: `const cssString = /* css */'button { color: hotpink
!important; }';`
- Template literals: ```const cssString = /* css */`button { color:
hotpink !important; }`;```

All injections support the format with whitespaces inside, i.e. `/* html
*/` and without them `/*html*/`.

## Screenshots

|before|after|
|---------|-----------|
| <img width="1596" height="1476" alt="CleanShot 2025-12-04 at 21 12
00@2x"
src="https://github.com/user-attachments/assets/8e0fb758-41f0-43a8-93e6-ae28f79d7c8f"
/> | <img width="1576" height="1496" alt="CleanShot 2025-12-04 at 21 08
35@2x"
src="https://github.com/user-attachments/assets/b47bb9c1-224e-4a24-8f08-a459f1081449"
/>|

Release Notes:

- Added language injections for string and tagged template literals in
JS/TS(X)

Change summary

crates/languages/src/javascript/injections.scm | 43 ++++++++++++++++++++
crates/languages/src/tsx/injections.scm        | 43 ++++++++++++++++++++
crates/languages/src/typescript/injections.scm | 43 ++++++++++++++++++++
3 files changed, 129 insertions(+)

Detailed changes

crates/languages/src/javascript/injections.scm 🔗

@@ -83,3 +83,46 @@
   arguments: (arguments (template_string (string_fragment) @injection.content
                               (#set! injection.language "isograph")))
 )
+
+; Parse the contents of strings and tagged template
+; literals with leading ECMAScript comments:
+; '/* html */' or '/*html*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*html\\s*\\*\\/")
+  (#set! injection.language "html")
+)
+
+; '/* sql */' or '/*sql*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*sql\\s*\\*\\/")
+  (#set! injection.language "sql")
+)
+
+; '/* gql */' or '/*gql*/'
+; '/* graphql */' or '/*graphql*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*(gql|graphql)\\s*\\*\\/")
+  (#set! injection.language "graphql")
+)
+
+; '/* css */' or '/*css*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*(css)\\s*\\*\\/")
+  (#set! injection.language "css")
+)

crates/languages/src/tsx/injections.scm 🔗

@@ -83,3 +83,46 @@
   arguments: (arguments (template_string (string_fragment) @injection.content
                               (#set! injection.language "isograph")))
 )
+
+; Parse the contents of strings and tagged template
+; literals with leading ECMAScript comments:
+; '/* html */' or '/*html*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*html\\s*\\*\\/")
+  (#set! injection.language "html")
+)
+
+; '/* sql */' or '/*sql*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*sql\\s*\\*\\/")
+  (#set! injection.language "sql")
+)
+
+; '/* gql */' or '/*gql*/'
+; '/* graphql */' or '/*graphql*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*(gql|graphql)\\s*\\*\\/")
+  (#set! injection.language "graphql")
+)
+
+; '/* css */' or '/*css*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*(css)\\s*\\*\\/")
+  (#set! injection.language "css")
+)

crates/languages/src/typescript/injections.scm 🔗

@@ -124,3 +124,46 @@
       ]
     )))
   (#set! injection.language "css"))
+
+; Parse the contents of strings and tagged template
+; literals with leading ECMAScript comments:
+; '/* html */' or '/*html*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*html\\s*\\*\\/")
+  (#set! injection.language "html")
+)
+
+; '/* sql */' or '/*sql*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*sql\\s*\\*\\/")
+  (#set! injection.language "sql")
+)
+
+; '/* gql */' or '/*gql*/'
+; '/* graphql */' or '/*graphql*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*(gql|graphql)\\s*\\*\\/")
+  (#set! injection.language "graphql")
+)
+
+; '/* css */' or '/*css*/'
+(
+  ((comment) @_ecma_comment [
+    (string (string_fragment) @injection.content)
+    (template_string (string_fragment) @injection.content)
+  ])
+  (#match? @_ecma_comment "^\\/\\*\\s*(css)\\s*\\*\\/")
+  (#set! injection.language "css")
+)