Add configPointer input, examples for package.json/pyproject.toml.

This commit is contained in:
David Anson
2026-03-25 21:04:58 -07:00
parent 154744fb7a
commit 08fc3a21b1
9 changed files with 401 additions and 3 deletions
+2
View File
@@ -1,5 +1,7 @@
CommonMark
config
configPointer
JSON
LLMs
markdownlint-cli2-action
npm
+30
View File
@@ -96,6 +96,36 @@ jobs:
id: test
- run: exit 1
if: steps.test.outcome != 'failure'
package-json:
name: package.json (test/errors.md, 2 errors)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: rm .markdownlint.json
- uses: ./
with:
config: 'config/package.json'
configPointer: '/markdownlint-cli2'
globs: 'test/*.md'
continue-on-error: true
id: test
- run: exit 1
if: steps.test.outcome != 'failure'
pyproject-toml:
name: pyproject.toml (test/errors.md, 2 errors)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: rm .markdownlint.json
- uses: ./
with:
config: 'config/pyproject.toml'
configPointer: '/tool/markdownlint-cli2'
globs: 'test/*.md'
continue-on-error: true
id: test
- run: exit 1
if: steps.test.outcome != 'failure'
fix:
name: fix (test/errors.md, 0 errors)
runs-on: ubuntu-latest
+29
View File
@@ -20,6 +20,14 @@ Path of a file to use for the base configuration object (defaults to none)
Equivalent to using the `--config` [command-line option][command-line] and
passing the specified configuration file.
### configPointer (optional)
[JSON Pointer][json-pointer] to a configuration object within the `--config`
file (defaults to none)
Equivalent to using the `--configPointer` [command-line option][command-line]
and passing the specified JSON Pointer.
### fix (optional)
Whether to fix supported issues automatically (any truthy value enables)
@@ -100,6 +108,26 @@ To specify a custom configuration file:
globs: '**/*.md'
```
To specify an embedded object in `package.json`:
```yaml
- uses: DavidAnson/markdownlint-cli2-action@v22
with:
config: 'package.json'
configPointer: '/markdownlint-cli2'
globs: '**/*.md'
```
To specify an embedded object in `pyproject.toml`:
```yaml
- uses: DavidAnson/markdownlint-cli2-action@v22
with:
config: 'pyproject.toml'
configPointer: '/tool/markdownlint-cli2'
globs: '**/*.md'
```
To prevent linting issues from failing the workflow run:
```yaml
@@ -119,6 +147,7 @@ and/or gradually introducing linting rules to a new repository).
[commonmark]: https://commonmark.org/
[example-yml]: .github/workflows/example.yml
[glob-syntax]: https://github.com/DavidAnson/markdownlint-cli2#use
[json-pointer]: https://datatracker.ietf.org/doc/html/rfc6901
[markdown]: https://wikipedia.org/wiki/Markdown
[markdownlint]: https://github.com/DavidAnson/markdownlint
[markdownlint-cli2]: https://github.com/DavidAnson/markdownlint-cli2
+4
View File
@@ -9,6 +9,10 @@ inputs:
description: Path of a file to use for the base configuration object (defaults to none)
default: ''
required: false
configPointer:
description: JSON Pointer to a configuration object within the --config file (defaults to none)
default: ''
required: false
fix:
description: Whether to fix supported issues automatically (any truthy value enables)
default: ''
+9
View File
@@ -0,0 +1,9 @@
{
"private": true,
"markdownlint-cli2": {
"config": {
"single-trailing-newline": false
}
}
}
+5
View File
@@ -0,0 +1,5 @@
[project]
name = "my-project"
[tool.markdownlint-cli2.config]
single-trailing-newline = false
+313 -1
View File
@@ -7192,6 +7192,8 @@ const path = __nccwpck_require__(6928);
const WIN_SLASH = '\\\\/';
const WIN_NO_SLASH = `[^${WIN_SLASH}]`;
const DEFAULT_MAX_EXTGLOB_RECURSION = 0;
/**
* Posix glob regex
*/
@@ -7255,6 +7257,7 @@ const WINDOWS_CHARS = {
*/
const POSIX_REGEX_SOURCE = {
__proto__: null,
alnum: 'a-zA-Z0-9',
alpha: 'a-zA-Z',
ascii: '\\x00-\\x7F',
@@ -7272,6 +7275,7 @@ const POSIX_REGEX_SOURCE = {
};
module.exports = {
DEFAULT_MAX_EXTGLOB_RECURSION,
MAX_LENGTH: 1024 * 64,
POSIX_REGEX_SOURCE,
@@ -7285,6 +7289,7 @@ module.exports = {
// Replace globs with equivalent patterns to reduce parsing time.
REPLACEMENTS: {
__proto__: null,
'***': '*',
'**/**': '**',
'**/**/**': '**'
@@ -7419,6 +7424,277 @@ const syntaxError = (type, char) => {
return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
};
const splitTopLevel = input => {
const parts = [];
let bracket = 0;
let paren = 0;
let quote = 0;
let value = '';
let escaped = false;
for (const ch of input) {
if (escaped === true) {
value += ch;
escaped = false;
continue;
}
if (ch === '\\') {
value += ch;
escaped = true;
continue;
}
if (ch === '"') {
quote = quote === 1 ? 0 : 1;
value += ch;
continue;
}
if (quote === 0) {
if (ch === '[') {
bracket++;
} else if (ch === ']' && bracket > 0) {
bracket--;
} else if (bracket === 0) {
if (ch === '(') {
paren++;
} else if (ch === ')' && paren > 0) {
paren--;
} else if (ch === '|' && paren === 0) {
parts.push(value);
value = '';
continue;
}
}
}
value += ch;
}
parts.push(value);
return parts;
};
const isPlainBranch = branch => {
let escaped = false;
for (const ch of branch) {
if (escaped === true) {
escaped = false;
continue;
}
if (ch === '\\') {
escaped = true;
continue;
}
if (/[?*+@!()[\]{}]/.test(ch)) {
return false;
}
}
return true;
};
const normalizeSimpleBranch = branch => {
let value = branch.trim();
let changed = true;
while (changed === true) {
changed = false;
if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
value = value.slice(2, -1);
changed = true;
}
}
if (!isPlainBranch(value)) {
return;
}
return value.replace(/\\(.)/g, '$1');
};
const hasRepeatedCharPrefixOverlap = branches => {
const values = branches.map(normalizeSimpleBranch).filter(Boolean);
for (let i = 0; i < values.length; i++) {
for (let j = i + 1; j < values.length; j++) {
const a = values[i];
const b = values[j];
const char = a[0];
if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) {
continue;
}
if (a === b || a.startsWith(b) || b.startsWith(a)) {
return true;
}
}
}
return false;
};
const parseRepeatedExtglob = (pattern, requireEnd = true) => {
if ((pattern[0] !== '+' && pattern[0] !== '*') || pattern[1] !== '(') {
return;
}
let bracket = 0;
let paren = 0;
let quote = 0;
let escaped = false;
for (let i = 1; i < pattern.length; i++) {
const ch = pattern[i];
if (escaped === true) {
escaped = false;
continue;
}
if (ch === '\\') {
escaped = true;
continue;
}
if (ch === '"') {
quote = quote === 1 ? 0 : 1;
continue;
}
if (quote === 1) {
continue;
}
if (ch === '[') {
bracket++;
continue;
}
if (ch === ']' && bracket > 0) {
bracket--;
continue;
}
if (bracket > 0) {
continue;
}
if (ch === '(') {
paren++;
continue;
}
if (ch === ')') {
paren--;
if (paren === 0) {
if (requireEnd === true && i !== pattern.length - 1) {
return;
}
return {
type: pattern[0],
body: pattern.slice(2, i),
end: i
};
}
}
}
};
const getStarExtglobSequenceOutput = pattern => {
let index = 0;
const chars = [];
while (index < pattern.length) {
const match = parseRepeatedExtglob(pattern.slice(index), false);
if (!match || match.type !== '*') {
return;
}
const branches = splitTopLevel(match.body).map(branch => branch.trim());
if (branches.length !== 1) {
return;
}
const branch = normalizeSimpleBranch(branches[0]);
if (!branch || branch.length !== 1) {
return;
}
chars.push(branch);
index += match.end + 1;
}
if (chars.length < 1) {
return;
}
const source = chars.length === 1
? utils.escapeRegex(chars[0])
: `[${chars.map(ch => utils.escapeRegex(ch)).join('')}]`;
return `${source}*`;
};
const repeatedExtglobRecursion = pattern => {
let depth = 0;
let value = pattern.trim();
let match = parseRepeatedExtglob(value);
while (match) {
depth++;
value = match.body.trim();
match = parseRepeatedExtglob(value);
}
return depth;
};
const analyzeRepeatedExtglob = (body, options) => {
if (options.maxExtglobRecursion === false) {
return { risky: false };
}
const max =
typeof options.maxExtglobRecursion === 'number'
? options.maxExtglobRecursion
: constants.DEFAULT_MAX_EXTGLOB_RECURSION;
const branches = splitTopLevel(body).map(branch => branch.trim());
if (branches.length > 1) {
if (
branches.some(branch => branch === '') ||
branches.some(branch => /^[*?]+$/.test(branch)) ||
hasRepeatedCharPrefixOverlap(branches)
) {
return { risky: true };
}
}
for (const branch of branches) {
const safeOutput = getStarExtglobSequenceOutput(branch);
if (safeOutput) {
return { risky: true, safeOutput };
}
if (repeatedExtglobRecursion(branch) > max) {
return { risky: true };
}
}
return { risky: false };
};
/**
* Parse the given input string.
* @param {String} input
@@ -7600,6 +7876,8 @@ const parse = (input, options) => {
token.prev = prev;
token.parens = state.parens;
token.output = state.output;
token.startIndex = state.index;
token.tokensIndex = tokens.length;
const output = (opts.capture ? '(' : '') + token.open;
increment('parens');
@@ -7609,6 +7887,34 @@ const parse = (input, options) => {
};
const extglobClose = token => {
const literal = input.slice(token.startIndex, state.index + 1);
const body = input.slice(token.startIndex + 2, state.index);
const analysis = analyzeRepeatedExtglob(body, opts);
if ((token.type === 'plus' || token.type === 'star') && analysis.risky) {
const safeOutput = analysis.safeOutput
? (token.output ? '' : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput)
: undefined;
const open = tokens[token.tokensIndex];
open.type = 'text';
open.value = literal;
open.output = safeOutput || utils.escapeRegex(literal);
for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
tokens[i].value = '';
tokens[i].output = '';
delete tokens[i].suffix;
}
state.output = token.output + open.output;
state.backtrack = true;
push({ type: 'paren', extglob: true, value, output: '' });
decrement('parens');
return;
}
let output = token.close + (opts.capture ? ')' : '');
let rest;
@@ -80009,7 +80315,7 @@ const markdownlint_cli2_main = async (/** @type {Parameters} */ params) => {
const logMessage = info;
const outputFormatter = (options) => {
const outputFormatter = (/** @type {any} */ options) => {
const { results } = options;
for (const lintError of results) {
const {
@@ -80030,6 +80336,7 @@ const outputFormatter = (options) => {
const information = ruleInformation ? ` ${ruleInformation}` : "";
const message =
`${fileName}${line}${column} ${name} ${ruleDescription}${detail}${context}${information}`;
/** @type {import("@actions/core").AnnotationProperties} */
const annotation = {
"title": ruleDescription,
"file": fileName,
@@ -80058,6 +80365,10 @@ const config = getInput("config");
if (config) {
argv.push("--config", config);
}
const configPointer = getInput("configPointer");
if (configPointer) {
argv.push("--configPointer", configPointer);
}
const fix = Boolean(getInput("fix"));
if (fix) {
argv.push("--fix");
@@ -80070,6 +80381,7 @@ const parameters = {
"outputFormatters": [ [ outputFormatter ] ]
}
};
// @ts-ignore
markdownlint_cli2_main(parameters).then(
(code) => code && setFailed(`Failed with exit code: ${code}`),
(error) => setFailed(`Failed due to error: ${error}`)
+2 -1
View File
@@ -16,13 +16,14 @@ export default [
"quotes": "double",
"semi": true
}),
eslintPluginUnicorn.configs["flat/all"],
eslintPluginUnicorn.configs.all,
{
"linterOptions": {
"reportUnusedDisableDirectives": true
},
"rules": {
"max-statements": "off",
"no-inline-comments": "off",
"no-magic-numbers": "off",
"no-ternary": "off",
"one-var": "off",
+7 -1
View File
@@ -4,7 +4,7 @@ import * as core from "@actions/core";
import { main as markdownlintCli2 } from "markdownlint-cli2";
const logMessage = core.info;
const outputFormatter = (options) => {
const outputFormatter = (/** @type {any} */ options) => {
const { results } = options;
for (const lintError of results) {
const {
@@ -25,6 +25,7 @@ const outputFormatter = (options) => {
const information = ruleInformation ? ` ${ruleInformation}` : "";
const message =
`${fileName}${line}${column} ${name} ${ruleDescription}${detail}${context}${information}`;
/** @type {import("@actions/core").AnnotationProperties} */
const annotation = {
"title": ruleDescription,
"file": fileName,
@@ -53,6 +54,10 @@ const config = core.getInput("config");
if (config) {
argv.push("--config", config);
}
const configPointer = core.getInput("configPointer");
if (configPointer) {
argv.push("--configPointer", configPointer);
}
const fix = Boolean(core.getInput("fix"));
if (fix) {
argv.push("--fix");
@@ -65,6 +70,7 @@ const parameters = {
"outputFormatters": [ [ outputFormatter ] ]
}
};
// @ts-ignore
markdownlintCli2(parameters).then(
(code) => code && core.setFailed(`Failed with exit code: ${code}`),
(error) => core.setFailed(`Failed due to error: ${error}`)