Repository: cypress-io/eslint-plugin-cypress (js)

Issue:
Errors when assigning cy.spy() return value
Since `cypress/no-assigning-return-values` is enabled and in the "recommended" ruleset, we get

> `error  Do not assign the return value of a Cypress command`

for code that uses `cy.spy()` synchronously e.g.

`const nameSpy = cy.spy(user, 'setName')`

From [the docs](https://docs.cypress.io/api/commands/spy.html#Yields):

> Unlike most Cypress commands, cy.spy() is synchronous and returns a value (the spy) instead of a Promise-like chain-able object.

I believe there are at two possible options here: either allow the rule to exclude cy.spy assignments in particular, **or** update the docs to declare that using return value is deprecated in favor of aliasing + change the linter error message to include such a recommendation IF indeed works / it actually does return a chainable.

Pull request description:
fix: allow assignment of children of whitelisted commands in no-assigning-return-values rule
fixes #56 

This corrects the lint errors that are thrown with the bundled examples of cypress v4.6.

I don't know the API well enough to know if chaining more commands and returning the results is valid and should be covered here. But this is a start :)

Patch:
diff --git a/lib/rules/assertion-before-screenshot.js b/lib/rules/assertion-before-screenshot.js
index 54688e0..cd1f449 100644
--- a/lib/rules/assertion-before-screenshot.js
+++ b/lib/rules/assertion-before-screenshot.js
@@ -1,8 +1,3 @@
-/**
- * @fileoverview Assert on the page state before taking a screenshot, so the screenshot is consistent
- * @author Luke Page
- */
-
 'use strict'
 
 const assertionCommands = [
@@ -17,7 +12,7 @@ const assertionCommands = [
   // not an assertion, but unlikely to require waiting for render
   'scrollIntoView',
   'scrollTo',
-];
+]
 
 module.exports = {
   meta: {
@@ -42,19 +37,22 @@ module.exports = {
   },
 }
 
-function isRootCypress(node) {
-  while(node.type === 'CallExpression') {
+function isRootCypress (node) {
+  while (node.type === 'CallExpression') {
     if (node.callee.type !== 'MemberExpression') return false
+
     if (node.callee.object.type === 'Identifier' &&
         node.callee.object.name === 'cy') {
       return true
     }
+
     node = node.callee.object
   }
+
   return false
 }
 
-function getPreviousInChain(node) {
+function getPreviousInChain (node) {
   return node.type === 'CallExpression' &&
          node.callee.type === 'MemberExpression' &&
          node.callee.object.type === 'CallExpression' &&
@@ -63,7 +61,7 @@ function getPreviousInChain(node) {
          node.callee.object.callee.property.name
 }
 
-function getCallExpressionCypressCommand(node) {
+function getCallExpressionCypressCommand (node) {
   return isRootCypress(node) &&
          node.callee.property.type === 'Identifier' &&
          node.callee.property.name
@@ -73,14 +71,14 @@ function isCallingCyScreenshot (node) {
   return getCallExpressionCypressCommand(node) === 'screenshot'
 }
 
-function getPreviousCypressCommand(node) {
+function getPreviousCypressCommand (node) {
   const previousInChain = getPreviousInChain(node)
 
   if (previousInChain) {
     return previousInChain
   }
 
-  while(node.parent && !node.parent.body) {
+  while (node.parent && !node.parent.body) {
     node = node.parent
   }
 
@@ -93,18 +91,20 @@ function getPreviousCypressCommand(node) {
   // in the case of a function declaration it won't be found
   if (index < 0) return null
 
-  if (index === 0) return getPreviousCypressCommand(node.parent);
+  if (index === 0) return getPreviousCypressCommand(node.parent)
 
   const previousStatement = body[index - 1]
 
   if (previousStatement.type !== 'ExpressionStatement' ||
-      previousStatement.expression.type !== 'CallExpression')
+      previousStatement.expression.type !== 'CallExpression') {
     return null
+  }
 
   return getCallExpressionCypressCommand(previousStatement.expression)
 }
 
 function isPreviousAnAssertion (node) {
   const previousCypressCommand = getPreviousCypressCommand(node)
+
   return assertionCommands.indexOf(previousCypressCommand) >= 0
 }
diff --git a/lib/rules/no-assigning-return-values.js b/lib/rules/no-assigning-return-values.js
index 08294bf..40060a6 100644
--- a/lib/rules/no-assigning-return-values.js
+++ b/lib/rules/no-assigning-return-values.js
@@ -1,18 +1,17 @@
-/**
- * @fileoverview Prevent assigning return value of cy calls
- * @author Elad Shahar
- */
-
 'use strict'
 
 // safely get nested object property
 function get (obj, propertyString = '') {
   const properties = propertyString.split('.')
+
   for (let i = 0; i < properties.length; i++) {
     const value = (obj || {})[properties[i]]
+
     if (value == null) return value
+
     obj = value
   }
+
   return obj
 }
 
@@ -57,9 +56,12 @@ function isCypressCommandDeclaration (declarator) {
 
     if (!object) return
   }
+
   const commandName = get(declarator, 'init.callee.property.name')
 
-  if (commandName && whitelistedCommands[commandName]) return
+  const parent = get(object, 'parent.property.name') || get(declarator, 'id.name')
+
+  if (commandName && (whitelistedCommands[commandName] || whitelistedCommands[parent])) return
 
   return object.name === 'cy'
 }
diff --git a/lib/rules/no-force.js b/lib/rules/no-force.js
index 4463e42..123f6e2 100644
--- a/lib/rules/no-force.js
+++ b/lib/rules/no-force.js
@@ -1,8 +1,3 @@
-/**
- * @fileoverview Disallow using of \'force: true\' option for click and type calls
- * @author Alex Kuznetsov
- */
-
 'use strict'
 
 //------------------------------------------------------------------------------
diff --git a/lib/rules/no-unnecessary-waiting.js b/lib/rules/no-unnecessary-waiting.js
index 21aa058..760019b 100644
--- a/lib/rules/no-unnecessary-waiting.js
+++ b/lib/rules/no-unnecessary-waiting.js
@@ -1,8 +1,3 @@
-/**
- * @fileoverview Prevent waiting for arbitrary time periods
- * @author Elad Shahar
- */
-
 'use strict'
 
 module.exports = {
diff --git a/lib/rules/require-data-selectors.js b/lib/rules/require-data-selectors.js
index 7325cff..0152546 100644
--- a/lib/rules/require-data-selectors.js
+++ b/lib/rules/require-data-selectors.js
@@ -1,7 +1,3 @@
-/**
- * @fileoverview Use data-* attribute selectors instead of classes or tag names
- * @author Sandeep Baldawa
- */
 'use strict'
 
 module.exports = {


Test patch:
diff --git a/tests/lib/rules/no-assigning-return-values.js b/tests/lib/rules/no-assigning-return-values.js
index 8218d12..9d57491 100644
--- a/tests/lib/rules/no-assigning-return-values.js
+++ b/tests/lib/rules/no-assigning-return-values.js
@@ -16,6 +16,7 @@ ruleTester.run('no-assigning-return-values', rule, {
     { code: 'const foo = bar();', parserOptions },
     { code: 'const foo = bar().baz();', parserOptions },
     { code: 'const spy = cy.spy();', parserOptions },
+    { code: 'const spy = cy.spy().as();', parserOptions },
     { code: 'const stub = cy.stub();', parserOptions },
     { code: 'const result = cy.now();', parserOptions },
     { code: 'const state = cy.state();', parserOptions },