Repository: shikijs/shiki (TypeScript)

Issue:
Using transformerRenderWhitespace together with the structure "inline" option throws an error
### 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

Hi,

When using `transformerRenderWhitespace` together with the `structure: "inline"` setting I receive this error:

```javascript
TypeError: Cannot read properties of undefined (reading 'forEach')
    at Object.root (http://localhost:3000/node_modules/.vite/deps/@shikijs_transformers.js?v=82b8e6ae:538:21)
    at tokensToHast (http://localhost:3000/node_modules/.vite/deps/chunk-EBWDM6ZB.js?v=82b8e6ae:1315:92)
    at codeToHast (http://localhost:3000/node_modules/.vite/deps/chunk-EBWDM6ZB.js?v=82b8e6ae:1188:10)
    at codeToHtml (http://localhost:3000/node_modules/.vite/deps/chunk-EBWDM6ZB.js?v=82b8e6ae:1400:27)
    at Object.codeToHtml (http://localhost:3000/node_modules/.vite/deps/chunk-EBWDM6ZB.js?v=82b8e6ae:1842:37)
    at Proxy.highlightCode (http://localhost:3000/lib/markdownHelpers.ts:89:22)
    at Proxy._sfc_render (http://localhost:3000/components/CodeBlock.vue:37:23)
    at renderComponentRoot (http://localhost:3000/node_modules/.vite/deps/chunk-IWYC5R2O.js?v=82b8e6ae:8581:17)
    at ReactiveEffect.componentUpdateFn [as fn] (http://localhost:3000/node_modules/.vite/deps/chunk-IWYC5R2O.js?v=82b8e6ae:7403:46)
    at ReactiveEffect.run (http://localhost:3000/node_modules/.vite/deps/chunk-IWYC5R2O.js?v=82b8e6ae:481:19)
```

Here is my test setup:

```typescript
const code = `
  const foo = 'bar'
  console.log(foo)
  `;

appEl.innerHTML = await codeToHtml(code, {
  lang: 'js',
  theme: 'nord',
  structure: "inline", // try removing this
  transformers: [
    transformerRenderWhitespace(),
  ],
});
```

Packages:
```json
{
  "name": "shiki-bug2",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "typescript": "~5.7.2",
    "vite": "^6.2.0"
  },
  "dependencies": {
    "@shikijs/transformers": "^3.1.0",
    "shiki": "^3.1.0"
  }
}
```

Repro [over here](https://stackblitz.com/~/github.com/marpe/shiki-structure-whitespace-bug)

### Reproduction

https://stackblitz.com/~/github.com/marpe/shiki-structure-whitespace-bug

### Contributes

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


Discussion:
I believe the issue lies here:

https://github.com/shikijs/shiki/blob/ac6bea51b807a82e0a5c9a83796c3abdda0fc293/packages/transformers/src/transformers/render-whitespace.ts#L45-L47

Since, for `structure: 'inline'` the output isn't wrapped in either `<pre>` nor `<code>` tags, this will error.

I'm going to try to submit a fix.



Patch:
diff --git a/packages/transformers/src/transformers/render-whitespace.ts b/packages/transformers/src/transformers/render-whitespace.ts
index a9404a292..7a21eef73 100644
--- a/packages/transformers/src/transformers/render-whitespace.ts
+++ b/packages/transformers/src/transformers/render-whitespace.ts
@@ -43,9 +43,9 @@ export function transformerRenderWhitespace(
     // We use `root` hook here to ensure it runs after all other transformers
     root(root) {
       const pre = root.children[0] as Element
-      const code = pre.children[0] as Element
+      const code = pre.tagName === 'pre' ? pre.children[0] as Element : { children: [root] }
       code.children.forEach((line) => {
-        if (line.type !== 'element')
+        if (line.type !== 'element' && line.type !== 'root')
           return
         const elements = line.children.filter(token => token.type === 'element')
         const last = elements.length - 1


Test patch:
diff --git a/packages/transformers/test/whitespace-inline.test.ts b/packages/transformers/test/whitespace-inline.test.ts
new file mode 100644
index 000000000..5da08b954
--- /dev/null
+++ b/packages/transformers/test/whitespace-inline.test.ts
@@ -0,0 +1,57 @@
+import { createHighlighter } from 'shiki'
+import { expect, it } from 'vitest'
+import { transformerRenderWhitespace } from '../src/transformers/render-whitespace'
+
+it('transformerRenderWhitespace', async () => {
+  using shiki = await createHighlighter({
+    themes: ['vitesse-dark', 'vitesse-light', 'nord'],
+    langs: ['typescript'],
+  })
+
+  const transformer = transformerRenderWhitespace()
+
+  const code = `
+    const a = Math.random() > 0.5 ? 1 : \`foo\`
+  `.trim()
+
+  const result = shiki.codeToHtml(code, {
+    lang: 'typescript',
+    themes: {
+      dark: 'vitesse-dark',
+      light: 'vitesse-light',
+      nord: 'nord',
+    },
+    defaultColor: false,
+    transformers: [transformer],
+    structure: 'inline',
+  })
+
+  expect(result.replace(/<span/g, '\n<span'))
+    .toMatchInlineSnapshot(`
+      "
+      <span style="--shiki-dark:#CB7676;--shiki-light:#AB5959;--shiki-nord:#81A1C1">const</span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#BD976A;--shiki-light:#B07D48;--shiki-nord:#D8DEE9">a</span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#666666;--shiki-light:#999999;--shiki-nord:#81A1C1">=</span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#BD976A;--shiki-light:#B07D48;--shiki-nord:#D8DEE9">Math</span>
+      <span style="--shiki-dark:#666666;--shiki-light:#999999;--shiki-nord:#ECEFF4">.</span>
+      <span style="--shiki-dark:#80A665;--shiki-light:#59873A;--shiki-nord:#88C0D0">random</span>
+      <span style="--shiki-dark:#666666;--shiki-light:#999999;--shiki-nord:#D8DEE9FF">()</span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#666666;--shiki-light:#999999;--shiki-nord:#81A1C1">></span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#4C9A91;--shiki-light:#2F798A;--shiki-nord:#B48EAD">0.5</span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#CB7676;--shiki-light:#AB5959;--shiki-nord:#81A1C1">?</span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#4C9A91;--shiki-light:#2F798A;--shiki-nord:#B48EAD">1</span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#CB7676;--shiki-light:#AB5959;--shiki-nord:#81A1C1">:</span>
+      <span class="space"> </span>
+      <span style="--shiki-dark:#C98A7D77;--shiki-light:#B5695977;--shiki-nord:#ECEFF4">\`</span>
+      <span style="--shiki-dark:#C98A7D;--shiki-light:#B56959;--shiki-nord:#A3BE8C">foo</span>
+      <span style="--shiki-dark:#C98A7D77;--shiki-light:#B5695977;--shiki-nord:#ECEFF4">\`</span>"
+    `)
+})