Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ jobs:

test_types:
name: Test Types
strategy:
matrix:
eslint: [9.39.0, 9.x, 10.x]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand All @@ -85,6 +88,8 @@ jobs:
node-version: "lts/*"
- name: Install dependencies
run: npm install
- name: Install ESLint@${{ matrix.eslint }}
run: npm install -D eslint@${{ matrix.eslint }} @eslint/js@${{ matrix.eslint }}
- name: Check Types
run: npm run test:types

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Lint Markdown with ESLint, as well JS, JSX, TypeScript, and more inside Markdown

### Installing

Install the plugin alongside ESLint v9.15.0 or greater.
Install the plugin alongside ESLint v9.15.0 or greater. Type compatibility is guaranteed with ESLint v9.39.0 or greater.

For Node.js and compatible runtimes:

Expand Down
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import rules from "./build/rules.js";
//-----------------------------------------------------------------------------

/**
* @import { Linter } from "eslint";
* @import { ConfigObject, RulesConfig } from "@eslint/core";
*/

//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------

/** @satisfies {Linter.RulesRecord} */
/** @satisfies {RulesConfig} */
const processorRulesConfig = {
// The Markdown parser automatically trims trailing
// newlines from code blocks.
Expand Down Expand Up @@ -97,7 +97,7 @@ const plugin = {
rules: recommendedRules,
},
],
processor: /** @type {Linter.Config[]} */ ([
processor: /** @type {ConfigObject[]} */ ([
{
name: "markdown/recommended/plugin",
plugins: (processorPlugins = {}),
Expand Down
23 changes: 10 additions & 13 deletions src/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,9 @@ import { fromMarkdown } from "mdast-util-from-markdown";
//-----------------------------------------------------------------------------

/**
* @import { LintMessage, RuleTextEdit, SourceRange } from "@eslint/core";
* @import { Node, Parent, Code, Html } from "mdast";
* @import { Linter, Rule, AST } from "eslint";
* @import { Block, RangeMap } from "./types.js";
* @typedef {Linter.LintMessage} Message
* @typedef {Rule.Fix} Fix
* @typedef {AST.Range} Range
*/

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -352,12 +349,12 @@ function preprocess(sourceText, filename) {
/**
* Adjusts a fix in a code block.
* @param {Block} block A code block.
* @param {Fix} fix A fix to adjust.
* @returns {Fix} The fix with adjusted ranges.
* @param {RuleTextEdit} fix A fix to adjust.
* @returns {RuleTextEdit} The fix with adjusted ranges.
*/
function adjustFix(block, fix) {
return {
range: /** @type {Range} */ (
range: /** @type {SourceRange} */ (
fix.range.map(range => {
// Advance through the block's range map to find the last
// matching range by finding the first range too far and
Expand All @@ -382,7 +379,7 @@ function adjustFix(block, fix) {
/**
* Creates a map function that adjusts messages in a code block.
* @param {Block} block A code block.
* @returns {(message: Message) => Message} A function that adjusts messages in a code block.
* @returns {(message: LintMessage) => LintMessage} A function that adjusts messages in a code block.
*/
Comment thread
lumirlumir marked this conversation as resolved.
function adjustBlock(block) {
const leadingCommentLines = block.comments.reduce(
Expand All @@ -394,8 +391,8 @@ function adjustBlock(block) {

/**
* Adjusts ESLint messages to point to the correct location in the Markdown.
* @param {Message} message A message from ESLint.
* @returns {Message} The same message, but adjusted to the correct location.
* @param {LintMessage} message A message from ESLint.
* @returns {LintMessage} The same message, but adjusted to the correct location.
*/
return function adjustMessage(message) {
if (!Number.isInteger(message.line)) {
Expand Down Expand Up @@ -440,7 +437,7 @@ function adjustBlock(block) {

/**
* Excludes unsatisfiable rules from the list of messages.
* @param {Message} message A message from the linter.
* @param {LintMessage} message A message from the linter.
* @returns {boolean} True if the message should be included in output.
*/
function excludeUnsatisfiableRules(message) {
Expand All @@ -449,10 +446,10 @@ function excludeUnsatisfiableRules(message) {

/**
* Transforms generated messages for output.
* @param {Array<Message[]>} messages An array containing one array of messages
* @param {Array<LintMessage[]>} messages An array containing one array of messages
* for each code block returned from `preprocess`.
* @param {string} filename The filename of the file
* @returns {Message[]} A flattened array of messages with mapped locations.
* @returns {LintMessage[]} A flattened array of messages with mapped locations.
*/
function postprocess(messages, filename) {
const blocks = blocksCache.get(filename);
Expand Down
29 changes: 28 additions & 1 deletion tests/types/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
Block,
} from "@eslint/markdown";
import type { Plugin, SourceLocation, SourceRange } from "@eslint/core";
import type { Linter } from "eslint";
import type { ESLint, Linter } from "eslint";
import type { Position } from "unist";
import type {
// Nodes (abstract)
Expand Down Expand Up @@ -92,6 +92,9 @@ const invalidBlock: Block = {
};

markdown satisfies Plugin;
// This type check verifies that the plugin is compatible with ESLint v9.39.0, v9.x, and v10.x.
// See: https://github.com/eslint/markdown/pull/648
markdown satisfies ESLint.Plugin;
markdown.meta.name satisfies string;
markdown.meta.version satisfies string;

Expand All @@ -102,6 +105,30 @@ markdown.processors.markdown satisfies object;
markdown.languages.commonmark satisfies object;
markdown.languages.gfm satisfies object;

declare const ruleName: keyof typeof markdown.rules;
markdown.rules[ruleName] satisfies MarkdownRuleDefinition;
markdown.rules["fenced-code-language"] satisfies MarkdownRuleDefinition;
markdown.rules["fenced-code-meta"] satisfies MarkdownRuleDefinition;
Comment thread
lumirlumir marked this conversation as resolved.
markdown.rules["heading-increment"] satisfies MarkdownRuleDefinition;
markdown.rules["no-bare-urls"] satisfies MarkdownRuleDefinition;
markdown.rules["no-duplicate-definitions"] satisfies MarkdownRuleDefinition;
markdown.rules["no-duplicate-headings"] satisfies MarkdownRuleDefinition;
markdown.rules["no-empty-definitions"] satisfies MarkdownRuleDefinition;
markdown.rules["no-empty-images"] satisfies MarkdownRuleDefinition;
markdown.rules["no-empty-links"] satisfies MarkdownRuleDefinition;
markdown.rules["no-html"] satisfies MarkdownRuleDefinition;
markdown.rules["no-invalid-label-refs"] satisfies MarkdownRuleDefinition;
markdown.rules["no-missing-atx-heading-space"] satisfies MarkdownRuleDefinition;
markdown.rules["no-missing-label-refs"] satisfies MarkdownRuleDefinition;
markdown.rules["no-missing-link-fragments"] satisfies MarkdownRuleDefinition;
markdown.rules["no-multiple-h1"] satisfies MarkdownRuleDefinition;
markdown.rules["no-reference-like-urls"] satisfies MarkdownRuleDefinition;
markdown.rules["no-reversed-media-syntax"] satisfies MarkdownRuleDefinition;
markdown.rules["no-space-in-emphasis"] satisfies MarkdownRuleDefinition;
markdown.rules["no-unused-definitions"] satisfies MarkdownRuleDefinition;
markdown.rules["require-alt-text"] satisfies MarkdownRuleDefinition;
markdown.rules["table-column-count"] satisfies MarkdownRuleDefinition;

markdown.configs["recommended-legacy"] satisfies Linter.LegacyConfig;
markdown.configs.recommended satisfies Linter.Config[];
markdown.configs.processor satisfies Linter.Config[];
Expand Down
17 changes: 14 additions & 3 deletions tools/build-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
const thisDir = path.dirname(fileURLToPath(import.meta.url));
const rulesPath = path.resolve(thisDir, "../src/rules");
const rules = fs.readdirSync(rulesPath);
Comment thread
lumirlumir marked this conversation as resolved.
Outdated
const ruleIds = rules.map(id => id.slice(0, -3));
const recommended = [];

for (const ruleId of rules) {
Expand Down Expand Up @@ -51,9 +52,19 @@ console.log("Recommended rules generated successfully.");
const rulesOutput = `
${rules.map((id, index) => `import rule${index} from "../rules/${id}";`).join("\n")}

export default {
${rules.map((id, index) => `"${id.slice(0, -3)}": rule${index},`).join("\n ")}
};
/**
* @typedef {(
* ${ruleIds
.map(
(id, index) => `"${id}"${index === ruleIds.length - 1 ? "" : " |"}`,
)
.join("\n * ")}
* )} RuleId
*/

export default /** @type {Record<RuleId, any>} */ ({
Comment thread
lumirlumir marked this conversation as resolved.
Outdated
${ruleIds.map((id, index) => `"${id}": rule${index},`).join("\n ")}
});
`.trim();

fs.writeFileSync(path.resolve(thisDir, "../src/build/rules.js"), rulesOutput);
Expand Down