1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.Set;
26
27 import javax.annotation.Nullable;
28
29 import com.puppycrawl.tools.checkstyle.StatelessCheck;
30 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31 import com.puppycrawl.tools.checkstyle.api.DetailAST;
32 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 @StatelessCheck
49 public class PatternVariableAssignmentCheck extends AbstractCheck {
50
51
52
53
54 public static final String MSG_KEY = "pattern.variable.assignment";
55
56
57
58
59 private static final Set<Integer> ASSIGN_TOKEN_TYPES = Set.of(
60 TokenTypes.ASSIGN, TokenTypes.PLUS_ASSIGN, TokenTypes.MINUS_ASSIGN, TokenTypes.STAR_ASSIGN,
61 TokenTypes.DIV_ASSIGN, TokenTypes.MOD_ASSIGN, TokenTypes.SR_ASSIGN, TokenTypes.BSR_ASSIGN,
62 TokenTypes.SL_ASSIGN, TokenTypes.BAND_ASSIGN, TokenTypes.BXOR_ASSIGN,
63 TokenTypes.BOR_ASSIGN);
64
65
66
67
68 public PatternVariableAssignmentCheck() {
69
70 }
71
72 @Override
73 public int[] getRequiredTokens() {
74 return new int[] {TokenTypes.LITERAL_INSTANCEOF};
75 }
76
77 @Override
78 public int[] getDefaultTokens() {
79 return getRequiredTokens();
80 }
81
82 @Override
83 public int[] getAcceptableTokens() {
84 return getRequiredTokens();
85 }
86
87 @Override
88 public void visitToken(DetailAST ast) {
89
90 final List<DetailAST> patternVariableIdents = getPatternVariableIdents(ast);
91 final List<DetailAST> reassignedVariableIdents = getReassignedVariableIdents(ast);
92
93 for (DetailAST patternVariableIdent : patternVariableIdents) {
94 checkForReassignment(patternVariableIdent, reassignedVariableIdents);
95 }
96 }
97
98
99
100
101
102
103
104 private static List<DetailAST> getPatternVariableIdents(DetailAST ast) {
105
106 final DetailAST outermostPatternVariable =
107 ast.findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF);
108
109 final DetailAST recordPatternDef;
110 if (ast.getType() == TokenTypes.LITERAL_INSTANCEOF) {
111 recordPatternDef = ast.findFirstToken(TokenTypes.RECORD_PATTERN_DEF);
112 }
113 else {
114 recordPatternDef = ast;
115 }
116
117 final List<DetailAST> patternVariableIdentsArray = new ArrayList<>();
118
119 if (outermostPatternVariable != null) {
120 patternVariableIdentsArray.add(
121 outermostPatternVariable.findFirstToken(TokenTypes.IDENT));
122 }
123 else if (recordPatternDef != null) {
124 final DetailAST recordPatternComponents = recordPatternDef
125 .findFirstToken(TokenTypes.RECORD_PATTERN_COMPONENTS);
126
127 if (recordPatternComponents != null) {
128 for (DetailAST innerPatternVariable = recordPatternComponents.getFirstChild();
129 innerPatternVariable != null;
130 innerPatternVariable = innerPatternVariable.getNextSibling()) {
131
132 if (innerPatternVariable.getType() == TokenTypes.PATTERN_VARIABLE_DEF) {
133 patternVariableIdentsArray.add(
134 innerPatternVariable.findFirstToken(TokenTypes.IDENT));
135 }
136 else {
137 patternVariableIdentsArray.addAll(
138 getPatternVariableIdents(innerPatternVariable));
139 }
140
141 }
142 }
143
144 }
145 return patternVariableIdentsArray;
146 }
147
148
149
150
151
152
153
154 private static List<DetailAST> getReassignedVariableIdents(DetailAST ast) {
155
156 final List<DetailAST> reassignedVariableIdents = new ArrayList<>();
157 final DetailAST scopeRoot = findReassignmentScopeRoot(ast);
158
159 if (scopeRoot != null) {
160
161 final List<DetailAST> branches =
162 expandReassignmentScopes(scopeRoot);
163
164 for (DetailAST branch : branches) {
165 for (DetailAST expressionBranch = branch;
166 expressionBranch != null;
167 expressionBranch = shiftToNextTraversedBranch(
168 expressionBranch, branch)) {
169
170 final DetailAST assignToken =
171 getMatchedAssignToken(expressionBranch);
172
173 if (assignToken != null) {
174 reassignedVariableIdents.add(assignToken.getFirstChild());
175 }
176 }
177 }
178 }
179
180 return reassignedVariableIdents;
181 }
182
183
184
185
186
187
188
189
190
191 private static List<DetailAST> getStatementsInExtendedScope(DetailAST conditionalStatement) {
192 final List<DetailAST> statements = new ArrayList<>();
193
194 DetailAST nextSibling = conditionalStatement.getNextSibling();
195
196 while (nextSibling != null) {
197 final int type = nextSibling.getType();
198 if (type == TokenTypes.EXPR || type == TokenTypes.LITERAL_RETURN
199 || type == TokenTypes.LITERAL_IF) {
200 statements.add(nextSibling);
201 }
202 else if (type != TokenTypes.SEMI) {
203 break;
204 }
205 nextSibling = nextSibling.getNextSibling();
206 }
207
208 return statements;
209 }
210
211
212
213
214
215
216
217
218 @Nullable
219 private static DetailAST shiftToNextTraversedBranch(DetailAST ast, DetailAST boundAst) {
220 DetailAST newAst = ast;
221
222 if (ast.getFirstChild() != null) {
223 newAst = ast.getFirstChild();
224 }
225 else {
226 while (newAst.getNextSibling() == null && !newAst.equals(boundAst)) {
227 newAst = newAst.getParent();
228 }
229 if (newAst.equals(boundAst)) {
230 newAst = null;
231 }
232 else {
233 newAst = newAst.getNextSibling();
234 }
235 }
236
237 return newAst;
238 }
239
240
241
242
243
244
245
246
247 @Nullable
248 private static DetailAST getMatchedAssignToken(DetailAST preAssignBranch) {
249 DetailAST matchedAssignToken = null;
250
251 for (int assignType : ASSIGN_TOKEN_TYPES) {
252 matchedAssignToken = preAssignBranch.findFirstToken(assignType);
253 if (matchedAssignToken != null) {
254 break;
255 }
256 }
257
258 return matchedAssignToken;
259 }
260
261
262
263
264
265
266
267 private void checkForReassignment(
268 DetailAST patternVariableIdent,
269 Iterable<DetailAST> reassignedVariableIdents) {
270
271 for (DetailAST assignTokenIdent : reassignedVariableIdents) {
272 if (patternVariableIdent.getText().equals(assignTokenIdent.getText())) {
273 log(assignTokenIdent, MSG_KEY, assignTokenIdent.getText());
274 }
275 }
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291 @Nullable
292 private static DetailAST findReassignmentScopeRoot(DetailAST ast) {
293
294 DetailAST result = null;
295
296 for (DetailAST node = ast; node != null && result == null;
297 node = node.getParent()) {
298
299 final int type = node.getType();
300
301 if (type == TokenTypes.LITERAL_IF
302 || type == TokenTypes.LITERAL_ELSE
303 || type == TokenTypes.QUESTION) {
304 result = node;
305 }
306 }
307
308 return result;
309 }
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 private static List<DetailAST> expandReassignmentScopes(
326 DetailAST scopeRoot) {
327
328 final List<DetailAST> branches = new ArrayList<>();
329
330 addBodyBranch(branches, scopeRoot);
331 branches.addAll(getStatementsInExtendedScope(scopeRoot));
332
333 return branches;
334 }
335
336
337
338
339
340
341
342
343
344 private static void addBodyBranch(Collection<DetailAST> branches,
345 DetailAST scopeRoot) {
346 if (scopeRoot.getType() == TokenTypes.LITERAL_IF) {
347 final DetailAST body = TokenUtil.findFirstTokenByPredicate(scopeRoot,
348 node -> node.getType() == TokenTypes.RPAREN)
349 .map(DetailAST::getNextSibling)
350 .orElse(scopeRoot);
351 branches.add(body);
352 }
353 else {
354 branches.add(scopeRoot);
355 }
356 }
357
358 }