From 870cb99a3e53d8b1c855f1b726ac6b29effef69e Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Wed, 3 Feb 2016 16:14:31 +0100 Subject: [PATCH] Update support for configuration comments * Add a new `ignore` keyword which ignores warnings in the following node; * Externalise `lib/filter` to `remark-message-control`. --- component.json | 8 ++- lib/filter.js | 139 -------------------------------------------- lib/index.js | 152 ++++++++----------------------------------------- package.json | 4 +- readme.md | 8 ++- test/index.js | 2 +- 6 files changed, 39 insertions(+), 274 deletions(-) delete mode 100644 lib/filter.js diff --git a/component.json b/component.json index d3e670cf..db8d61a2 100644 --- a/component.json +++ b/component.json @@ -11,13 +11,15 @@ ], "repository": "wooorm/remark-lint", "dependencies": { - "sindresorhus/plur": "^2.0.0", - "wooorm/remark-range": "^2.0.0", + "sindresorhus/decamelize": "^1.0.0", "wooorm/mdast-util-heading-style": "^1.0.0", "wooorm/mdast-util-position": "^1.0.0", "wooorm/mdast-util-to-string": "^1.0.0", + "eush77/npm-prefix": "^1.1.1", + "sindresorhus/plur": "^2.0.0", + "wooorm/remark-message-control": "^1.0.1", + "wooorm/remark-range": "^2.0.0", "wooorm/unist-util-visit": "^1.0.0", - "wooorm/mdast-zone": "^2.0.0", "wooorm/vfile-sort": "^1.0.0" }, "scripts": [ diff --git a/lib/filter.js b/lib/filter.js deleted file mode 100644 index a249ba20..00000000 --- a/lib/filter.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module remark:lint:filter - * @fileoverview remark plug-in used internally by - * remark-lint to filter ruleId’s by enabled and disabled - * ranges, or by gaps. - * @todo Externalise into its own repository. - */ - -'use strict'; - -/* eslint-env commonjs */ - -var position = require('mdast-util-position'); -var visit = require('unist-util-visit'); - -/** - * Remove warnings which are disabled, or are in gaps. - * - * @param {Node} ast - Root node. - * @param {File} file - Virtual file. - */ -function transformer(ast, file) { - var lastNode = ast.children[ast.children.length - 1]; - var gaps = []; - var offset = 0; - var isGap = false; - var scope = file.namespace('remark-lint').ranges; - - if (!file || !file.messages || !file.messages.length) { - return; - } - - /** - * Patch a new position. - * - * @param {number?} [latest] - Last found position. - */ - function update(latest) { - if (latest === undefined || latest === null) { - isGap = true; - - return; - } - - if (offset > latest) { - return; - } - - if (isGap) { - gaps.push({ - 'start': offset, - 'end': latest - }); - - isGap = false; - } - - offset = latest; - } - - visit(ast, function (node) { - var start = position.start(node); - var end = position.end(node); - - update(start && start.offset); - - if (!node.children) { - update(end && end.offset); - } - }); - - if (offset === position.end(lastNode).offset) { - update(); - update(file.toString().length - 1); - } - - file.messages = file.messages.filter(function (message) { - var ranges = scope[message.ruleId]; - var index = ranges && ranges.length; - var gapIndex = gaps.length; - var length = -1; - var pos; - var range; - - if (!message.line) { - message.line = 1; - } - - if (!message.column) { - message.column = 1; - } - - pos = file.positionToOffset(message); - - while (gapIndex--) { - if ( - gaps[gapIndex].start <= pos && - gaps[gapIndex].end > pos - ) { - return false; - } - } - - while (--index > length) { - range = ranges[index]; - - if ( - range.position.line < message.line || - ( - range.position.line === message.line && - range.position.column < message.column - ) - ) { - return range.state === true; - } - } - - /* xistanbul ignore next - Just to be safe */ - return true; - }); -} - -/** - * Return `transformer`. - * - * @return {Function} - See `transformer`. - */ -function attacher() { - return transformer; -} - -/* - * Expose. - */ - -module.exports = attacher; diff --git a/lib/index.js b/lib/index.js index 074be89c..f9b62255 100644 --- a/lib/index.js +++ b/lib/index.js @@ -24,9 +24,8 @@ var SOURCE = 'remark-lint'; var decamelize = require('decamelize'); var sort = require('vfile-sort'); var range = require('remark-range'); -var zone = require('mdast-zone'); +var control = require('remark-message-control'); var internals = require('./rules'); -var filter = require('./filter'); var npmPrefix = require('npm-prefix')(); /* @@ -249,10 +248,13 @@ function decamelizeSettings(source) { */ function lint(remark, options) { var settings = decamelizeSettings(options || {}); - var reset = settings.reset; var rules = loadExternals(settings.external); - var id; + var reset = options && options.reset; + var enable = []; + var disable = []; + var known = []; var setting; + var id; /* * Ensure offset information is added. @@ -260,151 +262,47 @@ function lint(remark, options) { remark.use(range); - /** - * Get the latest state of a rule. - * - * @param {string} ruleId - Unique rule name. - * @param {File} [file] - File (optional) - */ - function getState(ruleId, file) { - var scope = file && file.namespace('remark-lint'); - var ranges = scope && scope.ranges && scope.ranges[ruleId]; - - if (ranges) { - return ranges[ranges.length - 1].state; - } - - setting = settings[ruleId]; - - if (setting === false) { - return false; - } - - return !reset || (setting !== null && setting !== undefined); - } - - /** - * Store settings on `file`. - * - * @param {File} file - Virtual file. - */ - function store(file) { - var scope = file.namespace('remark-lint'); - var ranges = scope.ranges; - var ruleId; - - if (!ranges) { - ranges = {}; - - for (ruleId in rules) { - ranges[ruleId] = [{ - 'state': getState(ruleId), - 'position': { - 'line': 0, - 'column': 0 - } - }]; - } - - scope.ranges = ranges; - } - } - - remark.use(function () { - return function (ast, file) { - store(file); - }; - }); - /* * Add each rule as a seperate plugin. */ for (id in rules) { - remark.use(attachFactory(id, rules[id], settings[id])); - } - - /** - * Handle a rule. - * - * @param {VFile} file - Virtual file. - * @param {Object} marker - Marker context. - * @param {string} type - Type to toggle to. - * @param {*} ruleId - Rule to toggle. - */ - function toggle(file, marker, type, ruleId) { - var scope = file.namespace('remark-lint'); - var markers; - var currentState; - var previousState; + setting = settings[id]; - if (!(ruleId in rules)) { - file.fail('Unknown rule: cannot ' + type + ' `\'' + ruleId + '\'`', marker.node); + known.push(id); - return; - } - - markers = scope.ranges[ruleId]; - - previousState = getState(ruleId, file); - currentState = type === 'enable'; - - if (currentState !== previousState) { - markers.push({ - 'state': currentState, - 'position': marker.node.position.start - }); - } - } - - /** - * Handle a new-found marker. - * - * @param {Object} marker - Marker context. - * @param {Object} parser - Parser instance. - */ - function onparse(marker, parser) { - var file = parser.file; - var attributes = marker.attributes.split(' '); - var type = attributes[0]; - var ids = attributes.slice(1); - var length = ids.length; - var index = -1; - - if (type !== 'disable' && type !== 'enable') { - file.fail('Unknown lint keyword `' + type + '`: use either `\'enable\'` or `\'disable\'`', marker.node); - - return; + if (!(setting === null || setting === undefined)) { + if (setting === false) { + disable.push(id); + } else { + enable.push(id); + } } - store(file); - - while (++index < length) { - toggle(file, marker, type, ids[index]); - } + remark.use(attachFactory(id, rules[id], setting)); } - remark.use(zone({ - 'name': 'lint', - 'onparse': onparse - })); - /* - * Filter. + * Allow comments to toggle messages. */ - remark.use(filter); + remark.use(control, { + 'name': 'lint', + 'source': SOURCE, + 'reset': reset, + 'known': known, + 'enable': enable, + 'disable': disable + }); /** * Transformer sort messages. * * @param {Node} node - Syntax tree. * @param {VFile} file - Virtual file. - * @param {Function} next - Completion handler. */ - return function (node, file, next) { + return function (node, file) { sort(file); - next(); }; } diff --git a/package.json b/package.json index af547a22..750073ab 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,13 @@ "author": "Titus Wormer ", "dependencies": { "decamelize": "^1.0.0", - "remark-range": "^2.0.0", "mdast-util-heading-style": "^1.0.0", "mdast-util-position": "^1.0.0", "mdast-util-to-string": "^1.0.0", - "mdast-zone": "^2.0.0", "npm-prefix": "^1.1.1", "plur": "^2.0.0", + "remark-message-control": "^1.0.1", + "remark-range": "^2.0.0", "unist-util-visit": "^1.0.0", "vfile-sort": "^1.0.0" }, diff --git a/readme.md b/readme.md index f2272f86..0121d076 100644 --- a/readme.md +++ b/readme.md @@ -114,8 +114,10 @@ markdown code. Read more about the latter on [**remark**’s readme][remark-process]. In addition, you can also provide configuration comments to turn a rule -on or off inside a file (note that you cannot change what a setting, such as -`maximum-line-length`, you’re either enabling or disabling warnings). +on or off inside a file. Note that you cannot change what a setting, +such as `maximum-line-length`, checks for, as you’re either enabling +or disabling warnings). Read more about configuration comments in +[**remark-message-control**][message-control]s documentation. The following file will warn twice for the duplicate headings: @@ -234,3 +236,5 @@ excluding `remark-lint-no-` or `remark-lint-` [remark-process]: https://github.com/wooorm/remark#remarkprocessvalue-options-done [linter-markdown]: https://atom.io/packages/linter-markdown + +[message-control]: https://github.com/wooorm/remark-message-control#markers diff --git a/test/index.js b/test/index.js index 1297b7aa..c9df0b3d 100644 --- a/test/index.js +++ b/test/index.js @@ -357,7 +357,7 @@ describe('Comments', function () { describe('Invalid comments', function () { assertFile('comments-invalid-keyword.md', [ - 'comments-invalid-keyword.md:3:1-3:20: Unknown lint keyword `foo`: use either `\'enable\'` or `\'disable\'`' + 'comments-invalid-keyword.md:3:1-3:20: Unknown keyword `foo`: expected `\'enable\'`, `\'disable\'`, or `\'ignore\'`' ], null, { 'reset': true });