wip
Some checks failed
Checkers / linkcheck (push) Has been cancelled
Checkers / spellcheck (push) Has been cancelled
CI / build (20, macos-latest) (push) Has been cancelled
CI / build (20, ubuntu-latest) (push) Has been cancelled
CI / build (20, windows-latest) (push) Has been cancelled
CI / build (22, macos-latest) (push) Has been cancelled
CI / build (22, ubuntu-latest) (push) Has been cancelled
CI / build (22, windows-latest) (push) Has been cancelled
CI / build (24, macos-latest) (push) Has been cancelled
CI / build (24, ubuntu-latest) (push) Has been cancelled
CI / build (24, windows-latest) (push) Has been cancelled
CI / pnpm (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
TestRepos / build (latest, ubuntu-latest) (push) Has been cancelled
UpdateTestRepos / update (push) Has been cancelled

This commit is contained in:
David Anson 2025-09-13 17:00:37 -07:00
parent 9065f74bb8
commit e857672728
6 changed files with 1312 additions and 129 deletions

View file

@ -4,11 +4,13 @@ import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
/* eslint-disable jsdoc/no-undefined-types */
/**
* Gets the file name of the current module.
* Shims import.meta.filename for Node 18.
*
* @param {Object} meta ESM import.meta object.
* @param {ImportMeta} meta ESM import.meta object.
* @returns {string} File name.
*/
// eslint-disable-next-line no-underscore-dangle
@ -18,7 +20,7 @@ export const __filename = (meta) => fileURLToPath(meta.url);
* Gets the directory name of the current module.
* Shims import.meta.dirname for Node 18.
*
* @param {Object} meta ESM import.meta object.
* @param {ImportMeta} meta ESM import.meta object.
* @returns {string} Directory name.
*/
// eslint-disable-next-line no-underscore-dangle
@ -28,9 +30,9 @@ export const __dirname = (meta) => path.dirname(__filename(meta));
* Imports a file as JSON.
* Avoids "ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time".
*
* @param {Object} meta ESM import.meta object.
* @param {ImportMeta} meta ESM import.meta object.
* @param {string} file JSON file to import.
* @returns {Promise<Object>} JSON object.
* @returns {Promise<any>} JSON object.
*/
export const importWithTypeJson = async(meta, file) => (
// @ts-ignore

View file

@ -22,7 +22,9 @@ import * as constants from "../lib/constants.mjs";
import rules from "../lib/rules.mjs";
import customRules from "./rules/rules.cjs";
import { __dirname, importWithTypeJson } from "./esm-helpers.mjs";
/** @type {{exports: Object.<string, string>, homepage: string, version: string}} */
const packageJson = await importWithTypeJson(import.meta, "../package.json");
/** @type {{$id: string, properties: Object<string, any>}} */
const configSchema = await importWithTypeJson(import.meta, "../schema/markdownlint-config-schema.json");
const configSchemaStrict = await importWithTypeJson(import.meta, "../schema/markdownlint-config-schema-strict.json");
@ -31,6 +33,10 @@ const ajvOptions = {
"allowUnionTypes": true
};
/** @typedef {import("ava").ImplementationFn<[]>} ImplementationFn */
/** @typedef {import("markdownlint").Configuration} Configuration */
/** @typedef {import("markdownlint").LintResults} LintResults */
/**
* Gets an instance of a markdown-it factory, suitable for use with options.markdownItFactory.
*
@ -140,90 +146,98 @@ test("inputOnlyNewline", (t) => new Promise((resolve) => {
});
}));
test("defaultTrue", (t) => new Promise((resolve) => {
t.plan(2);
const options = {
"files": [
"./test/atx_heading_spacing.md",
"./test/first_heading_bad_atx.md"
],
"config": {
"default": true
},
"noInlineConfig": true,
"resultVersion": 0
};
lintAsync(options, function callback(err, actualResult) {
t.falsy(err);
const expectedResult = {
"./test/atx_heading_spacing.md": {
"MD018": [ 1 ],
"MD019": [ 3, 5 ],
"MD041": [ 1 ]
},
"./test/first_heading_bad_atx.md": {
"MD041": [ 1 ]
}
};
// @ts-ignore
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
resolve();
});
}));
/** @typedef {Object<string, string[]>} NormalizedLintResults */
test("defaultFalse", (t) => new Promise((resolve) => {
t.plan(2);
const options = {
"files": [
"./test/atx_heading_spacing.md",
"./test/first_heading_bad_atx.md"
],
"config": {
"default": false
},
"noInlineConfig": true,
"resultVersion": 0
};
lintAsync(options, function callback(err, actualResult) {
t.falsy(err);
const expectedResult = {
"./test/atx_heading_spacing.md": {},
"./test/first_heading_bad_atx.md": {}
};
// @ts-ignore
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
resolve();
});
}));
/**
* Normalizes LintResults.
*
* @param {LintResults} results LintResults.
* @returns {NormalizedLintResults} Normalized LintResults.
*/
function normalizeLintResults(results) {
return Object.fromEntries(
Object.entries(results).map(
([ source, errors ]) => [
source, errors.map(
({ lineNumber, ruleNames }) => `${ruleNames[0]} ${lineNumber}`
)
]
)
);
}
test("defaultUndefined", (t) => new Promise((resolve) => {
t.plan(2);
const options = {
"files": [
"./test/atx_heading_spacing.md",
"./test/first_heading_bad_atx.md"
],
"config": {},
"noInlineConfig": true,
"resultVersion": 0
};
lintAsync(options, function callback(err, actualResult) {
t.falsy(err);
const expectedResult = {
"./test/atx_heading_spacing.md": {
"MD018": [ 1 ],
"MD019": [ 3, 5 ],
"MD041": [ 1 ]
},
"./test/first_heading_bad_atx.md": {
"MD041": [ 1 ]
}
/**
* Gets a Configuration default value test implementation.
*
* @param {(config: Configuration) => void} setDefault Sets the value of the Configuration default value.
* @param {NormalizedLintResults} expected Expected result.
* @returns {ImplementationFn} Test implementation.
*/
function getConfigDefaultTestImplementation(setDefault, expected) {
return async(t) => {
t.plan(1);
const options = {
"files": [
"./test/atx_heading_spacing.md",
"./test/first_heading_bad_atx.md"
],
"config": {},
"noInlineConfig": true
};
// @ts-ignore
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
resolve();
});
}));
setDefault(options.config);
const actual = await lintPromise(options);
t.deepEqual(normalizeLintResults(actual), expected);
};
}
const configDefaultTestExpectedEnabled = {
"./test/atx_heading_spacing.md": [
"MD018 1",
"MD019 3",
"MD019 5",
"MD041 1"
],
"./test/first_heading_bad_atx.md": [
"MD041 1"
]
};
const configDefaultTestExpectedDisabled = {
"./test/atx_heading_spacing.md": [],
"./test/first_heading_bad_atx.md": []
};
test("defaultTrue", getConfigDefaultTestImplementation(
(config) => (config.default = true),
configDefaultTestExpectedEnabled
));
test("defaultFalse", getConfigDefaultTestImplementation(
(config) => (config.default = false),
configDefaultTestExpectedDisabled
));
test("defaultError", getConfigDefaultTestImplementation(
(config) => (config.default = "error"),
configDefaultTestExpectedEnabled
));
test("defaultWarning", getConfigDefaultTestImplementation(
// @ts-ignore
(config) => (config.default = "warning"),
configDefaultTestExpectedEnabled
));
test("defaultOff", getConfigDefaultTestImplementation(
// @ts-ignore
(config) => (config.default = "off"),
configDefaultTestExpectedEnabled
));
test("defaultUnset", getConfigDefaultTestImplementation(
() => {},
configDefaultTestExpectedEnabled
));
test("disableRules", (t) => new Promise((resolve) => {
t.plan(2);
@ -802,6 +816,7 @@ test("customFileSystemSync", (t) => {
t.plan(2);
const file = "/dir/file.md";
const fsApi = {
// @ts-ignore
"readFileSync": (p) => {
t.is(p, file);
return "# Heading";
@ -818,6 +833,7 @@ test("customFileSystemAsync", (t) => new Promise((resolve) => {
t.plan(3);
const file = "/dir/file.md";
const fsApi = {
// @ts-ignore
"readFile": (p, o, cb) => {
t.is(p, file);
cb(null, "# Heading");
@ -836,6 +852,7 @@ test("customFileSystemAsync", (t) => new Promise((resolve) => {
test("readme", async(t) => {
t.plan(132);
/** @type {Object.<string, string[]>} */
const tagToRules = {};
for (const rule of rules) {
for (const tag of rule.tags) {
@ -894,7 +911,7 @@ test("readme", async(t) => {
} else if (inTags) {
const parts =
token.content.replace(/[`*]/g, "").split(/ - |, |,\n/);
const tag = parts.shift();
const tag = parts.shift() || "";
t.deepEqual(parts, tagToRules[tag] || [],
"Rule mismatch for tag " + tag + ".");
delete tagToRules[tag];
@ -1248,6 +1265,7 @@ test("token-map-spans", (t) => {
"tags": [ "tms" ],
"parser": "markdownit",
"function": function tokenMapSpans(params) {
/** @type {number[]} */
const tokenLines = [];
let lastLineNumber = -1;
const inlines = params.parsers.markdownit.tokens.filter(