Repository: xjamundx/eslint-plugin-promise
Pull request #6 (2016-03-23 10:59:49)

Allow throw in always-return rule
Throwing an error is a valid way of terminating the function passed to `then` and will result in a rejected promise.

If this PR is merged it will allow something like this:

```js
function isUserLoggedIn (username) {
  return getUserByName(username).then(user => {
    if (user) {
      return user.isLoggedOut();
    }
    throw new Error('user not found');
  });
}
```



Patch:
diff --git a/rules/always-return.js b/rules/always-return.js
index b1af1c1..99068ff 100644
--- a/rules/always-return.js
+++ b/rules/always-return.js
@@ -1,36 +1,36 @@
+function isFunctionWithBlockStatement (node) {
+  if (node.type === 'FunctionExpression') {
+    return true
+  }
+  if (node.type === 'ArrowFunctionExpression') {
+    return node.body.type === 'BlockStatement'
+  }
+  return false
+}
+
+function isReturnOrThrowStatement (node) {
+  return node.type === 'ReturnStatement' || node.type === 'ThrowStatement'
+}
+
 module.exports = function (context) {
   return {
     MemberExpression: function (node) {
-      var body, lastItem
+      var firstArg, body, lastStatement
 
-      // hello.then(function() { })
-      if (node.property.name === 'then' &&
-        node.parent.type === 'CallExpression' &&
-        node.parent.arguments[0] &&
-        node.parent.arguments[0].type === 'FunctionExpression'
-      ) {
-        body = node.parent.arguments[0].body.body
-        lastItem = body[body.length - 1]
-        if (!lastItem || lastItem.type !== 'ReturnStatement') {
-          context.report(node, 'Each then() should return a value')
-        }
+      if (node.property.name !== 'then' || node.parent.type !== 'CallExpression') {
         return
       }
 
-      // hello.then(() => {})
-      if (node.property.name === 'then' &&
-        node.parent.type === 'CallExpression' &&
-        node.parent.arguments[0] &&
-        node.parent.arguments[0].type === 'ArrowFunctionExpression' &&
-        node.parent.arguments[0].body.type === 'BlockStatement'
-      ) {
-        body = node.parent.arguments[0].body.body
-        lastItem = body[body.length - 1]
-        if (!lastItem || lastItem.type !== 'ReturnStatement') {
-          context.report(node, 'Each then() should return a value')
-        }
+      firstArg = node.parent.arguments[0]
+      if (!firstArg || !isFunctionWithBlockStatement(firstArg)) {
         return
       }
+
+      body = firstArg.body.body
+      lastStatement = body[body.length - 1]
+      if (!lastStatement || !isReturnOrThrowStatement(lastStatement)) {
+        context.report(node, 'Each then() should return a value or throw')
+      }
     }
   }
 }


Test patch:
diff --git a/test/always-return.test.js b/test/always-return.test.js
index 1631432..302189c 100644
--- a/test/always-return.test.js
+++ b/test/always-return.test.js
@@ -2,7 +2,7 @@
 
 var rule = require('../rules/always-return')
 var RuleTester = require('eslint').RuleTester
-var message = 'Each then() should return a value'
+var message = 'Each then() should return a value or throw'
 var ecmaFeatures = { arrowFunctions: true }
 var ruleTester = new RuleTester()
 ruleTester.run('always-return', rule, {
@@ -13,7 +13,13 @@ ruleTester.run('always-return', rule, {
     { code: 'hey.then(function() { return 42; })', ecmaFeatures: ecmaFeatures },
     { code: 'hey.then(function() { return new Promise(); })', ecmaFeatures: ecmaFeatures },
     { code: 'hey.then(function() { return "x"; }).then(doSomethingWicked)' },
-    { code: 'hey.then(x => x).then(function() { return "3" })', ecmaFeatures: ecmaFeatures }
+    { code: 'hey.then(x => x).then(function() { return "3" })', ecmaFeatures: ecmaFeatures },
+    { code: 'hey.then(function() { throw new Error("msg"); })', ecmaFeatures: ecmaFeatures },
+    { code: 'hey.then(function(x) { if (!x) { throw new Error("no x"); } return x; })', ecmaFeatures: ecmaFeatures },
+    { code: 'hey.then(function(x) { if (x) { return x; } throw new Error("no x"); })', ecmaFeatures: ecmaFeatures },
+    { code: 'hey.then(x => { throw new Error("msg"); })', ecmaFeatures: ecmaFeatures },
+    { code: 'hey.then(x => { if (!x) { throw new Error("no x"); } return x; })', ecmaFeatures: ecmaFeatures },
+    { code: 'hey.then(x => { if (x) { return x; } throw new Error("no x"); })', ecmaFeatures: ecmaFeatures }
   ],
 
   invalid: [