mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
7.0 KiB
229 lines
7.0 KiB
/**
|
|
* @fileoverview RuleContext utility for rules
|
|
* @author Nicholas C. Zakas
|
|
*/
|
|
"use strict";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Requirements
|
|
//------------------------------------------------------------------------------
|
|
|
|
const assert = require("assert");
|
|
const ruleFixer = require("./util/rule-fixer");
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Constants
|
|
//------------------------------------------------------------------------------
|
|
|
|
const PASSTHROUGHS = [
|
|
"getAncestors",
|
|
"getDeclaredVariables",
|
|
"getFilename",
|
|
"getScope",
|
|
"markVariableAsUsed",
|
|
|
|
// DEPRECATED
|
|
"getAllComments",
|
|
"getComments",
|
|
"getFirstToken",
|
|
"getFirstTokens",
|
|
"getJSDocComment",
|
|
"getLastToken",
|
|
"getLastTokens",
|
|
"getNodeByRangeIndex",
|
|
"getSource",
|
|
"getSourceLines",
|
|
"getTokenAfter",
|
|
"getTokenBefore",
|
|
"getTokenByRangeStart",
|
|
"getTokens",
|
|
"getTokensAfter",
|
|
"getTokensBefore",
|
|
"getTokensBetween"
|
|
];
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Typedefs
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* An error message description
|
|
* @typedef {Object} MessageDescriptor
|
|
* @property {string} nodeType The type of node.
|
|
* @property {Location} loc The location of the problem.
|
|
* @property {string} message The problem message.
|
|
* @property {Object} [data] Optional data to use to fill in placeholders in the
|
|
* message.
|
|
* @property {Function} fix The function to call that creates a fix command.
|
|
*/
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Rule Definition
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Compares items in a fixes array by range.
|
|
* @param {Fix} a The first message.
|
|
* @param {Fix} b The second message.
|
|
* @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
|
|
* @private
|
|
*/
|
|
function compareFixesByRange(a, b) {
|
|
return a.range[0] - b.range[0] || a.range[1] - b.range[1];
|
|
}
|
|
|
|
/**
|
|
* Merges the given fixes array into one.
|
|
* @param {Fix[]} fixes The fixes to merge.
|
|
* @param {SourceCode} sourceCode The source code object to get the text between fixes.
|
|
* @returns {void}
|
|
*/
|
|
function mergeFixes(fixes, sourceCode) {
|
|
if (fixes.length === 0) {
|
|
return null;
|
|
}
|
|
if (fixes.length === 1) {
|
|
return fixes[0];
|
|
}
|
|
|
|
fixes.sort(compareFixesByRange);
|
|
|
|
const originalText = sourceCode.text;
|
|
const start = fixes[0].range[0];
|
|
const end = fixes[fixes.length - 1].range[1];
|
|
let text = "";
|
|
let lastPos = Number.MIN_SAFE_INTEGER;
|
|
|
|
for (const fix of fixes) {
|
|
assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report.");
|
|
|
|
if (fix.range[0] >= 0) {
|
|
text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]);
|
|
}
|
|
text += fix.text;
|
|
lastPos = fix.range[1];
|
|
}
|
|
text += originalText.slice(Math.max(0, start, lastPos), end);
|
|
|
|
return { range: [start, end], text };
|
|
}
|
|
|
|
/**
|
|
* Gets one fix object from the given descriptor.
|
|
* If the descriptor retrieves multiple fixes, this merges those to one.
|
|
* @param {Object} descriptor The report descriptor.
|
|
* @param {SourceCode} sourceCode The source code object to get text between fixes.
|
|
* @returns {Fix} The got fix object.
|
|
*/
|
|
function getFix(descriptor, sourceCode) {
|
|
if (typeof descriptor.fix !== "function") {
|
|
return null;
|
|
}
|
|
|
|
// @type {null | Fix | Fix[] | IterableIterator<Fix>}
|
|
const fix = descriptor.fix(ruleFixer);
|
|
|
|
// Merge to one.
|
|
if (fix && Symbol.iterator in fix) {
|
|
return mergeFixes(Array.from(fix), sourceCode);
|
|
}
|
|
return fix;
|
|
}
|
|
|
|
/**
|
|
* Rule context class
|
|
* Acts as an abstraction layer between rules and the main eslint object.
|
|
*/
|
|
class RuleContext {
|
|
|
|
/**
|
|
* @param {string} ruleId The ID of the rule using this object.
|
|
* @param {eslint} eslint The eslint object.
|
|
* @param {number} severity The configured severity level of the rule.
|
|
* @param {Array} options The configuration information to be added to the rule.
|
|
* @param {Object} settings The configuration settings passed from the config file.
|
|
* @param {Object} parserOptions The parserOptions settings passed from the config file.
|
|
* @param {Object} parserPath The parser setting passed from the config file.
|
|
* @param {Object} meta The metadata of the rule
|
|
* @param {Object} parserServices The parser services for the rule.
|
|
*/
|
|
constructor(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta, parserServices) {
|
|
|
|
// public.
|
|
this.id = ruleId;
|
|
this.options = options;
|
|
this.settings = settings;
|
|
this.parserOptions = parserOptions;
|
|
this.parserPath = parserPath;
|
|
this.meta = meta;
|
|
|
|
// create a separate copy and freeze it (it's not nice to freeze other people's objects)
|
|
this.parserServices = Object.freeze(Object.assign({}, parserServices));
|
|
|
|
// private.
|
|
this.eslint = eslint;
|
|
this.severity = severity;
|
|
|
|
Object.freeze(this);
|
|
}
|
|
|
|
/**
|
|
* Passthrough to eslint.getSourceCode().
|
|
* @returns {SourceCode} The SourceCode object for the code.
|
|
*/
|
|
getSourceCode() {
|
|
return this.eslint.getSourceCode();
|
|
}
|
|
|
|
/**
|
|
* Passthrough to eslint.report() that automatically assigns the rule ID and severity.
|
|
* @param {ASTNode|MessageDescriptor} nodeOrDescriptor The AST node related to the message or a message
|
|
* descriptor.
|
|
* @param {Object=} location The location of the error.
|
|
* @param {string} message The message to display to the user.
|
|
* @param {Object} opts Optional template data which produces a formatted message
|
|
* with symbols being replaced by this object's values.
|
|
* @returns {void}
|
|
*/
|
|
report(nodeOrDescriptor, location, message, opts) {
|
|
|
|
// check to see if it's a new style call
|
|
if (arguments.length === 1) {
|
|
const descriptor = nodeOrDescriptor;
|
|
const fix = getFix(descriptor, this.getSourceCode());
|
|
|
|
this.eslint.report(
|
|
this.id,
|
|
this.severity,
|
|
descriptor.node,
|
|
descriptor.loc || descriptor.node.loc.start,
|
|
descriptor.message,
|
|
descriptor.data,
|
|
fix,
|
|
this.meta
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// old style call
|
|
this.eslint.report(
|
|
this.id,
|
|
this.severity,
|
|
nodeOrDescriptor,
|
|
location,
|
|
message,
|
|
opts,
|
|
this.meta
|
|
);
|
|
}
|
|
}
|
|
|
|
// Copy over passthrough methods. All functions will have 5 or fewer parameters.
|
|
PASSTHROUGHS.forEach(function(name) {
|
|
this[name] = function(a, b, c, d, e) {
|
|
return this.eslint[name](a, b, c, d, e);
|
|
};
|
|
}, RuleContext.prototype);
|
|
|
|
module.exports = RuleContext;
|
|
|