mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-16 22:10:13 +01:00
Add MD043 required-headers "Required header structure" (fixes #22).
This commit is contained in:
parent
2612a96ae8
commit
c8ecec1953
26 changed files with 316 additions and 10 deletions
|
|
@ -82,6 +82,7 @@ playground for learning and exploring.
|
||||||
* **MD040** *fenced-code-language* - Fenced code blocks should have a language specified
|
* **MD040** *fenced-code-language* - Fenced code blocks should have a language specified
|
||||||
* **MD041** *first-line-h1* - First line in file should be a top level header
|
* **MD041** *first-line-h1* - First line in file should be a top level header
|
||||||
* **MD042** *no-empty-links* - No empty links
|
* **MD042** *no-empty-links* - No empty links
|
||||||
|
* **MD043** *required-headers* - Required header structure
|
||||||
|
|
||||||
See [Rules.md](doc/Rules.md) for more details.
|
See [Rules.md](doc/Rules.md) for more details.
|
||||||
|
|
||||||
|
|
@ -96,7 +97,7 @@ See [Rules.md](doc/Rules.md) for more details.
|
||||||
* **emphasis** - MD036, MD037
|
* **emphasis** - MD036, MD037
|
||||||
* **hard_tab** - MD010
|
* **hard_tab** - MD010
|
||||||
* **headers** - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023,
|
* **headers** - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023,
|
||||||
MD024, MD025, MD026, MD036, MD041
|
MD024, MD025, MD026, MD036, MD041, MD043
|
||||||
* **hr** - MD035
|
* **hr** - MD035
|
||||||
* **html** - MD033
|
* **html** - MD033
|
||||||
* **indentation** - MD005, MD006, MD007, MD027
|
* **indentation** - MD005, MD006, MD007, MD027
|
||||||
|
|
|
||||||
55
doc/Rules.md
55
doc/Rules.md
|
|
@ -222,7 +222,7 @@ Tags: whitespace
|
||||||
|
|
||||||
Aliases: no-trailing-spaces
|
Aliases: no-trailing-spaces
|
||||||
|
|
||||||
Parameters: br_spaces (number; default: 0)
|
Parameters: br_spaces (number; default 0)
|
||||||
|
|
||||||
This rule is triggered on any lines that end with whitespace. To fix this,
|
This rule is triggered on any lines that end with whitespace. To fix this,
|
||||||
find the line that is triggered and remove any trailing spaces from the end.
|
find the line that is triggered and remove any trailing spaces from the end.
|
||||||
|
|
@ -651,7 +651,7 @@ Tags: ol, ul, whitespace
|
||||||
|
|
||||||
Aliases: list-marker-space
|
Aliases: list-marker-space
|
||||||
|
|
||||||
Parameters: ul_single, ol_single, ul_multi, ol_multi (number, default 1)
|
Parameters: ul_single, ol_single, ul_multi, ol_multi (number; default 1)
|
||||||
|
|
||||||
This rule checks for the number of spaces between a list marker (e.g. '`-`',
|
This rule checks for the number of spaces between a list marker (e.g. '`-`',
|
||||||
'`*`', '`+`' or '`1.`') and the text of the list item.
|
'`*`', '`+`' or '`1.`') and the text of the list item.
|
||||||
|
|
@ -1026,3 +1026,54 @@ Empty fragments will trigger this rule:
|
||||||
But non-empty fragments will not:
|
But non-empty fragments will not:
|
||||||
|
|
||||||
[a valid fragment](#fragment)
|
[a valid fragment](#fragment)
|
||||||
|
|
||||||
|
## MD043 - Required header structure
|
||||||
|
|
||||||
|
Tags: headers
|
||||||
|
|
||||||
|
Aliases: required-headers
|
||||||
|
|
||||||
|
Parameters: headers (array of string; default `null` for disabled)
|
||||||
|
|
||||||
|
This rule is triggered when the headers in a file do not match the array of
|
||||||
|
headers passed to the rule. It can be used to enforce a standard header
|
||||||
|
structure for a set of files.
|
||||||
|
|
||||||
|
To require exactly the following structure:
|
||||||
|
|
||||||
|
# Head
|
||||||
|
## Item
|
||||||
|
### Detail
|
||||||
|
|
||||||
|
Set the `headers` parameter to:
|
||||||
|
|
||||||
|
[
|
||||||
|
"# Head",
|
||||||
|
"## Item",
|
||||||
|
"### Detail"
|
||||||
|
]
|
||||||
|
|
||||||
|
To allow optional headers as with the following structure:
|
||||||
|
|
||||||
|
# Head
|
||||||
|
## Item
|
||||||
|
### Detail (optional)
|
||||||
|
## Foot
|
||||||
|
### Notes (optional)
|
||||||
|
|
||||||
|
Use the special value `"*"` meaning "one or more unspecified headers" and set
|
||||||
|
the `headers` parameter to:
|
||||||
|
|
||||||
|
[
|
||||||
|
"# Head",
|
||||||
|
"## Item",
|
||||||
|
"*",
|
||||||
|
"## Foot",
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
|
||||||
|
When an error is detected, this rule outputs the line number of the first
|
||||||
|
problematic header (otherwise, it outputs the last line number of the file).
|
||||||
|
|
||||||
|
Note that while the `headers` parameter uses the "## Text" ATX header style for
|
||||||
|
simplicity, a file may use any supported header style.
|
||||||
|
|
|
||||||
38
lib/rules.js
38
lib/rules.js
|
|
@ -898,7 +898,7 @@ module.exports = [
|
||||||
"desc": "No empty links",
|
"desc": "No empty links",
|
||||||
"tags": [ "links" ],
|
"tags": [ "links" ],
|
||||||
"aliases": [ "no-empty-links" ],
|
"aliases": [ "no-empty-links" ],
|
||||||
"func": function MD034(params, errors) {
|
"func": function MD042(params, errors) {
|
||||||
forEachInlineChild(params, "link_open", function forToken(token) {
|
forEachInlineChild(params, "link_open", function forToken(token) {
|
||||||
token.attrs.forEach(function forAttr(attr) {
|
token.attrs.forEach(function forAttr(attr) {
|
||||||
if (attr[0] === "href" && (!attr[1] || (attr[1] === "#"))) {
|
if (attr[0] === "href" && (!attr[1] || (attr[1] === "#"))) {
|
||||||
|
|
@ -907,5 +907,41 @@ module.exports = [
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "MD043",
|
||||||
|
"desc": "Required header structure",
|
||||||
|
"tags": [ "headers" ],
|
||||||
|
"aliases": [ "required-headers" ],
|
||||||
|
"func": function MD043(params, errors) {
|
||||||
|
var requiredHeaders = params.options.headers;
|
||||||
|
if (requiredHeaders) {
|
||||||
|
var levels = {};
|
||||||
|
[ 1, 2, 3, 4, 5, 6 ].forEach(function forLevel(level) {
|
||||||
|
levels["h" + level] = "######".substr(-level);
|
||||||
|
});
|
||||||
|
var i = 0;
|
||||||
|
var optional = false;
|
||||||
|
forEachHeading(params, function forHeading(heading, content) {
|
||||||
|
if (!errors.length) {
|
||||||
|
var actual = levels[heading.tag] + " " + content;
|
||||||
|
var expected = requiredHeaders[i++] || "";
|
||||||
|
if (expected === "*") {
|
||||||
|
optional = true;
|
||||||
|
} else if (expected.toLowerCase() === actual.toLowerCase()) {
|
||||||
|
optional = false;
|
||||||
|
} else if (optional) {
|
||||||
|
i--;
|
||||||
|
} else {
|
||||||
|
errors.push(heading.lineNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if ((i < requiredHeaders.length) && !errors.length) {
|
||||||
|
errors.push(params.lines.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
{
|
{
|
||||||
"default": true,
|
"default": true,
|
||||||
"MD041": true
|
"MD041": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"## Header 1 {MD002} {MD041}",
|
||||||
|
"#### Header 2 {MD001}",
|
||||||
|
"# Broken"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#### Header 2 {MD001}
|
#### Header 2 {MD001}
|
||||||
|
|
||||||
# Header 3 {MD003} #
|
# Header 3 {MD003} {MD043} #
|
||||||
|
|
||||||
* list
|
* list
|
||||||
+ list {MD004} {MD006} {MD007} {MD030}
|
+ list {MD004} {MD006} {MD007} {MD030}
|
||||||
|
|
|
||||||
|
|
@ -632,6 +632,44 @@ module.exports.customFrontMatter = function customFrontMatter(test) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.readmeHeaders = function readmeHeaders(test) {
|
||||||
|
test.expect(2);
|
||||||
|
markdownlint({
|
||||||
|
"files": "README.md",
|
||||||
|
"config": {
|
||||||
|
"default": false,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"# markdownlint",
|
||||||
|
"## Install",
|
||||||
|
"## Overview",
|
||||||
|
"### Related",
|
||||||
|
"## Demonstration",
|
||||||
|
"## Rules / Aliases",
|
||||||
|
"## Tags",
|
||||||
|
"## Configuration",
|
||||||
|
"## API",
|
||||||
|
"### options",
|
||||||
|
"#### options.files",
|
||||||
|
"#### options.strings",
|
||||||
|
"#### options.frontMatter",
|
||||||
|
"#### options.config",
|
||||||
|
"### callback",
|
||||||
|
"### result",
|
||||||
|
"## Usage",
|
||||||
|
"## Browser",
|
||||||
|
"## History"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function callback(err, result) {
|
||||||
|
test.ifError(err);
|
||||||
|
var expected = { "README.md": {} };
|
||||||
|
test.deepEqual(result, expected, "Unexpected issues.");
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.filesArrayNotModified = function filesArrayNotModified(test) {
|
module.exports.filesArrayNotModified = function filesArrayNotModified(test) {
|
||||||
test.expect(2);
|
test.expect(2);
|
||||||
var files = [
|
var files = [
|
||||||
|
|
@ -733,7 +771,7 @@ module.exports.missingStringValue = function missingStringValue(test) {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.ruleNamesUpperCase = function ruleNamesUpperCase(test) {
|
module.exports.ruleNamesUpperCase = function ruleNamesUpperCase(test) {
|
||||||
test.expect(38);
|
test.expect(39);
|
||||||
rules.forEach(function forRule(rule) {
|
rules.forEach(function forRule(rule) {
|
||||||
test.equal(rule.name, rule.name.toUpperCase(), "Rule name not upper-case.");
|
test.equal(rule.name, rule.name.toUpperCase(), "Rule name not upper-case.");
|
||||||
});
|
});
|
||||||
|
|
@ -741,7 +779,7 @@ module.exports.ruleNamesUpperCase = function ruleNamesUpperCase(test) {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.uniqueAliases = function uniqueAliases(test) {
|
module.exports.uniqueAliases = function uniqueAliases(test) {
|
||||||
test.expect(76);
|
test.expect(78);
|
||||||
var tags = [];
|
var tags = [];
|
||||||
rules.forEach(function forRule(rule) {
|
rules.forEach(function forRule(rule) {
|
||||||
Array.prototype.push.apply(tags, rule.tags);
|
Array.prototype.push.apply(tags, rule.tags);
|
||||||
|
|
@ -758,7 +796,7 @@ module.exports.uniqueAliases = function uniqueAliases(test) {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.readme = function readme(test) {
|
module.exports.readme = function readme(test) {
|
||||||
test.expect(99);
|
test.expect(101);
|
||||||
var tagToRules = {};
|
var tagToRules = {};
|
||||||
rules.forEach(function forRule(rule) {
|
rules.forEach(function forRule(rule) {
|
||||||
rule.tags.forEach(function forTag(tag) {
|
rule.tags.forEach(function forTag(tag) {
|
||||||
|
|
@ -819,7 +857,7 @@ module.exports.readme = function readme(test) {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.doc = function doc(test) {
|
module.exports.doc = function doc(test) {
|
||||||
test.expect(281);
|
test.expect(289);
|
||||||
fs.readFile("doc/Rules.md", shared.utf8Encoding,
|
fs.readFile("doc/Rules.md", shared.utf8Encoding,
|
||||||
function readFile(err, contents) {
|
function readFile(err, contents) {
|
||||||
test.ifError(err);
|
test.ifError(err);
|
||||||
|
|
|
||||||
6
test/required-headers-all-optional.json
Normal file
6
test/required-headers-all-optional.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [ "*" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
11
test/required-headers-all-optional.md
Normal file
11
test/required-headers-all-optional.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# One
|
||||||
|
|
||||||
|
## Two
|
||||||
|
|
||||||
|
### THREE
|
||||||
|
|
||||||
|
#### four
|
||||||
|
|
||||||
|
##### Five
|
||||||
|
|
||||||
|
###### SiX
|
||||||
13
test/required-headers-all-present.json
Normal file
13
test/required-headers-all-present.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"# One",
|
||||||
|
"## Two",
|
||||||
|
"### Three",
|
||||||
|
"## Four",
|
||||||
|
"## Five",
|
||||||
|
"### Six"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
11
test/required-headers-all-present.md
Normal file
11
test/required-headers-all-present.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# One
|
||||||
|
|
||||||
|
## Two
|
||||||
|
|
||||||
|
### THREE
|
||||||
|
|
||||||
|
## four
|
||||||
|
|
||||||
|
## Five
|
||||||
|
|
||||||
|
### SiX
|
||||||
10
test/required-headers-missing-first.json
Normal file
10
test/required-headers-missing-first.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"# One",
|
||||||
|
"## Two",
|
||||||
|
"### Three"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
5
test/required-headers-missing-first.md
Normal file
5
test/required-headers-missing-first.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
text
|
||||||
|
|
||||||
|
## Two {MD002} {MD043}
|
||||||
|
|
||||||
|
### Three
|
||||||
10
test/required-headers-missing-last.json
Normal file
10
test/required-headers-missing-last.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"# One",
|
||||||
|
"## Two",
|
||||||
|
"### Three"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
7
test/required-headers-missing-last.md
Normal file
7
test/required-headers-missing-last.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
One
|
||||||
|
===
|
||||||
|
|
||||||
|
Two
|
||||||
|
---
|
||||||
|
|
||||||
|
{MD043}
|
||||||
11
test/required-headers-missing-middle.json
Normal file
11
test/required-headers-missing-middle.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"# One",
|
||||||
|
"## Two",
|
||||||
|
"### Three",
|
||||||
|
"#### Four"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
5
test/required-headers-missing-middle.md
Normal file
5
test/required-headers-missing-middle.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# One #
|
||||||
|
|
||||||
|
### Three {MD001} {MD043} ###
|
||||||
|
|
||||||
|
#### Four ####
|
||||||
6
test/required-headers-none.json
Normal file
6
test/required-headers-none.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
|
}
|
||||||
5
test/required-headers-none.md
Normal file
5
test/required-headers-none.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# One {MD043}
|
||||||
|
|
||||||
|
## Two
|
||||||
|
|
||||||
|
### Three
|
||||||
10
test/required-headers-optional-first.json
Normal file
10
test/required-headers-optional-first.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"*",
|
||||||
|
"### Three",
|
||||||
|
"#### Four"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
7
test/required-headers-optional-first.md
Normal file
7
test/required-headers-optional-first.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# One
|
||||||
|
|
||||||
|
## Two
|
||||||
|
|
||||||
|
### Three
|
||||||
|
|
||||||
|
#### Four
|
||||||
10
test/required-headers-optional-last.json
Normal file
10
test/required-headers-optional-last.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"# One",
|
||||||
|
"## Two",
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
7
test/required-headers-optional-last.md
Normal file
7
test/required-headers-optional-last.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# One
|
||||||
|
|
||||||
|
## Two
|
||||||
|
|
||||||
|
### Three
|
||||||
|
|
||||||
|
#### Four
|
||||||
12
test/required-headers-optional-middle.json
Normal file
12
test/required-headers-optional-middle.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"# One",
|
||||||
|
"*",
|
||||||
|
"### Three",
|
||||||
|
"*",
|
||||||
|
"##### Five"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
9
test/required-headers-optional-middle.md
Normal file
9
test/required-headers-optional-middle.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# One #
|
||||||
|
|
||||||
|
## Two ##
|
||||||
|
|
||||||
|
### Three ###
|
||||||
|
|
||||||
|
#### Four ####
|
||||||
|
|
||||||
|
##### Five #####
|
||||||
11
test/required-headers-optional-redundant.json
Normal file
11
test/required-headers-optional-redundant.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD043": {
|
||||||
|
"headers": [
|
||||||
|
"# One",
|
||||||
|
"*",
|
||||||
|
"*",
|
||||||
|
"#### Four"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
7
test/required-headers-optional-redundant.md
Normal file
7
test/required-headers-optional-redundant.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# One
|
||||||
|
|
||||||
|
## Two
|
||||||
|
|
||||||
|
### Three
|
||||||
|
|
||||||
|
#### Four
|
||||||
Loading…
Add table
Add a link
Reference in a new issue