From 5f19cbedaafbca222322fb41860721f945d46529 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Thu, 7 Nov 2024 14:26:37 +0100 Subject: [PATCH] fix: remove scaped char `\` from attributes value --- src/from-markdown.ts | 4 +- src/micromark-extension/factory-attributes.ts | 17 +++++++ src/micromark-extension/types.ts | 2 + src/to-markdown.ts | 2 +- .../block-component.test.ts.snap | 47 +++++++++++++++++++ test/block-component.test.ts | 6 +++ 6 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/from-markdown.ts b/src/from-markdown.ts index 94ea8986..a5c21835 100644 --- a/src/from-markdown.ts +++ b/src/from-markdown.ts @@ -224,7 +224,9 @@ export default (opts: RemarkMDCOptions = {}) => { function exitAttributeValue (this: CompileContext, token: Token) { const attributes = (this.data as any).componentAttributes - attributes[attributes.length - 1][1] = parseEntities(this.sliceSerialize(token)) + const lastAttribute = attributes[attributes.length - 1] + + lastAttribute[1] = (typeof lastAttribute[1] === 'string' ? lastAttribute[1] : '') + parseEntities(this.sliceSerialize(token)) } function exitAttributeName (this: CompileContext, token: Token) { diff --git a/src/micromark-extension/factory-attributes.ts b/src/micromark-extension/factory-attributes.ts index 74cd6e7a..0e338fab 100644 --- a/src/micromark-extension/factory-attributes.ts +++ b/src/micromark-extension/factory-attributes.ts @@ -296,6 +296,17 @@ export default function createAttributes ( } function valueQuoted (code: number) { + if (code === Codes.backSlash) { + effects.exit(attributeValueData) + effects.exit(attributeValueType) + effects.enter('escapeCharacter') + effects.consume(code) + effects.exit('escapeCharacter') + effects.enter(attributeValueType) + effects.enter(attributeValueData) + return valueQuotedEscape + } + if (code === marker || code === Codes.EOF || markdownLineEnding(code)) { effects.exit(attributeValueData) return valueQuotedBetween(code) @@ -305,6 +316,12 @@ export default function createAttributes ( return valueQuoted } + function valueQuotedEscape (code: number) { + effects.consume(code) + + return valueQuoted + } + function valueQuotedAfter (code: number) { return code === Codes.closingCurlyBracket || markdownLineEndingOrSpace(code) ? between(code) : end(code) } diff --git a/src/micromark-extension/types.ts b/src/micromark-extension/types.ts index 3d6ec521..1956454f 100644 --- a/src/micromark-extension/types.ts +++ b/src/micromark-extension/types.ts @@ -72,6 +72,8 @@ declare module 'micromark-util-types' { bindingContent: 'bindingContent', bindingFence: 'bindingFence', + escapeCharacter: 'escapeCharacter', + // Component Text componentText: 'componentText', componentTextMarker: 'componentTextMarker', diff --git a/src/to-markdown.ts b/src/to-markdown.ts index 8c05119b..3c9e8882 100644 --- a/src/to-markdown.ts +++ b/src/to-markdown.ts @@ -219,7 +219,7 @@ export default (opts: RemarkMDCOptions = {}) => { } else if (key.startsWith(':') && value === 'true') { values.push(key.slice(1)) } else if (key.startsWith(':') && isValidJSON(value)) { - values.push(`${key}='${value}'`) + values.push(`${key}='${value.replace(/([^/])'/g, '$1\\\'')}'`) } else { values.push(quoted(key, value)) } diff --git a/test/__snapshots__/block-component.test.ts.snap b/test/__snapshots__/block-component.test.ts.snap index 14d7759f..5c94ec43 100644 --- a/test/__snapshots__/block-component.test.ts.snap +++ b/test/__snapshots__/block-component.test.ts.snap @@ -1057,6 +1057,53 @@ exports[`block-component > ignore-code-fence 1`] = ` } `; +exports[`block-component > jsonScapeAttr 1`] = ` +{ + "children": [ + { + "attributes": { + ":test": "{"foo":"I'd love to"}", + }, + "children": [], + "data": { + "hName": "foo", + "hProperties": { + ":test": "{"foo":"I'd love to"}", + }, + }, + "fmAttributes": {}, + "name": "foo", + "position": { + "end": { + "column": 3, + "line": 2, + "offset": 40, + }, + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "type": "containerComponent", + }, + ], + "position": { + "end": { + "column": 3, + "line": 2, + "offset": 40, + }, + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "type": "root", +} +`; + exports[`block-component > nested-component 1`] = ` { "children": [ diff --git a/test/block-component.test.ts b/test/block-component.test.ts index a8b3c470..17fbe998 100644 --- a/test/block-component.test.ts +++ b/test/block-component.test.ts @@ -29,6 +29,12 @@ describe('block-component', () => { markdown: '::with-frontmatter\n---\nkey: value\narray:\n - item\n - itemKey: value\n---\n::', expected: '::with-frontmatter\n---\narray:\n - item\n - itemKey: value\nkey: value\n---\n::' }, + jsonScapeAttr: { + markdown: '::foo{:test=\'{"foo":"I\\\'d love to"}\'}\n::', + extra: (_md, ast) => { + expect(ast.children[0].type).toBe('containerComponent') + } + }, frontmatter1: { markdown: [ '::with-frontmatter',