remark-message-control
Advanced tools
Comparing version 2.0.2 to 2.0.3
580
index.js
@@ -11,8 +11,3 @@ /** | ||
/* eslint-env commonjs */ | ||
/* | ||
* Dependencies. | ||
*/ | ||
/* Dependencies. */ | ||
var trim = require('trim'); | ||
@@ -23,387 +18,310 @@ var vfileLocation = require('vfile-location'); | ||
/* | ||
* Map of allowed verbs. | ||
*/ | ||
/* Map of allowed verbs. */ | ||
var ALLOWED_VERBS = { | ||
'enable': true, | ||
'disable': true, | ||
'ignore': true | ||
enable: true, | ||
disable: true, | ||
ignore: true | ||
}; | ||
/** | ||
* Detect gaps in `ast`. | ||
* | ||
* @param {Node} ast - Syntax tree. | ||
* @param {VFile} file - Virtual file. | ||
* @return {Array.<Object>} - Gaps. | ||
*/ | ||
function detectGaps(ast, file) { | ||
var lastNode = ast.children[ast.children.length - 1]; | ||
var offset = 0; | ||
var isGap = false; | ||
var gaps = []; | ||
/* Expose. */ | ||
module.exports = attacher; | ||
/** | ||
* Patch a new position. | ||
* | ||
* @param {number?} [latest] - Last found position. | ||
*/ | ||
function update(latest) { | ||
if (latest === undefined || latest === null) { | ||
isGap = true; | ||
/* Filter .*/ | ||
function attacher(processor, options) { | ||
var name = options && options.name; | ||
var sources; | ||
var known; | ||
var reset; | ||
var enable; | ||
var disable; | ||
return; | ||
} | ||
if (!name) { | ||
throw new Error('Expected `name` in `options`, got `' + name + '`'); | ||
} | ||
if (offset >= latest) { | ||
return; | ||
} | ||
known = options.known; | ||
reset = options.reset; | ||
enable = options.enable || []; | ||
disable = options.disable || []; | ||
sources = options.source; | ||
if (isGap) { | ||
gaps.push({ | ||
'start': offset, | ||
'end': latest | ||
}); | ||
if (!sources) { | ||
sources = [name]; | ||
} else if (typeof sources === 'string') { | ||
sources = [sources]; | ||
} | ||
isGap = false; | ||
} | ||
return transformer; | ||
offset = latest; | ||
} | ||
function transformer(ast, file) { | ||
var location = vfileLocation(file); | ||
var initial = !reset; | ||
var gaps = detectGaps(ast, file); | ||
var scope = {}; | ||
var globals = []; | ||
/* | ||
* Find all gaps. | ||
/** | ||
* Helper to check (and possibly warn) if a ruleId | ||
* is unknown. | ||
* | ||
* @param {string} ruleId - Identifier. | ||
* @param {string} verb - Thing to do with `ruleId`. | ||
* @param {Position} pos - Position of marker. | ||
*/ | ||
function isKnown(ruleId, verb, pos) { | ||
var result = known ? known.indexOf(ruleId) !== -1 : true; | ||
visit(ast, function (node) { | ||
var pos = node.position; | ||
if (!result) { | ||
file.warn('Unknown rule: cannot ' + verb + ' `\'' + ruleId + '\'`', pos); | ||
} | ||
update(pos && pos.start && pos.start.offset); | ||
return result; | ||
} | ||
if (!node.children) { | ||
update(pos && pos.end && pos.end.offset); | ||
} | ||
}); | ||
/* Get the latest state of a rule. | ||
* When without `ruleId`, gets global state. */ | ||
function getState(ruleId) { | ||
var ranges = ruleId ? scope[ruleId] : globals; | ||
/* | ||
* Get the end of the document. | ||
* This detects if the last node was the last node. | ||
* If not, there’s an extra gap between the last node | ||
* and the end of the document. | ||
*/ | ||
if (ranges && ranges.length) { | ||
return ranges[ranges.length - 1].state; | ||
} | ||
if ( | ||
lastNode && | ||
lastNode.position && | ||
lastNode.position.end && | ||
offset === lastNode.position.end.offset && | ||
trim(file.toString().slice(offset)) !== '' | ||
) { | ||
update(); | ||
if (!ruleId) { | ||
return !reset; | ||
} | ||
update( | ||
ast && | ||
ast.position && | ||
ast.position.end && | ||
ast.position.end.offset - 1 | ||
); | ||
if (reset) { | ||
return enable.indexOf(ruleId) !== -1; | ||
} | ||
return disable.indexOf(ruleId) === -1; | ||
} | ||
return gaps; | ||
} | ||
/* Handle a rule. */ | ||
function toggle(pos, state, ruleId) { | ||
var markers = ruleId ? scope[ruleId] : globals; | ||
var currentState; | ||
var previousState; | ||
/** | ||
* Attacher. | ||
* | ||
* @param {Processor} processor - Instance. | ||
* @param {Object?} [options] - Configuration. | ||
* @return {Function} - Transformer. | ||
*/ | ||
function attacher(processor, options) { | ||
var name = options && options.name; | ||
var sources; | ||
var known; | ||
var reset; | ||
var enable; | ||
var disable; | ||
if (!markers) { | ||
markers = scope[ruleId] = []; | ||
} | ||
if (!name) { | ||
throw new Error( | ||
'Expected `name` in `options`, got `' + name + '`' | ||
); | ||
} | ||
previousState = getState(ruleId); | ||
currentState = state; | ||
known = options.known; | ||
reset = options.reset; | ||
enable = options.enable || []; | ||
disable = options.disable || []; | ||
sources = options.source; | ||
if (currentState !== previousState) { | ||
markers.push({state: currentState, position: pos}); | ||
} | ||
if (!sources) { | ||
sources = [name]; | ||
} else if (typeof sources === 'string') { | ||
sources = [sources]; | ||
/* Toggle all known rules. */ | ||
if (!ruleId) { | ||
for (ruleId in scope) { | ||
toggle(pos, state, ruleId); | ||
} | ||
} | ||
} | ||
return function (ast, file) { | ||
var location = vfileLocation(file); | ||
var initial = !reset; | ||
var gaps = detectGaps(ast, file); | ||
var scope = {}; | ||
var globals = []; | ||
visit(ast, 'html', function (node, position, parent) { | ||
var mark = marker(node); | ||
var ruleIds; | ||
var ruleId; | ||
var verb; | ||
var index; | ||
var length; | ||
var next; | ||
var pos; | ||
var tail; | ||
/** | ||
* Helper to check (and possibly warn) if a ruleId | ||
* is unknown. | ||
* | ||
* @param {string} ruleId - Identifier. | ||
* @param {string} verb - Thing to do with `ruleId`. | ||
* @param {Position} pos - Position of marker. | ||
*/ | ||
function isKnown(ruleId, verb, pos) { | ||
var result = known ? known.indexOf(ruleId) !== -1 : true; | ||
if (!mark || mark.name !== options.name) { | ||
return; | ||
} | ||
if (!result) { | ||
file.warn( | ||
'Unknown rule: cannot ' + verb + | ||
' `\'' + ruleId + '\'`', | ||
pos | ||
); | ||
} | ||
ruleIds = mark.attributes.split(/\s/g); | ||
verb = ruleIds.shift(); | ||
next = parent.children[position + 1]; | ||
pos = mark.node.position && mark.node.position.start; | ||
tail = next && next.position && next.position.end; | ||
return result; | ||
} | ||
if (!verb || !ALLOWED_VERBS[verb] === true) { | ||
return file.fail( | ||
'Unknown keyword `' + verb + '`: expected ' + | ||
'`\'enable\'`, `\'disable\'`, or `\'ignore\'`', | ||
mark.node | ||
); | ||
} | ||
/** | ||
* Get the latest state of a rule. | ||
* When without `ruleId`, gets global state. | ||
* | ||
* @param {string?} [ruleId] - Unique rule name. | ||
*/ | ||
function getState(ruleId) { | ||
var ranges = ruleId ? scope[ruleId] : globals; | ||
length = ruleIds.length; | ||
index = -1; | ||
if (ranges && ranges.length) { | ||
return ranges[ranges.length - 1].state; | ||
} | ||
while (++index < length) { | ||
ruleId = ruleIds[index]; | ||
if (!ruleId) { | ||
return !reset; | ||
} | ||
if (isKnown(ruleId, verb, mark.node)) { | ||
toggle(pos, verb === 'enable', ruleId); | ||
if (reset) { | ||
return enable.indexOf(ruleId) !== -1; | ||
} | ||
if (verb === 'ignore') { | ||
toggle(tail, true, ruleId); | ||
} | ||
} | ||
} | ||
return disable.indexOf(ruleId) === -1; | ||
/* Apply to all rules. */ | ||
if (!length) { | ||
if (verb === 'ignore') { | ||
toggle(pos, false); | ||
toggle(tail, true); | ||
} else { | ||
toggle(pos, verb === 'enable'); | ||
reset = verb !== 'enable'; | ||
} | ||
} | ||
}); | ||
/** | ||
* Handle a rule. | ||
* | ||
* @param {Position} pos - Starting position. | ||
* @param {boolean} state - State to toggle to. | ||
* @param {*?} [ruleId] - Rule to toggle. | ||
*/ | ||
function toggle(pos, state, ruleId) { | ||
var markers = ruleId ? scope[ruleId] : globals; | ||
var currentState; | ||
var previousState; | ||
/* Check all `ranges` for `message`. */ | ||
function check(message, ranges, id) { | ||
/* Check the state at the message's position. */ | ||
var index = ranges && ranges.length; | ||
var length = -1; | ||
var range; | ||
if (!markers) { | ||
markers = scope[ruleId] = []; | ||
} | ||
while (--index > length) { | ||
range = ranges[index]; | ||
previousState = getState(ruleId); | ||
currentState = state; | ||
/* istanbul ignore if - generated marker. */ | ||
if ( | ||
!range.position || | ||
!range.position.line || | ||
!range.position.column | ||
) { | ||
continue; | ||
} | ||
if (currentState !== previousState) { | ||
markers.push({ | ||
'state': currentState, | ||
'position': pos | ||
}); | ||
} | ||
/* | ||
* Toggle all known rules. | ||
*/ | ||
if (!ruleId) { | ||
for (ruleId in scope) { | ||
toggle(pos, state, ruleId); | ||
} | ||
} | ||
if ( | ||
range.position.line < message.line || | ||
( | ||
range.position.line === message.line && | ||
range.position.column < message.column | ||
) | ||
) { | ||
return range.state === true; | ||
} | ||
} | ||
visit(ast, 'html', function (node, position, parent) { | ||
var mark = marker(node); | ||
var ruleIds; | ||
var ruleId; | ||
var verb; | ||
var index; | ||
var length; | ||
var next; | ||
var pos; | ||
var tail; | ||
/* The first marker ocurred after the first | ||
* message, so we check the initial state. */ | ||
if (!id) { | ||
return initial || reset; | ||
} | ||
if (!mark || mark.name !== options.name) { | ||
return; | ||
} | ||
return reset ? enable.indexOf(id) !== -1 : disable.indexOf(id) === -1; | ||
} | ||
ruleIds = mark.attributes.split(/\s/g); | ||
verb = ruleIds.shift(); | ||
next = parent.children[position + 1]; | ||
pos = mark.node.position && mark.node.position.start; | ||
tail = next && next.position && next.position.end; | ||
file.messages = file.messages.filter(function (message) { | ||
var gapIndex = gaps.length; | ||
var ruleId = message.ruleId; | ||
var ranges = scope[ruleId]; | ||
var pos; | ||
if (!verb || !ALLOWED_VERBS[verb] === true) { | ||
return file.fail( | ||
'Unknown keyword `' + verb + '`: expected ' + | ||
'`\'enable\'`, `\'disable\'`, or `\'ignore\'`', | ||
mark.node | ||
); | ||
} | ||
/* Keep messages from a different source. */ | ||
if (!message.source || sources.indexOf(message.source) === -1) { | ||
return true; | ||
} | ||
length = ruleIds.length; | ||
index = -1; | ||
/* We only ignore messages if they‘re disabled, | ||
* *not* when they’re not in the document. */ | ||
if (!message.line) { | ||
message.line = 1; | ||
} | ||
while (++index < length) { | ||
ruleId = ruleIds[index]; | ||
if (!message.column) { | ||
message.column = 1; | ||
} | ||
if (isKnown(ruleId, verb, mark.node)) { | ||
toggle(pos, verb === 'enable', ruleId); | ||
/* Check whether the warning is inside a gap. */ | ||
pos = location.toOffset(message); | ||
if (verb === 'ignore') { | ||
toggle(tail, true, ruleId); | ||
} | ||
} | ||
} | ||
while (gapIndex--) { | ||
if ( | ||
gaps[gapIndex].start <= pos && | ||
gaps[gapIndex].end > pos | ||
) { | ||
return false; | ||
} | ||
} | ||
/* | ||
* Apply to all rules. | ||
*/ | ||
/* Check whether allowed by specific and global states. */ | ||
return check(message, ranges, ruleId) && check(message, globals); | ||
}); | ||
} | ||
} | ||
if (!length) { | ||
if (verb === 'ignore') { | ||
toggle(pos, false); | ||
toggle(tail, true); | ||
} else { | ||
toggle(pos, verb === 'enable'); | ||
reset = verb !== 'enable'; | ||
} | ||
} | ||
}); | ||
/** | ||
* Detect gaps in `ast`. | ||
* | ||
* @param {Node} ast - Syntax tree. | ||
* @param {VFile} file - Virtual file. | ||
* @return {Array.<Object>} - Gaps. | ||
*/ | ||
function detectGaps(ast, file) { | ||
var lastNode = ast.children[ast.children.length - 1]; | ||
var offset = 0; | ||
var isGap = false; | ||
var gaps = []; | ||
/** | ||
* Check all `ranges` for `message`. | ||
* | ||
* @param {Object} message - Message. | ||
* @param {Array} ranges - Current ranges. | ||
* @param {*?} id - Identifier. | ||
*/ | ||
function check(message, ranges, id) { | ||
/* | ||
* Check the state at the message's position. | ||
*/ | ||
/* Find all gaps. */ | ||
visit(ast, one); | ||
var index = ranges && ranges.length; | ||
var length = -1; | ||
var range; | ||
/* Get the end of the document. | ||
* This detects if the last node was the last node. | ||
* If not, there’s an extra gap between the last node | ||
* and the end of the document. */ | ||
if ( | ||
lastNode && | ||
lastNode.position && | ||
lastNode.position.end && | ||
offset === lastNode.position.end.offset && | ||
trim(file.toString().slice(offset)) !== '' | ||
) { | ||
update(); | ||
while (--index > length) { | ||
range = ranges[index]; | ||
update( | ||
ast && | ||
ast.position && | ||
ast.position.end && | ||
ast.position.end.offset - 1 | ||
); | ||
} | ||
/* istanbul ignore if - generated marker. */ | ||
if ( | ||
!range.position || | ||
!range.position.line || | ||
!range.position.column | ||
) { | ||
continue; | ||
} | ||
return gaps; | ||
if ( | ||
range.position.line < message.line || | ||
( | ||
range.position.line === message.line && | ||
range.position.column < message.column | ||
) | ||
) { | ||
return range.state === true; | ||
} | ||
} | ||
function one(node) { | ||
var pos = node.position; | ||
/* | ||
* The first marker ocurred after the first | ||
* message, so we check the initial state. | ||
*/ | ||
update(pos && pos.start && pos.start.offset); | ||
if (!id) { | ||
return initial || reset; | ||
} | ||
if (!node.children) { | ||
update(pos && pos.end && pos.end.offset); | ||
} | ||
} | ||
if (reset) { | ||
return enable.indexOf(id) !== -1; | ||
} | ||
/* Detect a new position. */ | ||
function update(latest) { | ||
if (latest == null) { | ||
isGap = true; | ||
return; | ||
} | ||
return disable.indexOf(id) === -1; | ||
} | ||
if (offset >= latest) { | ||
return; | ||
} | ||
file.messages = file.messages.filter(function (message) { | ||
var gapIndex = gaps.length; | ||
var ruleId = message.ruleId; | ||
var ranges = scope[ruleId]; | ||
var pos; | ||
if (isGap) { | ||
gaps.push({start: offset, end: latest}); | ||
isGap = false; | ||
} | ||
/* | ||
* Keep messages from a different source. | ||
*/ | ||
if (!message.source || sources.indexOf(message.source) === -1) { | ||
return true; | ||
} | ||
/* | ||
* We only ignore messages if they‘re disabled, | ||
* *not* when they’re not in the document. | ||
*/ | ||
if (!message.line) { | ||
message.line = 1; | ||
} | ||
if (!message.column) { | ||
message.column = 1; | ||
} | ||
/* | ||
* Check whether the warning is inside a gap. | ||
*/ | ||
pos = location.toOffset(message); | ||
while (gapIndex--) { | ||
if ( | ||
gaps[gapIndex].start <= pos && | ||
gaps[gapIndex].end > pos | ||
) { | ||
return false; | ||
} | ||
} | ||
/* | ||
* Check whether allowed by specific and global | ||
* states. | ||
*/ | ||
return check(message, ranges, ruleId) && check(message, globals); | ||
}); | ||
}; | ||
offset = latest; | ||
} | ||
} | ||
/* | ||
* Expose. | ||
*/ | ||
module.exports = attacher; |
{ | ||
"name": "remark-message-control", | ||
"version": "2.0.2", | ||
"version": "2.0.3", | ||
"description": "Enable, disable, and ignore messages with remark", | ||
@@ -33,18 +33,10 @@ "license": "MIT", | ||
"browserify": "^13.0.0", | ||
"eslint": "^2.0.0", | ||
"esmangle": "^1.0.0", | ||
"istanbul": "^0.4.0", | ||
"jscs": "^3.0.0", | ||
"jscs-jsdoc": "^2.0.0", | ||
"remark": "^5.0.0", | ||
"remark-cli": "^1.0.0", | ||
"remark-comment-config": "^4.0.0", | ||
"remark-github": "^5.0.0-alpha.1", | ||
"remark-lint": "^4.0.0", | ||
"remark-slug": "^4.0.0", | ||
"nyc": "^8.1.0", | ||
"remark": "^6.0.0", | ||
"remark-cli": "^2.0.0", | ||
"remark-preset-wooorm": "^1.0.0", | ||
"remark-toc": "^3.0.0", | ||
"remark-validate-links": "^4.0.0-alpha.1", | ||
"remark-yaml-config": "^3.0.0", | ||
"tape": "^4.0.0", | ||
"vfile-reporter": "^1.5.0" | ||
"xo": "^0.16.0" | ||
}, | ||
@@ -56,9 +48,26 @@ "scripts": { | ||
"build": "npm run build-md && npm run build-bundle && npm run build-mangle", | ||
"lint-api": "eslint .", | ||
"lint-style": "jscs --reporter inline .", | ||
"lint": "npm run lint-api && npm run lint-style", | ||
"lint": "xo", | ||
"test-api": "node test.js", | ||
"test-coverage": "istanbul cover test.js", | ||
"test-coverage": "nyc --reporter lcov tape test.js", | ||
"test": "npm run build && npm run lint && npm run test-coverage" | ||
}, | ||
"xo": { | ||
"space": true, | ||
"rules": { | ||
"no-eq-null": "off", | ||
"eqeqeq": [ | ||
2, | ||
"allow-null" | ||
], | ||
"guard-for-in": "off", | ||
"max-lines": "off" | ||
}, | ||
"ignores": [ | ||
"remark-message-control.js" | ||
] | ||
}, | ||
"remarkConfig": { | ||
"output": true, | ||
"presets": "wooorm" | ||
} | ||
} |
@@ -10,10 +10,36 @@ # remark-message-control [![Build Status][build-badge]][build-status] [![Coverage Status][coverage-badge]][coverage-status] [![Chat][chat-badge]][chat] | ||
```bash | ||
npm install remark-parse | ||
npm install remark-message-control | ||
``` | ||
**remark-message-control** is also available as an AMD, CommonJS, and | ||
globals module, [uncompressed and compressed][releases]. | ||
## Usage | ||
```js | ||
var remark = require('remark'); | ||
var report = require('vfile-reporter'); | ||
var control = require('remark-message-control'); | ||
remark().use(warn).use(control, {name: 'foo'}).process([ | ||
'<!--foo ignore-->', | ||
'', | ||
'## Heading', | ||
'' | ||
].join('\n'), function (err, file) { | ||
console.log(report(err || file)); | ||
}); | ||
function warn() { | ||
return function (tree, file) { | ||
var message = file.message('Whoops!', tree.children[1]); | ||
message.ruleId = 'thing'; | ||
message.source = 'foo'; | ||
}; | ||
} | ||
``` | ||
Yields: | ||
```txt | ||
no issues found | ||
``` | ||
## API | ||
@@ -23,3 +49,3 @@ | ||
Let comment markers control messages from a certain source. | ||
Let comment markers control messages from a certain sources. | ||
@@ -112,4 +138,2 @@ ###### `options` | ||
[releases]: https://github.com/wooorm/remark-message-control/releases | ||
[license]: LICENSE | ||
@@ -116,0 +140,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
9
143
13803
268