Skip to content

Recover from incomplete ESM using acorn-loose - #528

Open
jp-knj wants to merge 6 commits into
mdx-js:mainfrom
jp-knj:issue-267-investigate-acorn-recovery
Open

Recover from incomplete ESM using acorn-loose#528
jp-knj wants to merge 6 commits into
mdx-js:mainfrom
jp-knj:issue-267-investigate-acorn-recovery

Conversation

@jp-knj

@jp-knj jp-knj commented Mar 18, 2026

Copy link
Copy Markdown

Initial checklist

  • I read the support docs
  • I read the contributing guide
  • I agree to follow the code of conduct
  • I searched issues and discussions and couldn’t find anything or linked relevant results below
  • I made sure the docs are up to date
  • I included tests (or that’s not needed)

Description of changes

Closes #267

Problem

When users type incomplete ESM (e.g. export const x = or import { } from) in an MDX file, the strict acorn parser fails and the language service loses all IntelliSense — completions, diagnostics, and hover information disappear for the entire file until the syntax is corrected.

This is a poor editing experience because ESM is always incomplete while the user is actively typing it.

Approach

Introduce a "hybrid acorn" strategy that uses acorn-loose for ESM parsing and the strict acorn parser (with JSX) for expressions

Long-term perspective

This is a minimal, low-risk first step. Possible future improvements include working with micromark-extension-mdxjs-esm to support pluggable error recovery, reducing the need for patching at the language-service level.

@changeset-bot

changeset-bot Bot commented Mar 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4d350dd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@mdx-js/language-service Patch
@mdx-js/language-server Patch
@mdx-js/typescript-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the 👋 phase/new Post is being triaged automatically label Mar 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hi! It seems you removed the template which we require. Here are our templates (pick the one you want to use and click *raw* to see its source):

I won’t send you any further notifications about this, but I’ll keep on updating this comment, and hide it when done!

Thanks,
— bb

@codecov

codecov Bot commented Mar 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.91%. Comparing base (660067a) to head (4d350dd).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #528      +/-   ##
==========================================
+ Coverage   92.43%   92.91%   +0.48%     
==========================================
  Files          13       13              
  Lines        1982     2117     +135     
==========================================
+ Hits         1832     1967     +135     
  Misses        150      150              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@wooorm

wooorm commented Jun 1, 2026

Copy link
Copy Markdown
Member

I’m curious to hear what others, mostly @remcohaszing for example, think!

I am not that involved with this project. But we’ve had conversations before about the impossibility of some of this 🤔

Introduce a "hybrid acorn" strategy that uses acorn-loose for ESM parsing and the strict acorn parser (with JSX) for expressions

The arguments you use, would also mean that it’s a great idea to do for expressions. Why not?

This is a minimal, low-risk first step. Possible future improvements include working with micromark-extension-mdxjs-esm to support pluggable error recovery, reducing the need for patching at the language-service level.

MDX is a programming language. There are errors. Same with Rust or JS. A syntax error is a real error. We do want crashes when people have syntax errors in JS/Rust/MDX/etc.

Have you looked into putting the code in micromark-extension-mdxjs-esm? Why did you do it here first?

@remcohaszing

Copy link
Copy Markdown
Member

Yes, I have been meaning to look into this, prioritize it even! But the timing was inconvenient for me and this got pushed down my todo list. I appreciate the ping!

This is a really cool feature, but it also need thorough manual testing. I’ll dedicate some proper time this week.

@remcohaszing remcohaszing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s great that this allows the language service to process files further. But these are still syntax errors that need to be propagated to the editor.

In virtual-code.js, we need to iterate the estree for all MDX specific node types. If we find a dummy node using the isDummy function exported from acorn-loose, we should report it as a VFileMessage() on the VirtualMdxCode.

Comment thread packages/language-service/package.json Outdated
Comment on lines +42 to +44
"acorn": "^8.14.0",
"acorn-jsx": "^5.3.0",
"acorn-loose": "^8.5.2",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use loose ranges in the unified ecosystem.

Suggested change
"acorn": "^8.14.0",
"acorn-jsx": "^5.3.0",
"acorn-loose": "^8.5.2",
"acorn": "^8.0.0",
"acorn-jsx": "^5.0.0",
"acorn-loose": "^8.0.0",

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6c33953 fixes, Thanks


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use the same type of assertion as other tests?

assert.deepEqual(code, [
  // …
])

I find it helpful to have a full picture of what the virtual code looks like.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bfc2182 fixes, Thanks

Comment on lines +5336 to +5343
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"'))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use the same type of assertion as other tests?

assert.deepEqual(code, [
  // …
])

I find it helpful to have a full picture of what the virtual code looks like.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bfc2182 fixes, Thanks

Comment on lines +5300 to +5302
// 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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use the same type of assertion as other tests?

assert.deepEqual(code, [
  // …
])

I find it helpful to have a full picture of what the virtual code looks like.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bfc2182 fixes, Thanks

@jp-knj
jp-knj force-pushed the issue-267-investigate-acorn-recovery branch 6 times, most recently from cf1a2d6 to 4973d36 Compare June 17, 2026 15:45
@jp-knj
jp-knj force-pushed the issue-267-investigate-acorn-recovery branch from 4973d36 to bfc2182 Compare June 17, 2026 16:03
Comment thread package.json
Comment on lines +7 to +12
"overrides": {
"vscode-markdown-languageservice": "0.5.0-alpha.11",
"vscode-languageserver": "9.0.1",
"vscode-languageserver-protocol": "3.17.5",
"vscode-languageserver-types": "3.17.5"
},

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without them,test-types fails in CI.

@wooorm
wooorm requested a review from remcohaszing June 18, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👋 phase/new Post is being triaged automatically

Development

Successfully merging this pull request may close these issues.

Support loose (invalid) JavaScript

3 participants