Skip to content

feat: add no-space-in-emphasis rule #403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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 @@ -102,6 +102,7 @@ export default defineConfig([
| [`no-missing-link-fragments`](./docs/rules/no-missing-link-fragments.md) | Disallow link fragments that do not reference valid headings | yes |
| [`no-multiple-h1`](./docs/rules/no-multiple-h1.md) | Disallow multiple H1 headings in the same document | yes |
| [`no-reversed-media-syntax`](./docs/rules/no-reversed-media-syntax.md) | Disallow reversed link and image syntax | yes |
| [`no-space-in-emphasis`](./docs/rules/no-space-in-emphasis.md) | Disallow spaces around emphasis markers | yes |
| [`require-alt-text`](./docs/rules/require-alt-text.md) | Require alternative text for images | yes |
| [`table-column-count`](./docs/rules/table-column-count.md) | Disallow data rows in a GitHub Flavored Markdown table from having more cells than the header row | yes |
<!-- Rule Table End -->
Expand Down
43 changes: 43 additions & 0 deletions docs/rules/no-space-in-emphasis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# no-space-in-emphasis

Disallow spaces around emphasis markers.

## Background

In Markdown, emphasis (bold and italic) is created using asterisks (`*`) or underscores (`_`), and strikethrough is created using tildes (`~`). The emphasis markers must be directly adjacent to the text they're emphasizing, with no spaces between the markers and the text. When spaces are present, the emphasis is not rendered correctly.

## Rule Details

This rule warns when it finds emphasis markers that have spaces between the markers and the text they're emphasizing.

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

```markdown
<!-- eslint markdown/no-space-in-emphasis: "error" -->

Here is some ** bold ** text.
Here is some * italic * text.
Here is some __ bold __ text.
Here is some _ italic _ text.
Here is some ~~ strikethrough ~~ text.
```

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

```markdown
<!-- eslint markdown/no-space-in-emphasis: "error" -->

Here is some **bold** text.
Here is some *italic* text.
Here is some __bold__ text.
Here is some _italic_ text.
Here is some ~~strikethrough~~ text.
```

## When Not to Use It

If you aren't concerned with proper emphasis rendering in your Markdown documents, you can safely disable this rule.

## Prior Art

* [MD037 - Spaces inside emphasis markers](https://github.com/DavidAnson/markdownlint/blob/main/doc/md037.md)
145 changes: 145 additions & 0 deletions src/rules/no-space-in-emphasis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* @fileoverview Rule to prevent spaces around emphasis markers in Markdown.
* @author Pixel998
*/

//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------

import { findOffsets } from "../util.js";

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

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

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

const markerPattern = /(?<!\\)(\*\*\*|\*\*|\*|___|__|_|~~|~)/gu;
const whitespacePattern = /\s/u;

/**
* Finds all emphasis markers in the text
* @param {string} text The text to search
* @returns {Array<{marker: string, start: number, end: number}>} Array of emphasis markers
*/
function findEmphasisMarkers(text) {
const markers = [];
let match;

while ((match = markerPattern.exec(text)) !== null) {
const marker = match[1];
const start = match.index;
const end = start + marker.length;

markers.push({ marker, start, end });
}

return markers;
}

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

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

docs: {
recommended: true,
description: "Disallow spaces around emphasis markers",
url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-space-in-emphasis.md",
},

fixable: "whitespace",

messages: {
spaceInEmphasis: "Unexpected space around emphasis marker.",
},
},

create(context) {
const { sourceCode } = context;

return {
text(node) {
const text = sourceCode.getText(node);
const markers = findEmphasisMarkers(text);

for (let i = 0; i < markers.length - 1; i += 2) {
const startMarker = markers[i];
const endMarker = markers[i + 1];

if (startMarker.marker !== endMarker.marker) {
continue;
}

const hasStartSpace = whitespacePattern.test(
text[startMarker.end],
);
const hasEndSpace = whitespacePattern.test(
text[endMarker.start - 1],
);

if (hasStartSpace || hasEndSpace) {
const {
lineOffset: startLineOffset,
columnOffset: startColumnOffset,
} = findOffsets(text, startMarker.start);
const {
lineOffset: endLineOffset,
columnOffset: endColumnOffset,
} = findOffsets(text, endMarker.end);

const startLine =
node.position.start.line + startLineOffset;
const endLine =
node.position.start.line + endLineOffset;
const startColumn =
node.position.start.column + startColumnOffset;
const endColumn =
node.position.start.column + endColumnOffset;

context.report({
loc: {
start: { line: startLine, column: startColumn },
end: { line: endLine, column: endColumn },
},
messageId: "spaceInEmphasis",
fix(fixer) {
const betweenText = text.slice(
startMarker.end,
endMarker.start,
);

const fixedText =
startMarker.marker +
betweenText.trim() +
endMarker.marker;

const nodeStart = node.position.start.offset;
const relativeStart =
nodeStart + startMarker.start;
const relativeEnd = nodeStart + endMarker.end;

return fixer.replaceTextRange(
[relativeStart, relativeEnd],
fixedText,
);
},
});
}
}
},
};
},
};
Loading