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.

119 lines
3.8 KiB

/**
* @fileoverview Ensure handling of errors when we know they exist.
* @author Jamund Ferguson
* @copyright 2014 Jamund Ferguson. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var errorArgument = context.options[0] || "err";
var callbacks = [];
var scopes = 0;
/**
* Checks if the given argument should be interpreted as a regexp pattern.
* @param {string} stringToCheck The string which should be checked.
* @returns {boolean} Whether or not the string should be interpreted as a pattern.
*/
function isPattern(stringToCheck) {
var firstChar = stringToCheck[0];
return firstChar === "^";
}
/**
* Checks if the given name matches the configured error argument.
* @param {string} name The name which should be compared.
* @returns {boolean} Whether or not the given name matches the configured error variable name.
*/
function matchesConfiguredErrorName(name) {
if (isPattern(errorArgument)) {
var regexp = new RegExp(errorArgument);
return regexp.test(name);
}
return name === errorArgument;
}
/**
* Check the arguments to see if we need to start tracking the error object.
* @param {ASTNode} node The AST node to check.
* @returns {void}
*/
function startFunction(node) {
// keep track of nested scopes
scopes++;
// check if the first argument matches our argument name
var firstArg = node.params && node.params[0];
if (firstArg && matchesConfiguredErrorName(firstArg.name)) {
callbacks.push({handled: false, depth: scopes, errorVariableName: firstArg.name});
}
}
/**
* At the end of a function check to see if the error was handled.
* @param {ASTNode} node The AST node to check.
* @returns {void}
*/
function endFunction(node) {
var callback = callbacks[callbacks.length - 1] || {};
// check if a callback is ending, if so pop it off the stack
if (callback.depth === scopes) {
callbacks.pop();
// check if there were no handled errors since the last callback
if (!callback.handled) {
context.report(node, "Expected error to be handled.");
}
}
// less nested functions
scopes--;
}
/**
* Check to see if we're handling the error object properly.
* @param {ASTNode} node The AST node to check.
* @returns {void}
*/
function checkForError(node) {
if (callbacks.length > 0) {
var callback = callbacks[callbacks.length - 1] || {};
// make sure the node's name matches our error argument name
var isAboutError = node.name === callback.errorVariableName;
// we don't consider these use cases as "handling" the error
var doNotCount = ["FunctionDeclaration", "ArrowFunctionExpression", "FunctionExpression", "CatchClause"];
// make sure this identifier isn't used as part of one of them
var isHandled = doNotCount.indexOf(node.parent.type) === -1;
if (isAboutError && isHandled) {
// record that this callback handled its error
callback.handled = true;
}
}
}
return {
"FunctionDeclaration": startFunction,
"FunctionExpression": startFunction,
"ArrowFunctionExpression": startFunction,
"Identifier": checkForError,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction
};
};