Repository: shikijs/shiki (ts)

Issue:
Fix `transformerRemoveNotationEscape` incompatible with rehype plugin
### 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

I found that the current implementation of `transformerRemoveNotationEscape` doesn't support rehype plugin usage due to `postprocess`, I've made a similar transformer for my project which utilitizes `line()` instead and works for rehype.

I would like to contribute a fix

### Reproduction

https://github.com/fuma-nama/shiki-reproduce-1

### Contributes

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

Pull request description:
feat(transformers): `transformerRemoveNotationEscape` support rehype plugin usage


<!-- 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

Support usage with `rehypeShiki`, which doesn't support `postprocess` and some other transformer hooks.

This is possible by using `line` hook instead.

### Linked Issues

fixes #1010 

### Additional context

<!-- e.g. is there anything you'd like reviewers to focus on? -->


Patch:
diff --git a/packages/core/vendor/vscode-textmate b/packages/core/vendor/vscode-textmate
new file mode 160000
index 00000000..8b07a3c2
--- /dev/null
+++ b/packages/core/vendor/vscode-textmate
@@ -0,0 +1,1 @@
+Subproject commit 8b07a3c2be6fe4674f9ce6bba6d5c962a7f50df5
diff --git a/packages/rehype/src/index.ts b/packages/rehype/src/index.ts
index 551160dd..666efa2a 100644
--- a/packages/rehype/src/index.ts
+++ b/packages/rehype/src/index.ts
@@ -16,6 +16,11 @@ export type RehypeShikiOptions = RehypeShikiCoreOptions
      * @default Object.keys(bundledLanguages)
      */
     langs?: Array<LanguageInput | BuiltinLanguage>
+    /**
+     * Alias of languages
+     * @example { 'my-lang': 'javascript' }
+     */
+    langAlias?: Record<string, string>
   }
 
 const rehypeShiki: Plugin<[RehypeShikiOptions], Root> = function (
@@ -23,6 +28,7 @@ const rehypeShiki: Plugin<[RehypeShikiOptions], Root> = function (
 ) {
   const themeNames = ('themes' in options ? Object.values(options.themes) : [options.theme]).filter(Boolean) as BuiltinTheme[]
   const langs = options.langs || Object.keys(bundledLanguages)
+  const langAlias = options.langAlias || {}
 
   let getHandler: Promise<any>
 
@@ -31,6 +37,7 @@ const rehypeShiki: Plugin<[RehypeShikiOptions], Root> = function (
       getHandler = getSingletonHighlighter({
         themes: themeNames,
         langs,
+        langAlias,
       })
         .then(highlighter => rehypeShikiFromHighlighter.call(this, highlighter, options))
     }
diff --git a/packages/transformers/src/transformers/remove-notation-escape.ts b/packages/transformers/src/transformers/remove-notation-escape.ts
index d696a198..a20a7072 100644
--- a/packages/transformers/src/transformers/remove-notation-escape.ts
+++ b/packages/transformers/src/transformers/remove-notation-escape.ts
@@ -1,4 +1,5 @@
 import type { ShikiTransformer } from '@shikijs/types'
+import type { ElementContent } from 'hast'
 
 /**
  * Remove notation escapes.
@@ -8,8 +9,20 @@ import type { ShikiTransformer } from '@shikijs/types'
 export function transformerRemoveNotationEscape(): ShikiTransformer {
   return {
     name: '@shikijs/transformers:remove-notation-escape',
-    postprocess(code) {
-      return code.replace(/\[\\!code/g, '[!code')
+    code(hast) {
+      function replace(node: ElementContent): void {
+        if (node.type === 'text') {
+          node.value = node.value.replace('\\[!code', '[!code')
+        }
+        else if ('children' in node) {
+          for (const child of node.children) {
+            replace(child)
+          }
+        }
+      }
+
+      replace(hast)
+      return hast
     },
   }
 }


Test patch:
diff --git a/packages/rehype/test/fixtures/lang-alias.md b/packages/rehype/test/fixtures/lang-alias.md
new file mode 100644
index 00000000..d0d16822
--- /dev/null
+++ b/packages/rehype/test/fixtures/lang-alias.md
@@ -0,0 +1,7 @@
+```mylang
+console.log("Hi")
+```
+
+```mylang2
+console.log("Hi")
+```
diff --git a/packages/rehype/test/fixtures/lang-alias.out.html b/packages/rehype/test/fixtures/lang-alias.out.html
new file mode 100644
index 00000000..b368484d
--- /dev/null
+++ b/packages/rehype/test/fixtures/lang-alias.out.html
@@ -0,0 +1,2 @@
+<pre class="shiki vitesse-light" style="background-color:#ffffff;color:#393a34" tabindex="0"><code class="language-mylang"><span class="line"><span style="color:#B07D48">console</span><span style="color:#999999">.</span><span style="color:#59873A">log</span><span style="color:#999999">(</span><span style="color:#B5695977">"</span><span style="color:#B56959">Hi</span><span style="color:#B5695977">"</span><span style="color:#999999">)</span></span></code></pre>
+<pre class="shiki vitesse-light" style="background-color:#ffffff;color:#393a34" tabindex="0"><code class="language-mylang2"><span class="line"><span style="color:#B07D48">console</span><span style="color:#999999">.</span><span style="color:#59873A">log</span><span style="color:#999999">(</span><span style="color:#B5695977">"</span><span style="color:#B56959">Hi</span><span style="color:#B5695977">"</span><span style="color:#999999">)</span></span></code></pre>
\ No newline at end of file
diff --git a/packages/rehype/test/index.test.ts b/packages/rehype/test/index.test.ts
index 6eabc51e..8a456e89 100644
--- a/packages/rehype/test/index.test.ts
+++ b/packages/rehype/test/index.test.ts
@@ -7,6 +7,24 @@ import { unified } from 'unified'
 import { expect, it } from 'vitest'
 import rehypeShiki from '../src'
 
+it('lang-alias', async () => {
+  const file = await unified()
+    .use(remarkParse)
+    .use(remarkRehype)
+    .use(rehypeShiki, {
+      theme: 'vitesse-light',
+      addLanguageClass: true,
+      langAlias: {
+        mylang: 'javascript',
+        mylang2: 'js', // nested alias
+      },
+    })
+    .use(rehypeStringify)
+    .process(await fs.readFile(new URL('./fixtures/lang-alias.md', import.meta.url)))
+
+  await expect(file.toString()).toMatchFileSnapshot('./fixtures/lang-alias.out.html')
+})
+
 it('run', async () => {
   const file = await unified()
     .use(remarkParse)