eslint-plugin-markdown
Advanced tools
Comparing version 1.0.0-beta.2 to 1.0.0-beta.3
@@ -9,2 +9,3 @@ /** | ||
var assign = require("object-assign"); | ||
var parse5 = require("parse5"); | ||
var remark = require("remark"); | ||
@@ -23,9 +24,10 @@ | ||
* @param {object} callbacks A map of node types to callbacks. | ||
* @param {object} [parent] The node's parent AST node. | ||
* @returns {void} | ||
*/ | ||
function traverse(node, callbacks) { | ||
function traverse(node, callbacks, parent) { | ||
var i; | ||
if (callbacks[node.type]) { | ||
callbacks[node.type](node); | ||
callbacks[node.type](node, parent); | ||
} | ||
@@ -35,3 +37,3 @@ | ||
for (i = 0; i < node.children.length; i++) { | ||
traverse(node.children[i], callbacks); | ||
traverse(node.children[i], callbacks, node); | ||
} | ||
@@ -42,5 +44,32 @@ } | ||
/** | ||
* Converts leading HTML comments to JS block comments. | ||
* @param {string} html The text content of an HTML AST node. | ||
* @returns {string[]} An array of JS block comments. | ||
*/ | ||
function getComments(html) { | ||
var ast = parse5.parse(html, { locationInfo: true }); | ||
var nodes = ast.childNodes.filter(function(node) { | ||
return node.__location; // eslint-disable-line no-underscore-dangle | ||
}); | ||
var comments = []; | ||
var index; | ||
for (index = nodes.length - 1; index >= 0; index--) { | ||
if ( | ||
nodes[index].nodeName === "#comment" | ||
&& nodes[index].data.trim().slice(0, "eslint".length) === "eslint" | ||
) { | ||
comments.unshift("/*" + nodes[index].data + "*/"); | ||
} else { | ||
break; | ||
} | ||
} | ||
return comments; | ||
} | ||
/** | ||
* Extracts lintable JavaScript code blocks from Markdown text. | ||
* @param {string} text The text of the file. | ||
* @returns {[string]} Code blocks to lint. | ||
* @returns {string[]} Source code strings to lint. | ||
*/ | ||
@@ -52,5 +81,14 @@ function preprocess(text) { | ||
traverse(ast, { | ||
"code": function(node) { | ||
"code": function(node, parent) { | ||
var comments = []; | ||
var index, previousNode; | ||
if (node.lang && SUPPORTED_SYNTAXES.indexOf(node.lang.toLowerCase()) >= 0) { | ||
blocks.push(node); | ||
index = parent.children.indexOf(node); | ||
previousNode = parent.children[index - 1]; | ||
if (previousNode && previousNode.type === "html") { | ||
comments = getComments(previousNode.value) || []; | ||
} | ||
blocks.push(assign({}, node, { comments: comments })); | ||
} | ||
@@ -61,3 +99,3 @@ } | ||
return blocks.map(function(block) { | ||
return block.value; | ||
return block.comments.concat(block.value).join("\n"); | ||
}); | ||
@@ -67,2 +105,30 @@ } | ||
/** | ||
* Creates a map function that adjusts messages in a code block. | ||
* @param {Block} block A code block. | ||
* @returns {function} A function that adjusts messages in a code block. | ||
*/ | ||
function adjustBlock(block) { | ||
var leadingCommentLines = block.comments.reduce(function(count, comment) { | ||
return count + comment.split("\n").length; | ||
}, 0); | ||
/** | ||
* Adjusts ESLint messages to point to the correct location in the Markdown. | ||
* @param {Message} message A message from ESLint. | ||
* @returns {Message} The same message, but adjusted ot the correct location. | ||
*/ | ||
return function adjustMessage(message) { | ||
var lineInCode = message.line - leadingCommentLines; | ||
if (lineInCode < 1) { | ||
return null; | ||
} | ||
return assign({}, message, { | ||
line: lineInCode + block.position.start.line, | ||
column: message.column + block.position.indent[lineInCode - 1] - 1 | ||
}); | ||
}; | ||
} | ||
/** | ||
* Excludes unsatisfiable rules from the list of messages. | ||
@@ -73,3 +139,3 @@ * @param {Message} message A message from the linter. | ||
function excludeUnsatisfiableRules(message) { | ||
return UNSATISFIABLE_RULES.indexOf(message.ruleId) < 0; | ||
return message && UNSATISFIABLE_RULES.indexOf(message.ruleId) < 0; | ||
} | ||
@@ -85,8 +151,4 @@ | ||
return [].concat.apply([], messages.map(function(group, i) { | ||
return group.filter(excludeUnsatisfiableRules).map(function(message) { | ||
return assign({}, message, { | ||
line: message.line + blocks[i].position.start.line, | ||
column: message.column + blocks[i].position.indent[message.line - 1] - 1 | ||
}); | ||
}); | ||
var adjust = adjustBlock(blocks[i]); | ||
return group.map(adjust).filter(excludeUnsatisfiableRules); | ||
})); | ||
@@ -93,0 +155,0 @@ } |
{ | ||
"name": "eslint-plugin-markdown", | ||
"version": "1.0.0-beta.2", | ||
"version": "1.0.0-beta.3", | ||
"description": "An ESLint plugin to lint JavaScript in Markdown code fences.", | ||
@@ -43,4 +43,5 @@ "license": "MIT", | ||
"object-assign": "^4.0.1", | ||
"parse5": "^2.2.2", | ||
"remark": "^4.1.1" | ||
} | ||
} |
@@ -5,4 +5,2 @@ # eslint-plugin-markdown | ||
[![Join the chat at https://gitter.im/eslint/eslint-plugin-markdown](https://badges.gitter.im/eslint/eslint-plugin-markdown.svg)](https://gitter.im/eslint/eslint-plugin-markdown?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||
An [ESLint](http://eslint.org/) plugin to lint JavaScript in Markdown. | ||
@@ -75,2 +73,35 @@ | ||
## Configuration Comments | ||
The processor will convert HTML comments immediately preceding a code block into JavaScript block comments and insert them at the beginning of the source code that it passes to ESLint. This permits configuring ESLint via configuration comments while keeping the configuration comments themselves hidden when the markdown is rendered. Comment bodies are passed through unmodified, so the plugin supports any [configuration comments](http://eslint.org/docs/user-guide/configuring) supported by ESLint itself. | ||
This example enables the `browser` environment, disables the `no-alert` rule, and configures the `quotes` rule to prefer single quotes: | ||
<!-- eslint-env browser --> | ||
<!-- eslint-disable no-alert --> | ||
<!-- eslint quotes: ["error", "single"] --> | ||
```js | ||
alert('Hello, world!'); | ||
``` | ||
Each code block in a file is linted separately, so configuration comments apply only to the code block that immediately follows. | ||
Assuming `no-alert` is enabled in `.eslintrc`, the first code block will have no error from `no-alert`: | ||
<!-- eslint-env browser --> | ||
<!-- eslint-disable no-alert --> | ||
```js | ||
alert("Hello, world!"); | ||
``` | ||
But the next code block will have an error from `no-alert`: | ||
<!-- eslint-env browser --> | ||
```js | ||
alert("Hello, world!"); | ||
``` | ||
## Unsatisfiable Rules | ||
@@ -77,0 +108,0 @@ |
10303
150
122
3
+ Addedparse5@^2.2.2
+ Addedparse5@2.2.3(transitive)