-
Notifications
You must be signed in to change notification settings - Fork 76
feat: add fenced-code-meta rule #512
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'Info string' is the term used in the CommonMark spec, so I stuck with that for consistency. Happy to adjust if you think 'information string' reads better in context. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should stick with "info string": @TKDev7 when referencing the spec, go ahead and include a link to the section you're referring to. That helps move the conversation forward. |
||||||
|
||||||
## 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. | ||||||
TKDev7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
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!"); | ||||||
~~~ | ||||||
TKDev7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
``` | ||||||
|
||||||
## 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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/** | ||
* @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"], | ||
}, | ||
], | ||
Comment on lines
+37
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest we define a schema option for more flexibility, like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking we could follow the pattern many ESLint rules use — keep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think using |
||
|
||
defaultOptions: ["always"], | ||
}, | ||
|
||
create(context) { | ||
const [mode] = context.options; | ||
const { sourceCode } = context; | ||
|
||
return { | ||
code(node) { | ||
const lineText = sourceCode.lines[node.position.start.line - 1]; | ||
|
||
if (mode === "always") { | ||
if (node.lang && !node.meta) { | ||
const langStart = lineText.indexOf(node.lang); | ||
TKDev7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
context.report({ | ||
loc: { | ||
start: node.position.start, | ||
end: { | ||
line: node.position.start.line, | ||
column: | ||
node.position.start.column + | ||
langStart + | ||
node.lang.trim().length, | ||
}, | ||
}, | ||
messageId: "missingMetadata", | ||
}); | ||
} | ||
|
||
return; | ||
} | ||
|
||
if (node.meta) { | ||
const metaStart = lineText.lastIndexOf(node.meta); | ||
TKDev7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
context.report({ | ||
loc: { | ||
start: { | ||
line: node.position.start.line, | ||
column: node.position.start.column + metaStart, | ||
}, | ||
end: { | ||
line: node.position.start.line, | ||
column: | ||
node.position.start.column + | ||
metaStart + | ||
node.meta.trim().length, | ||
}, | ||
}, | ||
messageId: "disallowedMetadata", | ||
}); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.