|
19 | 19 | */ |
20 | 20 | package org.sonar.python.checks; |
21 | 21 |
|
22 | | -import java.util.ArrayList; |
23 | 22 | import java.util.List; |
24 | | -import java.util.Objects; |
25 | 23 | import org.sonar.check.Rule; |
26 | 24 | import org.sonar.plugins.python.api.LocationInFile; |
27 | | -import org.sonar.plugins.python.api.PythonSubscriptionCheck; |
28 | | -import org.sonar.plugins.python.api.SubscriptionContext; |
29 | 25 | import org.sonar.plugins.python.api.symbols.ClassSymbol; |
30 | 26 | import org.sonar.plugins.python.api.symbols.FunctionSymbol; |
31 | 27 | import org.sonar.plugins.python.api.symbols.Symbol; |
32 | | -import org.sonar.plugins.python.api.tree.AssignmentStatement; |
33 | 28 | import org.sonar.plugins.python.api.tree.CallExpression; |
34 | | -import org.sonar.plugins.python.api.tree.ComprehensionFor; |
35 | 29 | import org.sonar.plugins.python.api.tree.Expression; |
36 | | -import org.sonar.plugins.python.api.tree.ExpressionList; |
37 | | -import org.sonar.plugins.python.api.tree.ForStatement; |
38 | 30 | import org.sonar.plugins.python.api.tree.HasSymbol; |
39 | 31 | import org.sonar.plugins.python.api.tree.Tree; |
40 | | -import org.sonar.plugins.python.api.tree.UnpackingExpression; |
41 | | -import org.sonar.plugins.python.api.tree.YieldExpression; |
42 | | -import org.sonar.plugins.python.api.tree.YieldStatement; |
43 | 32 | import org.sonar.plugins.python.api.types.InferredType; |
44 | | -import org.sonar.python.api.PythonPunctuator; |
45 | 33 | import org.sonar.python.semantic.ClassSymbolImpl; |
46 | 34 | import org.sonar.python.types.InferredTypes; |
47 | 35 |
|
48 | 36 | @Rule(key = "S3862") |
49 | | -public class IterationOnNonIterableCheck extends PythonSubscriptionCheck { |
| 37 | +public class IterationOnNonIterableCheck extends IterationOnNonIterable { |
50 | 38 |
|
51 | 39 | private static final String MESSAGE = "Replace this expression with an iterable object."; |
52 | 40 |
|
53 | | - @Override |
54 | | - public void initialize(Context context) { |
55 | | - context.registerSyntaxNodeConsumer(Tree.Kind.UNPACKING_EXPR, IterationOnNonIterableCheck::checkUnpackingExpression); |
56 | | - context.registerSyntaxNodeConsumer(Tree.Kind.ASSIGNMENT_STMT, IterationOnNonIterableCheck::checkAssignment); |
57 | | - context.registerSyntaxNodeConsumer(Tree.Kind.FOR_STMT, IterationOnNonIterableCheck::checkForStatement); |
58 | | - context.registerSyntaxNodeConsumer(Tree.Kind.COMP_FOR, IterationOnNonIterableCheck::checkForComprehension); |
59 | | - context.registerSyntaxNodeConsumer(Tree.Kind.YIELD_STMT, IterationOnNonIterableCheck::checkYieldStatement); |
60 | | - } |
61 | | - |
62 | | - private static void checkAssignment(SubscriptionContext ctx) { |
63 | | - AssignmentStatement assignmentStatement = (AssignmentStatement) ctx.syntaxNode(); |
64 | | - ExpressionList expressionList = assignmentStatement.lhsExpressions().get(0); |
65 | | - List<LocationInFile> secondaries = new ArrayList<>(); |
66 | | - if (isLhsIterable(expressionList) && !isValidIterable(assignmentStatement.assignedValue(), secondaries)) { |
67 | | - reportIssue(ctx, assignmentStatement.assignedValue(), secondaries, MESSAGE); |
68 | | - } |
69 | | - } |
70 | | - |
71 | | - private static boolean isLhsIterable(ExpressionList expressionList) { |
72 | | - if (expressionList.expressions().size() > 1) { |
73 | | - return true; |
74 | | - } |
75 | | - Expression expression = expressionList.expressions().get(0); |
76 | | - return expression.is(Tree.Kind.LIST_LITERAL) || expression.is(Tree.Kind.TUPLE); |
77 | | - } |
78 | | - |
79 | | - private static void checkForComprehension(SubscriptionContext ctx) { |
80 | | - ComprehensionFor comprehensionFor = (ComprehensionFor) ctx.syntaxNode(); |
81 | | - Expression expression = comprehensionFor.iterable(); |
82 | | - List<LocationInFile> secondaries = new ArrayList<>(); |
83 | | - if (!isValidIterable(expression, secondaries)) { |
84 | | - reportIssue(ctx, expression, secondaries, MESSAGE); |
85 | | - } |
86 | | - } |
87 | | - |
88 | | - private static void reportIssue(SubscriptionContext ctx, Expression expression, List<LocationInFile> secondaries, String message) { |
89 | | - PreciseIssue preciseIssue = ctx.addIssue(expression, message); |
90 | | - secondaries.stream().filter(Objects::nonNull).forEach(location -> preciseIssue.secondary(location, null)); |
91 | | - } |
92 | | - |
93 | | - private static void checkYieldStatement(SubscriptionContext ctx) { |
94 | | - YieldStatement yieldStatement = (YieldStatement) ctx.syntaxNode(); |
95 | | - YieldExpression yieldExpression = yieldStatement.yieldExpression(); |
96 | | - if (yieldExpression.fromKeyword() == null) { |
97 | | - return; |
98 | | - } |
99 | | - Expression expression = yieldExpression.expressions().get(0); |
100 | | - List<LocationInFile> secondaries = new ArrayList<>(); |
101 | | - if (!isValidIterable(expression, secondaries)) { |
102 | | - reportIssue(ctx, expression, secondaries, MESSAGE); |
103 | | - } |
104 | | - } |
105 | | - |
106 | | - private static void checkUnpackingExpression(SubscriptionContext ctx) { |
107 | | - UnpackingExpression unpackingExpression = (UnpackingExpression) ctx.syntaxNode(); |
108 | | - if (unpackingExpression.starToken().type().equals(PythonPunctuator.MUL_MUL)) { |
109 | | - return; |
110 | | - } |
111 | | - Expression expression = unpackingExpression.expression(); |
112 | | - List<LocationInFile> secondaries = new ArrayList<>(); |
113 | | - if (!isValidIterable(expression, secondaries)) { |
114 | | - reportIssue(ctx, expression, secondaries, MESSAGE); |
115 | | - } |
116 | | - } |
117 | | - |
118 | | - private static void checkForStatement(SubscriptionContext ctx) { |
119 | | - ForStatement forStatement = (ForStatement) ctx.syntaxNode(); |
120 | | - List<Expression> testExpressions = forStatement.testExpressions(); |
121 | | - boolean isAsync = forStatement.asyncKeyword() != null; |
122 | | - if (testExpressions.size() > 1) { |
123 | | - return; |
124 | | - } |
125 | | - Expression expression = testExpressions.get(0); |
126 | | - List<LocationInFile> secondaries = new ArrayList<>(); |
127 | | - if (!isAsync && !isValidIterable(expression, secondaries)) { |
128 | | - String message = isAsyncIterable(expression) ? "Add \"async\" before \"for\"; This expression is an async generator." : MESSAGE; |
129 | | - reportIssue(ctx, expression, secondaries, message); |
130 | | - } |
131 | | - } |
132 | | - |
133 | | - private static boolean isAsyncIterable(Expression expression) { |
134 | | - if (expression.is(Tree.Kind.CALL_EXPR)) { |
135 | | - CallExpression callExpression = (CallExpression) expression; |
136 | | - Symbol calleeSymbol = callExpression.calleeSymbol(); |
137 | | - if (calleeSymbol != null && calleeSymbol.is(Symbol.Kind.FUNCTION)) { |
138 | | - return ((FunctionSymbol) calleeSymbol).isAsynchronous(); |
139 | | - } |
140 | | - } |
141 | | - return expression.type().canHaveMember("__aiter__"); |
142 | | - } |
143 | | - |
144 | | - private static boolean isValidIterable(Expression expression, List<LocationInFile> secondaries) { |
| 41 | + boolean isValidIterable(Expression expression, List<LocationInFile> secondaries) { |
145 | 42 | if (expression.is(Tree.Kind.CALL_EXPR)) { |
146 | 43 | CallExpression callExpression = (CallExpression) expression; |
147 | 44 | Symbol calleeSymbol = callExpression.calleeSymbol(); |
@@ -170,4 +67,21 @@ private static boolean isValidIterable(Expression expression, List<LocationInFil |
170 | 67 | secondaries.add(InferredTypes.typeClassLocation(type)); |
171 | 68 | return type.canHaveMember("__iter__") || type.canHaveMember("__getitem__"); |
172 | 69 | } |
| 70 | + |
| 71 | + @Override |
| 72 | + boolean isAsyncIterable(Expression expression) { |
| 73 | + if (expression.is(Tree.Kind.CALL_EXPR)) { |
| 74 | + CallExpression callExpression = (CallExpression) expression; |
| 75 | + Symbol calleeSymbol = callExpression.calleeSymbol(); |
| 76 | + if (calleeSymbol != null && calleeSymbol.is(Symbol.Kind.FUNCTION)) { |
| 77 | + return ((FunctionSymbol) calleeSymbol).isAsynchronous(); |
| 78 | + } |
| 79 | + } |
| 80 | + return expression.type().canHaveMember("__aiter__"); |
| 81 | + } |
| 82 | + |
| 83 | + @Override |
| 84 | + String message(Expression expression, boolean isForLoop) { |
| 85 | + return isForLoop && isAsyncIterable(expression) ? "Add \"async\" before \"for\"; This expression is an async generator." : MESSAGE; |
| 86 | + } |
173 | 87 | } |
0 commit comments