Skip to content

Commit 70e39bd

Browse files
BetterAndBetterIIfarnabaz
authored andcommitted
fix: skip _ inside attributes when only the attributes plugin is on
auto-close treated {target="_blank"} as italic and appended _. Enable attribute-brace skipping whenever the attributes plugin is registered, without turning on component fences. Co-authored-by: Yuzhong Zhang <BetterAndBetterII@users.noreply.github.com>
1 parent 8801b47 commit 70e39bd

4 files changed

Lines changed: 49 additions & 6 deletions

File tree

packages/comark/src/internal/parse/auto-close/index.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@ import { closeTables } from './table.ts'
1313
* @param options - `frontmatter` completes an unclosed leading frontmatter block.
1414
* `syntax: false` disables Comark component-fence handling (`::` closers and
1515
* props braces), for input parsed without the components plugin.
16+
* `attributes: true` still treats `{...}` as an attribute scope (so `_` / `*`
17+
* inside values are not closed) without enabling component fences.
1618
* @returns The markdown with unclosed syntax closed
1719
*/
18-
export function autoCloseMarkdown(markdown: string, options: { frontmatter?: boolean; syntax?: boolean } = {}): string {
20+
export function autoCloseMarkdown(
21+
markdown: string,
22+
options: { frontmatter?: boolean; syntax?: boolean; attributes?: boolean } = {}
23+
): string {
1924
if (!markdown || markdown === '') return markdown
2025

2126
const syntaxEnabled = options.syntax !== false
27+
const attributesEnabled = options.attributes ?? syntaxEnabled
2228

2329
const lines = markdown.split('\n')
2430
const n = lines.length
@@ -152,7 +158,7 @@ export function autoCloseMarkdown(markdown: string, options: { frontmatter?: boo
152158
// Fix inline markers on last line (skip inside block-level structures)
153159
const lastIdx = n - 1
154160
if (!inFrontmatter && !inBlockMath && lines[lastIdx].trim() !== '$$') {
155-
lines[lastIdx] = closeInlineMarkersLinear(lines[lastIdx], syntaxEnabled)
161+
lines[lastIdx] = closeInlineMarkersLinear(lines[lastIdx], attributesEnabled)
156162
}
157163

158164
let result = lines.join('\n')
@@ -256,9 +262,9 @@ function scanDelimiterRun(line: string, start: number, marker: string) {
256262
* Closes inline markers (*, **, ***, ~~, `, $, $$, [, () on the last line
257263
* without using regex - pure character scanning in O(n) time
258264
*
259-
* With `syntax` false, `{...}` is literal text instead of an attribute scope.
265+
* With `attributesEnabled` false, `{...}` is literal text instead of an attribute scope.
260266
*/
261-
function closeInlineMarkersLinear(line: string, syntax: boolean): string {
267+
function closeInlineMarkersLinear(line: string, attributesEnabled: boolean): string {
262268
const len = line.length
263269
if (len === 0) return line
264270

@@ -318,11 +324,11 @@ function closeInlineMarkersLinear(line: string, syntax: boolean): string {
318324
continue
319325
}
320326

321-
if (syntax && ch === '{' && prevCh !== ' ') {
327+
if (attributesEnabled && ch === '{' && prevCh !== ' ') {
322328
inAttributes++
323329
continue
324330
}
325-
if (syntax && ch === '}') {
331+
if (attributesEnabled && ch === '}') {
326332
if (inAttributes > 0) inAttributes--
327333
continue
328334
}

packages/comark/src/parse.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export function createMarkdownParser<const TPlugins extends readonly ComarkPlugi
144144
autoCloseMarkdown(state.markdown, {
145145
frontmatter: hasPlugin('frontmatter') && opts.streaming,
146146
syntax: hasPlugin('components'),
147+
attributes: hasPlugin('components') || hasPlugin('attributes'),
147148
})
148149
)
149150
}

packages/comark/test/auto-close.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,19 @@ describe('autoCloseMarkdown - syntax option', () => {
775775
expect(autoCloseMarkdown('text{.cls **bold', { syntax: false })).toBe('text{.cls **bold**')
776776
})
777777

778+
it('skips _ inside attribute values when attributes are enabled without component syntax', () => {
779+
expect(
780+
autoCloseMarkdown('this is a [link with an attribute](https://example.com){target="_blank"}', {
781+
syntax: false,
782+
attributes: true,
783+
})
784+
).toBe('this is a [link with an attribute](https://example.com){target="_blank"}')
785+
})
786+
787+
it('does not treat unclosed :: as a component when only attributes are enabled', () => {
788+
expect(autoCloseMarkdown('::alert\nContent', { syntax: false, attributes: true })).toBe('::alert\nContent')
789+
})
790+
778791
it('still completes frontmatter with syntax: false', () => {
779792
expect(autoCloseMarkdown('---\ntitle: Hello', { frontmatter: true, syntax: false })).toBe('---\ntitle: Hello\n---')
780793
})

packages/comark/test/plugins/default-plugins.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it, vi } from 'vitest'
22
import { parseMarkdown } from '../../src/parse'
3+
import attributes from '../../src/plugins/attributes'
34
import components from '../../src/plugins/components'
45
import frontmatter from '../../src/plugins/frontmatter'
56
import html from '../../src/plugins/html'
@@ -128,5 +129,27 @@ describe('default plugin options', () => {
128129
})
129130
expect(tree.nodes).toEqual([['alert', {}, 'Content']])
130131
})
132+
133+
it('renders attributes when only attributes enabled', async () => {
134+
const tree = await parseMarkdown('this is a [link with an attribute](https://example.com){target="_blank"}', {
135+
registerDefaultPlugins: false,
136+
plugins: [attributes()],
137+
})
138+
expect(tree.nodes).toEqual([
139+
[
140+
'p',
141+
{},
142+
'this is a ',
143+
[
144+
'a',
145+
{
146+
href: 'https://example.com',
147+
target: '_blank',
148+
},
149+
'link with an attribute',
150+
],
151+
],
152+
])
153+
})
131154
})
132155
})

0 commit comments

Comments
 (0)