From 8fca1c457b726802c318f6955077c0859ce73c18 Mon Sep 17 00:00:00 2001 From: David Anson Date: Thu, 4 Sep 2025 22:19:42 -0700 Subject: [PATCH] wip --- lib/exports.d.mts | 3 +- lib/exports.mjs | 3 +- lib/markdownlint.d.mts | 23 +++++--- lib/markdownlint.mjs | 51 ++++++++++++++---- test/markdownlint-test.mjs | 35 ++++++------ .../markdownlint-test-exports.mjs.md | 2 + .../markdownlint-test-exports.mjs.snap | Bin 1544 -> 1583 bytes 7 files changed, 81 insertions(+), 36 deletions(-) diff --git a/lib/exports.d.mts b/lib/exports.d.mts index 7d9639af..afac5561 100644 --- a/lib/exports.d.mts +++ b/lib/exports.d.mts @@ -26,5 +26,4 @@ export type RuleOnErrorFixInfo = import("./markdownlint.mjs").RuleOnErrorFixInfo export type RuleOnErrorFixInfoNormalized = import("./markdownlint.mjs").RuleOnErrorFixInfoNormalized; export type RuleOnErrorInfo = import("./markdownlint.mjs").RuleOnErrorInfo; export type RuleParams = import("./markdownlint.mjs").RuleParams; -export type ToStringCallback = import("./markdownlint.mjs").ToStringCallback; -export { applyFix, applyFixes, getVersion } from "./markdownlint.mjs"; +export { applyFix, applyFixes, formatLintError, getVersion, parseLintErrorString } from "./markdownlint.mjs"; diff --git a/lib/exports.mjs b/lib/exports.mjs index dc0d137d..d96ab78b 100644 --- a/lib/exports.mjs +++ b/lib/exports.mjs @@ -1,6 +1,6 @@ // @ts-check -export { applyFix, applyFixes, getVersion } from "./markdownlint.mjs"; +export { applyFix, applyFixes, formatLintError, getVersion, parseLintErrorString } from "./markdownlint.mjs"; export { resolveModule } from "./resolve-module.cjs"; /** @typedef {import("./markdownlint.mjs").Configuration} Configuration */ @@ -30,4 +30,3 @@ export { resolveModule } from "./resolve-module.cjs"; /** @typedef {import("./markdownlint.mjs").RuleOnErrorFixInfoNormalized} RuleOnErrorFixInfoNormalized */ /** @typedef {import("./markdownlint.mjs").RuleOnErrorInfo} RuleOnErrorInfo */ /** @typedef {import("./markdownlint.mjs").RuleParams} RuleParams */ -/** @typedef {import("./markdownlint.mjs").ToStringCallback} ToStringCallback */ diff --git a/lib/markdownlint.d.mts b/lib/markdownlint.d.mts index 1117fd0d..8a6df4d6 100644 --- a/lib/markdownlint.d.mts +++ b/lib/markdownlint.d.mts @@ -76,6 +76,21 @@ export function applyFix(line: string, fixInfo: RuleOnErrorFixInfo, lineEnding?: * @returns {string} Fixed content. */ export function applyFixes(input: string, errors: RuleOnErrorInfo[]): string; +/** + * TBD + * + * @param {string} source Source file name or identifier. + * @param {LintError} lintError Lint error. + * @returns {string} Lint error string. + */ +export function formatLintError(source: string, lintError: LintError): string; +/** + * TBD + * + * @param {string} lintErrorString Lint error string. + * @returns {Object|null} Lint error (if valid). + */ +export function parseLintErrorString(lintErrorString: string): any | null; /** * Gets the (semantic) version of the library. * @@ -442,10 +457,6 @@ export type Options = { * A markdown-it plugin. */ export type Plugin = any[]; -/** - * Function to pretty-print lint results. - */ -export type ToStringCallback = () => string; /** * Lint results. */ @@ -483,11 +494,11 @@ export type LintError = { /** * Column number (1-based) and length. */ - errorRange: number[]; + errorRange: number[] | null; /** * Fix information. */ - fixInfo?: FixInfo; + fixInfo: FixInfo | null; }; /** * Fix information. diff --git a/lib/markdownlint.mjs b/lib/markdownlint.mjs index 3bbad333..d3cee171 100644 --- a/lib/markdownlint.mjs +++ b/lib/markdownlint.mjs @@ -522,6 +522,7 @@ function lintContent( "config": null }); // Function to run for each rule + /** @type {LintError[]} */ const results = []; /** * @param {Rule} rule Rule. @@ -1303,6 +1304,44 @@ export function applyFixes(input, errors) { return lines.filter((line) => line !== null).join(lineEnding); } +/** + * TBD + * + * @param {string} source Source file name or identifier. + * @param {LintError} lintError Lint error. + * @returns {string} Lint error string. + */ +export function formatLintError(source, lintError) { + const { lineNumber, ruleNames, ruleDescription, errorDetail, errorContext, errorRange } = lintError; + const ruleName = ruleNames.join("/"); + const description = ruleDescription + + (errorDetail ? ` [${errorDetail}]` : "") + + (errorContext ? ` [Context: "${errorContext}"]` : ""); + const column = (errorRange && errorRange[0]) || 0; + const columnText = column ? `:${column}` : ""; + return `${source}:${lineNumber}${columnText} ${ruleName} ${description}`; +} + +/** + * TBD + * + * @param {string} lintErrorString Lint error string. + * @returns {Object|null} Lint error (if valid). + */ +export function parseLintErrorString(lintErrorString) { + const matchRe = /^([^:]+):\s*(\d+)(?::(\d+))?:?\s(\S+)\s(.+)$/; + const match = matchRe.exec(lintErrorString); + return match ? + { + "source": match[1], + "line": match[2], + "column": match[3], + "rule": match[4], + "message": match[5] + } : + null; +} + /** * Gets the (semantic) version of the library. * @@ -1492,18 +1531,10 @@ export function getVersion() { * @typedef {Array} Plugin */ -/** - * Function to pretty-print lint results. - * - * @callback ToStringCallback - * @returns {string} Pretty-printed results. - */ - /** * Lint results. * * @typedef {Object.} LintResults - * @property {ToStringCallback} toString String representation. */ /** @@ -1516,8 +1547,8 @@ export function getVersion() { * @property {string} ruleInformation Link to more information. * @property {string} errorDetail Detail about the error. * @property {string} errorContext Context for the error. - * @property {number[]} errorRange Column number (1-based) and length. - * @property {FixInfo} [fixInfo] Fix information. + * @property {number[]|null} errorRange Column number (1-based) and length. + * @property {FixInfo|null} fixInfo Fix information. */ /** diff --git a/test/markdownlint-test.mjs b/test/markdownlint-test.mjs index 1d7879d4..ae62da8e 100644 --- a/test/markdownlint-test.mjs +++ b/test/markdownlint-test.mjs @@ -13,7 +13,7 @@ import pluginInline from "markdown-it-for-inline"; import pluginSub from "markdown-it-sub"; import pluginSup from "markdown-it-sup"; import test from "ava"; -import { getVersion } from "markdownlint"; +import { formatLintError, getVersion, parseLintErrorString } from "markdownlint"; import { lint as lintAsync } from "markdownlint/async"; import { lint as lintPromise } from "markdownlint/promise"; import { lint as lintSync } from "markdownlint/sync"; @@ -1392,27 +1392,30 @@ test("getVersion", (t) => { t.is(actual, expected, "Version string not correct."); }); -const matcherRe = /^(?[^:]+):\s*(?\d+)(?::(?\d+))?:?\s(?\S+)\s(?.+)$/; - -test("problemMatcher", async(t) => { - t.plan(2); - const content = "# Heading\nText `code ` text "; +test("formatLintError-parseLintErrorString", async(t) => { + t.plan(29); + t.is(null, parseLintErrorString("")); + const content = "# Heading\nText `code ` text \n\n"; const options = { "strings": { "relative/path/file name.md": content } }; const results = await lintPromise(options); - const getMatches = (input) => input.split("\n").map((line) => matcherRe.exec(line)?.groups); - t.snapshot(getMatches(results.toString())); - // eslint-disable-next-line camelcase - const cli_0_45_0__cli2_0_18_1 = - `relative/path/file name.md:1:3 MD019/no-multiple-space-atx Multiple spaces after hash on atx style heading [Context: "# Heading"] -relative/path/file name.md:1 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "# Heading"] -relative/path/file name.md:2:18 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] -relative/path/file name.md:2:11 MD038/no-space-in-code Spaces inside code span elements [Context: "\`code \`"] -relative/path/file name.md:2:18 MD047/single-trailing-newline Files should end with a single newline character`; - t.snapshot(getMatches(cli_0_45_0__cli2_0_18_1)); + for (const source of Object.keys(results)) { + for (const lintError of results[source]) { + const formatted = formatLintError(source, lintError); + const parsed = parseLintErrorString(formatted); + t.is(parsed.source, source); + t.is(parsed.line, lintError.lineNumber.toString()); + t.is(Boolean(parsed.column), Boolean(lintError.errorRange)); + if (lintError.errorRange) { + t.is(parsed.column, lintError.errorRange[0].toString()); + } + t.is(parsed.rule, lintError.ruleNames.join("/")); + t.is(parsed.message.replaceAll(/ \[[^\]]+\]/g, ""), lintError.ruleDescription); + } + } }); test("constants", (t) => { diff --git a/test/snapshots/markdownlint-test-exports.mjs.md b/test/snapshots/markdownlint-test-exports.mjs.md index f03e1f08..5745084e 100644 --- a/test/snapshots/markdownlint-test-exports.mjs.md +++ b/test/snapshots/markdownlint-test-exports.mjs.md @@ -12,7 +12,9 @@ Generated by [AVA](https://avajs.dev). markdownlint: [ 'applyFix', 'applyFixes', + 'formatLintError', 'getVersion', + 'parseLintErrorString', 'resolveModule', ], 'markdownlint/async': [ diff --git a/test/snapshots/markdownlint-test-exports.mjs.snap b/test/snapshots/markdownlint-test-exports.mjs.snap index bf686f39cc606c40a1d48aa09feaf53c82875be7..26faffb6733e8db0c71c020938d74ec4fdb76d74 100644 GIT binary patch literal 1583 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