Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/renderer/md.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ import { marked } from 'marked';
// なんか増えたらココに追記
import code from './md/code.js';

// CJK約物()など)の直後にある closing ** が認識されない CommonMark 由来の問題を修正する。
// emStrongRDelimAst の group1 lookahead に \p{L} を追加し、
// 約物 + ** + 文字(CJK含む)の並びも closing delimiter と見なすようにする。
let _cjkFixed = false;
marked.use({
tokenizer: {
emStrong(src, maskedSrc, prevChar = '') {
if (!_cjkFixed) {
const orig = this.rules.inline.emStrongRDelimAst;
this.rules.inline.emStrongRDelimAst = new RegExp(
orig.source.replace('(?=[\\s]|$)', '(?=[\\s\\p{L}]|$)'),
orig.flags
);
Comment on lines +15 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This regex modification has two significant issues:

  1. Missing u flag: The use of Unicode property escapes like \p{L} requires the u (unicode) flag to be set on the regular expression. Without this flag, new RegExp() will throw a SyntaxError in modern JavaScript environments (Node.js, modern browsers).
  2. Incorrect string escaping: In the replace call, the string literal '[\s]' results in [s] because the backslash is not escaped within the string. To correctly match the literal \s in the regex source, you must use '[\\s]' (double backslash).

Additionally, consider if \p{P} (punctuation) should also be included in the lookahead to fully support CommonMark's flanking rules for CJK punctuation (e.g., **foo(bar)**。).

Suggested change
this.rules.inline.emStrongRDelimAst = new RegExp(
orig.source.replace('(?=[\\s]|$)', '(?=[\\s\\p{L}]|$)'),
orig.flags
);
this.rules.inline.emStrongRDelimAst = new RegExp(
orig.source.replace('(?=[\\\\s]|$)', '(?=[\\\\s\\\\p{L}]|$)'),
orig.flags.includes('u') ? orig.flags : orig.flags + 'u'
);

_cjkFixed = true;
}
return false;
}
}
});
Comment on lines +10 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling marked.use() at the top level of the module modifies the global marked instance. This side effect affects all other parts of the application that might use marked, which can lead to unexpected behavior or conflicts. It is recommended to use a scoped instance of marked (e.g., new marked.Marked()) and apply extensions to that instance instead of the global one.


const renderFunc = {
code,
};
Expand Down
8 changes: 8 additions & 0 deletions test/cgmd/renderer/md.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ describe('#renderToken', function() {
});
});

describe('#render (CJK punctuation)', function() {
it('CJK約物の直後の closing ** が正しく認識されること', function() {
const res = renderer.render('**foo(bar)**baz**qux**');
assert(res.includes('<strong>foo(bar)</strong>'), 'CJK約物で閉じるboldが壊れている');
assert(res.includes('<strong>qux</strong>'), '2つ目のboldが壊れている');
});
});

describe('#render', function() {
it('markedと同じ内容でふつうのMarkdownをレンダリングできること', function() {
const res1 = renderer.render('- foo\n- bar');
Expand Down