From a467f8b3e611692dc56c6118971f9e4a30e8295f Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 25 Jul 2015 22:18:30 -0700 Subject: [PATCH] Automatically ignore common front matter; provide option for customization (fixes #2). --- .eslintrc | 2 +- README.md | 29 +++++++++++++- lib/markdownlint.js | 69 ++++++++++++++------------------ lib/shared.js | 3 ++ test/front-matter-embedded.md | 9 +++++ test/front-matter-empty.md | 5 +++ test/front-matter-with-dashes.md | 10 +++++ test/front-matter.md | 7 ++++ test/ignore_frontmatter.json | 5 --- test/ignore_frontmatter.md | 7 ---- test/markdownlint-test.js | 42 +++++++++++++++++++ 11 files changed, 134 insertions(+), 54 deletions(-) create mode 100644 test/front-matter-embedded.md create mode 100644 test/front-matter-empty.md create mode 100644 test/front-matter-with-dashes.md create mode 100644 test/front-matter.md delete mode 100644 test/ignore_frontmatter.json delete mode 100644 test/ignore_frontmatter.md 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 673e8e7a..1cd76739 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ playground for learning and exploring. * **MD039** - Spaces inside link text * **MD040** - Fenced code blocks should have a language specified * **MD041** - First line in file should be a top level header -* **ignore** - Ignore certain parts of the markdown file (can be `RegExp`, `Function`, `"frontmatter"`), should be used with disabled `MD012` See [Rules.md](doc/Rules.md) for more details. @@ -169,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 81a3a845..6cdf9d3f 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -53,23 +53,17 @@ function uniqueFilterForSorted(value, index, array) { return (index === 0) || (value > array[index - 1]); } -function ignoreContent(content, config) { - if (typeof config.ignore === "function") { - // allow custom ignore callback - return config.ignore(content); - } - - return content.replace(config.ignore, function removeNonLineBreaks(m) { - // maintain line breaks - return m.replace(/[^\n]+/g, ""); - }); -} - // Lints a single string -function lintContent(content, config) { - // ignore portions of content - if (config.ignore) { - content = ignoreContent(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, {}); @@ -138,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; + }); } } }); @@ -146,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 @@ -183,40 +181,31 @@ 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(); - if (config.ignore) { - if (config.ignore === "frontmatter") { - // see http://jekyllrb.com/docs/frontmatter/ - config.ignore = /^---([\s\S]+?)---/; - } - if (typeof config.ignore !== "function" && - !config.ignore.test && - !config.ignore.exec - ) { - throw new TypeError("config.ignore must be a RegExp or Function"); - } - } // Helper to lint the next file in the array 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/ignore_frontmatter.json b/test/ignore_frontmatter.json deleted file mode 100644 index 6db88384..00000000 --- a/test/ignore_frontmatter.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "default": true, - "ignore": "frontmatter", - "MD012": false -} diff --git a/test/ignore_frontmatter.md b/test/ignore_frontmatter.md deleted file mode 100644 index 0b1bd05c..00000000 --- a/test/ignore_frontmatter.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -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 = [