Repository: shikijs/shiki (ts)

Issue:
Astro syntax highlighting issue with curly braces
### Validations

- [X] Follow our [Code of Conduct](https://github.com/shikijs/shiki/blob/main/CODE_OF_CONDUCT.md)
- [X] Read the [Contributing Guide](https://github.com/shikijs/shiki/blob/main/CONTRIBUTING.md).
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.

### Describe the bug

When I use curly braces to interpolate a value in an Astro template, the following closing HTML element doesn't seem to be highlighted properly.
![Screenshot 2024-11-07 at 8 02 05 PM](https://github.com/user-attachments/assets/1d6501e6-b07c-4929-9cdd-b6583a12301e)

This doesn't seem to happen when I try the same thing on https://textmate-grammars-themes.netlify.app/

### Reproduction

https://shiki.style/

### Contributes

- [ ] I am willing to submit a PR to fix this issue
- [ ] I am willing to submit a PR with failing tests

Pull request description:
fix(shiki): astro partial syntax highlighting error
<!-- DO NOT IGNORE THE TEMPLATE!

Thank you for contributing!

Before submitting the PR, please make sure you do the following:

- Read the [Contributing Guide](https://github.com/shikijs/shiki/blob/main/CONTRIBUTING.md).
- Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
- Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).
- Ideally, include relevant tests that fail without this PR but pass with it.

-->

### Description

<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->

Personally, I think that unlike Vue, Astro should not include 'tsx' in embeddedLangsLazy, as doing so causes the syntax highlighting issue with curly braces mentioned in issue #830.

### Linked Issues
fixes #830

### Additional context

#### before fixed
![1750297346512](https://github.com/user-attachments/assets/e8b9166a-09f9-4e88-a386-51ee103beade)

#### after fixed
![1750297478751](https://github.com/user-attachments/assets/6c889ee0-9437-4aaf-b413-46cb3cecbe8f)



<!-- e.g. is there anything you'd like reviewers to focus on? -->


Patch:
diff --git a/packages/langs/scripts/langs.ts b/packages/langs/scripts/langs.ts
index 71de39a2..fe85bada 100644
--- a/packages/langs/scripts/langs.ts
+++ b/packages/langs/scripts/langs.ts
@@ -18,36 +18,53 @@ export const LANGS_LAZY_EMBEDDED_ALL = {
  * Single-file-component-like languages that have embedded langs
  * For these langs, we exclude the standalone embedded langs from the main bundle
  */
-export const LANGS_LAZY_EMBEDDED_PARTIAL = [
-  'vue',
-  'vue-html',
-  'svelte',
-  'pug',
-  'haml',
-  'astro',
-]
-
-/**
- * Languages to be excluded from SFC langs
- */
-export const STANDALONE_LANGS_EMBEDDED = [
-  'pug',
-  'stylus',
-  'sass',
-  'scss',
-  'coffee',
-  'jsonc',
-  'json5',
-  'yaml',
-  'toml',
-  'scss',
-  'graphql',
-  'markdown',
-  'less',
-  'jsx',
-  'tsx',
-  'ruby',
-]
+export const LANGS_LAZY_EMBEDDED_PARTIAL = {
+  'vue': [
+    'markdown',
+    'pug',
+    'stylus',
+    'sass',
+    'scss',
+    'less',
+    'jsx',
+    'tsx',
+    'coffee',
+    'jsonc',
+    'json5',
+    'yaml',
+    'toml',
+    'graphql',
+  ],
+  'vue-html': [],
+  'svelte': [
+    'coffee',
+    'stylus',
+    'sass',
+    'scss',
+    'less',
+    'pug',
+    'markdown',
+  ],
+  'pug': [
+    'sass',
+    'scss',
+    'stylus',
+    'coffee',
+  ],
+  'haml': [
+    'ruby',
+    'sass',
+    'coffee',
+    'markdown',
+  ],
+  // Since Astro is a extension of MDX, we don't exclude `jsx` here.
+  'astro': [
+    'sass',
+    'scss',
+    'stylus',
+    'less',
+  ],
+} as Record<string, string[]>
 
 export async function loadLangs() {
   const allLangFiles = await fg('*.json', {
@@ -83,9 +100,10 @@ export async function loadLangs() {
       json.embeddedLangsLazy = (json.embeddedLangs || []).filter(i => !includes.includes(i)) || []
       json.embeddedLangs = includes
     }
-    else if (LANGS_LAZY_EMBEDDED_PARTIAL.includes(lang.name)) {
-      json.embeddedLangsLazy = (json.embeddedLangs || []).filter(i => STANDALONE_LANGS_EMBEDDED.includes(i)) || []
-      json.embeddedLangs = (json.embeddedLangs || []).filter(i => !STANDALONE_LANGS_EMBEDDED.includes(i)) || []
+    else if (LANGS_LAZY_EMBEDDED_PARTIAL[lang.name]) {
+      const includes = LANGS_LAZY_EMBEDDED_PARTIAL[lang.name]
+      json.embeddedLangsLazy = includes
+      json.embeddedLangs = (json.embeddedLangs || []).filter(i => !includes.includes(i)) || []
     }
 
     resolvedLangs.push(json)


Test patch:
diff --git a/packages/shiki/test/astro.test.ts b/packages/shiki/test/astro.test.ts
new file mode 100644
index 00000000..dd7b7b3a
--- /dev/null
+++ b/packages/shiki/test/astro.test.ts
@@ -0,0 +1,26 @@
+import { createHighlighter } from 'shiki'
+import { describe, expect, it } from 'vitest'
+
+describe('should', async () => {
+  it('astro syntax highlighting', async () => {
+    const highlighter = await createHighlighter({
+      langs: ['astro'],
+      themes: ['vitesse-dark'],
+    })
+    const code = `---
+const title = "Astro";
+---
+
+<p>{title}</p>
+`
+    expect(highlighter.codeToHtml(code, { lang: 'astro', theme: 'vitesse-dark' })).toMatchInlineSnapshot(`
+      "<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#758575DD">---</span></span>
+      <span class="line"><span style="color:#CB7676">const </span><span style="color:#BD976A">title</span><span style="color:#666666"> =</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">Astro</span><span style="color:#C98A7D77">"</span><span style="color:#666666">;</span></span>
+      <span class="line"><span style="color:#758575DD">---</span></span>
+      <span class="line"></span>
+      <span class="line"><span style="color:#666666">&#x3C;</span><span style="color:#4D9375">p</span><span style="color:#666666">>{</span><span style="color:#BD976A">title</span><span style="color:#666666">}&#x3C;/</span><span style="color:#4D9375">p</span><span style="color:#666666">></span></span>
+      <span class="line"></span></code></pre>"
+    `)
+    highlighter.dispose()
+  })
+})