Skip to content

Commit

Permalink
Chore: refactor reporting logic (refs #9161)
Browse files Browse the repository at this point in the history
  • Loading branch information
not-an-aardvark committed Aug 27, 2017
1 parent e6b115c commit e44f4a9
Show file tree
Hide file tree
Showing 6 changed files with 910 additions and 1,087 deletions.
166 changes: 64 additions & 102 deletions lib/linter.js
Expand Up @@ -9,10 +9,10 @@
// Requirements
//------------------------------------------------------------------------------

const assert = require("assert"),
EventEmitter = require("events").EventEmitter,
const EventEmitter = require("events").EventEmitter,
eslintScope = require("eslint-scope"),
levn = require("levn"),
lodash = require("lodash"),
blankScriptAST = require("../conf/blank-script.json"),
defaultConfig = require("../conf/default-config-options.js"),
replacements = require("../conf/replacements.json"),
Expand All @@ -23,7 +23,7 @@ const assert = require("assert"),
NodeEventGenerator = require("./util/node-event-generator"),
SourceCode = require("./util/source-code"),
Traverser = require("./util/traverser"),
RuleContext = require("./rule-context"),
createReportTranslator = require("./report-translator"),
Rules = require("./rules"),
timing = require("./timing"),
astUtils = require("./ast-utils"),
Expand Down Expand Up @@ -685,6 +685,34 @@ function parse(text, config, filePath, messages) {
}
}

const RULE_CONTEXT_PASSTHROUGHS = [
"getAncestors",
"getDeclaredVariables",
"getFilename",
"getScope",
"getSourceCode",
"markVariableAsUsed",

// DEPRECATED
"getAllComments",
"getComments",
"getFirstToken",
"getFirstTokens",
"getJSDocComment",
"getLastToken",
"getLastTokens",
"getNodeByRangeIndex",
"getSource",
"getSourceLines",
"getTokenAfter",
"getTokenBefore",
"getTokenByRangeStart",
"getTokens",
"getTokensAfter",
"getTokensBefore",
"getTokensBetween"
];

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -833,45 +861,61 @@ class Linter extends EventEmitter {
ConfigOps.normalize(config);

// enable appropriate rules
Object.keys(config.rules).filter(key => getRuleSeverity(config.rules[key]) > 0).forEach(key => {
Object.keys(config.rules).filter(ruleId => getRuleSeverity(config.rules[ruleId]) > 0).forEach(ruleId => {
let ruleCreator;

ruleCreator = this.rules.get(key);
ruleCreator = this.rules.get(ruleId);

if (!ruleCreator) {
const replacementMsg = getRuleReplacementMessage(key);
const replacementMsg = getRuleReplacementMessage(ruleId);

if (replacementMsg) {
ruleCreator = createStubRule(replacementMsg);
} else {
ruleCreator = createStubRule(`Definition for rule '${key}' was not found`);
ruleCreator = createStubRule(`Definition for rule '${ruleId}' was not found`);
}
this.rules.define(key, ruleCreator);
this.rules.define(ruleId, ruleCreator);
}

const severity = getRuleSeverity(config.rules[key]);
const options = getRuleOptions(config.rules[key]);
const severity = getRuleSeverity(config.rules[ruleId]);
const ruleContext = RULE_CONTEXT_PASSTHROUGHS.reduce(
(contextInfo, methodName) => Object.assign(contextInfo, { [methodName]: this[methodName].bind(this) }),
{
options: getRuleOptions(config.rules[ruleId]),
settings: config.settings,
parserOptions: config.parserOptions,
parserPath: config.parser,
parserServices: parseResult && parseResult.services || {},
report: lodash.flow([
createReportTranslator({ ruleId, severity, sourceCode: this.sourceCode }),
problem => {
if (problem.fix && ruleCreator.meta && !ruleCreator.meta.fixable) {
throw new Error("Fixable rules should export a `meta.fixable` property.");
}
if (!isDisabledByReportingConfig(this.reportingConfig, ruleId, problem)) {
this.messages.push(problem);
}
}
])
}
);

Object.freeze(ruleContext);

try {
const ruleContext = new RuleContext(
key, this, severity, options,
config.settings, config.parserOptions, config.parser,
ruleCreator.meta,
(parseResult && parseResult.services ? parseResult.services : {})
);

const rule = ruleCreator.create ? ruleCreator.create(ruleContext)
const rule = ruleCreator.create
? ruleCreator.create(ruleContext)
: ruleCreator(ruleContext);

// add all the selectors from the rule as listeners
Object.keys(rule).forEach(selector => {
this.on(selector, timing.enabled
? timing.time(key, rule[selector])
? timing.time(ruleId, rule[selector])
: rule[selector]
);
});
} catch (ex) {
ex.message = `Error while loading rule '${key}': ${ex.message}`;
ex.message = `Error while loading rule '${ruleId}': ${ex.message}`;
throw ex;
}
});
Expand Down Expand Up @@ -933,88 +977,6 @@ class Linter extends EventEmitter {
return this.messages;
}

/**
* Reports a message from one of the rules.
* @param {string} ruleId The ID of the rule causing the message.
* @param {number} severity The severity level of the rule as configured.
* @param {ASTNode} node The AST node that the message relates to.
* @param {Object=} location An object containing the error line and column
* numbers. If location is not provided the node's start location will
* be used.
* @param {string} message The actual message.
* @param {Object} opts Optional template data which produces a formatted message
* with symbols being replaced by this object's values.
* @param {Object} fix A fix command description.
* @param {Object} meta Metadata of the rule
* @returns {void}
*/
report(ruleId, severity, node, location, message, opts, fix, meta) {
if (node) {
assert.strictEqual(typeof node, "object", "Node must be an object");
}

let endLocation;

if (typeof location === "string") {
assert.ok(node, "Node must be provided when reporting error if location is not provided");

meta = fix;
fix = opts;
opts = message;
message = location;
location = node.loc.start;
endLocation = node.loc.end;
} else {
endLocation = location.end;
}

location = location.start || location;

if (isDisabledByReportingConfig(this.reportingConfig, ruleId, location)) {
return;
}

if (opts) {
message = message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => {
if (term in opts) {
return opts[term];
}

// Preserve old behavior: If parameter name not provided, don't replace it.
return fullMatch;
});
}

const problem = {
ruleId,
severity,
message,
line: location.line,
column: location.column + 1, // switch to 1-base instead of 0-base
nodeType: node && node.type,
source: this.sourceCode.lines[location.line - 1] || ""
};

// Define endLine and endColumn if exists.
if (endLocation) {
problem.endLine = endLocation.line;
problem.endColumn = endLocation.column + 1; // switch to 1-base instead of 0-base
}

// ensure there's range and text properties, otherwise it's not a valid fix
if (fix && Array.isArray(fix.range) && (typeof fix.text === "string")) {

// If rule uses fix, has metadata, but has no metadata.fixable, we should throw
if (meta && !meta.fixable) {
throw new Error("Fixable rules should export a `meta.fixable` property.");
}

problem.fix = fix;
}

this.messages.push(problem);
}

/**
* Gets the SourceCode object representing the parsed source.
* @returns {SourceCode} The SourceCode object.
Expand Down

0 comments on commit e44f4a9

Please sign in to comment.