-
-
Notifications
You must be signed in to change notification settings - Fork 354
/
no-lonely-if.js
90 lines (80 loc) · 2.66 KB
/
no-lonely-if.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const needsSemicolon = require('./utils/needs-semicolon');
const MESSAGE_ID = 'no-lonely-if';
const messages = {
[MESSAGE_ID]: 'Unexpected `if` as the only statement in a `if` block without `else`.'
};
const ifStatementWithoutAlternate = 'IfStatement:not([alternate])';
const selector = `:matches(${
[
// `if (a) { if (b) {} }`
[
ifStatementWithoutAlternate,
'>',
'BlockStatement.consequent',
'[body.length=1]',
'>',
`${ifStatementWithoutAlternate}.body`
].join(''),
// `if (a) if (b) {}`
`${ifStatementWithoutAlternate} > ${ifStatementWithoutAlternate}.consequent`
].join(', ')
})`;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
// Lower precedence than `&&`
const needParenthesis = node => (
(node.type === 'LogicalExpression' && (node.operator === '||' || node.operator === '??')) ||
node.type === 'ConditionalExpression' ||
node.type === 'AssignmentExpression' ||
node.type === 'YieldExpression' ||
node.type === 'SequenceExpression'
);
const create = context => {
const sourceCode = context.getSourceCode();
const getText = node => sourceCode.getText(node);
const getTestNodeText = node => needParenthesis(node) ? `(${getText(node)})` : getText(node);
return {
[selector](inner) {
const {parent} = inner;
const outer = parent.type === 'BlockStatement' ? parent.parent : parent;
context.report({
node: inner,
messageId: MESSAGE_ID,
* fix(fixer) {
// Merge `test`
yield fixer.replaceText(outer.test, `${getTestNodeText(outer.test)} && ${getTestNodeText(inner.test)}`);
// Replace `consequent`
const {consequent} = inner;
let consequentText = getText(consequent);
// If the `if` statement has no block, and is not followed by a semicolon,
// make sure that fixing the issue would not change semantics due to ASI.
// Similar logic https://github.com/eslint/eslint/blob/2124e1b5dad30a905dc26bde9da472bf622d3f50/lib/rules/no-lonely-if.js#L61-L77
if (
consequent.type !== 'BlockStatement' &&
outer.consequent.type === 'BlockStatement' &&
!consequentText.endsWith(';')
) {
const lastToken = sourceCode.getLastToken(consequent);
const nextToken = sourceCode.getTokenAfter(outer);
if (needsSemicolon(lastToken, sourceCode, nextToken.value)) {
consequentText += ';';
}
}
yield fixer.replaceText(outer.consequent, consequentText);
}
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages
}
};