diff --git a/.eslintrc b/.eslintrc index ca9865a0..64cf63ad 100644 --- a/.eslintrc +++ b/.eslintrc @@ -141,7 +141,7 @@ "max-depth": [2, 4], "max-len": [2, 80, 2], "max-nested-callbacks": [2, 3], - "max-params": [2, 4], + "max-params": [2, 5], "max-statements": [2, 20], "new-cap": 2, "new-parens": 2, diff --git a/README.md b/README.md index 1548750e..1cd76739 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,34 @@ Example: } ``` +#### options.frontMatter + +Type: `RegExp` + +Matches any [front matter](http://jekyllrb.com/docs/frontmatter/) found at the +beginning of a file. + +Some Markdown content begins with metadata; the default `RegExp` for this option +ignores common forms of "front matter". To match differently, specify a custom +`RegExp` or use the value `null` to disable the feature. + +Note: Matches must occur at the start of the file. + +Default: + +```js +/^---$[^]*?^---$(\r\n|\r|\n)/m +``` + +Ignores: + +```text +--- +layout: post +title: Title +--- +``` + #### options.config Type: `Object` mapping `String` to `Boolean | Object` diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 07d888d5..6cdf9d3f 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -54,7 +54,17 @@ function uniqueFilterForSorted(value, index, array) { } // Lints a single string -function lintContent(content, config) { +function lintContent(content, config, frontMatter) { + // Remove front matter (if present at beginning of content) + var frontMatterLines = 0; + if (frontMatter) { + var frontMatterMatch = frontMatter.exec(content); + if (frontMatterMatch && !frontMatterMatch.index) { + var contentMatched = frontMatterMatch[0]; + content = content.slice(contentMatched.length); + frontMatterLines = contentMatched.split(shared.newLineRe).length - 1; + } + } // Parse content into tokens and lines var tokens = md.parse(content, {}); var lines = content.split(shared.newLineRe); @@ -122,7 +132,11 @@ function lintContent(content, config) { // Record any errors if (errors.length) { errors.sort(numberComparison); - result[rule.name] = errors.filter(uniqueFilterForSorted); + result[rule.name] = errors + .filter(uniqueFilterForSorted) + .map(function adjustLineNumbers(error) { + return error + frontMatterLines; + }); } } }); @@ -130,12 +144,12 @@ function lintContent(content, config) { } // Lints a single file -function lintFile(file, config, synchronous, callback) { +function lintFile(file, config, frontMatter, synchronous, callback) { function lintContentWrapper(err, content) { if (err) { return callback(err); } - var result = lintContent(content, config); + var result = lintContent(content, config, frontMatter); callback(null, result); } // Make a/synchronous call to read file @@ -167,6 +181,8 @@ function markdownlint(options, callback) { callback = callback || function noop() {}; var files = (options.files || []).slice(); var strings = options.strings || {}; + var frontMatter = (options.frontMatter === undefined) ? + shared.frontMatterRe : options.frontMatter; var config = options.config || { "default": true }; var synchronous = (callback === markdownlintSynchronousCallback); var results = new Results(); @@ -174,21 +190,22 @@ function markdownlint(options, callback) { function lintFilesArray() { var file = files.shift(); if (file) { - lintFile(file, config, synchronous, function lintedFile(err, result) { - if (err) { - return callback(err); - } - // Record errors and lint next file - results[file] = result; - lintFilesArray(); - }); + lintFile(file, config, frontMatter, synchronous, + function lintedFile(err, result) { + if (err) { + return callback(err); + } + // Record errors and lint next file + results[file] = result; + lintFilesArray(); + }); } else { callback(null, results); } } // Lint strings Object.keys(strings).forEach(function forKey(key) { - var result = lintContent(strings[key] || "", config); + var result = lintContent(strings[key] || "", config, frontMatter); results[key] = result; }); // Lint files diff --git a/lib/shared.js b/lib/shared.js index 1d33d7ee..718e8159 100644 --- a/lib/shared.js +++ b/lib/shared.js @@ -3,5 +3,8 @@ // Regular expression for matching common newline characters module.exports.newLineRe = /\r\n|\r|\n/; +// Regular expression for matching common front matter +module.exports.frontMatterRe = /^---$[^]*?^---$(\r\n|\r|\n)/m; + // readFile options for reading with the UTF-8 encoding module.exports.utf8Encoding = { "encoding": "utf8" }; diff --git a/test/front-matter-embedded.md b/test/front-matter-embedded.md new file mode 100644 index 00000000..5a5cc90a --- /dev/null +++ b/test/front-matter-embedded.md @@ -0,0 +1,9 @@ +Text text text + +--- +layout: post +hard: tab {MD010} +title: embedded +--- + +Text text text diff --git a/test/front-matter-empty.md b/test/front-matter-empty.md new file mode 100644 index 00000000..2dad3852 --- /dev/null +++ b/test/front-matter-empty.md @@ -0,0 +1,5 @@ +--- +--- +# Header + +# Another {MD025} diff --git a/test/front-matter-with-dashes.md b/test/front-matter-with-dashes.md new file mode 100644 index 00000000..26895e7a --- /dev/null +++ b/test/front-matter-with-dashes.md @@ -0,0 +1,10 @@ +--- +layout: post +title: Title with --- +tags: front matter +--- +## Header {MD002} + +--- + + Hard tab {MD010} diff --git a/test/front-matter.md b/test/front-matter.md new file mode 100644 index 00000000..16820c72 --- /dev/null +++ b/test/front-matter.md @@ -0,0 +1,7 @@ +--- +front: matter +--- + +# Header 1 + +## Header 2 diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 03fba0a0..c21d33a3 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -505,6 +505,48 @@ module.exports.styleRelaxed = function styleRelaxed(test) { }); }; +module.exports.nullFrontMatter = function nullFrontMatter(test) { + test.expect(2); + markdownlint({ + "strings": { + "content": "---\n\t\n---\n# Header\n" + }, + "frontMatter": null, + "config": { + "default": false, + "MD010": true + } + }, function callback(err, result) { + test.ifError(err); + var expectedResult = { + "content": { "MD010": [ 2 ] } + }; + test.deepEqual(result, expectedResult, "Undetected issues."); + test.done(); + }); +}; + +module.exports.customFrontMatter = function customFrontMatter(test) { + test.expect(2); + markdownlint({ + "strings": { + "content": "\n\t\n\n# Header\n" + }, + "frontMatter": /[^]*<\/head>/, + "config": { + "default": false, + "MD010": true + } + }, function callback(err, result) { + test.ifError(err); + var expectedResult = { + "content": {} + }; + test.deepEqual(result, expectedResult, "Did not get empty results."); + test.done(); + }); +}; + module.exports.filesArrayNotModified = function filesArrayNotModified(test) { test.expect(2); var files = [