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:
```markdown
# Head
# Heading
## Item
### Detail
```
@ -14,7 +14,7 @@ Set the `headings` parameter to:
```json
[
"# Head",
"# Heading",
"## Item",
"### Detail"
]
@ -23,7 +23,7 @@ Set the `headings` parameter to:
To allow optional headings as with the following structure:
```markdown
# Head
# Heading
## Item
### Detail (optional)
## Foot
@ -36,7 +36,7 @@ special value `"+"` meaning "one or more unspecified headings" and set the
```json
[
"# Head",
"# Heading",
"## Item",
"*",
"## 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
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:
```markdown
# Head
# Heading
## Item
### Detail
```
@ -1776,7 +1776,7 @@ Set the `headings` parameter to:
```json
[
"# Head",
"# Heading",
"## Item",
"### Detail"
]
@ -1785,7 +1785,7 @@ Set the `headings` parameter to:
To allow optional headings as with the following structure:
```markdown
# Head
# Heading
## Item
### Detail (optional)
## Foot
@ -1798,7 +1798,7 @@ special value `"+"` meaning "one or more unspecified headings" and set the
```json
[
"# Head",
"# Heading",
"## Item",
"*",
"## 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
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:
```markdown
# Head
# Heading
## Item
### Detail
```
@ -25,7 +25,7 @@ Set the `headings` parameter to:
```json
[
"# Head",
"# Heading",
"## Item",
"### Detail"
]
@ -34,7 +34,7 @@ Set the `headings` parameter to:
To allow optional headings as with the following structure:
```markdown
# Head
# Heading
## Item
### Detail (optional)
## Foot
@ -47,7 +47,7 @@ special value `"+"` meaning "one or more unspecified headings" and set the
```json
[
"# Head",
"# Heading",
"## Item",
"*",
"## 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
problematic heading (otherwise, it outputs the last line number of the file).

View file

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

View file

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

View file

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

View file

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

View file

@ -907,7 +907,7 @@ test("readme", async(t) => {
});
test("validateJsonUsingConfigSchemaStrict", async(t) => {
t.plan(187);
t.plan(192);
// @ts-ignore
const ajv = new Ajv(ajvOptions);
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
> Snapshot 1