diff --git a/src/rules/no-missing-atx-heading-space.js b/src/rules/no-missing-atx-heading-space.js index bffc2c41..795f4441 100644 --- a/src/rules/no-missing-atx-heading-space.js +++ b/src/rules/no-missing-atx-heading-space.js @@ -18,38 +18,10 @@ // Helpers //----------------------------------------------------------------------------- -const leadingAtxHeadingHashPattern = /^(#{1,6})(?:[^# \t]|$)/u; +const leadingAtxHeadingHashPattern = + /(?:^|(?<=[\r\n]))(?#{1,6})(?:[^# \t]|$)/gu; const trailingAtxHeadingHashPattern = - /(?[ \t]*)(?<=(?#+)[ \t]*$/u; //----------------------------------------------------------------------------- // Rule Definition @@ -90,6 +62,7 @@ export default { }, create(context) { + const { sourceCode } = context; const [{ checkClosedHeadings }] = context.options; return { @@ -98,80 +71,68 @@ export default { return; } - const text = context.sourceCode.getText(node); - const lineNum = node.position.start.line; - const startColumn = node.position.start.column; + const text = sourceCode.getText(node); + const match = trailingAtxHeadingHashPattern.exec(text); + + if (match === null) { + return; + } + + const { spaces, hashes } = match.groups; + + if (spaces.length > 0) { + return; + } + + const startOffset = node.position.start.offset + match.index; + const endOffset = startOffset + hashes.length; + + context.report({ + loc: { + start: sourceCode.getLocFromIndex(startOffset - 1), + end: sourceCode.getLocFromIndex(endOffset), + }, + messageId: "missingSpace", + data: { position: "before" }, + fix(fixer) { + return fixer.insertTextBeforeRange( + [startOffset, startOffset + 1], + " ", + ); + }, + }); + }, + + paragraph(node) { + const text = sourceCode.getText(node); + + /** @type {RegExpExecArray | null} */ + let match; + + while ( + (match = leadingAtxHeadingHashPattern.exec(text)) !== null + ) { + const { hashes } = match.groups; + const startOffset = + node.position.start.offset + match.index; + const endOffset = startOffset + hashes.length; - const missingSpace = findMissingSpaceBeforeClosingHash(text); - if (missingSpace) { context.report({ loc: { - start: { - line: lineNum, - column: - startColumn + missingSpace.beforeHashIdx, - }, - end: { - line: lineNum, - column: startColumn + missingSpace.endIdx, - }, + start: sourceCode.getLocFromIndex(startOffset), + end: sourceCode.getLocFromIndex(endOffset + 1), }, messageId: "missingSpace", - data: { position: "before" }, + data: { position: "after" }, fix(fixer) { - return fixer.insertTextBeforeRange( - [ - node.position.start.offset + - missingSpace.closingHashIdx, - node.position.start.offset + - missingSpace.closingHashIdx + - 1, - ], + return fixer.insertTextAfterRange( + [endOffset - 1, endOffset], " ", ); }, }); } }, - - paragraph(node) { - const text = context.sourceCode.getText(node); - const lines = text.split(newLinePattern); - const startColumn = node.position.start.column; - let offset = node.position.start.offset; - - lines.forEach((line, idx) => { - const match = leadingAtxHeadingHashPattern.exec(line); - const lineNum = node.position.start.line + idx; - - if (match) { - const hashes = match[1]; - - context.report({ - loc: { - start: { line: lineNum, column: startColumn }, - end: { - line: lineNum, - column: startColumn + hashes.length + 1, - }, - }, - messageId: "missingSpace", - data: { position: "after" }, - fix(fixer) { - return fixer.insertTextAfterRange( - [ - offset + hashes.length - 1, - offset + hashes.length, - ], - " ", - ); - }, - }); - } - - offset += line.length + 1; - }); - }, }; }, }; diff --git a/tests/rules/no-missing-atx-heading-space.test.js b/tests/rules/no-missing-atx-heading-space.test.js index ddb40758..7c854f7a 100644 --- a/tests/rules/no-missing-atx-heading-space.test.js +++ b/tests/rules/no-missing-atx-heading-space.test.js @@ -62,6 +62,7 @@ const validHeadings = [ "Not a heading", "This is a paragraph with a #hashtag", "Text with # in the middle", + "foo\u2028\u2028#Bar\u2029\u2029#Baz", // with line and paragraph separators // 7. Code blocks containing hash symbols // 7.1 Fenced code blocks @@ -463,6 +464,34 @@ const invalidTests = [ }, ], }, + { + code: "Text before\r\n#Heading with ``` code markers\r\nText after", + output: "Text before\r\n# Heading with ``` code markers\r\nText after", + errors: [ + { + messageId: "missingSpace", + data: { position: "after" }, + line: 2, + column: 1, + endLine: 2, + endColumn: 3, + }, + ], + }, + { + code: "Text before\r#Heading with ``` code markers\rText after", + output: "Text before\r# Heading with ``` code markers\rText after", + errors: [ + { + messageId: "missingSpace", + data: { position: "after" }, + line: 2, + column: 1, + endLine: 2, + endColumn: 3, + }, + ], + }, { code: " ##Heading 2",