107 lines
4.3 KiB
JavaScript
107 lines
4.3 KiB
JavaScript
/**
|
|
* @fileoverview Rule to enforce languages for fenced code.
|
|
* @author Nicholas C. Zakas
|
|
*/
|
|
//-----------------------------------------------------------------------------
|
|
// Type Definitions
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @import { MarkdownRuleDefinition } from "../types.js";
|
|
* @typedef {"missingLanguage" | "disallowedLanguage"} FencedCodeLanguageMessageIds
|
|
* @typedef {[{ required?: string[] }]} FencedCodeLanguageOptions
|
|
* @typedef {MarkdownRuleDefinition<{ RuleOptions: FencedCodeLanguageOptions, MessageIds: FencedCodeLanguageMessageIds }>} FencedCodeLanguageRuleDefinition
|
|
*/
|
|
//-----------------------------------------------------------------------------
|
|
// Helpers
|
|
//-----------------------------------------------------------------------------
|
|
const fencedCodeCharacters = new Set(["`", "~"]);
|
|
//-----------------------------------------------------------------------------
|
|
// Rule Definition
|
|
//-----------------------------------------------------------------------------
|
|
export default /** @satisfies {FencedCodeLanguageRuleDefinition} */ ({
|
|
meta: {
|
|
type: "problem",
|
|
docs: {
|
|
recommended: true,
|
|
description: "Require languages for fenced code blocks",
|
|
url: "https://github.com/eslint/markdown/blob/main/docs/rules/fenced-code-language.md",
|
|
},
|
|
messages: {
|
|
missingLanguage: "Missing code block language.",
|
|
disallowedLanguage: 'Code block language "{{lang}}" is not allowed.',
|
|
},
|
|
schema: [
|
|
{
|
|
type: "object",
|
|
properties: {
|
|
required: {
|
|
type: "array",
|
|
items: {
|
|
type: "string",
|
|
},
|
|
uniqueItems: true,
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
defaultOptions: [
|
|
{
|
|
required: [],
|
|
},
|
|
],
|
|
},
|
|
create(context) {
|
|
const required = new Set(context.options[0].required);
|
|
const { sourceCode } = context;
|
|
return {
|
|
code(node) {
|
|
if (!node.lang) {
|
|
// only check fenced code blocks
|
|
if (!fencedCodeCharacters.has(sourceCode.text[node.position.start.offset])) {
|
|
return;
|
|
}
|
|
/** @type {number} */
|
|
let openingCodeFenceEndOffset;
|
|
for (openingCodeFenceEndOffset = node.position.start.offset; fencedCodeCharacters.has(sourceCode.text[openingCodeFenceEndOffset]); openingCodeFenceEndOffset++) {
|
|
// Find the end offset of the opening code fence.
|
|
}
|
|
context.report({
|
|
loc: {
|
|
start: node.position.start,
|
|
end: {
|
|
line: node.position.start.line,
|
|
column: node.position.start.column +
|
|
openingCodeFenceEndOffset -
|
|
node.position.start.offset,
|
|
},
|
|
},
|
|
messageId: "missingLanguage",
|
|
});
|
|
return;
|
|
}
|
|
if (required.size && !required.has(node.lang)) {
|
|
const langIndex = sourceCode
|
|
.getText(node)
|
|
.indexOf(node.lang);
|
|
context.report({
|
|
loc: {
|
|
start: node.position.start,
|
|
end: {
|
|
line: node.position.start.line,
|
|
column: node.position.start.column +
|
|
langIndex +
|
|
node.lang.length,
|
|
},
|
|
},
|
|
messageId: "disallowedLanguage",
|
|
data: {
|
|
lang: node.lang,
|
|
},
|
|
});
|
|
}
|
|
},
|
|
};
|
|
},
|
|
});
|