/** * @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} 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;