Update MD043/required-headings to support "?" meaning "exactly one unspecified heading" (fixes #475).

This commit is contained in:
David Anson 2025-03-10 21:57:28 -07:00
parent 06b60b7372
commit 5749c8dcf7
15 changed files with 297 additions and 18 deletions

View file

@ -5,7 +5,7 @@ structure for a set of files.
To require exactly the following structure: To require exactly the following structure:
```markdown ```markdown
# Head # Heading
## Item ## Item
### Detail ### Detail
``` ```
@ -14,7 +14,7 @@ Set the `headings` parameter to:
```json ```json
[ [
"# Head", "# Heading",
"## Item", "## Item",
"### Detail" "### Detail"
] ]
@ -23,7 +23,7 @@ Set the `headings` parameter to:
To allow optional headings as with the following structure: To allow optional headings as with the following structure:
```markdown ```markdown
# Head # Heading
## Item ## Item
### Detail (optional) ### Detail (optional)
## Foot ## Foot
@ -36,7 +36,7 @@ special value `"+"` meaning "one or more unspecified headings" and set the
```json ```json
[ [
"# Head", "# Heading",
"## Item", "## Item",
"*", "*",
"## Foot", "## Foot",
@ -44,6 +44,24 @@ special value `"+"` meaning "one or more unspecified headings" and set the
] ]
``` ```
To allow a single required heading to vary as with a project name:
```markdown
# Project Name
## Description
## Examples
```
Use the special value `"?"` meaning "exactly one unspecified heading":
```json
[
"?",
"## Description",
"## Examples"
]
```
When an error is detected, this rule outputs the line number of the first When an error is detected, this rule outputs the line number of the first
problematic heading (otherwise, it outputs the last line number of the file). problematic heading (otherwise, it outputs the last line number of the file).

View file

@ -1767,7 +1767,7 @@ structure for a set of files.
To require exactly the following structure: To require exactly the following structure:
```markdown ```markdown
# Head # Heading
## Item ## Item
### Detail ### Detail
``` ```
@ -1776,7 +1776,7 @@ Set the `headings` parameter to:
```json ```json
[ [
"# Head", "# Heading",
"## Item", "## Item",
"### Detail" "### Detail"
] ]
@ -1785,7 +1785,7 @@ Set the `headings` parameter to:
To allow optional headings as with the following structure: To allow optional headings as with the following structure:
```markdown ```markdown
# Head # Heading
## Item ## Item
### Detail (optional) ### Detail (optional)
## Foot ## Foot
@ -1798,7 +1798,7 @@ special value `"+"` meaning "one or more unspecified headings" and set the
```json ```json
[ [
"# Head", "# Heading",
"## Item", "## Item",
"*", "*",
"## Foot", "## Foot",
@ -1806,6 +1806,24 @@ special value `"+"` meaning "one or more unspecified headings" and set the
] ]
``` ```
To allow a single required heading to vary as with a project name:
```markdown
# Project Name
## Description
## Examples
```
Use the special value `"?"` meaning "exactly one unspecified heading":
```json
[
"?",
"## Description",
"## Examples"
]
```
When an error is detected, this rule outputs the line number of the first When an error is detected, this rule outputs the line number of the first
problematic heading (otherwise, it outputs the last line number of the file). problematic heading (otherwise, it outputs the last line number of the file).

View file

@ -16,7 +16,7 @@ structure for a set of files.
To require exactly the following structure: To require exactly the following structure:
```markdown ```markdown
# Head # Heading
## Item ## Item
### Detail ### Detail
``` ```
@ -25,7 +25,7 @@ Set the `headings` parameter to:
```json ```json
[ [
"# Head", "# Heading",
"## Item", "## Item",
"### Detail" "### Detail"
] ]
@ -34,7 +34,7 @@ Set the `headings` parameter to:
To allow optional headings as with the following structure: To allow optional headings as with the following structure:
```markdown ```markdown
# Head # Heading
## Item ## Item
### Detail (optional) ### Detail (optional)
## Foot ## Foot
@ -47,7 +47,7 @@ special value `"+"` meaning "one or more unspecified headings" and set the
```json ```json
[ [
"# Head", "# Heading",
"## Item", "## Item",
"*", "*",
"## Foot", "## Foot",
@ -55,6 +55,24 @@ special value `"+"` meaning "one or more unspecified headings" and set the
] ]
``` ```
To allow a single required heading to vary as with a project name:
```markdown
# Project Name
## Description
## Examples
```
Use the special value `"?"` meaning "exactly one unspecified heading":
```json
[
"?",
"## Description",
"## Examples"
]
```
When an error is detected, this rule outputs the line number of the first When an error is detected, this rule outputs the line number of the first
problematic heading (otherwise, it outputs the last line number of the file). problematic heading (otherwise, it outputs the last line number of the file).

View file

@ -38,6 +38,8 @@ export default {
} }
} else if (expected === "+") { } else if (expected === "+") {
matchAny = true; matchAny = true;
} else if (expected === "?") {
// Allow current, match next
} else if (handleCase(expected) === handleCase(actual)) { } else if (handleCase(expected) === handleCase(actual)) {
matchAny = false; matchAny = false;
} else if (matchAny) { } else if (matchAny) {

View file

@ -396,7 +396,7 @@ for (const rule of rules) {
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"pattern": "^(\\*|\\+|#{1,6} .*)$" "pattern": "^(\\*|\\+|\\?|#{1,6}\\s+\\S.*)$"
}, },
"default": [] "default": []
}, },

View file

@ -1201,7 +1201,7 @@
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"pattern": "^(\\*|\\+|#{1,6} .*)$" "pattern": "^(\\*|\\+|\\?|#{1,6}\\s+\\S.*)$"
}, },
"default": [] "default": []
}, },
@ -1226,7 +1226,7 @@
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"pattern": "^(\\*|\\+|#{1,6} .*)$" "pattern": "^(\\*|\\+|\\?|#{1,6}\\s+\\S.*)$"
}, },
"default": [] "default": []
}, },

View file

@ -1201,7 +1201,7 @@
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"pattern": "^(\\*|\\+|#{1,6} .*)$" "pattern": "^(\\*|\\+|\\?|#{1,6}\\s+\\S.*)$"
}, },
"default": [] "default": []
}, },
@ -1226,7 +1226,7 @@
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"pattern": "^(\\*|\\+|#{1,6} .*)$" "pattern": "^(\\*|\\+|\\?|#{1,6}\\s+\\S.*)$"
}, },
"default": [] "default": []
}, },

View file

@ -907,7 +907,7 @@ test("readme", async(t) => {
}); });
test("validateJsonUsingConfigSchemaStrict", async(t) => { test("validateJsonUsingConfigSchemaStrict", async(t) => {
t.plan(187); t.plan(192);
// @ts-ignore // @ts-ignore
const ajv = new Ajv(ajvOptions); const ajv = new Ajv(ajvOptions);
const validateSchemaStrict = ajv.compile(configSchemaStrict); const validateSchemaStrict = ajv.compile(configSchemaStrict);

View file

@ -0,0 +1,15 @@
# Project Name
## Description
<!-- markdownlint-configure-file {
"required-headings": {
"headings": [
"# Project Name",
"## Description",
"?"
]
}
} -->
{MD043:+1}

View file

@ -0,0 +1,15 @@
# Project Name
## Description
## Examples
<!-- markdownlint-configure-file {
"required-headings": {
"headings": [
"?",
"## Description",
"## Examples"
]
}
} -->

View file

@ -0,0 +1,15 @@
# Project Name
## Description
## Examples
<!-- markdownlint-configure-file {
"required-headings": {
"headings": [
"# Project Name",
"## Description",
"?"
]
}
} -->

View file

@ -0,0 +1,15 @@
# Project Name
## Description
## Examples
<!-- markdownlint-configure-file {
"required-headings": {
"headings": [
"# Project Name",
"?",
"## Examples"
]
}
} -->

View file

@ -0,0 +1,15 @@
# Project Name
## Examples
<!-- markdownlint-configure-file {
"required-headings": {
"headings": [
"# Project Name",
"?",
"## Examples"
]
}
} -->
{MD043:+1}

View file

@ -48588,6 +48588,154 @@ Generated by [AVA](https://avajs.dev).
`, `,
} }
## required-headings-question-extra.md
> Snapshot 1
{
errors: [
{
errorContext: '?',
errorDetail: null,
errorRange: null,
fixInfo: null,
lineNumber: 16,
ruleDescription: 'Required heading structure',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md043.md',
ruleNames: [
'MD043',
'required-headings',
],
},
],
fixed: `# Project Name␊
## Description␊
<!-- markdownlint-configure-file {␊
"required-headings": {␊
"headings": [␊
"# Project Name",␊
"## Description",␊
"?"␊
]␊
}␊
} -->␊
{MD043:+1}␊
`,
}
## required-headings-question-first.md
> Snapshot 1
{
errors: [],
fixed: `# Project Name␊
## Description␊
## Examples␊
<!-- markdownlint-configure-file {␊
"required-headings": {␊
"headings": [␊
"?",␊
"## Description",␊
"## Examples"␊
]␊
}␊
} -->␊
`,
}
## required-headings-question-last.md
> Snapshot 1
{
errors: [],
fixed: `# Project Name␊
## Description␊
## Examples␊
<!-- markdownlint-configure-file {␊
"required-headings": {␊
"headings": [␊
"# Project Name",␊
"## Description",␊
"?"␊
]␊
}␊
} -->␊
`,
}
## required-headings-question-middle.md
> Snapshot 1
{
errors: [],
fixed: `# Project Name␊
## Description␊
## Examples␊
<!-- markdownlint-configure-file {␊
"required-headings": {␊
"headings": [␊
"# Project Name",␊
"?",␊
"## Examples"␊
]␊
}␊
} -->␊
`,
}
## required-headings-question-missing.md
> Snapshot 1
{
errors: [
{
errorContext: '## Examples',
errorDetail: null,
errorRange: null,
fixInfo: null,
lineNumber: 16,
ruleDescription: 'Required heading structure',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md043.md',
ruleNames: [
'MD043',
'required-headings',
],
},
],
fixed: `# Project Name␊
## Examples␊
<!-- markdownlint-configure-file {␊
"required-headings": {␊
"headings": [␊
"# Project Name",␊
"?",␊
"## Examples"␊
]␊
}␊
} -->␊
{MD043:+1}␊
`,
}
## required-headings-wrong-match-case.md ## required-headings-wrong-match-case.md
> Snapshot 1 > Snapshot 1