From caae14a444ca9bc36e3e2e976a777d84c666cae4 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Fri, 9 Jan 2026 19:17:02 +0100 Subject: [PATCH 1/4] fix: do not escape underscore inside of words Makes escape rules for `_` more specific to prevent unnecessary escaping inside of words such as HELLO_WORLD or urls (https://some/web_site). Related to https://github.com/remarkjs/remark/issues/1455 --- lib/unsafe.js | 13 ++++++++++++- test/index.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/lib/unsafe.js b/lib/unsafe.js index c5c5d4a..5a4e0e5 100644 --- a/lib/unsafe.js +++ b/lib/unsafe.js @@ -130,7 +130,18 @@ export const unsafe = [ // Caret is not used in markdown for constructs. // An underscore can start emphasis, strong, or a thematic break. {atBreak: true, character: '_'}, - {character: '_', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans}, + { + character: '_', + before: '[\\s]', + inConstruct: 'phrasing', + notInConstruct: fullPhrasingSpans + }, + { + character: '_', + after: '[\\s]', + inConstruct: 'phrasing', + notInConstruct: fullPhrasingSpans + }, // A grave accent can start code (fenced or text), or it can break out of // a grave accent code fence. {atBreak: true, character: '`'}, diff --git a/test/index.js b/test/index.js index e47facd..6d94f72 100644 --- a/test/index.js +++ b/test/index.js @@ -3895,6 +3895,39 @@ test('escape', async function (t) { } ) + await t.test('should escape underscore at word start', async function () { + assert.equal( + to({ + type: 'paragraph', + children: [{type: 'text', value: '_WORLD'}] + }), + '\\_WORLD\n' + ) + }) + + await t.test('should escape underscore at word end', async function () { + assert.equal( + to({ + type: 'paragraph', + children: [{type: 'text', value: 'HELLO_'}] + }), + 'HELLO\\_\n' + ) + }) + + await t.test( + 'should not escape underscore inside of word', + async function () { + assert.equal( + to({ + type: 'paragraph', + children: [{type: 'text', value: 'HELLO_WORLD'}] + }), + 'HELLO_WORLD\n' + ) + } + ) + await t.test( 'should escape what would otherwise be a heading (atx)', async function () { @@ -4503,6 +4536,15 @@ test('roundtrip', async function (t) { ) }) + await t.test( + 'should roundtrip underscore inside of words', + async function () { + const value = 'HELLO_WORLD https://some/web_site\n' + + assert.equal(to(from(value)), value) + } + ) + await t.test( 'should roundtrip a sole blank line in fenced code', async function () { From 36591c02dd7a1bb224474f147897cd76701dab0b Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Fri, 9 Jan 2026 20:57:25 +0100 Subject: [PATCH 2/4] test: verify underscore usage alongside punctuation --- test/index.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/test/index.js b/test/index.js index 6d94f72..da1cb52 100644 --- a/test/index.js +++ b/test/index.js @@ -4537,9 +4537,28 @@ test('roundtrip', async function (t) { }) await t.test( - 'should roundtrip underscore inside of words', + 'should roundtrip underscore without escaping', async function () { - const value = 'HELLO_WORLD https://some/web_site\n' + const value = `Separate paragraphs: + +HELLO_WORLD https://some/web_site + +HELLO_WORLD. HELLO_WORLD, H_W; HELLO_OTHER! HELLO_YES? HELLO_NO + +HELLO_WORLD.HELLO_WORLD,H_W;HELLO_OTHER!HELLO_YES?HELLO_NO + +HELLO_WORLD - THIS_GOOD + +HELLO_WORLD-THIS_GOOD + +One Paragraph: + +HELLO_WORLD https://some/web_site +HELLO_WORLD. HELLO_WORLD, H_W; HELLO_OTHER! HELLO_YES? HELLO_NO +HELLO_WORLD.HELLO_WORLD,H_W;HELLO_OTHER!HELLO_YES?HELLO_NO +HELLO_WORLD - THIS_GOOD +HELLO_WORLD-THIS_GOOD +` assert.equal(to(from(value)), value) } From 27525c142c9286b731badafde0c917dfd13d2a58 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Fri, 9 Jan 2026 21:29:08 +0100 Subject: [PATCH 3/4] test: very against additional standard markdown cases --- test/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/index.js b/test/index.js index da1cb52..e11a4ad 100644 --- a/test/index.js +++ b/test/index.js @@ -4541,6 +4541,10 @@ test('roundtrip', async function (t) { async function () { const value = `Separate paragraphs: +:heavy_check_mark: + +[HELLO_WORLD.md](HELLO_WORLD.md) + HELLO_WORLD https://some/web_site HELLO_WORLD. HELLO_WORLD, H_W; HELLO_OTHER! HELLO_YES? HELLO_NO @@ -4553,6 +4557,8 @@ HELLO_WORLD-THIS_GOOD One Paragraph: +:heavy_check_mark: +[HELLO_WORLD.md](HELLO_WORLD.md) HELLO_WORLD https://some/web_site HELLO_WORLD. HELLO_WORLD, H_W; HELLO_OTHER! HELLO_YES? HELLO_NO HELLO_WORLD.HELLO_WORLD,H_W;HELLO_OTHER!HELLO_YES?HELLO_NO From 30e46e7d889318aed3a36e88418416c63a7e9307 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Sat, 10 Jan 2026 11:10:27 +0100 Subject: [PATCH 4/4] test: expand existing underscore roundtrip test incorporate underscore_in_word --- test/index.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index e11a4ad..a182b1e 100644 --- a/test/index.js +++ b/test/index.js @@ -4848,6 +4848,20 @@ a __\\_ is this emphasis? __\\_ a _\\__ is this emphasis? _\\__ +a _ is_this emphasis? _ + +a __ is_this emphasis? __ + +a ___ is_this emphasis? ___ + +a _\\_ is_this emphasis? _\\_ + +a \\__ is_this emphasis? \\__ + +a __\\_ is_this emphasis? __\\_ + +a _\\__ is_this emphasis? _\\__ + One paragraph: a _ is this emphasis? _ @@ -4856,7 +4870,15 @@ a ___ is this emphasis? ___ a _\\_ is this emphasis? _\\_ a \\__ is this emphasis? \\__ a __\\_ is this emphasis? __\\_ -a _\\__ is this emphasis? _\\__` +a _\\__ is this emphasis? _\\__ + +a _ is_this emphasis? _ +a __ is_this emphasis? __ +a ___ is_this emphasis? ___ +a _\\_ is_this emphasis? _\\_ +a \\__ is_this emphasis? \\__ +a __\\_ is_this emphasis? __\\_ +a _\\__ is_this emphasis? _\\__` const tree = from(value) assert.deepEqual(