Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export default defineConfig([
| [`no-empty-links`](./docs/rules/no-empty-links.md) | Disallow empty links | yes |
| [`no-html`](./docs/rules/no-html.md) | Disallow HTML tags | no |
| [`no-invalid-label-refs`](./docs/rules/no-invalid-label-refs.md) | Disallow invalid label references | yes |
| [`no-missing-atx-heading-space`](./docs/rules/no-missing-atx-heading-space.md) | Disallow headings without a space after the hash characters | yes |
| [`no-missing-label-refs`](./docs/rules/no-missing-label-refs.md) | Disallow missing label references | yes |
<!-- Rule Table End -->

Expand Down
48 changes: 48 additions & 0 deletions docs/rules/no-missing-atx-heading-space.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# no-missing-atx-heading-space

This rule warns when spaces are missing after the hash characters in an ATX style heading.

## Rule Details

In Markdown, headings can be created using ATX style (using hash (`#`) characters at the beginning of the line) or Setext style (using underlining with equals (`=`) or hyphens (`-`)).

For ATX style headings, a space should be used after the hash characters to improve readability and ensure proper rendering across various Markdown parsers.

This rule is automatically fixable by the `--fix` command line option.

Examples of **incorrect** code for this rule:

```md
#Heading 1
##Heading 2
###Heading 3
```

Examples of **correct** code for this rule:

```md
# Heading 1
## Heading 2
### Heading 3

<h1>#Some Heading</h1>

[#123](link.com)

![#alttext][link.png]

This is a paragraph with a #hashtag, not a heading.
```

## When Not To Use It

You might want to turn this rule off if you're working with a Markdown variant that doesn't require spaces after hash characters in headings.

## Prior Art

- [MD018 - No space after hash on atx style heading](https://github.com/DavidAnson/markdownlint/blob/main/doc/md018.md)

## Further Reading

- [Markdown Syntax: Headings](https://daringfireball.net/projects/markdown/syntax#header)
- [CommonMark Spec: ATX Headings](https://spec.commonmark.org/0.30/#atx-headings)
96 changes: 96 additions & 0 deletions src/rules/no-missing-atx-heading-space.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @fileoverview Rule to ensure there is a space after hash on ATX style headings in Markdown.
* @author Sweta Tanwar (@SwetaTanwar)
*/

//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------

/**
* @typedef {import("../types.ts").MarkdownRuleDefinition<{ RuleOptions: []; }>}
* NoMissingAtxHeadingSpaceRuleDefinition
*/

//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------

const HEADING_PATTERN = /^(#{1,6})(?:[^#\s]|$)/u;
const NEW_LINE_PATTERN = /\r?\n/u;

//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------

/** @type {NoMissingAtxHeadingSpaceRuleDefinition} */
export default {
meta: {
type: "problem",

docs: {
recommended: true,
description:
"Disallow headings without a space after the hash characters",
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-missing-atx-heading-space.md",
},

fixable: "whitespace",

messages: {
missingSpace: "Missing space after hash(es) on ATX style heading.",
},
},

create(context) {
return {
paragraph(node) {
if (node.children && node.children.length > 0) {
const firstTextChild = node.children.find(
child => child.type === "text",
);
if (!firstTextChild) {
return;
}

const text = context.sourceCode.getText(firstTextChild);
const lines = text.split(NEW_LINE_PATTERN);

lines.forEach((line, idx) => {
const lineNum =
firstTextChild.position.start.line + idx;

const match = HEADING_PATTERN.exec(line);
if (!match) {
return;
}

const hashes = match[1];

context.report({
loc: {
start: { line: lineNum, column: hashes.length },
end: { line: lineNum, column: line.length },
},
messageId: "missingSpace",
fix(fixer) {
const offset =
firstTextChild.position.start.offset +
lines.slice(0, idx).join("\n").length +
(idx > 0 ? 1 : 0);

return fixer.insertTextAfterRange(
[
offset + hashes.length - 1,
offset + hashes.length,
],
" ",
);
},
});
});
}
},
};
},
};
Loading