Skip to content

Commit 0511e69

Browse files
Merge pull request #721 from bryceosterhaus/pr-692
2 parents dedad31 + 15b8100 commit 0511e69

File tree

5 files changed

+306
-0
lines changed

5 files changed

+306
-0
lines changed

projects/eslint-plugin/configs/general.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ const config = {
102102
'@liferay/no-abbreviations': 'error',
103103
'@liferay/no-absolute-import': 'error',
104104
'@liferay/no-anonymous-exports': 'error',
105+
'@liferay/no-conditional-object-keys': 'error',
105106
'@liferay/no-duplicate-imports': 'error',
106107
'@liferay/no-dynamic-require': 'error',
107108
'@liferay/no-get-data-attribute': 'error',
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Don't use negated `!Object.keys()` or double negated `!!Object.keys()` expressions as they always evaluate to `false` and `true`, respectively
2+
3+
This rule enforces that engineers don't use a truthy or falsy value that `Object.keys()` can return. Instead, use an expression that is more precise.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
if (Object.keys({foo: 'bar'})) {
11+
// do your magic
12+
}
13+
14+
if (!Object.keys({foo: 'bar'})) {
15+
// do your magic
16+
}
17+
18+
!!Object.keys({foo: 'bar'}) && 'test';
19+
20+
const negatedObjectKeys = !Object.keys({foo: 'bar'});
21+
22+
!Object.keys({foo: 'bar'}) && true;
23+
```
24+
25+
Examples of **correct** code for this rule:
26+
27+
```js
28+
if (Object.keys({foo: 'bar'}).length) {
29+
// do your magic
30+
}
31+
32+
Object.keys({foo: 'bar'}).length && 'test';
33+
34+
if (Object.keys({foo: 'bar'}).find((i) => true)) {
35+
// do your magic
36+
}
37+
```
38+
39+
## Further Reading
40+
41+
- https://github.com/liferay/liferay-frontend-projects/issues/10

projects/eslint-plugin/rules/general/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
'no-absolute-import': require('./lib/rules/no-absolute-import'),
1616
'no-anonymous-exports': require('./lib/rules/no-anonymous-exports'),
1717
'no-arrow': require('./lib/rules/no-arrow'),
18+
'no-conditional-object-keys': require('./lib/rules/no-conditional-object-keys'),
1819
'no-duplicate-class-names': require('./lib/rules/no-duplicate-class-names'),
1920
'no-duplicate-imports': require('./lib/rules/no-duplicate-imports'),
2021
'no-dynamic-require': require('./lib/rules/no-dynamic-require'),
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* SPDX-FileCopyrightText: © 2017 Liferay, Inc. <https://liferay.com>
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
module.exports = {
7+
create(context) {
8+
return {
9+
CallExpression(node) {
10+
if (
11+
node.callee.object &&
12+
node.callee.object.name === 'Object' &&
13+
node.callee.property &&
14+
node.callee.property.name === 'keys'
15+
) {
16+
const {parent} = node;
17+
18+
if (parent.type === 'MemberExpression') {
19+
return;
20+
}
21+
22+
const grandParent = parent && parent.parent;
23+
24+
let message;
25+
26+
if (
27+
parent.type === 'UnaryExpression' &&
28+
parent.operator === '!'
29+
) {
30+
if (
31+
grandParent &&
32+
grandParent.type === 'UnaryExpression' &&
33+
grandParent.operator === '!'
34+
) {
35+
message = `!!Object.keys({}) always evaluates to true`;
36+
}
37+
else {
38+
message = `!Object.keys({}) always evaluates to false`;
39+
}
40+
}
41+
else if (
42+
parent.type === 'LogicalExpression' ||
43+
parent.type === 'IfStatement'
44+
) {
45+
message = `Object.keys({}) always evaluates to truthy`;
46+
}
47+
48+
if (message) {
49+
context.report({
50+
message,
51+
node,
52+
});
53+
}
54+
}
55+
},
56+
};
57+
},
58+
59+
meta: {
60+
docs: {
61+
category: 'Best Practices',
62+
description: 'Object.keys({}) always evaluates truthy',
63+
recommended: false,
64+
url:
65+
'https://github.com/liferay/liferay-frontend-projects/issues/10',
66+
},
67+
fixable: 'code',
68+
schema: [],
69+
type: 'problem',
70+
},
71+
};
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/**
2+
* SPDX-FileCopyrightText: © 2017 Liferay, Inc. <https://liferay.com>
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
const MultiTester = require('../../../../../scripts/MultiTester');
7+
const rule = require('../../../lib/rules/no-conditional-object-keys');
8+
9+
const parserOptions = {
10+
parserOptions: {
11+
ecmaVersion: 6,
12+
sourceType: 'module',
13+
},
14+
};
15+
16+
const ruleTester = new MultiTester(parserOptions);
17+
18+
const message = 'Object.keys({}) always evaluates to truthy';
19+
const negationMessage = '!Object.keys({}) always evaluates to false';
20+
const doubleNegationMessage = '!!Object.keys({}) always evaluates to true';
21+
22+
ruleTester.run('no-conditional-object-keys', rule, {
23+
invalid: [
24+
{
25+
code: `
26+
if (Object.keys({foo: 'bar'})) {}
27+
`,
28+
errors: [
29+
{
30+
message,
31+
type: 'CallExpression',
32+
},
33+
],
34+
},
35+
{
36+
code: `
37+
if (!Object.keys({foo: 'bar'})) {}
38+
`,
39+
errors: [
40+
{
41+
message: negationMessage,
42+
type: 'CallExpression',
43+
},
44+
],
45+
},
46+
{
47+
code: `
48+
if (!!Object.keys({foo: 'bar'})) {}
49+
`,
50+
errors: [
51+
{
52+
message: doubleNegationMessage,
53+
type: 'CallExpression',
54+
},
55+
],
56+
},
57+
{
58+
code: `
59+
!Object.keys({foo: 'bar'})
60+
`,
61+
errors: [
62+
{
63+
message: negationMessage,
64+
type: 'CallExpression',
65+
},
66+
],
67+
},
68+
{
69+
code: `
70+
!!Object.keys({foo: 'bar'})
71+
`,
72+
errors: [
73+
{
74+
message: doubleNegationMessage,
75+
type: 'CallExpression',
76+
},
77+
],
78+
},
79+
{
80+
code: `
81+
Object.keys({foo: 'bar'}) && 'test'
82+
`,
83+
errors: [
84+
{
85+
message,
86+
type: 'CallExpression',
87+
},
88+
],
89+
},
90+
{
91+
code: `
92+
!Object.keys({foo: 'bar'}) && 'test'
93+
`,
94+
errors: [
95+
{
96+
message: negationMessage,
97+
type: 'CallExpression',
98+
},
99+
],
100+
},
101+
{
102+
code: `
103+
!!Object.keys({foo: 'bar'}) && 'test'
104+
`,
105+
errors: [
106+
{
107+
message: doubleNegationMessage,
108+
type: 'CallExpression',
109+
},
110+
],
111+
},
112+
{
113+
code: `
114+
const keysNegated = !Object.keys({foo: 'bar'});
115+
`,
116+
errors: [
117+
{
118+
message: negationMessage,
119+
type: 'CallExpression',
120+
},
121+
],
122+
},
123+
{
124+
code: `
125+
const keysDoubleNegated = !!Object.keys({foo: 'bar'});
126+
`,
127+
errors: [
128+
{
129+
message: doubleNegationMessage,
130+
type: 'CallExpression',
131+
},
132+
],
133+
},
134+
],
135+
valid: [
136+
{
137+
code: `
138+
if (Object.keys({foo: 'bar'}).length) {}
139+
`,
140+
},
141+
{
142+
code: `
143+
if (!Object.keys({foo: 'bar'}).length) {}
144+
`,
145+
},
146+
{
147+
code: `
148+
!Object.keys({foo: 'bar'}).length;
149+
`,
150+
},
151+
{
152+
code: `
153+
!!Object.keys({foo: 'bar'}).length;
154+
`,
155+
},
156+
{
157+
code: `
158+
Object.keys({foo: 'bar'}).length && 'test';
159+
`,
160+
},
161+
{
162+
code: `
163+
!Object.keys({foo: 'bar'}).length && 'test';
164+
`,
165+
},
166+
{
167+
code: `
168+
!!Object.keys({foo: 'bar'}).length && 'test';
169+
`,
170+
},
171+
{
172+
code: `
173+
typeof Object.keys({foo: 'bar'});
174+
`,
175+
},
176+
{
177+
code: `
178+
Object.keys({foo: 'bar'}).find(i => true);
179+
`,
180+
},
181+
{
182+
code: `
183+
if (Object.keys({foo: 'bar'}).find(i => true)) {};
184+
`,
185+
},
186+
{
187+
code: `
188+
const keys = Object.keys({foo: 'bar'});
189+
`,
190+
},
191+
],
192+
});

0 commit comments

Comments
 (0)