Merge branch 'stack-reconcile' into stack-final

This commit is contained in:
Neil
2026-09-18 19:43:26 -07:00
5 changed files with 31 additions and 8 deletions
@@ -123,6 +123,9 @@ export function createMarkdownCodeSpanScanner(content: string): MarkdownCodeSpan
}
previousWasBoundary = isBoundary || endsWithLine
previousQuoteDepth = quoteDepth
if (isBoundary) {
return
}
let index = lineStart
while (index < lineEnd) {
@@ -9,7 +9,7 @@ export function stripMarkdownCode(content: string): string {
let rangeIndex = 0
let sanitized = ''
forEachMarkdownLine(content, (lineStart, lineEnd) => {
forEachMarkdownLine(content, (lineStart, lineEnd, nextLineStart) => {
while (rangeIndex < ranges.length && ranges[rangeIndex][1] <= lineStart) {
rangeIndex += 1
}
@@ -21,7 +21,7 @@ export function stripMarkdownCode(content: string): string {
}
sanitized += content.slice(cursor, lineEnd)
if (lineEnd < content.length) {
sanitized += '\n'
sanitized += content.slice(lineEnd, nextLineStart)
}
})
@@ -32,7 +32,7 @@ describe('createMarkdownFenceTracker', () => {
['exact-length closer', '```\nbody\n```'],
['longer closer', '```\nbody\n`````'],
['closer with trailing whitespace', '```\nbody\n``` \t'],
['indented closer', '```\nbody\n ```'],
['indented closer', '```\nbody\n ```'],
['tilde closer', '~~~\nbody\n~~~']
])('closes on a %s', (_name, content) => {
expect(insideFenceLines(content)).toEqual(['body'])
@@ -6,14 +6,12 @@ export type MarkdownFenceTracker = {
consume: (line: string) => boolean
}
// A top-level opener may be indented by at most three spaces. Closers are matched
// separately below because marked allows them to be indented within the fence.
// Top-level fence delimiters may be indented by at most three spaces.
const FENCE_LINE = /^[ ]{0,3}(`{3,}|~{3,})/
const INDENTED_FENCE_LINE = /^[ \t]*(`{3,}|~{3,})/
// marked lets a closer trail a run of fence characters, e.g. ```~~~ closes a ``` block.
const CLOSING_FENCE_SUFFIX = /^[~`]*[ \t\r]*$/
/** Tracks CommonMark fenced code blocks across the lines of one document. */
/** Tracks fenced blocks using the editor parser's closing rules. */
export function createMarkdownFenceTracker(): MarkdownFenceTracker {
let marker = ''
let length = 0
@@ -23,7 +21,7 @@ export function createMarkdownFenceTracker(): MarkdownFenceTracker {
return length > 0
},
consume(line: string): boolean {
const match = length > 0 ? INDENTED_FENCE_LINE.exec(line) : FENCE_LINE.exec(line)
const match = FENCE_LINE.exec(line)
if (!match) {
return false
}
@@ -1,3 +1,6 @@
import { marked } from 'marked'
import { stripMarkdownCode } from './markdown-code-stripping'
import { getMarkdownFenceRanges } from './markdown-fence-scanner'
import { describe, expect, it } from 'vitest'
import { markdownCodeSpanRanges, markdownFenceRanges } from './markdown-scan-ranges'
import { createMarkdownCodeSpanScanner } from './markdown-code-span-scanner'
@@ -19,3 +22,22 @@ describe('standalone Markdown boundaries', () => {
expect(markdownFenceRanges(source)).toEqual([[0, 13]])
})
})
it('does not expose inline spans inside fenced content', () => {
const source = '```\n`x`\n```'
expect(createMarkdownCodeSpanScanner(source).findSpanEnd(4)).toBeNull()
})
it.each(['\n', '\r\n', '\r'])('retains %j terminators while stripping code', (eol) => {
expect(stripMarkdownCode(['before', '`code`', 'after'].join(eol))).toBe(
['before', '', 'after'].join(eol)
)
})
it.each(['```~~~', ' ```'])('matches the parser on the closer %j', (closer) => {
const source = `\`\`\`\ncode\n${closer}\n<div>after</div>\n`
const code = marked.lexer(source)[0]
expect(code.type).toBe('code')
expect(getMarkdownFenceRanges(source)).toEqual([[0, code.raw.length]])
expect(stripMarkdownCode(source).includes('<div>after</div>')).toBe(closer === '```~~~')
})