Skip to content

Commit 423a623

Browse files
joke1196sonartech
authored andcommitted
SONARPY-4954 Migrate S6908 to TypeV2
GitOrigin-RevId: 8650a1a7d8d99302a8374650f7b645313faeeb3b
1 parent fa5bf43 commit 423a623

4 files changed

Lines changed: 65 additions & 13 deletions

File tree

‎python-checks/src/main/java/org/sonar/python/checks/TfFunctionRecursivityCheck.java‎

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,24 @@
2121
import org.sonar.check.Rule;
2222
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2323
import org.sonar.plugins.python.api.SubscriptionContext;
24-
import org.sonar.plugins.python.api.symbols.FunctionSymbol;
25-
import org.sonar.plugins.python.api.symbols.Symbol;
24+
import org.sonar.plugins.python.api.symbols.v2.SymbolV2;
25+
import org.sonar.plugins.python.api.types.v2.FunctionType;
26+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
27+
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
2628
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
2729
import org.sonar.plugins.python.api.tree.CallExpression;
2830
import org.sonar.plugins.python.api.tree.Expression;
2931
import org.sonar.plugins.python.api.tree.FunctionDef;
32+
import org.sonar.plugins.python.api.tree.Name;
33+
import org.sonar.plugins.python.api.tree.QualifiedExpression;
3034
import org.sonar.plugins.python.api.tree.Tree;
31-
import org.sonar.python.tree.TreeUtils;
3235

3336
@Rule(key = "S6908")
3437
public class TfFunctionRecursivityCheck extends PythonSubscriptionCheck {
3538

3639
private static final String MESSAGE = "Make sure to avoid recursive calls in this function.";
3740
private static final String SECONDARY_MESSAGE = "Recursive call is here.";
41+
private static final TypeMatcher TENSORFLOW_FUNCTION_MATCHER = TypeMatchers.withFQN("tensorflow.function");
3842

3943
@Override
4044
public void initialize(Context context) {
@@ -43,14 +47,15 @@ public void initialize(Context context) {
4347

4448
private static void checkFunctionDef(SubscriptionContext context) {
4549
FunctionDef functionDef = (FunctionDef) context.syntaxNode();
46-
if (!TreeUtils.isFunctionWithGivenDecoratorFQN(functionDef, "tensorflow.function")) {
50+
if (!hasTensorflowFunctionDecorator(functionDef, context)) {
4751
return;
4852
}
49-
FunctionSymbol functionSymbol = TreeUtils.getFunctionSymbolFromDef(functionDef);
50-
if (functionSymbol == null) {
53+
SymbolV2 functionSymbol = functionDef.name().symbolV2();
54+
if (functionSymbol == null || functionSymbol.getSingleBindingUsage().isEmpty()
55+
|| !(functionDef.name().typeV2() instanceof FunctionType functionType)) {
5156
return;
5257
}
53-
CallCollector collector = new CallCollector(functionSymbol);
58+
CallCollector collector = new CallCollector(functionSymbol, functionType);
5459
functionDef.accept(collector);
5560
if (collector.expressionList.isEmpty()) {
5661
return;
@@ -59,19 +64,28 @@ private static void checkFunctionDef(SubscriptionContext context) {
5964
collector.expressionList.forEach(call -> issue.secondary(call, SECONDARY_MESSAGE));
6065
}
6166

67+
private static boolean hasTensorflowFunctionDecorator(FunctionDef functionDef, SubscriptionContext context) {
68+
return functionDef.decorators().stream()
69+
.anyMatch(decorator -> TENSORFLOW_FUNCTION_MATCHER.isTrueFor(decorator.expression(), context));
70+
}
71+
6272
private static class CallCollector extends BaseTreeVisitor {
63-
private final Symbol originalSymbol;
73+
private final SymbolV2 originalSymbol;
74+
private final FunctionType originalType;
6475
List<Expression> expressionList = new ArrayList<>();
6576

66-
private CallCollector(Symbol originalSymbol) {
77+
private CallCollector(SymbolV2 originalSymbol, FunctionType originalType) {
6778
this.originalSymbol = originalSymbol;
79+
this.originalType = originalType;
6880
}
6981

7082
@Override
7183
public void visitCallExpression(CallExpression callExpression) {
72-
TreeUtils.getSymbolFromTree(callExpression.callee())
73-
.filter(symbol -> symbol.equals(originalSymbol))
74-
.ifPresent(symbol -> expressionList.add(callExpression.callee()));
84+
Expression callee = callExpression.callee();
85+
if ((callee instanceof Name name && originalSymbol.equals(name.symbolV2()))
86+
|| (callee instanceof QualifiedExpression && originalType.equals(callee.typeV2()))) {
87+
expressionList.add(callExpression.callee());
88+
}
7589
super.visitCallExpression(callExpression);
7690
}
7791
}

‎python-checks/src/test/java/org/sonar/python/checks/TfFunctionRecursivityCheckTest.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ class TfFunctionRecursivityCheckTest {
2424
void test() {
2525
PythonCheckVerifier.verify("src/test/resources/checks/tfFunctionRecursivity.py", new TfFunctionRecursivityCheck());
2626
}
27+
28+
@Test
29+
void methodRecursion() {
30+
PythonCheckVerifier.verify("src/test/resources/checks/tfFunctionRecursivityMethod.py", new TfFunctionRecursivityCheck());
31+
}
2732
}

‎python-checks/src/test/resources/checks/tfFunctionRecursivity.py‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,17 @@ def our_decorated_function(n): # Noncompliant {{Make sure to avoid recursive cal
7373
return our_decorated_function(n-1)
7474

7575
@unknown_decorator
76-
def some_function(): ...
76+
def some_function(): ...
77+
78+
class RecursiveMethods:
79+
@tf.function
80+
def via_self(self, n): # Noncompliant {{Make sure to avoid recursive calls in this function.}}
81+
#^^^^^^^^
82+
return self.via_self(n - 1)
83+
#^^^^^^^^^^^^^< {{Recursive call is here.}}
84+
85+
@tf.function
86+
def via_class(self, n): # Noncompliant {{Make sure to avoid recursive calls in this function.}}
87+
#^^^^^^^^^
88+
return RecursiveMethods.via_class(self, n - 1)
89+
#^^^^^^^^^^^^^^^^^^^^^^^^^^< {{Recursive call is here.}}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import tensorflow as tf
2+
3+
4+
class RecursiveMethod:
5+
# fmt: off
6+
@tf.function
7+
def recurse(self, n): # Noncompliant {{Make sure to avoid recursive calls in this function.}}
8+
#^^^^^^^
9+
return self.recurse(n - 1)
10+
# ^^^^^^^^^^^^< {{Recursive call is here.}}
11+
# fmt: on
12+
13+
14+
class NonRecursiveMethod:
15+
@tf.function
16+
def recurse(self, n):
17+
return self.other(n)
18+
19+
def other(self, n):
20+
return n

0 commit comments

Comments
 (0)