Skip to content

Commit 8690a6a

Browse files
authored
Dedent virtual markdown chunks (#370)
MDX allows to use indentation. In regular markdown however, indented code represents a code block. To work around this, all virtual markdown chunks are now dedented. This may change the semantic meaning of certain markdown, but it doesn’t affect editor features as far as I’m aware.
1 parent f7ac84b commit 8690a6a

File tree

2 files changed

+131
-10
lines changed

2 files changed

+131
-10
lines changed

packages/language-service/lib/language-module.js

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,24 @@ function addOffset(mapping, source, generated, startOffset, endOffset) {
9696
return generated
9797
}
9898

99-
mapping.sourceOffsets.push(startOffset)
100-
mapping.generatedOffsets.push(generated.length)
101-
mapping.lengths.push(endOffset - startOffset)
99+
const length = endOffset - startOffset
100+
const previousSourceOffset = mapping.sourceOffsets.at(-1)
101+
const previousGeneratedOffset = mapping.generatedOffsets.at(-1)
102+
const previousLength = mapping.lengths.at(-1)
103+
if (
104+
previousSourceOffset !== undefined &&
105+
previousGeneratedOffset !== undefined &&
106+
previousLength !== undefined &&
107+
previousSourceOffset + previousLength === startOffset &&
108+
previousGeneratedOffset + previousLength === generated.length
109+
) {
110+
mapping.lengths[mapping.lengths.length - 1] += length
111+
} else {
112+
mapping.sourceOffsets.push(startOffset)
113+
mapping.generatedOffsets.push(generated.length)
114+
mapping.lengths.push(length)
115+
}
116+
102117
return generated + source.slice(startOffset, endOffset)
103118
}
104119

@@ -323,13 +338,30 @@ function getVirtualFiles(fileName, snapshot, ts, processor) {
323338
*/
324339
function updateMarkdownFromOffsets(startOffset, endOffset) {
325340
if (nextMarkdownSourceStart !== startOffset) {
326-
markdown = addOffset(
327-
markdownMapping,
328-
mdx,
329-
markdown,
330-
nextMarkdownSourceStart,
331-
startOffset
332-
)
341+
const slice = mdx.slice(nextMarkdownSourceStart, startOffset)
342+
for (const match of slice.matchAll(/^[\t ]*(.*\r?\n?)/gm)) {
343+
if (match.index === undefined) {
344+
continue
345+
}
346+
347+
const [line, lineContent] = match
348+
if (line.length === 0) {
349+
continue
350+
}
351+
352+
const lineEnd = nextMarkdownSourceStart + match.index + line.length
353+
let lineStart = lineEnd - lineContent.length
354+
if (
355+
match.index === 0 &&
356+
nextMarkdownSourceStart !== 0 &&
357+
mdx[lineStart - 1] !== '\n'
358+
) {
359+
lineStart = nextMarkdownSourceStart + match.index
360+
}
361+
362+
markdown = addOffset(markdownMapping, mdx, markdown, lineStart, lineEnd)
363+
}
364+
333365
if (startOffset !== endOffset) {
334366
markdown += '<!---->'
335367
}

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

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,95 @@ test('create virtual file w/ mdxTextExpression', () => {
15821582
})
15831583
})
15841584

1585+
test('create virtual file w/ dedented markdown content', () => {
1586+
const module = getLanguageModule(typescript)
1587+
1588+
const snapshot = snapshotFromLines(
1589+
' | Language |',
1590+
' | --- |',
1591+
' | MDX |',
1592+
' | JavaScript |',
1593+
'| TypeScript |'
1594+
)
1595+
1596+
const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
1597+
1598+
assert.deepEqual(file, {
1599+
fileName: '/test.mdx',
1600+
languageId: 'mdx',
1601+
mappings: [
1602+
{
1603+
sourceOffsets: [0],
1604+
generatedOffsets: [0],
1605+
lengths: [81],
1606+
data: {
1607+
completion: true,
1608+
format: true,
1609+
navigation: true,
1610+
semantic: true,
1611+
structure: true,
1612+
verification: true
1613+
}
1614+
}
1615+
],
1616+
snapshot,
1617+
embeddedFiles: [
1618+
{
1619+
embeddedFiles: [],
1620+
fileName: '/test.mdx.jsx',
1621+
languageId: 'javascriptreact',
1622+
typescript: {
1623+
scriptKind: 2
1624+
},
1625+
mappings: [],
1626+
snapshot: snapshotFromLines(
1627+
'',
1628+
'/**',
1629+
' * Render the MDX contents.',
1630+
' *',
1631+
' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
1632+
' * The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
1633+
' */',
1634+
'export default function MDXContent(props) {',
1635+
" return <><>{''}</></>",
1636+
'}',
1637+
'',
1638+
'// @ts-ignore',
1639+
'/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
1640+
''
1641+
)
1642+
},
1643+
{
1644+
embeddedFiles: [],
1645+
fileName: '/test.mdx.md',
1646+
languageId: 'markdown',
1647+
mappings: [
1648+
{
1649+
sourceOffsets: [5, 19, 39, 52],
1650+
generatedOffsets: [0, 13, 21, 29],
1651+
lengths: [13, 8, 8, 29],
1652+
data: {
1653+
completion: true,
1654+
format: false,
1655+
navigation: true,
1656+
semantic: true,
1657+
structure: true,
1658+
verification: true
1659+
}
1660+
}
1661+
],
1662+
snapshot: snapshotFromLines(
1663+
'| Language |',
1664+
'| --- |',
1665+
'| MDX |',
1666+
'| JavaScript |',
1667+
'| TypeScript |'
1668+
)
1669+
}
1670+
]
1671+
})
1672+
})
1673+
15851674
test('create virtual file w/ syntax error', () => {
15861675
const module = getLanguageModule(typescript)
15871676

0 commit comments

Comments
 (0)