eslint-plugin-security-node
Version:
Create a security plugin for node.js
138 lines (129 loc) • 4.57 kB
JavaScript
/**
* @fileoverview detect missing error handler in asynchronous calls
* @author PauMacasaet
*/
const { getDocsUrl } = require('../utils')
function isAwaitCatchProperty (bodyArg) {
if (bodyArg.type === 'ExpressionStatement') {
if (bodyArg.expression.type === 'AwaitExpression') {
if (bodyArg.expression.argument.hasOwnProperty('callee')) {
if (bodyArg.expression.argument.callee.hasOwnProperty('property')) {
if (bodyArg.expression.argument.callee.property.name === 'catch') return true
}
}
}
}
}
function isIfWithThrow (bodyArg) {
return bodyArg.type === 'IfStatement' &&
(/^.*(e|E)rr/.test(bodyArg.test.name) || bodyArg.test.type === 'UnaryExpression') &&
(bodyArg.consequent.type === 'ThrowStatement' &&
bodyArg.consequent.argument.type === 'NewExpression' &&
/^.*(e|E)rr/.test(bodyArg.consequent.argument.callee.name))
}
function isIfWithReturn (bodyArg) {
if (bodyArg.type === 'IfStatement') {
if (bodyArg.hasOwnProperty('consequent')) {
if (bodyArg.consequent.type === 'ReturnStatement') {
return true
}
}
}
}
function isTryCatchStatement (bodyArg) {
return bodyArg.type === 'TryStatement' &&
bodyArg.handler.type === 'CatchClause' &&
bodyArg.handler.param &&
/^(e|err|error|Error|anySpecificError)$/.test(bodyArg.handler.param.name)
}
function isReturnStatement (bodyArg) {
return bodyArg.type === 'ReturnStatement'
}
function isResponseExpression (bodyArg) {
if (bodyArg.type === 'ExpressionStatement') {
if (bodyArg.expression.type === 'AwaitExpression') {
if (bodyArg.expression.argument.type === 'CallExpression') {
return true
}
}
if (bodyArg.expression.type === 'AssignmentExpression') {
if (bodyArg.expression.left.type === 'MemberExpression') {
if (bodyArg.expression.left.hasOwnProperty('object')) {
if (/^(ctx|res|response)$/.test(bodyArg.expression.left.object.name)) {
return true
}
}
}
}
if (bodyArg.expression.type === 'CallExpression') {
return true
}
}
}
module.exports = {
meta: {
type: 'suggestion',
messages: {
msg: 'Unhandled errors detected in asynchronous function'
},
docs: {
description: 'Handle errors in asynchronous calls',
category: 'Possible Errors',
recommended: true,
url: getDocsUrl('detect-unhandled-async-errors')
},
fixable: null
},
create: function (context) {
return {
'FunctionDeclaration': function (node) {
if (node.async === true) {
let functionBody = node.body.body
if (functionBody.length > 0) {
let errorHandlers = []
for (var i in functionBody) {
if (functionBody[i].hasOwnProperty('consequent')) {
if (functionBody[i].consequent.hasOwnProperty('type')) {
if (functionBody[i].consequent.type === 'BlockStatement') {
if (functionBody[i].consequent.body.length > 0) {
const blockBody = functionBody[i].consequent.body
for (let bodyItem in blockBody) {
if (isTryCatchStatement(blockBody[bodyItem]) ||
isAwaitCatchProperty(blockBody[bodyItem]) ||
isIfWithThrow(blockBody[bodyItem]) ||
isIfWithReturn(blockBody[bodyItem]) ||
isReturnStatement(blockBody[bodyItem]) ||
isResponseExpression(blockBody[bodyItem])) {
errorHandlers.push(blockBody[bodyItem].type)
}
}
}
}
}
}
if (isTryCatchStatement(functionBody[i]) ||
isAwaitCatchProperty(functionBody[i]) ||
isIfWithThrow(functionBody[i]) ||
isIfWithReturn(functionBody[i]) ||
isReturnStatement(functionBody[i]) ||
isResponseExpression(functionBody[i])) {
errorHandlers.push(functionBody[i].type)
}
}
if (errorHandlers.length === 0) {
context.report({
node: node,
messageId: 'msg',
loc: {
start: functionBody[i].loc.start,
end: functionBody[i].loc.end
}
})
}
}
}
}
}
}
}