Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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 @@ -89,6 +89,7 @@ export default defineConfig([
| **Rule Name** | **Description** | **Recommended** |
| :----------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | :-------------: |
| [`fenced-code-language`](./docs/rules/fenced-code-language.md) | Require languages for fenced code blocks | yes |
| [`fenced-code-meta`](./docs/rules/fenced-code-meta.md) | Require or disallow metadata for fenced code blocks | no |
| [`heading-increment`](./docs/rules/heading-increment.md) | Enforce heading levels increment by one | yes |
| [`no-bare-urls`](./docs/rules/no-bare-urls.md) | Disallow bare URLs | no |
| [`no-duplicate-definitions`](./docs/rules/no-duplicate-definitions.md) | Disallow duplicate definitions | yes |
Expand Down
76 changes: 76 additions & 0 deletions docs/rules/fenced-code-meta.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# fenced-code-meta

Require or disallow metadata for fenced code blocks.

## Background

Fenced code blocks can include an info string after the opening fence. The first word typically specifies the language (e.g., `js`). Many tools also support additional metadata after the language (separated by whitespace), such as titles or line highlighting parameters. This rule enforces a consistent policy for including such metadata.

## Rule Details

This rule warns when the presence of metadata in a fenced code block's info string does not match the configured mode.

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

````markdown
<!-- eslint markdown/fenced-code-meta: "error" -->

```js
console.log("Hello, world!");
```
````

## Options

This rule accepts a single string option:

- `"always"` (default): Require metadata when a language is specified.
- `"never"`: Disallow metadata in the info string.

Examples of **incorrect** code when configured as `"fenced-code-meta": ["error", "always"]`:

````markdown
<!-- eslint markdown/fenced-code-meta: ["error", "always"] -->

```js
console.log("Hello, world!");
```
````

Examples of **correct** code when configured as `"fenced-code-meta": ["error", "always"]`:

````markdown
<!-- eslint markdown/fenced-code-meta: ["error", "always"] -->

```js title="example.js"
console.log("Hello, world!");
```
````

Examples of **incorrect** code when configured as `"fenced-code-meta": ["error", "never"]`:

````markdown
<!-- eslint markdown/fenced-code-meta: ["error", "never"] -->

```js title="example.js"
console.log("Hello, world!");
```
````

Examples of **correct** code when configured as `"fenced-code-meta": ["error", "never"]`:

````markdown
<!-- eslint markdown/fenced-code-meta: ["error", "never"] -->

```js
console.log("Hello, world!");
```
````

## When Not to Use It

If you aren't concerned with metadata in info strings, you can safely disable this rule.

## Prior Art

* [MD040 - Fenced code blocks should have a language specified](https://github.com/DavidAnson/markdownlint/blob/main/doc/md040.md)
102 changes: 102 additions & 0 deletions src/rules/fenced-code-meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* @fileoverview Rule to require or disallow metadata for fenced code blocks.
* @author TKDev7
*/

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

/**
* @import { MarkdownRuleDefinition } from "../types.js";
* @typedef {"missingMetadata" | "disallowedMetadata"} FencedCodeMetaMessageIds
* @typedef {["always" | "never"]} FencedCodeMetaOptions
* @typedef {MarkdownRuleDefinition<{ RuleOptions: FencedCodeMetaOptions, MessageIds: FencedCodeMetaMessageIds }>} FencedCodeMetaRuleDefinition
*/

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

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

docs: {
recommended: false,
description: "Require or disallow metadata for fenced code blocks",
url: "https://github.com/eslint/markdown/blob/main/docs/rules/fenced-code-meta.md",
},

messages: {
missingMetadata: "Missing code block metadata.",
disallowedMetadata: "Code block metadata is not allowed.",
},

schema: [
{
enum: ["always", "never"],
},
],

defaultOptions: ["always"],
},

create(context) {
const [mode] = context.options;
const { sourceCode } = context;

return {
code(node) {
const lineText = sourceCode.lines[node.position.start.line - 1];
const fenceLineText = lineText.slice(
node.position.start.column - 1,
);

if (mode === "always") {
if (node.lang && !node.meta) {
const langIndex = fenceLineText.indexOf(node.lang);

context.report({
loc: {
start: node.position.start,
end: {
line: node.position.start.line,
column:
node.position.start.column +
langIndex +
node.lang.trim().length,
},
},
messageId: "missingMetadata",
});
}

return;
}

if (node.meta) {
const metaIndex = fenceLineText.lastIndexOf(node.meta);

context.report({
loc: {
start: {
line: node.position.start.line,
column: node.position.start.column + metaIndex,
},
end: {
line: node.position.start.line,
column:
node.position.start.column +
metaIndex +
node.meta.trim().length,
},
},
messageId: "disallowedMetadata",
});
}
},
};
},
};
Loading