diff --git a/src/index.ts b/src/index.ts index abc9e14..b1a8b77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ const AST_FORMAT = 'heading-case-mdast'; type MarkdownNode = { children?: MarkdownNode[]; + referenceType?: string; type?: string; value?: string; }; @@ -29,7 +30,20 @@ const collectWordNodes = (node: MarkdownNode, words: MarkdownNode[]) => { const formatHeading = (heading: MarkdownNode) => { const wordNodes: MarkdownNode[] = []; - collectWordNodes(heading, wordNodes); + const children = heading.children ?? []; + + // A leading `[Label]` can be parsed as a shortcut link reference when the + // document contains a matching reference definition. Treat it as a heading + // prefix either way, so the word after it remains the first English word. + const contentNodes = + children[0]?.type === 'linkReference' && + children[0].referenceType === 'shortcut' + ? children.slice(1) + : children; + + for (const child of contentNodes) { + collectWordNodes(child, wordNodes); + } const words: WordMeta[] = wordNodes.map((node) => ({ type: node.type === 'whitespace' ? 'space' : 'word', diff --git a/test/index.test.js b/test/index.test.js index 064f32d..bc5e277 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -36,6 +36,12 @@ test('should format line as expected', () => { // Number assert.deepStrictEqual(formatLine('# 1. Hello World'), '# 1. Hello world'); + // Prefix + assert.deepStrictEqual( + formatLine('## [Important] Package name'), + '## [Important] Package name', + ); + // Chinese assert.deepStrictEqual( formatLine('# 你好 Hello World'), diff --git a/test/prettier.test.js b/test/prettier.test.js index 9008bf6..5365056 100644 --- a/test/prettier.test.js +++ b/test/prettier.test.js @@ -13,6 +13,8 @@ test('should format Markdown and MDX headings with Prettier', async () => { '', '# Hello **Bold World** and React Router', '', + '## [Important] Package name', + '', 'Setext Heading', '==============', '', @@ -29,6 +31,8 @@ test('should format Markdown and MDX headings with Prettier', async () => { '', '# Hello **bold world** and React Router', '', + '## [Important] Package name', + '', 'Setext heading', '==============', '', @@ -67,6 +71,30 @@ test('should format Markdown and MDX headings with Prettier', async () => { ); }); +test('should preserve the first word after a bracketed heading prefix', async () => { + const markdown = [ + '## [Important] Package Name', + '', + '[Important]: https://example.com', + '', + ].join('\n'); + + const expected = [ + '## [Important] Package name', + '', + '[Important]: https://example.com', + '', + ].join('\n'); + + assert.strictEqual( + await prettier.format(markdown, { + parser: 'markdown', + plugins: [plugin], + }), + expected, + ); +}); + test('should load the package entry as a Prettier plugin through rs fmt', async () => { const projectRoot = path.resolve(import.meta.dirname, '..'); const cwd = await fs.mkdtemp(path.join(projectRoot, '.heading-case-rs-fmt-'));