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 @Override
66 public int[] getRequiredTokens() {
67 return new int[] {TokenTypes.LITERAL_INSTANCEOF};
68 }
69
70 @Override
71 public int[] getDefaultTokens() {
72 return getRequiredTokens();
73 }
74
75 @Override
76 public int[] getAcceptableTokens() {
77 return getRequiredTokens();
78 }
79
80 @Override
81 public void visitToken(DetailAST ast) {
82
83 final List<DetailAST> patternVariableIdents = getPatternVariableIdents(ast);
84 final List<DetailAST> reassignedVariableIdents = getReassignedVariableIdents(ast);
85
86 for (DetailAST patternVariableIdent : patternVariableIdents) {
87 checkForReassignment(patternVariableIdent, reassignedVariableIdents);
88 }
89 }
90
91
92
93
94
95
96
97 private static List<DetailAST> getPatternVariableIdents(DetailAST ast) {
98
99 final DetailAST outermostPatternVariable =
100 ast.findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF);
101
102 final DetailAST recordPatternDef;
103 if (ast.getType() == TokenTypes.LITERAL_INSTANCEOF) {
104 recordPatternDef = ast.findFirstToken(TokenTypes.RECORD_PATTERN_DEF);
105 }
106 else {
107 recordPatternDef = ast;
108 }
109
110 final List<DetailAST> patternVariableIdentsArray = new ArrayList<>();
111
112 if (outermostPatternVariable != null) {
113 patternVariableIdentsArray.add(
114 outermostPatternVariable.findFirstToken(TokenTypes.IDENT));
115 }
116 else if (recordPatternDef != null) {
117 final DetailAST recordPatternComponents = recordPatternDef
118 .findFirstToken(TokenTypes.RECORD_PATTERN_COMPONENTS);
119
120 if (recordPatternComponents != null) {
121 for (DetailAST innerPatternVariable = recordPatternComponents.getFirstChild();
122 innerPatternVariable != null;
123 innerPatternVariable = innerPatternVariable.getNextSibling()) {
124
125 if (innerPatternVariable.getType() == TokenTypes.PATTERN_VARIABLE_DEF) {
126 patternVariableIdentsArray.add(
127 innerPatternVariable.findFirstToken(TokenTypes.IDENT));
128 }
129 else {
130 patternVariableIdentsArray.addAll(
131 getPatternVariableIdents(innerPatternVariable));
132 }
133
134 }
135 }
136
137 }
138 return patternVariableIdentsArray;
139 }
140
141
142
143
144
145
146
147 private static List<DetailAST> getReassignedVariableIdents(DetailAST ast) {
148
149 final List<DetailAST> reassignedVariableIdents = new ArrayList<>();
150 final DetailAST scopeRoot = findReassignmentScopeRoot(ast);
151
152 if (scopeRoot != null) {
153
154 final List<DetailAST> branches =
155 expandReassignmentScopes(scopeRoot);
156
157 for (DetailAST branch : branches) {
158 for (DetailAST expressionBranch = branch;
159 expressionBranch != null;
160 expressionBranch = traverseUntilNeededBranchType(
161 expressionBranch, branch, TokenTypes.EXPR)) {
162
163 final DetailAST assignToken =
164 getMatchedAssignToken(expressionBranch);
165
166 if (assignToken != null) {
167 final DetailAST neededAssignIdent =
168 getNeededAssignIdent(assignToken);
169
170 if (neededAssignIdent.getPreviousSibling() == null) {
171 reassignedVariableIdents.add(neededAssignIdent);
172 }
173 }
174 }
175 }
176 }
177
178 return reassignedVariableIdents;
179 }
180
181
182
183
184
185
186
187
188
189 private static List<DetailAST> getStatementsInExtendedScope(DetailAST conditionalStatement) {
190 final List<DetailAST> statements = new ArrayList<>();
191
192 DetailAST nextSibling = conditionalStatement.getNextSibling();
193
194 while (nextSibling != null) {
195 final int type = nextSibling.getType();
196 if (type == TokenTypes.EXPR || type == TokenTypes.LITERAL_RETURN
197 || type == TokenTypes.LITERAL_IF) {
198 statements.add(nextSibling);
199 }
200 else if (type != TokenTypes.SEMI) {
201 break;
202 }
203 nextSibling = nextSibling.getNextSibling();
204 }
205
206 return statements;
207 }
208
209
210
211
212
213
214
215
216
217 @Nullable
218 private static DetailAST traverseUntilNeededBranchType(DetailAST startingBranch,
219 DetailAST bound, int neededTokenType) {
220
221 DetailAST match = null;
222
223 DetailAST iteratedBranch = shiftToNextTraversedBranch(startingBranch, bound);
224
225 while (iteratedBranch != null) {
226 if (iteratedBranch.getType() == neededTokenType) {
227 match = iteratedBranch;
228 break;
229 }
230
231 iteratedBranch = shiftToNextTraversedBranch(iteratedBranch, bound);
232 }
233
234 return match;
235 }
236
237
238
239
240
241
242
243
244 @Nullable
245 private static DetailAST shiftToNextTraversedBranch(DetailAST ast, DetailAST boundAst) {
246 DetailAST newAst = ast;
247
248 if (ast.getFirstChild() != null) {
249 newAst = ast.getFirstChild();
250 }
251 else {
252 while (newAst.getNextSibling() == null && !newAst.equals(boundAst)) {
253 newAst = newAst.getParent();
254 }
255 if (newAst.equals(boundAst)) {
256 newAst = null;
257 }
258 else {
259 newAst = newAst.getNextSibling();
260 }
261 }
262
263 return newAst;
264 }
265
266
267
268
269
270
271
272
273 @Nullable
274 private static DetailAST getMatchedAssignToken(DetailAST preAssignBranch) {
275 DetailAST matchedAssignToken = null;
276
277 for (int assignType : ASSIGN_TOKEN_TYPES) {
278 matchedAssignToken = preAssignBranch.findFirstToken(assignType);
279 if (matchedAssignToken != null) {
280 break;
281 }
282 }
283
284 return matchedAssignToken;
285 }
286
287
288
289
290
291
292
293 private static DetailAST getNeededAssignIdent(DetailAST assignToken) {
294 DetailAST assignIdent = assignToken;
295
296 while (traverseUntilNeededBranchType(
297 assignIdent, assignToken.getFirstChild(), TokenTypes.IDENT) != null) {
298
299 assignIdent =
300 traverseUntilNeededBranchType(assignIdent, assignToken, TokenTypes.IDENT);
301 }
302
303 return assignIdent;
304 }
305
306
307
308
309
310
311
312 private void checkForReassignment(
313 DetailAST patternVariableIdent,
314 Iterable<DetailAST> reassignedVariableIdents) {
315
316 for (DetailAST assignTokenIdent : reassignedVariableIdents) {
317 if (patternVariableIdent.getText().equals(assignTokenIdent.getText())) {
318 log(assignTokenIdent, MSG_KEY, assignTokenIdent.getText());
319 }
320 }
321 }
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336 @Nullable
337 private static DetailAST findReassignmentScopeRoot(DetailAST ast) {
338
339 DetailAST result = null;
340
341 for (DetailAST node = ast; node != null && result == null;
342 node = node.getParent()) {
343
344 final int type = node.getType();
345
346 if (type == TokenTypes.LITERAL_IF
347 || type == TokenTypes.LITERAL_ELSE
348 || type == TokenTypes.QUESTION) {
349 result = node;
350 }
351 }
352
353 return result;
354 }
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370 private static List<DetailAST> expandReassignmentScopes(
371 DetailAST scopeRoot) {
372
373 final List<DetailAST> branches = new ArrayList<>();
374
375 addBodyBranch(branches, scopeRoot);
376 branches.addAll(getStatementsInExtendedScope(scopeRoot));
377
378 return branches;
379 }
380
381
382
383
384
385
386
387
388
389 private static void addBodyBranch(Collection<DetailAST> branches,
390 DetailAST scopeRoot) {
391 if (scopeRoot.getType() == TokenTypes.LITERAL_IF) {
392 final DetailAST body = TokenUtil.findFirstTokenByPredicate(scopeRoot,
393 node -> node.getType() == TokenTypes.RPAREN)
394 .map(DetailAST::getNextSibling)
395 .orElse(scopeRoot);
396 branches.add(body);
397 }
398 else {
399 branches.add(scopeRoot);
400 }
401 }
402
403 }