mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-09-22 05:40:48 +02:00
Add options.resultVersion for more detailed error reporting (fixes #23).
This commit is contained in:
parent
3a356467cd
commit
0ca8bc7bb6
11 changed files with 454 additions and 160 deletions
|
@ -174,8 +174,8 @@
|
||||||
"max-len": "error",
|
"max-len": "error",
|
||||||
"max-lines": "off",
|
"max-lines": "off",
|
||||||
"max-nested-callbacks": "error",
|
"max-nested-callbacks": "error",
|
||||||
"max-params": ["error", 5],
|
"max-params": ["error", 6],
|
||||||
"max-statements": ["error", 21],
|
"max-statements": ["error", 26],
|
||||||
"max-statements-per-line": "error",
|
"max-statements-per-line": "error",
|
||||||
"new-cap": "error",
|
"new-cap": "error",
|
||||||
"new-parens": "error",
|
"new-parens": "error",
|
||||||
|
|
74
README.md
74
README.md
|
@ -275,6 +275,20 @@ See the [style](style) directory for more samples.
|
||||||
See [markdownlint-config-schema.json](schema/markdownlint-config-schema.json)
|
See [markdownlint-config-schema.json](schema/markdownlint-config-schema.json)
|
||||||
for the [JSON Schema](http://json-schema.org/) of the `options.config` object.
|
for the [JSON Schema](http://json-schema.org/) of the `options.config` object.
|
||||||
|
|
||||||
|
#### options.resultVersion
|
||||||
|
|
||||||
|
Type: `Number`
|
||||||
|
|
||||||
|
Specifies which version of the `result` object to return (see the "Usage" section
|
||||||
|
below for examples).
|
||||||
|
|
||||||
|
Passing a `resultVersion` of `0` corresponds to the original, simple format where
|
||||||
|
each error is identified by rule name and line number. This is the default.
|
||||||
|
|
||||||
|
Passing a `resultVersion` of `1` corresponds to a more detailed format where each
|
||||||
|
error includes information about the line number, rule name, alias, description,
|
||||||
|
as well as any additional detail or context that is available.
|
||||||
|
|
||||||
### callback
|
### callback
|
||||||
|
|
||||||
Type: `Function` taking (`Error`, `Object`)
|
Type: `Function` taking (`Error`, `Object`)
|
||||||
|
@ -322,7 +336,8 @@ bad.md: 1: MD018 No space after hash on atx style header
|
||||||
bad.md: 3: MD018 No space after hash on atx style header
|
bad.md: 3: MD018 No space after hash on atx style header
|
||||||
```
|
```
|
||||||
|
|
||||||
Or invoke `markdownlint.sync` for a synchronous call:
|
Or invoke `markdownlint.sync` for a synchronous call and/or pass `true` to `toString`
|
||||||
|
to use rule aliases instead of names:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var result = markdownlint.sync(options);
|
var result = markdownlint.sync(options);
|
||||||
|
@ -354,19 +369,62 @@ Output:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"good.string": {},
|
|
||||||
"bad.string": {
|
|
||||||
"MD010": [ 3 ],
|
|
||||||
"MD018": [ 1, 3 ]
|
|
||||||
},
|
|
||||||
"good.md": {},
|
"good.md": {},
|
||||||
"bad.md": {
|
"bad.md": {
|
||||||
"MD010": [ 3 ],
|
MD010: [ 3 ],
|
||||||
"MD018": [ 1, 3 ]
|
MD018: [ 1, 3 ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For more detailed error reporting, set `options.resultVersion` to `1`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var options = {
|
||||||
|
"files": [ "good.md", "bad.md" ],
|
||||||
|
"resultVersion": 1
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
With that, the output of `result.toString` looks like:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bad.string: 3: MD010/no-hard-tabs Hard tabs [Column: 19]
|
||||||
|
bad.string: 1: MD018/no-missing-space-atx No space after hash on atx style header [Context: "#bad.string"]
|
||||||
|
bad.string: 3: MD018/no-missing-space-atx No space after hash on atx style header [Context: "#This string fails some rules."]
|
||||||
|
bad.md: 3: MD010/no-hard-tabs Hard tabs [Column: 17]
|
||||||
|
bad.md: 1: MD018/no-missing-space-atx No space after hash on atx style header [Context: "#bad.md"]
|
||||||
|
bad.md: 3: MD018/no-missing-space-atx No space after hash on atx style header [Context: "#This file fails some rules."]
|
||||||
|
```
|
||||||
|
|
||||||
|
And the `result` object becomes:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"good.md": [],
|
||||||
|
"bad.md": [
|
||||||
|
{ lineNumber: 3,
|
||||||
|
ruleName: "MD010",
|
||||||
|
ruleAlias: "no-hard-tabs",
|
||||||
|
ruleDescription: "Hard tabs",
|
||||||
|
errorDetail: "Column: 17",
|
||||||
|
errorContext: null },
|
||||||
|
{ lineNumber: 1,
|
||||||
|
ruleName: "MD018",
|
||||||
|
ruleAlias: "no-missing-space-atx",
|
||||||
|
ruleDescription: "No space after hash on atx style header",
|
||||||
|
errorDetail: null,
|
||||||
|
errorContext: "#bad.md" },
|
||||||
|
{ lineNumber: 3,
|
||||||
|
ruleName: "MD018",
|
||||||
|
ruleAlias: "no-missing-space-atx",
|
||||||
|
ruleDescription: "No space after hash on atx style header",
|
||||||
|
errorDetail: null,
|
||||||
|
errorContext: "#This file fails\tsome rules." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Integration with the [gulp](http://gulpjs.com/) build system is straightforward:
|
Integration with the [gulp](http://gulpjs.com/) build system is straightforward:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|
|
@ -42,6 +42,11 @@ textarea {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
.detail {
|
||||||
|
font-size: 80%;
|
||||||
|
font-style: italic;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
.flex-rows {
|
.flex-rows {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -15,6 +15,14 @@
|
||||||
var rulesMd = "https://github.com/DavidAnson/markdownlint" +
|
var rulesMd = "https://github.com/DavidAnson/markdownlint" +
|
||||||
"/blob/master/doc/Rules.md";
|
"/blob/master/doc/Rules.md";
|
||||||
|
|
||||||
|
// Sanitize string for HTML display
|
||||||
|
function sanitize(str) {
|
||||||
|
return str
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle input
|
// Handle input
|
||||||
function onMarkdownInput() {
|
function onMarkdownInput() {
|
||||||
// Markdown
|
// Markdown
|
||||||
|
@ -26,10 +34,7 @@
|
||||||
var padding = lines.length.toString().replace(/\d/g, " ");
|
var padding = lines.length.toString().replace(/\d/g, " ");
|
||||||
numbered.innerHTML = lines
|
numbered.innerHTML = lines
|
||||||
.map(function mapNumberedLine(line, index) {
|
.map(function mapNumberedLine(line, index) {
|
||||||
line = line
|
line = sanitize(line);
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">");
|
|
||||||
index++;
|
index++;
|
||||||
var paddedIndex = (padding + index).slice(-padding.length);
|
var paddedIndex = (padding + index).slice(-padding.length);
|
||||||
return "<span id='l" + index + "'><em>" + paddedIndex + "</em>: " +
|
return "<span id='l" + index + "'><em>" + paddedIndex + "</em>: " +
|
||||||
|
@ -42,19 +47,27 @@
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"MD013": false
|
"MD013": false
|
||||||
}
|
},
|
||||||
|
"resultVersion": 1
|
||||||
};
|
};
|
||||||
var results = window.markdownlint.sync(options).toString();
|
var results = window.markdownlint.sync(options);
|
||||||
violations.innerHTML = results.split(newLineRe)
|
violations.innerHTML = results.content.map(function mapResult(result) {
|
||||||
.map(function mapResultLine(line) {
|
var ruleRef = rulesMd + "#" + result.ruleName.toLowerCase() + "---" +
|
||||||
return line.replace(/^content: (\d+): (MD\d\d\d) (.*)$/,
|
result.ruleDescription.toLowerCase().replace(/ /g, "-");
|
||||||
function replacer(match, p1, p2, p3) {
|
return "<a href='#" + result.lineNumber + "'><em>" + result.lineNumber +
|
||||||
var ruleRef = rulesMd + "#" + p2.toLowerCase() + "---" +
|
"</em></a> - <a href='" + ruleRef + "'>" + result.ruleName + "</a> " +
|
||||||
p3.toLowerCase().replace(/ /g, "-");
|
result.ruleDescription +
|
||||||
return "<a href='#" + p1 + "'><em>" + p1 + "</em></a> - " +
|
(result.errorDetail ?
|
||||||
"<a href='" + ruleRef + "'>" + p2 + "</a> " + p3;
|
" [<span class='detail'>" +
|
||||||
});
|
sanitize(result.errorDetail) +
|
||||||
}).join("<br/>");
|
"</span>]" :
|
||||||
|
"") +
|
||||||
|
(result.errorContext ?
|
||||||
|
" [<span class='detail'>Context: \"" +
|
||||||
|
sanitize(result.errorContext) +
|
||||||
|
"\"</span>]" :
|
||||||
|
"");
|
||||||
|
}).join("<br/>");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load from a string or File object
|
// Load from a string or File object
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
CACHE MANIFEST
|
CACHE MANIFEST
|
||||||
# 2016-07-05
|
# 2016-10-23
|
||||||
default.css
|
default.css
|
||||||
default.htm
|
default.htm
|
||||||
default.js
|
default.js
|
||||||
|
|
|
@ -24,6 +24,14 @@ markdownlint(options, function callback(err, result) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Again, using resultVersion 1 for more detail
|
||||||
|
options.resultVersion = 1;
|
||||||
|
markdownlint(options, function callback(err, result) {
|
||||||
|
if (!err) {
|
||||||
|
console.dir(result, { "colors": true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Make a synchronous call, passing true to toString()
|
// Make a synchronous call, passing true to toString()
|
||||||
var result = markdownlint.sync(options);
|
var result = markdownlint.sync(options);
|
||||||
console.log(result.toString(true));
|
console.log(result.toString(true));
|
||||||
|
|
|
@ -38,34 +38,51 @@ Results.prototype.toString = function resultsToString(useAlias) {
|
||||||
var results = [];
|
var results = [];
|
||||||
Object.keys(that).forEach(function forFile(file) {
|
Object.keys(that).forEach(function forFile(file) {
|
||||||
var fileResults = that[file];
|
var fileResults = that[file];
|
||||||
Object.keys(fileResults).forEach(function forRule(ruleName) {
|
if (Array.isArray(fileResults)) {
|
||||||
var rule = ruleNameToRule[ruleName];
|
fileResults.forEach(function forResult(result) {
|
||||||
var ruleResults = fileResults[ruleName];
|
results.push(
|
||||||
ruleResults.forEach(function forLine(lineNumber) {
|
|
||||||
var result =
|
|
||||||
file + ": " +
|
file + ": " +
|
||||||
lineNumber + ": " +
|
result.lineNumber + ": " +
|
||||||
(useAlias ? rule.aliases[0] : rule.name) + " " +
|
result.ruleName + "/" +
|
||||||
rule.desc;
|
result.ruleAlias + " " +
|
||||||
results.push(result);
|
result.ruleDescription +
|
||||||
|
(result.errorDetail ?
|
||||||
|
" [" + result.errorDetail + "]" :
|
||||||
|
"") +
|
||||||
|
(result.errorContext ?
|
||||||
|
" [Context: \"" + result.errorContext + "\"]" :
|
||||||
|
""));
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
|
Object.keys(fileResults).forEach(function forRule(ruleName) {
|
||||||
|
var rule = ruleNameToRule[ruleName];
|
||||||
|
var ruleResults = fileResults[ruleName];
|
||||||
|
ruleResults.forEach(function forLine(lineNumber) {
|
||||||
|
var result =
|
||||||
|
file + ": " +
|
||||||
|
lineNumber + ": " +
|
||||||
|
(useAlias ? rule.aliases[0] : rule.name) + " " +
|
||||||
|
rule.desc;
|
||||||
|
results.push(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return results.join("\n");
|
return results.join("\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Array.sort comparison for number objects
|
// Array.sort comparison for objects in errors array
|
||||||
function numberComparison(a, b) {
|
function lineNumberComparison(a, b) {
|
||||||
return a - b;
|
return a.lineNumber - b.lineNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to return unique values from a sorted array
|
// Function to return unique values from a sorted errors array
|
||||||
function uniqueFilterForSorted(value, index, array) {
|
function uniqueFilterForSortedErrors(value, index, array) {
|
||||||
return (index === 0) || (value > array[index - 1]);
|
return (index === 0) || (value.lineNumber > array[index - 1].lineNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lints a single string
|
// Lints a single string
|
||||||
function lintContent(content, config, frontMatter) { // eslint-disable-line
|
function lintContent(content, config, frontMatter, resultVersion) {
|
||||||
// Remove front matter (if present at beginning of content)
|
// Remove front matter (if present at beginning of content)
|
||||||
var frontMatterLines = 0;
|
var frontMatterLines = 0;
|
||||||
if (frontMatter) {
|
if (frontMatter) {
|
||||||
|
@ -163,7 +180,7 @@ function lintContent(content, config, frontMatter) { // eslint-disable-line
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var enabledRulesPerLineNumber = [ null ];
|
var enabledRulesPerLineNumber = new Array(1 + frontMatterLines);
|
||||||
lines.forEach(function forLine(line) {
|
lines.forEach(function forLine(line) {
|
||||||
var match = shared.inlineCommentRe.exec(line);
|
var match = shared.inlineCommentRe.exec(line);
|
||||||
if (match) {
|
if (match) {
|
||||||
|
@ -182,25 +199,69 @@ function lintContent(content, config, frontMatter) { // eslint-disable-line
|
||||||
"lines": lines
|
"lines": lines
|
||||||
};
|
};
|
||||||
// Run each rule
|
// Run each rule
|
||||||
var result = {};
|
var result = (resultVersion === 1) ? [] : {};
|
||||||
rules.forEach(function forRule(rule) {
|
rules.forEach(function forRule(rule) {
|
||||||
// Configure rule
|
// Configure rule
|
||||||
params.options = mergedRules[rule.name];
|
params.options = mergedRules[rule.name];
|
||||||
var errors = [];
|
var errors = [];
|
||||||
|
function addError(lineNumber, detail, context) {
|
||||||
|
errors.push({
|
||||||
|
"lineNumber": lineNumber + frontMatterLines,
|
||||||
|
"detail": detail || null,
|
||||||
|
"context": context || null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
errors.add = function add(lineNumber) {
|
||||||
|
addError(lineNumber);
|
||||||
|
};
|
||||||
|
errors.addDetail = function addDetail(lineNumber, detail) {
|
||||||
|
addError(lineNumber, detail);
|
||||||
|
};
|
||||||
|
errors.addDetailIf = function addDetailIf(lineNumber, expected, actual) {
|
||||||
|
if (expected !== actual) {
|
||||||
|
addError(lineNumber, "Expected: " + expected + "; Actual: " + actual);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
errors.addContext = function addContext(lineNumber, context, left, right) {
|
||||||
|
if (context.length <= 30) {
|
||||||
|
// Nothing to do
|
||||||
|
} else if (left && right) {
|
||||||
|
context = context.substr(0, 15) + "..." + context.substr(-15);
|
||||||
|
} else if (right) {
|
||||||
|
context = "..." + context.substr(-30);
|
||||||
|
} else {
|
||||||
|
context = context.substr(0, 30) + "...";
|
||||||
|
}
|
||||||
|
addError(lineNumber, null, context);
|
||||||
|
};
|
||||||
rule.func(params, errors);
|
rule.func(params, errors);
|
||||||
// Record any errors (significant performance benefit from length check)
|
// Record any errors (significant performance benefit from length check)
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
errors.sort(numberComparison);
|
errors.sort(lineNumberComparison);
|
||||||
var filteredErrors = errors
|
var filteredErrors = errors
|
||||||
.filter(uniqueFilterForSorted)
|
.filter(uniqueFilterForSortedErrors)
|
||||||
.filter(function removeDisabledRules(lineNumber) {
|
.filter(function removeDisabledRules(error) {
|
||||||
return enabledRulesPerLineNumber[lineNumber][rule.name];
|
return enabledRulesPerLineNumber[error.lineNumber][rule.name];
|
||||||
})
|
})
|
||||||
.map(function adjustLineNumbers(error) {
|
.map(function formatResults(error) {
|
||||||
return error + frontMatterLines;
|
if (resultVersion === 1) {
|
||||||
|
return {
|
||||||
|
"lineNumber": error.lineNumber,
|
||||||
|
"ruleName": rule.name,
|
||||||
|
"ruleAlias": rule.aliases[0],
|
||||||
|
"ruleDescription": rule.desc,
|
||||||
|
"errorDetail": error.detail,
|
||||||
|
"errorContext": error.context
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return error.lineNumber;
|
||||||
});
|
});
|
||||||
if (filteredErrors.length) {
|
if (filteredErrors.length) {
|
||||||
result[rule.name] = filteredErrors;
|
if (resultVersion === 1) {
|
||||||
|
result.push.apply(result, filteredErrors);
|
||||||
|
} else {
|
||||||
|
result[rule.name] = filteredErrors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -208,12 +269,18 @@ function lintContent(content, config, frontMatter) { // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lints a single file
|
// Lints a single file
|
||||||
function lintFile(file, config, frontMatter, synchronous, callback) {
|
function lintFile(
|
||||||
|
file,
|
||||||
|
config,
|
||||||
|
frontMatter,
|
||||||
|
resultVersion,
|
||||||
|
synchronous,
|
||||||
|
callback) {
|
||||||
function lintContentWrapper(err, content) {
|
function lintContentWrapper(err, content) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
var result = lintContent(content, config, frontMatter);
|
var result = lintContent(content, config, frontMatter, resultVersion);
|
||||||
callback(null, result);
|
callback(null, result);
|
||||||
}
|
}
|
||||||
// Make a/synchronous call to read file
|
// Make a/synchronous call to read file
|
||||||
|
@ -253,13 +320,14 @@ function markdownlint(options, callback) {
|
||||||
var frontMatter = (options.frontMatter === undefined) ?
|
var frontMatter = (options.frontMatter === undefined) ?
|
||||||
shared.frontMatterRe : options.frontMatter;
|
shared.frontMatterRe : options.frontMatter;
|
||||||
var config = options.config || { "default": true };
|
var config = options.config || { "default": true };
|
||||||
|
var resultVersion = options.resultVersion || 0;
|
||||||
var synchronous = (callback === markdownlintSynchronousCallback);
|
var synchronous = (callback === markdownlintSynchronousCallback);
|
||||||
var results = new Results();
|
var results = new Results();
|
||||||
// Helper to lint the next file in the array
|
// Helper to lint the next file in the array
|
||||||
function lintFilesArray() {
|
function lintFilesArray() {
|
||||||
var file = files.shift();
|
var file = files.shift();
|
||||||
if (file) {
|
if (file) {
|
||||||
lintFile(file, config, frontMatter, synchronous,
|
lintFile(file, config, frontMatter, resultVersion, synchronous,
|
||||||
function lintedFile(err, result) {
|
function lintedFile(err, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
@ -274,7 +342,11 @@ function markdownlint(options, callback) {
|
||||||
}
|
}
|
||||||
// Lint strings
|
// Lint strings
|
||||||
Object.keys(strings).forEach(function forKey(key) {
|
Object.keys(strings).forEach(function forKey(key) {
|
||||||
var result = lintContent(strings[key] || "", config, frontMatter);
|
var result = lintContent(
|
||||||
|
strings[key] || "",
|
||||||
|
config,
|
||||||
|
frontMatter,
|
||||||
|
resultVersion);
|
||||||
results[key] = result;
|
results[key] = result;
|
||||||
});
|
});
|
||||||
// Lint files
|
// Lint files
|
||||||
|
|
238
lib/rules.js
238
lib/rules.js
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
var shared = require("./shared");
|
var shared = require("./shared");
|
||||||
|
|
||||||
|
// Escapes a string for use in a RegExp
|
||||||
|
function escapeForRegExp(str) {
|
||||||
|
return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the indent for a token
|
// Returns the indent for a token
|
||||||
function indentFor(token) {
|
function indentFor(token) {
|
||||||
var line = token.line.replace(/^[\s>]*(> |>)/, "");
|
var line = token.line.replace(/^[\s>]*(> |>)/, "");
|
||||||
|
@ -161,8 +166,9 @@ module.exports = [
|
||||||
var prevLevel = 0;
|
var prevLevel = 0;
|
||||||
filterTokens(params, "heading_open", function forToken(token) {
|
filterTokens(params, "heading_open", function forToken(token) {
|
||||||
var level = parseInt(token.tag.slice(1), 10);
|
var level = parseInt(token.tag.slice(1), 10);
|
||||||
if (prevLevel && (level > prevLevel + 1)) {
|
if (prevLevel && (level > prevLevel)) {
|
||||||
errors.push(token.lineNumber);
|
errors.addDetailIf(token.lineNumber,
|
||||||
|
"h" + (prevLevel + 1), "h" + level);
|
||||||
}
|
}
|
||||||
prevLevel = level;
|
prevLevel = level;
|
||||||
});
|
});
|
||||||
|
@ -179,9 +185,7 @@ module.exports = [
|
||||||
var tag = "h" + level;
|
var tag = "h" + level;
|
||||||
params.tokens.every(function forToken(token) {
|
params.tokens.every(function forToken(token) {
|
||||||
if (token.type === "heading_open") {
|
if (token.type === "heading_open") {
|
||||||
if (token.tag !== tag) {
|
errors.addDetailIf(token.lineNumber, tag, token.tag);
|
||||||
errors.push(token.lineNumber);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -203,17 +207,22 @@ module.exports = [
|
||||||
}
|
}
|
||||||
if (styleForToken !== style) {
|
if (styleForToken !== style) {
|
||||||
var h12 = /h[12]/.test(token.tag);
|
var h12 = /h[12]/.test(token.tag);
|
||||||
var hOther = /h[^12]/.test(token.tag);
|
|
||||||
var setextWithAtx =
|
var setextWithAtx =
|
||||||
(style === "setext_with_atx") &&
|
(style === "setext_with_atx") &&
|
||||||
((h12 && (styleForToken === "setext")) ||
|
((h12 && (styleForToken === "setext")) ||
|
||||||
(hOther && (styleForToken === "atx")));
|
(!h12 && (styleForToken === "atx")));
|
||||||
var setextWithAtxClosed =
|
var setextWithAtxClosed =
|
||||||
(style === "setext_with_atx_closed") &&
|
(style === "setext_with_atx_closed") &&
|
||||||
((h12 && (styleForToken === "setext")) ||
|
((h12 && (styleForToken === "setext")) ||
|
||||||
(hOther && (styleForToken === "atx_closed")));
|
(!h12 && (styleForToken === "atx_closed")));
|
||||||
if (!setextWithAtx && !setextWithAtxClosed) {
|
if (!setextWithAtx && !setextWithAtxClosed) {
|
||||||
errors.push(token.lineNumber);
|
var expected = style;
|
||||||
|
if (style === "setext_with_atx") {
|
||||||
|
expected = h12 ? "setext" : "atx";
|
||||||
|
} else if (style === "setext_with_atx_closed") {
|
||||||
|
expected = h12 ? "setext" : "atx_closed";
|
||||||
|
}
|
||||||
|
errors.addDetailIf(token.lineNumber, expected, styleForToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -241,11 +250,12 @@ module.exports = [
|
||||||
if (!nestingStyles[nesting] &&
|
if (!nestingStyles[nesting] &&
|
||||||
(itemStyle !== nestingStyles[nesting - 1])) {
|
(itemStyle !== nestingStyles[nesting - 1])) {
|
||||||
nestingStyles[nesting] = itemStyle;
|
nestingStyles[nesting] = itemStyle;
|
||||||
} else if (itemStyle !== nestingStyles[nesting]) {
|
} else {
|
||||||
errors.push(item.lineNumber);
|
errors.addDetailIf(item.lineNumber,
|
||||||
|
nestingStyles[nesting], itemStyle);
|
||||||
}
|
}
|
||||||
} else if (itemStyle !== expectedStyle) {
|
} else {
|
||||||
errors.push(item.lineNumber);
|
errors.addDetailIf(item.lineNumber, expectedStyle, itemStyle);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -262,9 +272,7 @@ module.exports = [
|
||||||
flattenLists(params).forEach(function forList(list) {
|
flattenLists(params).forEach(function forList(list) {
|
||||||
var indent = indentFor(list.items[0]);
|
var indent = indentFor(list.items[0]);
|
||||||
list.items.forEach(function forItem(item) {
|
list.items.forEach(function forItem(item) {
|
||||||
if (indentFor(item) !== indent) {
|
errors.addDetailIf(item.lineNumber, indent, indentFor(item));
|
||||||
errors.push(item.lineNumber);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -277,8 +285,8 @@ module.exports = [
|
||||||
"aliases": [ "ul-start-left" ],
|
"aliases": [ "ul-start-left" ],
|
||||||
"func": function MD006(params, errors) {
|
"func": function MD006(params, errors) {
|
||||||
flattenLists(params).forEach(function forList(list) {
|
flattenLists(params).forEach(function forList(list) {
|
||||||
if (list.unordered && !list.nesting && indentFor(list.open)) {
|
if (list.unordered && !list.nesting) {
|
||||||
errors.push(list.open.lineNumber);
|
errors.addDetailIf(list.open.lineNumber, 0, indentFor(list.open));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -295,9 +303,9 @@ module.exports = [
|
||||||
flattenLists(params).forEach(function forList(list) {
|
flattenLists(params).forEach(function forList(list) {
|
||||||
if (list.unordered && list.parentsUnordered) {
|
if (list.unordered && list.parentsUnordered) {
|
||||||
var indent = indentFor(list.open);
|
var indent = indentFor(list.open);
|
||||||
if ((indent > prevIndent) &&
|
if (indent > prevIndent) {
|
||||||
((indent - prevIndent) !== optionsIndent)) {
|
errors.addDetailIf(list.open.lineNumber,
|
||||||
errors.push(list.open.lineNumber);
|
prevIndent + optionsIndent, indent);
|
||||||
}
|
}
|
||||||
prevIndent = indent;
|
prevIndent = indent;
|
||||||
}
|
}
|
||||||
|
@ -313,10 +321,10 @@ module.exports = [
|
||||||
"func": function MD009(params, errors) {
|
"func": function MD009(params, errors) {
|
||||||
var brSpaces = params.options.br_spaces || 0;
|
var brSpaces = params.options.br_spaces || 0;
|
||||||
params.lines.forEach(function forLine(line, lineIndex) {
|
params.lines.forEach(function forLine(line, lineIndex) {
|
||||||
if (/\s$/.test(line) &&
|
if (/\s$/.test(line)) {
|
||||||
((brSpaces < 2) ||
|
var expected = (brSpaces < 2) ? 0 : brSpaces;
|
||||||
(line.length - line.trimRight().length !== brSpaces))) {
|
errors.addDetailIf(lineIndex + 1,
|
||||||
errors.push(lineIndex + 1);
|
expected, line.length - line.trimRight().length);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -332,7 +340,8 @@ module.exports = [
|
||||||
var includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
|
var includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
|
||||||
forEachLine(params, function forLine(line, lineIndex, inCode) {
|
forEachLine(params, function forLine(line, lineIndex, inCode) {
|
||||||
if (/\t/.test(line) && (!inCode || includeCodeBlocks)) {
|
if (/\t/.test(line) && (!inCode || includeCodeBlocks)) {
|
||||||
errors.push(lineIndex + 1);
|
errors.addDetail(lineIndex + 1,
|
||||||
|
"Column: " + (line.indexOf("\t") + 1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -345,8 +354,9 @@ module.exports = [
|
||||||
"aliases": [ "no-reversed-links" ],
|
"aliases": [ "no-reversed-links" ],
|
||||||
"func": function MD011(params, errors) {
|
"func": function MD011(params, errors) {
|
||||||
forEachInlineChild(params, "text", function forToken(token) {
|
forEachInlineChild(params, "text", function forToken(token) {
|
||||||
if (/\([^)]+\)\[[^\]^][^\]]*\]/.test(token.content)) {
|
var match = /\([^)]+\)\[[^\]^][^\]]*\]/.exec(token.content);
|
||||||
errors.push(token.lineNumber);
|
if (match) {
|
||||||
|
errors.addDetail(token.lineNumber, match[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -363,7 +373,7 @@ module.exports = [
|
||||||
forEachLine(params, function forLine(line, lineIndex, inCode) {
|
forEachLine(params, function forLine(line, lineIndex, inCode) {
|
||||||
count = (inCode || line.trim().length) ? 0 : count + 1;
|
count = (inCode || line.trim().length) ? 0 : count + 1;
|
||||||
if (maximum < count) {
|
if (maximum < count) {
|
||||||
errors.push(lineIndex + 1);
|
errors.addDetailIf(lineIndex + 1, maximum, count);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -386,7 +396,7 @@ module.exports = [
|
||||||
if ((includeCodeBlocks || !inCode) &&
|
if ((includeCodeBlocks || !inCode) &&
|
||||||
(includeTables || !inTable) &&
|
(includeTables || !inTable) &&
|
||||||
re.test(line)) {
|
re.test(line)) {
|
||||||
errors.push(lineIndex + 1);
|
errors.addDetailIf(lineIndex + 1, lineLength, line.length);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -405,7 +415,8 @@ module.exports = [
|
||||||
.every(function forLine(line) {
|
.every(function forLine(line) {
|
||||||
return !line || (allBlank = false) || /^\$\s/.test(line);
|
return !line || (allBlank = false) || /^\$\s/.test(line);
|
||||||
}) && !allBlank) {
|
}) && !allBlank) {
|
||||||
errors.push(token.lineNumber);
|
errors.addContext(token.lineNumber,
|
||||||
|
token.content.split(shared.newLineRe)[0].trim());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -420,7 +431,7 @@ module.exports = [
|
||||||
"func": function MD018(params, errors) {
|
"func": function MD018(params, errors) {
|
||||||
forEachLine(params, function forLine(line, lineIndex, inCode) {
|
forEachLine(params, function forLine(line, lineIndex, inCode) {
|
||||||
if (!inCode && /^#+[^#\s]/.test(line) && !/#$/.test(line)) {
|
if (!inCode && /^#+[^#\s]/.test(line) && !/#$/.test(line)) {
|
||||||
errors.push(lineIndex + 1);
|
errors.addContext(lineIndex + 1, line.trim());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -435,7 +446,7 @@ module.exports = [
|
||||||
filterTokens(params, "heading_open", function forToken(token) {
|
filterTokens(params, "heading_open", function forToken(token) {
|
||||||
if ((headingStyleFor(token) === "atx") &&
|
if ((headingStyleFor(token) === "atx") &&
|
||||||
/^#+\s\s/.test(token.line)) {
|
/^#+\s\s/.test(token.line)) {
|
||||||
errors.push(token.lineNumber);
|
errors.addContext(token.lineNumber, token.line.trim());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -448,9 +459,12 @@ module.exports = [
|
||||||
"aliases": [ "no-missing-space-closed-atx" ],
|
"aliases": [ "no-missing-space-closed-atx" ],
|
||||||
"func": function MD020(params, errors) {
|
"func": function MD020(params, errors) {
|
||||||
forEachLine(params, function forLine(line, lineIndex, inCode) {
|
forEachLine(params, function forLine(line, lineIndex, inCode) {
|
||||||
if (!inCode && /^#+[^#]*[^\\]#+$/.test(line) &&
|
if (!inCode && /^#+[^#]*[^\\]#+$/.test(line)) {
|
||||||
(/^#+[^#\s]/.test(line) || /[^#\s]#+$/.test(line))) {
|
var left = /^#+[^#\s]/.test(line);
|
||||||
errors.push(lineIndex + 1);
|
var right = /[^#\s]#+$/.test(line);
|
||||||
|
if (left || right) {
|
||||||
|
errors.addContext(lineIndex + 1, line.trim(), left, right);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -463,9 +477,12 @@ module.exports = [
|
||||||
"aliases": [ "no-multiple-space-closed-atx" ],
|
"aliases": [ "no-multiple-space-closed-atx" ],
|
||||||
"func": function MD021(params, errors) {
|
"func": function MD021(params, errors) {
|
||||||
filterTokens(params, "heading_open", function forToken(token) {
|
filterTokens(params, "heading_open", function forToken(token) {
|
||||||
if ((headingStyleFor(token) === "atx_closed") &&
|
if (headingStyleFor(token) === "atx_closed") {
|
||||||
(/^#+\s\s/.test(token.line) || /\s\s#+$/.test(token.line))) {
|
var left = /^#+\s\s/.test(token.line);
|
||||||
errors.push(token.lineNumber);
|
var right = /\s\s#+$/.test(token.line);
|
||||||
|
if (left || right) {
|
||||||
|
errors.addContext(token.lineNumber, token.line.trim(), left, right);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -483,7 +500,7 @@ module.exports = [
|
||||||
params.tokens.forEach(function forToken(token) {
|
params.tokens.forEach(function forToken(token) {
|
||||||
if (token.type === "heading_open") {
|
if (token.type === "heading_open") {
|
||||||
if ((token.map[0] - prevMaxLineIndex) === 0) {
|
if ((token.map[0] - prevMaxLineIndex) === 0) {
|
||||||
errors.push(token.lineNumber);
|
errors.addContext(token.lineNumber, token.line.trim());
|
||||||
}
|
}
|
||||||
} else if (token.type === "heading_close") {
|
} else if (token.type === "heading_close") {
|
||||||
needBlankLine = true;
|
needBlankLine = true;
|
||||||
|
@ -491,7 +508,8 @@ module.exports = [
|
||||||
if (token.map) {
|
if (token.map) {
|
||||||
if (needBlankLine) {
|
if (needBlankLine) {
|
||||||
if ((token.map[0] - prevMaxLineIndex) === 0) {
|
if ((token.map[0] - prevMaxLineIndex) === 0) {
|
||||||
errors.push(prevHeadingLineNumber);
|
errors.addContext(prevHeadingLineNumber,
|
||||||
|
params.lines[prevHeadingLineNumber - 1].trim());
|
||||||
}
|
}
|
||||||
needBlankLine = false;
|
needBlankLine = false;
|
||||||
}
|
}
|
||||||
|
@ -512,7 +530,7 @@ module.exports = [
|
||||||
"func": function MD023(params, errors) {
|
"func": function MD023(params, errors) {
|
||||||
filterTokens(params, "heading_open", function forToken(token) {
|
filterTokens(params, "heading_open", function forToken(token) {
|
||||||
if (/^\s/.test(token.line)) {
|
if (/^\s/.test(token.line)) {
|
||||||
errors.push(token.lineNumber);
|
errors.addContext(token.lineNumber, token.line);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -529,7 +547,7 @@ module.exports = [
|
||||||
if (knownContent.indexOf(content) === -1) {
|
if (knownContent.indexOf(content) === -1) {
|
||||||
knownContent.push(content);
|
knownContent.push(content);
|
||||||
} else {
|
} else {
|
||||||
errors.push(heading.lineNumber);
|
errors.addContext(heading.lineNumber, heading.line.trim());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -547,7 +565,7 @@ module.exports = [
|
||||||
filterTokens(params, "heading_open", function forToken(token) {
|
filterTokens(params, "heading_open", function forToken(token) {
|
||||||
if (token.tag === tag) {
|
if (token.tag === tag) {
|
||||||
if (hasTopLevelHeading) {
|
if (hasTopLevelHeading) {
|
||||||
errors.push(token.lineNumber);
|
errors.addContext(token.lineNumber, token.line.trim());
|
||||||
} else if (token.lineNumber === 1) {
|
} else if (token.lineNumber === 1) {
|
||||||
hasTopLevelHeading = true;
|
hasTopLevelHeading = true;
|
||||||
}
|
}
|
||||||
|
@ -565,8 +583,10 @@ module.exports = [
|
||||||
var punctuation = params.options.punctuation || ".,;:!?";
|
var punctuation = params.options.punctuation || ".,;:!?";
|
||||||
var re = new RegExp("[" + punctuation + "]$");
|
var re = new RegExp("[" + punctuation + "]$");
|
||||||
forEachHeading(params, function forHeading(heading, content) {
|
forEachHeading(params, function forHeading(heading, content) {
|
||||||
if (re.test(content)) {
|
var match = re.exec(content);
|
||||||
errors.push(heading.lineNumber);
|
if (match) {
|
||||||
|
errors.addDetail(heading.lineNumber,
|
||||||
|
"Punctuation: '" + match[0] + "'");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -585,11 +605,13 @@ module.exports = [
|
||||||
} else if (token.type === "blockquote_close") {
|
} else if (token.type === "blockquote_close") {
|
||||||
blockquoteNesting--;
|
blockquoteNesting--;
|
||||||
} else if ((token.type === "inline") && (blockquoteNesting > 0)) {
|
} else if ((token.type === "inline") && (blockquoteNesting > 0)) {
|
||||||
|
if (/^(\s*>)+\s\s/.test(token.line)) {
|
||||||
|
errors.addContext(token.lineNumber, token.line);
|
||||||
|
}
|
||||||
token.content.split(shared.newLineRe)
|
token.content.split(shared.newLineRe)
|
||||||
.forEach(function forLine(line, offset) {
|
.forEach(function forLine(line, offset) {
|
||||||
if (/^\s/.test(line) ||
|
if (/^\s/.test(line)) {
|
||||||
(!offset && /^(\s*>)+\s\s/.test(token.line))) {
|
errors.addContext(token.lineNumber + offset, "> " + line);
|
||||||
errors.push(token.lineNumber + offset);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -607,7 +629,7 @@ module.exports = [
|
||||||
params.tokens.forEach(function forToken(token) {
|
params.tokens.forEach(function forToken(token) {
|
||||||
if ((token.type === "blockquote_open") &&
|
if ((token.type === "blockquote_open") &&
|
||||||
(prevToken.type === "blockquote_close")) {
|
(prevToken.type === "blockquote_close")) {
|
||||||
errors.push(token.lineNumber - 1);
|
errors.add(token.lineNumber - 1);
|
||||||
}
|
}
|
||||||
prevToken = token;
|
prevToken = token;
|
||||||
});
|
});
|
||||||
|
@ -625,10 +647,9 @@ module.exports = [
|
||||||
if (!list.unordered) {
|
if (!list.unordered) {
|
||||||
var number = 1;
|
var number = 1;
|
||||||
list.items.forEach(function forItem(item) {
|
list.items.forEach(function forItem(item) {
|
||||||
var re = new RegExp("^[\\s>]*" + String(number) + "[\\.)]");
|
var match = /^[\s>]*([^\.)]*)[\.)]/.exec(item.line);
|
||||||
if (!re.test(item.line)) {
|
errors.addDetailIf(item.lineNumber,
|
||||||
errors.push(item.lineNumber);
|
String(number), !match || match[1]);
|
||||||
}
|
|
||||||
if (style === "ordered") {
|
if (style === "ordered") {
|
||||||
number++;
|
number++;
|
||||||
}
|
}
|
||||||
|
@ -656,9 +677,8 @@ module.exports = [
|
||||||
(allSingle ? olSingle : olMulti);
|
(allSingle ? olSingle : olMulti);
|
||||||
list.items.forEach(function forItem(item) {
|
list.items.forEach(function forItem(item) {
|
||||||
var match = /^[\s>]*\S+(\s+)/.exec(item.line);
|
var match = /^[\s>]*\S+(\s+)/.exec(item.line);
|
||||||
if (!match || (match[1].length !== expectedSpaces)) {
|
errors.addDetailIf(item.lineNumber,
|
||||||
errors.push(item.lineNumber);
|
expectedSpaces, !match || match[1].length);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -674,7 +694,7 @@ module.exports = [
|
||||||
forEachLine(params, function forLine(line, i, inCode, onFence) {
|
forEachLine(params, function forLine(line, i, inCode, onFence) {
|
||||||
if (((onFence > 0) && (i - 1 >= 0) && lines[i - 1].length) ||
|
if (((onFence > 0) && (i - 1 >= 0) && lines[i - 1].length) ||
|
||||||
((onFence < 0) && (i + 1 < lines.length) && lines[i + 1].length)) {
|
((onFence < 0) && (i + 1 < lines.length) && lines[i + 1].length)) {
|
||||||
errors.push(i + 1);
|
errors.addContext(i + 1, lines[i].trim());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -690,11 +710,12 @@ module.exports = [
|
||||||
var prevLine = "";
|
var prevLine = "";
|
||||||
forEachLine(params, function forLine(line, lineIndex, inCode, onFence) {
|
forEachLine(params, function forLine(line, lineIndex, inCode, onFence) {
|
||||||
if (!inCode || onFence) {
|
if (!inCode || onFence) {
|
||||||
var listMarker = /^([\*\+\-]|(\d+\.))\s/.test(line.trim());
|
var lineTrim = line.trim();
|
||||||
|
var listMarker = /^([\*\+\-]|(\d+\.))\s/.test(lineTrim);
|
||||||
if (listMarker && !inList && !/^($|\s)/.test(prevLine)) {
|
if (listMarker && !inList && !/^($|\s)/.test(prevLine)) {
|
||||||
errors.push(lineIndex + 1);
|
errors.addContext(lineIndex + 1, lineTrim);
|
||||||
} else if (!listMarker && inList && !/^($|\s)/.test(line)) {
|
} else if (!listMarker && inList && !/^($|\s)/.test(line)) {
|
||||||
errors.push(lineIndex);
|
errors.addContext(lineIndex, lineTrim);
|
||||||
}
|
}
|
||||||
inList = listMarker;
|
inList = listMarker;
|
||||||
}
|
}
|
||||||
|
@ -723,11 +744,12 @@ module.exports = [
|
||||||
.map(function forElement(element) {
|
.map(function forElement(element) {
|
||||||
return element.slice(1).toLowerCase();
|
return element.slice(1).toLowerCase();
|
||||||
})
|
})
|
||||||
.every(function forElement(element) {
|
.filter(function forElement(element) {
|
||||||
return allowedElements.indexOf(element) !== -1;
|
return allowedElements.indexOf(element) === -1;
|
||||||
});
|
});
|
||||||
if (!allowed) {
|
if (allowed.length) {
|
||||||
errors.push(token.lineNumber + offset);
|
errors.addDetail(token.lineNumber + offset,
|
||||||
|
"Element: " + allowed[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -745,14 +767,15 @@ module.exports = [
|
||||||
filterTokens(params, "inline", function forToken(token) {
|
filterTokens(params, "inline", function forToken(token) {
|
||||||
var inLink = false;
|
var inLink = false;
|
||||||
token.children.forEach(function forChild(child) {
|
token.children.forEach(function forChild(child) {
|
||||||
|
var match = null;
|
||||||
if (child.type === "link_open") {
|
if (child.type === "link_open") {
|
||||||
inLink = true;
|
inLink = true;
|
||||||
} else if (child.type === "link_close") {
|
} else if (child.type === "link_close") {
|
||||||
inLink = false;
|
inLink = false;
|
||||||
} else if ((child.type === "text") &&
|
} else if ((child.type === "text") &&
|
||||||
!inLink &&
|
!inLink &&
|
||||||
/https?:\/\//.test(child.content)) {
|
(match = /(http|ftp)s?:\/\/[^\s]*/.exec(child.content))) {
|
||||||
errors.push(child.lineNumber);
|
errors.addContext(child.lineNumber, match[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -767,12 +790,11 @@ module.exports = [
|
||||||
"func": function MD035(params, errors) {
|
"func": function MD035(params, errors) {
|
||||||
var style = params.options.style || "consistent";
|
var style = params.options.style || "consistent";
|
||||||
filterTokens(params, "hr", function forToken(token) {
|
filterTokens(params, "hr", function forToken(token) {
|
||||||
|
var lineTrim = token.line.trim();
|
||||||
if (style === "consistent") {
|
if (style === "consistent") {
|
||||||
style = token.line;
|
style = lineTrim;
|
||||||
}
|
|
||||||
if (token.line !== style) {
|
|
||||||
errors.push(token.lineNumber);
|
|
||||||
}
|
}
|
||||||
|
errors.addDetailIf(token.lineNumber, style, lineTrim);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -794,7 +816,7 @@ module.exports = [
|
||||||
(t.children[0].type === "em_open")) &&
|
(t.children[0].type === "em_open")) &&
|
||||||
(t.children[1].type === "text") &&
|
(t.children[1].type === "text") &&
|
||||||
!re.test(t.children[1].content)) {
|
!re.test(t.children[1].content)) {
|
||||||
errors.push(t.lineNumber);
|
errors.addContext(t.lineNumber, t.children[1].content);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (token.type === "blockquote_open") {
|
} else if (token.type === "blockquote_open") {
|
||||||
|
@ -825,9 +847,12 @@ module.exports = [
|
||||||
"aliases": [ "no-space-in-emphasis" ],
|
"aliases": [ "no-space-in-emphasis" ],
|
||||||
"func": function MD037(params, errors) {
|
"func": function MD037(params, errors) {
|
||||||
forEachInlineChild(params, "text", function forToken(token) {
|
forEachInlineChild(params, "text", function forToken(token) {
|
||||||
if (/\s(\*\*?|__?)\s.+\1/.test(token.content) ||
|
var left = /\s(\*\*?|__?)\s.+\1/.exec(token.content);
|
||||||
/(\*\*?|__?).+\s\1\s/.test(token.content)) {
|
var right = /(\*\*?|__?).+\s\1\s/.exec(token.content);
|
||||||
errors.push(token.lineNumber);
|
if (left) {
|
||||||
|
errors.addContext(token.lineNumber, left[0].trim());
|
||||||
|
} else if (right) {
|
||||||
|
errors.addContext(token.lineNumber, right[0].trim(), false, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -841,8 +866,15 @@ module.exports = [
|
||||||
"func": function MD038(params, errors) {
|
"func": function MD038(params, errors) {
|
||||||
forEachInlineChild(params, "code_inline",
|
forEachInlineChild(params, "code_inline",
|
||||||
function forToken(token, inline) {
|
function forToken(token, inline) {
|
||||||
if (inline.content.indexOf("`" + token.content + "`") === -1) {
|
var escapedContent = escapeForRegExp(token.content);
|
||||||
errors.push(token.lineNumber);
|
var left = (new RegExp("`\\s+" + escapedContent + "\\s*`"))
|
||||||
|
.exec(inline.content);
|
||||||
|
var right = (new RegExp("`\\s*" + escapedContent + "\\s+`"))
|
||||||
|
.exec(inline.content);
|
||||||
|
if (left) {
|
||||||
|
errors.addContext(token.lineNumber, left[0]);
|
||||||
|
} else if (right) {
|
||||||
|
errors.addContext(token.lineNumber, right[0], false, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -856,26 +888,20 @@ module.exports = [
|
||||||
"func": function MD039(params, errors) {
|
"func": function MD039(params, errors) {
|
||||||
filterTokens(params, "inline", function forToken(token) {
|
filterTokens(params, "inline", function forToken(token) {
|
||||||
var inLink = false;
|
var inLink = false;
|
||||||
var index = 0;
|
var linkText = "";
|
||||||
var lastChildRightSpaceLineNumber = 0;
|
|
||||||
token.children.forEach(function forChild(child) {
|
token.children.forEach(function forChild(child) {
|
||||||
if (child.type === "link_open") {
|
if (child.type === "link_open") {
|
||||||
inLink = true;
|
inLink = true;
|
||||||
index = 0;
|
linkText = "";
|
||||||
} else if (child.type === "link_close") {
|
} else if (child.type === "link_close") {
|
||||||
inLink = false;
|
inLink = false;
|
||||||
if (lastChildRightSpaceLineNumber) {
|
var left = linkText.trimLeft().length !== linkText.length;
|
||||||
errors.push(lastChildRightSpaceLineNumber);
|
var right = linkText.trimRight().length !== linkText.length;
|
||||||
|
if (left || right) {
|
||||||
|
errors.addContext(token.lineNumber, linkText, left, right);
|
||||||
}
|
}
|
||||||
} else if (inLink) {
|
} else if (inLink) {
|
||||||
if ((index === 0) &&
|
linkText += child.content;
|
||||||
(child.content.trimLeft().length !== child.content.length)) {
|
|
||||||
errors.push(child.lineNumber);
|
|
||||||
}
|
|
||||||
lastChildRightSpaceLineNumber =
|
|
||||||
(child.content.trimRight().length === child.content.length) ?
|
|
||||||
0 : child.lineNumber;
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -890,7 +916,7 @@ module.exports = [
|
||||||
"func": function MD040(params, errors) {
|
"func": function MD040(params, errors) {
|
||||||
filterTokens(params, "fence", function forToken(token) {
|
filterTokens(params, "fence", function forToken(token) {
|
||||||
if (!token.info.trim()) {
|
if (!token.info.trim()) {
|
||||||
errors.push(token.lineNumber);
|
errors.addContext(token.lineNumber, token.line);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -917,7 +943,7 @@ module.exports = [
|
||||||
if (!firstHeader ||
|
if (!firstHeader ||
|
||||||
(firstHeader.lineNumber !== 1) ||
|
(firstHeader.lineNumber !== 1) ||
|
||||||
(firstHeader.tag !== tag)) {
|
(firstHeader.tag !== tag)) {
|
||||||
errors.push(1);
|
errors.addContext(1, params.lines[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -928,10 +954,26 @@ module.exports = [
|
||||||
"tags": [ "links" ],
|
"tags": [ "links" ],
|
||||||
"aliases": [ "no-empty-links" ],
|
"aliases": [ "no-empty-links" ],
|
||||||
"func": function MD042(params, errors) {
|
"func": function MD042(params, errors) {
|
||||||
forEachInlineChild(params, "link_open", function forToken(token) {
|
filterTokens(params, "inline", function forToken(token) {
|
||||||
token.attrs.forEach(function forAttr(attr) {
|
var inLink = false;
|
||||||
if (attr[0] === "href" && (!attr[1] || (attr[1] === "#"))) {
|
var linkText = "";
|
||||||
errors.push(token.lineNumber);
|
var emptyLink = false;
|
||||||
|
token.children.forEach(function forChild(child) {
|
||||||
|
if (child.type === "link_open") {
|
||||||
|
inLink = true;
|
||||||
|
linkText = "";
|
||||||
|
child.attrs.forEach(function forAttr(attr) {
|
||||||
|
if (attr[0] === "href" && (!attr[1] || (attr[1] === "#"))) {
|
||||||
|
emptyLink = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (child.type === "link_close") {
|
||||||
|
inLink = false;
|
||||||
|
if (emptyLink) {
|
||||||
|
errors.addContext(child.lineNumber, "[" + linkText + "]");
|
||||||
|
}
|
||||||
|
} else if (inLink) {
|
||||||
|
linkText += child.content;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -963,12 +1005,12 @@ module.exports = [
|
||||||
} else if (optional) {
|
} else if (optional) {
|
||||||
i--;
|
i--;
|
||||||
} else {
|
} else {
|
||||||
errors.push(heading.lineNumber);
|
errors.addDetailIf(heading.lineNumber, expected, actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if ((i < requiredHeaders.length) && !errors.length) {
|
if ((i < requiredHeaders.length) && !errors.length) {
|
||||||
errors.push(params.lines.length);
|
errors.addContext(params.lines.length, requiredHeaders[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ Header 1
|
||||||
Header 2
|
Header 2
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
## Header 2 {MD003}
|
||||||
|
|
||||||
### Header 3
|
### Header 3
|
||||||
|
|
||||||
#### Header 4 {MD003} ####
|
#### Header 4 {MD003} ####
|
||||||
|
|
|
@ -4,6 +4,8 @@ Header 1
|
||||||
Header 2
|
Header 2
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
## Header 2 {MD003} ##
|
||||||
|
|
||||||
### Header 3 ###
|
### Header 3 ###
|
||||||
|
|
||||||
#### Header 4 {MD003}
|
#### Header 4 {MD003}
|
||||||
|
|
|
@ -80,7 +80,7 @@ module.exports.projectFiles = function projectFiles(test) {
|
||||||
test.expect(2);
|
test.expect(2);
|
||||||
var options = {
|
var options = {
|
||||||
"files": [ "README.md" ],
|
"files": [ "README.md" ],
|
||||||
"config": { "MD013": false }
|
"config": { "MD013": { "line_length": 150 } }
|
||||||
};
|
};
|
||||||
markdownlint(options, function callback(err, actual) {
|
markdownlint(options, function callback(err, actual) {
|
||||||
test.ifError(err);
|
test.ifError(err);
|
||||||
|
@ -90,7 +90,7 @@ module.exports.projectFiles = function projectFiles(test) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.resultFormatting = function resultFormatting(test) {
|
module.exports.resultFormattingV0 = function resultFormattingV0(test) {
|
||||||
test.expect(4);
|
test.expect(4);
|
||||||
var options = {
|
var options = {
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -142,14 +142,15 @@ module.exports.resultFormatting = function resultFormatting(test) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.resultFormattingSync = function resultFormattingSync(test) {
|
module.exports.resultFormattingSyncV0 = function resultFormattingSyncV0(test) {
|
||||||
test.expect(3);
|
test.expect(3);
|
||||||
var options = {
|
var options = {
|
||||||
"files": [
|
"files": [
|
||||||
"./test/atx_header_spacing.md",
|
"./test/atx_header_spacing.md",
|
||||||
"./test/first_header_bad_atx.md"
|
"./test/first_header_bad_atx.md"
|
||||||
],
|
],
|
||||||
"config": defaultConfig
|
"config": defaultConfig,
|
||||||
|
"resultVersion": 0
|
||||||
};
|
};
|
||||||
var actualResult = markdownlint.sync(options);
|
var actualResult = markdownlint.sync(options);
|
||||||
var expectedResult = {
|
var expectedResult = {
|
||||||
|
@ -192,6 +193,93 @@ module.exports.resultFormattingSync = function resultFormattingSync(test) {
|
||||||
test.done();
|
test.done();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.resultFormattingV1 = function resultFormattingV1(test) {
|
||||||
|
test.expect(3);
|
||||||
|
var options = {
|
||||||
|
"strings": {
|
||||||
|
"truncate":
|
||||||
|
"# Multiple spaces inside hashes on closed atx style header #"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./test/atx_header_spacing.md",
|
||||||
|
"./test/first_header_bad_atx.md"
|
||||||
|
],
|
||||||
|
"config": defaultConfig,
|
||||||
|
"resultVersion": 1
|
||||||
|
};
|
||||||
|
markdownlint(options, function callback(err, actualResult) {
|
||||||
|
test.ifError(err);
|
||||||
|
var expectedResult = {
|
||||||
|
"truncate": [
|
||||||
|
{ "lineNumber": 1,
|
||||||
|
"ruleName": "MD021",
|
||||||
|
"ruleAlias": "no-multiple-space-closed-atx",
|
||||||
|
"ruleDescription":
|
||||||
|
"Multiple spaces inside hashes on closed atx style header",
|
||||||
|
"errorDetail": null,
|
||||||
|
"errorContext": "# Multiple spa...style header #" }
|
||||||
|
],
|
||||||
|
"./test/atx_header_spacing.md": [
|
||||||
|
{ "lineNumber": 3,
|
||||||
|
"ruleName": "MD002",
|
||||||
|
"ruleAlias": "first-header-h1",
|
||||||
|
"ruleDescription": "First header should be a top level header",
|
||||||
|
"errorDetail": "Expected: h1; Actual: h2",
|
||||||
|
"errorContext": null },
|
||||||
|
{ "lineNumber": 1,
|
||||||
|
"ruleName": "MD018",
|
||||||
|
"ruleAlias": "no-missing-space-atx",
|
||||||
|
"ruleDescription": "No space after hash on atx style header",
|
||||||
|
"errorDetail": null,
|
||||||
|
"errorContext": "#Header 1 {MD018}" },
|
||||||
|
{ "lineNumber": 3,
|
||||||
|
"ruleName": "MD019",
|
||||||
|
"ruleAlias": "no-multiple-space-atx",
|
||||||
|
"ruleDescription": "Multiple spaces after hash on atx style header",
|
||||||
|
"errorDetail": null,
|
||||||
|
"errorContext": "## Header 2 {MD019}" },
|
||||||
|
{ "lineNumber": 5,
|
||||||
|
"ruleName": "MD019",
|
||||||
|
"ruleAlias": "no-multiple-space-atx",
|
||||||
|
"ruleDescription": "Multiple spaces after hash on atx style header",
|
||||||
|
"errorDetail": null,
|
||||||
|
"errorContext": "## Header 3 {MD019}" }
|
||||||
|
],
|
||||||
|
"./test/first_header_bad_atx.md": [
|
||||||
|
{ "lineNumber": 1,
|
||||||
|
"ruleName": "MD002",
|
||||||
|
"ruleAlias": "first-header-h1",
|
||||||
|
"ruleDescription": "First header should be a top level header",
|
||||||
|
"errorDetail": "Expected: h1; Actual: h2",
|
||||||
|
"errorContext": null }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||||
|
var actualMessage = actualResult.toString();
|
||||||
|
var expectedMessage =
|
||||||
|
"truncate: 1: MD021/no-multiple-space-closed-atx" +
|
||||||
|
" Multiple spaces inside hashes on closed atx style header" +
|
||||||
|
" [Context: \"# Multiple spa...style header #\"]\n" +
|
||||||
|
"./test/atx_header_spacing.md: 3: MD002/first-header-h1" +
|
||||||
|
" First header should be a top level header" +
|
||||||
|
" [Expected: h1; Actual: h2]\n" +
|
||||||
|
"./test/atx_header_spacing.md: 1: MD018/no-missing-space-atx" +
|
||||||
|
" No space after hash on atx style header" +
|
||||||
|
" [Context: \"#Header 1 {MD018}\"]\n" +
|
||||||
|
"./test/atx_header_spacing.md: 3: MD019/no-multiple-space-atx" +
|
||||||
|
" Multiple spaces after hash on atx style header" +
|
||||||
|
" [Context: \"## Header 2 {MD019}\"]\n" +
|
||||||
|
"./test/atx_header_spacing.md: 5: MD019/no-multiple-space-atx" +
|
||||||
|
" Multiple spaces after hash on atx style header" +
|
||||||
|
" [Context: \"## Header 3 {MD019}\"]\n" +
|
||||||
|
"./test/first_header_bad_atx.md: 1: MD002/first-header-h1" +
|
||||||
|
" First header should be a top level header" +
|
||||||
|
" [Expected: h1; Actual: h2]";
|
||||||
|
test.equal(actualMessage, expectedMessage, "Incorrect message.");
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.stringInputLineEndings = function stringInputLineEndings(test) {
|
module.exports.stringInputLineEndings = function stringInputLineEndings(test) {
|
||||||
test.expect(2);
|
test.expect(2);
|
||||||
var options = {
|
var options = {
|
||||||
|
@ -639,6 +727,9 @@ module.exports.readmeHeaders = function readmeHeaders(test) {
|
||||||
"files": "README.md",
|
"files": "README.md",
|
||||||
"config": {
|
"config": {
|
||||||
"default": false,
|
"default": false,
|
||||||
|
"MD013": {
|
||||||
|
"line_length": 150
|
||||||
|
},
|
||||||
"MD043": {
|
"MD043": {
|
||||||
"headers": [
|
"headers": [
|
||||||
"# markdownlint",
|
"# markdownlint",
|
||||||
|
@ -655,6 +746,7 @@ module.exports.readmeHeaders = function readmeHeaders(test) {
|
||||||
"#### options.strings",
|
"#### options.strings",
|
||||||
"#### options.frontMatter",
|
"#### options.frontMatter",
|
||||||
"#### options.config",
|
"#### options.config",
|
||||||
|
"#### options.resultVersion",
|
||||||
"### callback",
|
"### callback",
|
||||||
"### result",
|
"### result",
|
||||||
"## Usage",
|
"## Usage",
|
||||||
|
@ -689,7 +781,7 @@ module.exports.filesArrayAsString = function filesArrayAsString(test) {
|
||||||
test.expect(2);
|
test.expect(2);
|
||||||
markdownlint({
|
markdownlint({
|
||||||
"files": "README.md",
|
"files": "README.md",
|
||||||
"config": { "MD013": false }
|
"config": { "MD013": { "line_length": 150 } }
|
||||||
}, function callback(err, actual) {
|
}, function callback(err, actual) {
|
||||||
test.ifError(err);
|
test.ifError(err);
|
||||||
var expected = { "README.md": {} };
|
var expected = { "README.md": {} };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue