forked from hhvm/hhast
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NoEmptyStatementsLinter.hack
148 lines (133 loc) · 4.57 KB
/
NoEmptyStatementsLinter.hack
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
namespace Facebook\HHAST;
use type Facebook\HHAST\{ExpressionStatement, Node, NodeList, Script, Token};
final class NoEmptyStatementsLinter extends AutoFixingASTLinter {
const type TNode = ExpressionStatement;
<<__Override>>
public function getTitleForFix(LintError $_): string {
return 'Remove statement';
}
const type TContext = Script;
<<__Override>>
public function getLintErrorForNode(
Script $_context,
ExpressionStatement $stmt,
): ?ASTLintError {
$expr = $stmt->getExpression();
if ($expr === null) {
return new ASTLintError(
$this,
'This statement is empty',
$stmt,
() ==> $this->getFixedNode($stmt),
);
}
if ($this->isEmptyExpression($expr)) {
return new ASTLintError(
$this,
'This statement includes an expression that has no effect',
$stmt,
);
}
return null;
}
public function getFixedNode(ExpressionStatement $stmt): Node {
$semicolon = $stmt->getSemicolon();
// Some places where an expression statement can appear
// can not be replaced with a NodeList.
// Trying to do so is a TypeError (uncaught Throwable).
// More places like this could exist, but there is no way to enumerate them.
// If you have found another place where this method goes bad,
// feel free to add another patch below.
// Patching body likes of control flow statements.
// if (side_effect()) ;
// ^^
// This expression statement can't be a NodeList.
// We can add an empty CompoundStatement here.
$owner = $this->getAST()->getParentOfDescendant($stmt);
if ($owner is IControlFlowStatement) {
$i_am_the_body = control_flow_statement_get_body_like($owner) === $stmt;
if ($i_am_the_body) {
return new CompoundStatement(
new LeftBraceToken($semicolon->getLeading(), null),
null,
new RightBraceToken(null, $semicolon->getTrailing()),
);
}
}
// This could cause a TypeError still,
// if it does, add more patches.
return NodeList::concat(
$semicolon->getLeading(),
$semicolon->getTrailing(),
);
}
/**
* Returns whether the given expression is empty.
*/
private function isEmptyExpression(Node $expr): bool {
return $expr is AnonymousFunction ||
(
$expr is BinaryExpression &&
$this->isOperatorWithoutSideEffects($expr->getOperator())
) ||
$expr is CastExpression ||
$expr is CollectionLiteralExpression ||
$expr is DarrayIntrinsicExpression ||
$expr is DictionaryIntrinsicExpression ||
$expr is IsExpression ||
$expr is IssetExpression ||
$expr is KeysetIntrinsicExpression ||
$expr is LambdaExpression ||
$expr is LiteralExpression ||
$expr is NameExpression ||
(
$expr is ParenthesizedExpression &&
$this->isEmptyExpression($expr->getExpression())
) ||
$expr is SubscriptExpression ||
$expr is VectorIntrinsicExpression ||
$expr is VariableExpression ||
$expr is VarrayIntrinsicExpression;
}
/**
* Returns whether the given token is an operator that does not result in
* assignment or other operations that can have side effects.
*/
private function isOperatorWithoutSideEffects(Token $op): bool {
// The pipe operator does not necessarily have any side effects but it
// typically implies function invocation which can have side effects.
return !$this->isAssignmentOperator($op) && !$op is BarGreaterThanToken;
}
/**
* Returns whether the given token is an assignment operator.
*
* This list is all the types returned from ExpressionStatement::getOperator
* that include "Equal" and are not comparison operators (==, >=, etc.);
*/
private function isAssignmentOperator(Token $op): bool {
return $op is AmpersandEqualToken ||
$op is BarEqualToken ||
$op is CaratEqualToken ||
$op is DotEqualToken ||
$op is EqualToken ||
$op is GreaterThanEqualToken ||
$op is GreaterThanGreaterThanEqualToken ||
$op is LessThanEqualToken ||
$op is LessThanLessThanEqualToken ||
$op is MinusEqualToken ||
$op is PercentEqualToken ||
$op is PlusEqualToken ||
$op is QuestionQuestionEqualToken ||
$op is SlashEqualToken ||
$op is StarEqualToken ||
$op is StarStarEqualToken;
}
}