Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/language-server/test/diagnostics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ test('parse errors', async () => {
kind: 'full',
items: [
{
code: 'micromark-extension-mdxjs-esm:acorn',
code: 'micromark-extension-mdxjs-esm:non-esm',
codeDescription: {
href: 'https://github.com/micromark/micromark-extension-mdxjs-esm#could-not-parse-importexports-with-acorn'
href: 'https://github.com/micromark/micromark-extension-mdxjs-esm#unexpected-type-in-code-only-importexports-are-supported'
},
data: {
documentUri: String(
Expand All @@ -50,10 +50,11 @@ test('parse errors', async () => {
uri: fixtureUri('node16/syntax-error.mdx'),
version: 0
},
message: 'Could not parse import/exports with acorn',
message:
'Unexpected `ExpressionStatement` in code: only import/exports are supported',
range: {
end: {
character: 7,
character: 10,
line: 0
},
start: {
Expand Down
14 changes: 13 additions & 1 deletion packages/language-service/lib/language-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@
* @import {VirtualCodePlugin} from './plugins/plugin.js'
*/

import {Parser} from 'acorn'
import acornJsx from 'acorn-jsx'
import {LooseParser} from 'acorn-loose'
import remarkMdx from 'remark-mdx'
import remarkParse from 'remark-parse'
import {unified} from 'unified'
import {VirtualMdxCode} from './virtual-code.js'

const StrictParser = Parser.extend(acornJsx())

const hybridAcorn = {
parse: LooseParser.parse.bind(LooseParser),
parseExpressionAt: StrictParser.parseExpressionAt.bind(StrictParser)
}

/**
* Create a [Volar](https://volarjs.dev) language plugin to support MDX.
*
Expand All @@ -32,7 +42,9 @@ export function createMdxLanguagePlugin(
checkMdx = false,
jsxImportSource = 'react'
) {
const processor = unified().use(remarkParse).use(remarkMdx)
const processor = unified()
.use(remarkParse)
.use(remarkMdx, {acorn: hybridAcorn})
if (remarkPlugins) {
processor.use(remarkPlugins)
}
Expand Down
3 changes: 3 additions & 0 deletions packages/language-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
"@types/mdast": "^4.0.0",
"@types/unist": "^3.0.0",
"@volar/language-service": "~2.4.0",
"acorn": "^8.14.0",
"acorn-jsx": "^5.3.0",
"acorn-loose": "^8.5.2",
"estree-util-scope": "^1.0.0",
"estree-walker": "^3.0.0",
"mdast-util-mdxjs-esm": "^2.0.0",
Expand Down
57 changes: 57 additions & 0 deletions packages/language-service/test/language-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5286,6 +5286,63 @@ test('rehype-mdx-title matching rank', () => {
])
})

test('create virtual code w/ invalid ESM recovery', () => {
const plugin = createMdxLanguagePlugin()

const snapshot = snapshotFromLines('export const x =', '', 'Hello {true}', '')

const code = plugin.createVirtualCode?.('/test.mdx', 'mdx', snapshot, {
getAssociatedScript: () => undefined
})

assert.ok(code instanceof VirtualMdxCode)
assert.ifError(code.error)
// The key assertion: embedded code has non-empty mappings despite broken ESM.
// Before acorn-loose recovery, this would have empty mappings ([]).
assert.ok(code.embeddedCodes[0].mappings.length > 0)
})

test('create virtual code w/ incomplete import recovery', () => {
const plugin = createMdxLanguagePlugin()

const snapshot = snapshotFromLines('import { } from', '', '# Hello', '')

const code = plugin.createVirtualCode?.('/test.mdx', 'mdx', snapshot, {
getAssociatedScript: () => undefined
})

assert.ok(code instanceof VirtualMdxCode)
assert.ifError(code.error)
assert.ok(code.embeddedCodes[0].mappings.length > 0)
})

test('create virtual code w/ valid ESM after recovery support', () => {
const plugin = createMdxLanguagePlugin()

const snapshot = snapshotFromLines(
'import {Planet} from "./Planet.js"',
'export const greeting = "hello"',
'',
'Hello {greeting}',
''
)

const code = plugin.createVirtualCode?.('/test.mdx', 'mdx', snapshot, {
getAssociatedScript: () => undefined
})

assert.ok(code instanceof VirtualMdxCode)
assert.ifError(code.error)
assert.ok(code.embeddedCodes[0].mappings.length > 0)
// Verify the generated JS contains both the import and export
const jsText = code.embeddedCodes[0].snapshot.getText(
0,
code.embeddedCodes[0].snapshot.getLength()
)
assert.ok(jsText.includes('import {Planet} from "./Planet.js"'))
assert.ok(jsText.includes('export const greeting = "hello"'))
})

/**
* @param {string[]} lines
* @returns {typescript.IScriptSnapshot}
Expand Down
Loading