Skip to content

Commit b42e9c4

Browse files
committed
feat(rules): add 'no-array-finder-methods' rule
1 parent 408e916 commit b42e9c4

File tree

6 files changed

+218
-1
lines changed

6 files changed

+218
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Rule | Default Error Level | Auto-fixable | Options
4040
[no-browser-pause][] | 2 | |
4141
[correct-chaining][] | 2 | Yes |
4242
[no-invalid-selectors][] | 2 | |
43+
[no-array-finder-methods][] | 2 | |
4344
[missing-wait-message][] | 1 (Warning) | |
4445
[no-browser-sleep][] | 1 | |
4546
[no-by-xpath][] | 1 | |
@@ -106,6 +107,7 @@ See [configuring rules][] for more information.
106107
[no-angular-attributes]: docs/rules/no-angular-attributes.md
107108
[no-invalid-selectors]: docs/rules/no-invalid-selectors.md
108109
[use-promise-all]: docs/rules/use-promise-all.md
110+
[no-array-finder-methods]: docs/rules/no-array-finder-methods.md
109111
[configuring rules]: http://eslint.org/docs/user-guide/configuring#configuring-rules
110112

111113
## Recommended configuration
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Disallow using `ElementArrayFinder` methods on `ElementFinder`
2+
3+
This rule is inspired by [this problem](http://stackoverflow.com/questions/39710881/protractor-locator-issues) when `ElementArrayFinder` methods were unintentionally used on `ElementFinder`.
4+
5+
This rule is especially useful if ESLint is configured in your IDE to scan on the fly - you would catch these kinds of problems early.
6+
7+
## Rule details
8+
9+
Here is the current list of methods the rule is looking for:
10+
11+
'first', 'last', 'get', 'filter', 'map', 'each', 'reduce', 'count'
12+
13+
Any use of the following patterns are considered warnings:
14+
15+
```js
16+
element(by.css(".class")).get(0);
17+
$(".class").first();
18+
element(by.css(".class")).last();
19+
element(by.css(".class")).map(function (elm) {});
20+
$(".class").filter(function (elm) {});
21+
element(by.css(".class")).each(function (elm) {});
22+
element(by.css(".class1")).element(by.css(".class2")).first();
23+
element(by.css(".class1")).$(".class2").first();
24+
$(".class1").element(by.css(".class2")).first();
25+
$(".class1").$(".class2").first();
26+
```
27+
28+
The following patterns are not warnings:
29+
30+
```js
31+
element.all(by.css(".class")).get(0);
32+
$$(".class").first();
33+
element.all(by.css(".class")).last();
34+
element.all(by.css(".class")).map(function (elm) {});
35+
$$(".class").filter(function (elm) {});
36+
element.all(by.css(".class")).each(function (elm) {});
37+
element(by.css(".class1")).element.all(by.css(".class2")).first();
38+
element(by.css(".class1")).$$(".class2").first();
39+
$(".class1").element.all(by.css(".class2")).first();
40+
$(".class1").$$(".class2").first();
41+
```

index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var noGetInnerOuterHtml = require('./lib/rules/no-get-inner-outer-html')
2626
var noAngularAttributes = require('./lib/rules/no-angular-attributes')
2727
var noInvalidSelectors = require('./lib/rules/no-invalid-selectors')
2828
var usePromiseAll = require('./lib/rules/use-promise-all')
29+
var noArrayFinderMethods = require('./lib/rules/no-array-finder-methods')
2930

3031
module.exports = {
3132
rules: {
@@ -54,7 +55,8 @@ module.exports = {
5455
'no-get-inner-outer-html': noGetInnerOuterHtml,
5556
'no-angular-attributes': noAngularAttributes,
5657
'no-invalid-selectors': noInvalidSelectors,
57-
'use-promise-all': usePromiseAll
58+
'use-promise-all': usePromiseAll,
59+
'no-array-finder-methods': noArrayFinderMethods
5860
},
5961
configs: {
6062
recommended: {
@@ -63,6 +65,7 @@ module.exports = {
6365
'protractor/no-browser-pause': 2,
6466
'protractor/correct-chaining': 2,
6567
'protractor/no-invalid-selectors': 2,
68+
'protractor/no-array-finder-methods': 2,
6669
'protractor/missing-wait-message': 1,
6770
'protractor/no-browser-sleep': 1,
6871
'protractor/no-by-xpath': 1,

lib/is-element-finder.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict'
2+
3+
/**
4+
* Checks if a given node is an ElementFinder instance.
5+
*
6+
* @fileoverview Utility function to determine if a node is an ElementFinder
7+
* @author Alexander Afanasyev
8+
*/
9+
module.exports = function (node) {
10+
// handling $ shortcut
11+
var callee = node.callee
12+
if (callee) {
13+
if (callee.name === '$' || callee.name === 'element') {
14+
return true
15+
}
16+
17+
// handling $ and element chaining
18+
if (callee.type === 'MemberExpression' && callee.property) {
19+
if (callee.property.name === '$' || callee.property.name === 'element') {
20+
return true
21+
}
22+
}
23+
}
24+
25+
return false
26+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict'
2+
3+
/**
4+
* @fileoverview Disallow using `ElementArrayFinder` methods on `ElementFinder`
5+
* @author Alexander Afanasyev
6+
*/
7+
8+
var isElementFinder = require('../is-element-finder')
9+
var elementArrayFinderMethods = [
10+
'first',
11+
'last',
12+
'get',
13+
'filter',
14+
'map',
15+
'each',
16+
'reduce',
17+
'count'
18+
]
19+
20+
module.exports = {
21+
meta: {
22+
schema: []
23+
},
24+
25+
create: function (context) {
26+
return {
27+
MemberExpression: function (node) {
28+
var property = node.property
29+
30+
if (property && elementArrayFinderMethods.indexOf(property.name) > -1) {
31+
if (isElementFinder(node.object)) {
32+
context.report({
33+
node: node,
34+
message: 'Unexpected "' + property.name + '()" call on ElementFinder'
35+
})
36+
}
37+
}
38+
}
39+
}
40+
}
41+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use strict'
2+
3+
var rule = require('../../lib/rules/no-array-finder-methods')
4+
var RuleTester = require('eslint').RuleTester
5+
6+
var eslintTester = new RuleTester()
7+
8+
eslintTester.run('no-array-finder-methods', rule, {
9+
valid: [
10+
'element.all(by.css(".class")).get(0);',
11+
'$$(".class").first();',
12+
'element.all(by.css(".class")).last();',
13+
'element.all(by.css(".class")).map(function (elm) {});',
14+
'$$(".class").filter(function (elm) {});',
15+
'element.all(by.css(".class")).each(function (elm) {});',
16+
'element(by.css(".class1")).element.all(by.css(".class2")).first();',
17+
'element(by.css(".class1")).$$(".class2").first();',
18+
'$(".class1").element.all(by.css(".class2")).first();',
19+
'$(".class1").$$(".class2").first();'
20+
],
21+
22+
invalid: [
23+
{
24+
code: 'element(by.css(".class")).get(0);',
25+
errors: [
26+
{
27+
message: 'Unexpected "get()" call on ElementFinder'
28+
}
29+
]
30+
},
31+
{
32+
code: '$(".class").first();',
33+
errors: [
34+
{
35+
message: 'Unexpected "first()" call on ElementFinder'
36+
}
37+
]
38+
},
39+
{
40+
code: 'element(by.css(".class")).last();',
41+
errors: [
42+
{
43+
message: 'Unexpected "last()" call on ElementFinder'
44+
}
45+
]
46+
},
47+
{
48+
code: 'element(by.css(".class")).map(function (elm) {});',
49+
errors: [
50+
{
51+
message: 'Unexpected "map()" call on ElementFinder'
52+
}
53+
]
54+
},
55+
{
56+
code: '$(".class").filter(function (elm) {});',
57+
errors: [
58+
{
59+
message: 'Unexpected "filter()" call on ElementFinder'
60+
}
61+
]
62+
},
63+
{
64+
code: 'element(by.css(".class")).each(function (elm) {});',
65+
errors: [
66+
{
67+
message: 'Unexpected "each()" call on ElementFinder'
68+
}
69+
]
70+
},
71+
{
72+
code: 'element(by.css(".class1")).element(by.css(".class2")).first();',
73+
errors: [
74+
{
75+
message: 'Unexpected "first()" call on ElementFinder'
76+
}
77+
]
78+
},
79+
{
80+
code: 'element(by.css(".class1")).$(".class2").first();',
81+
errors: [
82+
{
83+
message: 'Unexpected "first()" call on ElementFinder'
84+
}
85+
]
86+
},
87+
{
88+
code: '$(".class1").element(by.css(".class2")).first();',
89+
errors: [
90+
{
91+
message: 'Unexpected "first()" call on ElementFinder'
92+
}
93+
]
94+
},
95+
{
96+
code: '$(".class1").$(".class2").first();',
97+
errors: [
98+
{
99+
message: 'Unexpected "first()" call on ElementFinder'
100+
}
101+
]
102+
}
103+
]
104+
})

0 commit comments

Comments
 (0)