Skip to content

Commit b2e9706

Browse files
authored
Explicitly handle JSX tags in order (#486)
Virtual code generation from estree expressions was based on an assumption of the order in which JSXIdentifier nodes are visited. `estree-walker` visits the nodes in property order. The parser generates AST properties in an order that broke our code generation. As a result, the string `_components.` was injected in an incorrect position in the virtual code, yielding syntax errors. This is now resolved by explicitly handling `JSXElement` nodes in both the enter and leave methods, instead of just handling `JSXIdentifier` nodes. Closes #485
1 parent d73050c commit b2e9706

3 files changed

Lines changed: 67 additions & 17 deletions

File tree

.changeset/sweet-rabbits-lick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@mdx-js/language-service': patch
3+
---
4+
5+
Fix bug in virtual code generation for JSX closing elements

packages/language-service/lib/virtual-code.js

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @import {CodeMapping, VirtualCode} from '@volar/language-service'
3-
* @import {ExportDefaultDeclaration, Program} from 'estree'
3+
* @import {ExportDefaultDeclaration, JSXClosingElement, JSXOpeningElement, Program} from 'estree-jsx'
44
* @import {Nodes, Root} from 'mdast'
55
* @import {MdxjsEsm} from 'mdast-util-mdxjs-esm'
66
* @import {IScriptSnapshot} from 'typescript'
@@ -449,18 +449,32 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
449449
function processJsxExpression(program, lastIndex) {
450450
let newIndex = lastIndex
451451
let functionNesting = 0
452+
453+
/**
454+
* @param {JSXClosingElement | JSXOpeningElement} node
455+
* @returns {undefined}
456+
*/
457+
function processJsxTag(node) {
458+
const {name} = node
459+
460+
if (name.type !== 'JSXIdentifier') {
461+
return
462+
}
463+
464+
if (!isInjectableComponent(name.name, variables)) {
465+
return
466+
}
467+
468+
jsx =
469+
addOffset(jsxMapping, mdx, jsx, newIndex, name.start) + '_components.'
470+
newIndex = name.start
471+
}
472+
452473
walk(program, {
453474
enter(node) {
454475
switch (node.type) {
455-
case 'JSXIdentifier': {
456-
if (!isInjectableComponent(node.name, variables)) {
457-
return
458-
}
459-
460-
jsx =
461-
addOffset(jsxMapping, mdx, jsx, newIndex, node.start) +
462-
'_components.'
463-
newIndex = node.start
476+
case 'JSXElement': {
477+
processJsxTag(node.openingElement)
464478
break
465479
}
466480

@@ -500,6 +514,16 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
500514
break
501515
}
502516

517+
case 'JSXElement': {
518+
const {closingElement} = node
519+
520+
if (closingElement) {
521+
processJsxTag(closingElement)
522+
}
523+
524+
break
525+
}
526+
503527
default:
504528
}
505529
}

packages/language-service/test/language-plugin.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,8 +1752,12 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
17521752
'{<div>{""}</div>}',
17531753
'{<Injected />}',
17541754
'{<Injected>{""}</Injected>}',
1755+
'{<Injected><Injected>{""}</Injected></Injected>}',
17551756
'{<Local />}',
1756-
'{<Local>{""}</Local>}'
1757+
'{<Local>{""}</Local>}',
1758+
'{<Local><Local>{""}</Local></Local>}',
1759+
'{<Local><Injected>{""}</Injected></Local>}',
1760+
'{<Injected><Local>{""}</Local></Injected>}'
17571761
)
17581762

17591763
const code = plugin.createVirtualCode?.('/test.mdx', 'mdx', snapshot, {
@@ -1799,9 +1803,18 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
17991803
}
18001804
},
18011805
{
1802-
sourceOffsets: [28, 38, 56, 58, 71, 73, 88, 99, 111],
1803-
generatedOffsets: [843, 857, 879, 893, 910, 924, 951, 966, 982],
1804-
lengths: [9, 17, 2, 12, 2, 15, 10, 11, 21],
1806+
sourceOffsets: [
1807+
28, 38, 56, 58, 71, 73, 88, 99, 101, 111, 126, 137, 148, 160, 182,
1808+
219, 228, 243, 262, 264, 294
1809+
],
1810+
generatedOffsets: [
1811+
843, 857, 879, 893, 910, 924, 951, 966, 980, 1002, 1029, 1052, 1067,
1812+
1083, 1109, 1150, 1171, 1198, 1221, 1235, 1277
1813+
],
1814+
lengths: [
1815+
9, 17, 2, 12, 2, 15, 10, 2, 10, 15, 11, 10, 11, 21, 36, 9, 15, 18,
1816+
2, 30, 10
1817+
],
18051818
data: {
18061819
completion: true,
18071820
format: false,
@@ -1845,8 +1858,12 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
18451858
' {<div>{""}</div>}',
18461859
' {<_components.Injected />}',
18471860
' {<_components.Injected>{""}</_components.Injected>}',
1861+
' {<_components.Injected><_components.Injected>{""}</_components.Injected></_components.Injected>}',
18481862
' {<Local />}',
18491863
' {<Local>{""}</Local>}',
1864+
' {<Local><Local>{""}</Local></Local>}',
1865+
' {<Local><_components.Injected>{""}</_components.Injected></Local>}',
1866+
' {<_components.Injected><Local>{""}</Local></_components.Injected>}',
18501867
' </>',
18511868
'}',
18521869
'',
@@ -1870,9 +1887,9 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
18701887
languageId: 'markdown',
18711888
mappings: [
18721889
{
1873-
sourceOffsets: [26, 37, 55, 70, 98, 110],
1874-
generatedOffsets: [0, 9, 17, 25, 33, 41],
1875-
lengths: [2, 1, 1, 1, 1, 1],
1890+
sourceOffsets: [26, 37, 55, 70, 98, 147, 159, 181, 218, 261],
1891+
generatedOffsets: [0, 9, 17, 25, 33, 41, 49, 57, 65, 73],
1892+
lengths: [2, 1, 1, 1, 1, 1, 1, 1, 1, 1],
18761893
data: {
18771894
completion: true,
18781895
format: false,
@@ -1891,6 +1908,10 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
18911908
'<!---->',
18921909
'<!---->',
18931910
'<!---->',
1911+
'<!---->',
1912+
'<!---->',
1913+
'<!---->',
1914+
'<!---->',
18941915
'<!---->'
18951916
)
18961917
}

0 commit comments

Comments
 (0)