Repository: xjamundx/eslint-plugin-promise
Pull request #13 (2016-04-12 15:23:28)

Catch or return
This change:

1. Extends the things treated as Promises to include the result of `Promise.all` etc.
2. Adds an option to change the `terminationMethod` (defaulting to `catch`).  This lets you require promise chains to be terminated with the method of your choice (e.g. `.done()`, which is common practice in most non-native Promise implementations).


Patch:
diff --git a/README.md b/README.md
index fa02c29..97ffccb 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,12 @@ You can pass an `{ allowThen: true }` as an option to this rule
  to allow for `.then(null, fn)` to be used instead of `catch()` at
  the end of the promise chain.
 
+#### `terminationMethod`
+
+You can pass a `{ terminationMethod: 'done' }` as an option to this rule
+ to require `done()` instead of `catch()` at the end of the promise chain.
+ This is useful for many non-standard Promise implementations.
+
 ### `always-return`
 
 Ensure that inside a `then()` you make sure to `return` a new promise or value.
diff --git a/rules/catch-or-return.js b/rules/catch-or-return.js
index cd394bf..00aa0f4 100644
--- a/rules/catch-or-return.js
+++ b/rules/catch-or-return.js
@@ -1,34 +1,58 @@
 module.exports = function (context) {
   var options = context.options[0] || {}
   var allowThen = options.allowThen
-
+  var terminationMethod = options.terminationMethod || 'catch'
+  var STATIC_METHODS = [
+    'all',
+    'race',
+    'reject',
+    'resolve'
+  ]
+  function isPromise (expression) {
+    return ( // hello.then()
+      expression.type === 'CallExpression' &&
+      expression.callee.type === 'MemberExpression' &&
+      expression.callee.property.name === 'then'
+    ) || ( // hello.catch()
+      expression.type === 'CallExpression' &&
+      expression.callee.type === 'MemberExpression' &&
+      expression.callee.property.name === 'catch'
+    ) || ( // somePromise.ANYTHING()
+      expression.type === 'CallExpression' &&
+      expression.callee.type === 'MemberExpression' &&
+      isPromise(expression.callee.object)
+    ) || ( // Promise.STATIC_METHOD()
+      expression.type === 'CallExpression' &&
+      expression.callee.type === 'MemberExpression' &&
+      expression.callee.object.type === 'Identifier' &&
+      expression.callee.object.name === 'Promise' &&
+      STATIC_METHODS.indexOf(expression.callee.property.name) !== -1
+    )
+  }
   return {
     ExpressionStatement: function (node) {
-      // hello.then()
-      if (node.expression.type === 'CallExpression' &&
+      if (!isPromise(node.expression)) {
+        return
+      }
+
+      // somePromise.then(a, b)
+      if (allowThen &&
+        node.expression.type === 'CallExpression' &&
         node.expression.callee.type === 'MemberExpression' &&
-        node.expression.callee.property.name === 'then'
+        node.expression.callee.property.name === 'then' &&
+        node.expression.arguments.length === 2
       ) {
-        // hello.then().then(a, b)
-        if (allowThen && node.expression.arguments.length === 2) {
-          return
-        }
-        context.report(node, 'Expected catch() or return')
         return
       }
 
-      // hello.then().then().catch()
+      // somePromise.catch()
       if (node.expression.type === 'CallExpression' &&
         node.expression.callee.type === 'MemberExpression' &&
-        node.expression.callee.object.type === 'CallExpression' &&
-        node.expression.callee.object.callee.type === 'MemberExpression' &&
-        node.expression.callee.object.callee.property.name === 'then'
+        node.expression.callee.property.name === terminationMethod
       ) {
-        var propName = node.expression.callee.property.name
-        if (propName !== 'catch') {
-          context.report(node, 'Expected catch() or return')
-        }
+        return
       }
+      context.report(node, 'Expected ' + terminationMethod + '() or return')
     }
   }
 }


Test patch:
diff --git a/test/catch-or-return.test.js b/test/catch-or-return.test.js
index deb0f01..acbfbbe 100644
--- a/test/catch-or-return.test.js
+++ b/test/catch-or-return.test.js
@@ -12,6 +12,8 @@ ruleTester.run('catch-or-return', rule, {
     'frank().then(go).then().then().then().catch(doIt)',
     'frank().then(go).then().catch(function() { /* why bother */ })',
     'frank.then(go).then(to).catch(jail)',
+    'Promise.resolve(frank).catch(jail)',
+    'frank.then(to).finally(fn).catch(jail)',
 
     // return
     'function a() { return frank().then(go) }',
@@ -37,7 +39,10 @@ ruleTester.run('catch-or-return', rule, {
     { code: 'frank().then(go).then(zam, doIt)', options: [{ 'allowThen': true }] },
     { code: 'frank().then(go).then().then().then().then(wham, doIt)', options: [{ 'allowThen': true }] },
     { code: 'frank().then(go).then().then(function() {}, function() { /* why bother */ })', options: [{ 'allowThen': true }] },
-    { code: 'frank.then(go).then(to).then(pewPew, jail)', options: [{ 'allowThen': true }] }
+    { code: 'frank.then(go).then(to).then(pewPew, jail)', options: [{ 'allowThen': true }] },
+
+    // terminationMethod=done - .done(null, fn)
+    { code: 'frank().then(go).done()', options: [{ 'terminationMethod': 'done' }] }
 
   ],
 
@@ -60,12 +65,23 @@ ruleTester.run('catch-or-return', rule, {
       code: 'a.then(function() { return "x"; }).then(function(y) { throw y; })',
       errors: [ { message: message } ]
     },
+    {
+      code: 'Promise.resolve(frank)',
+      errors: [ { message: message } ]
+    },
+    {
+      code: 'frank.then(to).finally(fn)',
+      errors: [ { message: message } ]
+    },
 
     // return failures
     { code: 'function a() { frank().then(go) }', errors: [{ message: message }] },
     { code: 'function a() { frank().then(go).then().then().then() }', errors: [{ message: message }] },
     { code: 'function a() { frank().then(go).then()}', errors: [{ message: message }] },
-    { code: 'function a() { frank.then(go).then(to) }', errors: [{ message: message }] }
+    { code: 'function a() { frank.then(go).then(to) }', errors: [{ message: message }] },
 
+    // terminationMethod=done - .done(null, fn)
+    { code: 'frank().then(go)', options: [{ 'terminationMethod': 'done' }], errors: [{ message: 'Expected done() or return' }] },
+    { code: 'frank().catch(go)', options: [{ 'terminationMethod': 'done' }], errors: [{ message: 'Expected done() or return' }] }
   ]
 })