Skip to content

Commit 29a3265

Browse files
thomas-serre-sonarsourceSeppli11
authored andcommitted
SONARPY-2690 Fix FP on S1172 to avoid raising issues on conventional method signatures (#442)
Co-authored-by: Sebastian Zumbrunn <sebastian.zumbrunn@sonarsource.com> GitOrigin-RevId: 718636a1ffbca0445dd48283ed01e08abf22b71e
1 parent 4ce892c commit 29a3265

7 files changed

Lines changed: 76 additions & 2 deletions

File tree

python-checks/src/main/java/org/sonar/python/checks/UnusedFunctionParameterCheck.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.sonar.plugins.python.api.tree.Expression;
3535
import org.sonar.plugins.python.api.tree.ExpressionStatement;
3636
import org.sonar.plugins.python.api.tree.FunctionDef;
37+
import org.sonar.plugins.python.api.tree.Name;
3738
import org.sonar.plugins.python.api.tree.Parameter;
3839
import org.sonar.plugins.python.api.tree.ReturnStatement;
3940
import org.sonar.plugins.python.api.tree.Statement;
@@ -68,6 +69,7 @@ private static void checkFunctionParameter(SubscriptionContext ctx, FunctionDef
6869
.filter(UnusedFunctionParameterCheck::isUnused)
6970
.filter(symbol -> !isUsedInStringLiteralOrComment(symbol.name(), functionDef))
7071
.map(symbol -> (Parameter) symbol.usages().get(0).tree().parent())
72+
.filter(param -> !isSpecialArgument(param))
7173
.forEach(param -> ctx.addIssue(param, String.format(MESSAGE, param.name().name())));
7274
}
7375

@@ -88,6 +90,20 @@ private static boolean isUsedInStringLiteralOrComment(String symbolName, Functio
8890
comments.stream().anyMatch(str -> p.matcher(str).find());
8991
}
9092

93+
private static boolean isSpecialArgument(Parameter parameter) {
94+
String starTokenValue = Optional.ofNullable(parameter.starToken())
95+
.map(Token::value)
96+
.orElse("");
97+
String paramName = Optional.ofNullable(parameter.name())
98+
.map(Name::name)
99+
.orElse("");
100+
101+
boolean isArgsParam = "*".equals(starTokenValue) && "args".equals(paramName);
102+
boolean isKwArgsParam = "**".equals(starTokenValue) && "kwargs".equals(paramName);
103+
104+
return isArgsParam || isKwArgsParam;
105+
}
106+
91107
private static boolean isIgnoredSymbolName(String symbolName) {
92108
return "self".equals(symbolName) || symbolName.startsWith("_") || AWS_LAMBDA_PARAMETERS.contains(symbolName);
93109
}
@@ -102,6 +118,7 @@ private static boolean isException(SubscriptionContext ctx, FunctionDef function
102118
isSpecialMethod(functionDef) ||
103119
hasNonCallUsages(functionSymbol) ||
104120
isTestFunction(ctx, functionDef) ||
121+
isDjangoView(functionDef) ||
105122
isAbstractClass(functionDef);
106123
}
107124

@@ -178,4 +195,10 @@ private static List<String> collectComments(Tree element) {
178195
}
179196
return comments;
180197
}
198+
199+
private static boolean isDjangoView(FunctionDef functionDef) {
200+
FunctionSymbol functionSymbol = ((FunctionDefImpl) functionDef).functionSymbol();
201+
FunctionSymbolImpl functionSymbolImpl = (FunctionSymbolImpl) functionSymbol;
202+
return functionSymbolImpl != null && functionSymbolImpl.isDjangoView();
203+
}
181204
}

python-checks/src/test/java/org/sonar/python/checks/UnusedFunctionParameterCheckTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.sonar.python.checks;
1818

19+
import java.util.Arrays;
1920
import java.util.List;
2021
import org.junit.jupiter.api.Test;
2122
import org.sonar.python.checks.utils.PythonCheckVerifier;
@@ -48,4 +49,12 @@ void test_test_file() {
4849
PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/unusedFunctionParameter/test_something.py", new UnusedFunctionParameterCheck());
4950
}
5051

52+
@Test
53+
void test_django() {
54+
PythonCheckVerifier.verify(Arrays.asList(
55+
"src/test/resources/checks/unusedFunctionParameter/django/urls.py",
56+
"src/test/resources/checks/unusedFunctionParameter/django/views.py"
57+
),
58+
new UnusedFunctionParameterCheck());
59+
}
5160
}

python-checks/src/test/resources/checks/unusedFunctionParameter/django/__init__.py

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.urls import path
2+
3+
from . import views
4+
5+
urlpatterns = [
6+
path('view_func1', views.view_func1, name='view_func1'),
7+
path('view_func2', views.view_func2, name='view_func2'),
8+
path('view_func3', views.view_func3, name='view_func3'),
9+
]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from django.views.decorators.http import require_http_methods, require_POST, require_GET, require_safe, other_decorator
2+
3+
def not_a_view(request): # Noncompliant
4+
print("smth")
5+
6+
def view_func1(request): # Compliant
7+
print("smth")
8+
9+
@require_http_methods(["GET", "POST"]) # Compliant
10+
def view_func2(request):
11+
print("smth")
12+
13+
def view_func3(request, unused_parameter): # Compliant FN, unused_parameter is not used in the function body and is not mandatory
14+
print("smth")
15+

python-checks/src/test/resources/checks/unusedFunctionParameter/unusedFunctionParameter.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@ def g2(a): # Noncompliant
1818
c = 2
1919
compute(b)
2020

21+
def function_with_args_and_no_star(a, args): # Noncompliant
22+
print(a)
23+
24+
def function_with_args_and_star(a, *args): # Compliant, *args is often unused and acts as a catch-all
25+
print(a)
26+
27+
def function_with_args_and_two_star(a, **args): # Noncompliant
28+
print(a)
29+
30+
def function_with_kwargs_and_no_star(a, kwargs): # Noncompliant
31+
print(a)
32+
33+
def function_with_kwargs_and_star(a, *kwargs): # Noncompliant
34+
print(a)
35+
36+
def function_with_kwargs_and_two_star(a, **kwargs): # Compliant, **kwargs is often unused and acts as a catch-all
37+
print(a)
38+
2139
class MyInterface:
2240

2341
def write_alignment(self, a):

python-frontend/src/main/java/org/sonar/python/semantic/SymbolTableBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ private void createAmbiguousSymbols() {
159159
if (bindingUsages.size() > 1 &&
160160
bindingUsages.stream().anyMatch(usage -> usage.kind() == Usage.Kind.FUNC_DECLARATION || usage.kind() == Usage.Kind.CLASS_DECLARATION)) {
161161

162-
Set<Symbol> alternativeDefinitions = getAlternativeDefinitions(symbol, bindingUsages);
162+
Set<Symbol> alternativeDefinitions = createAlternativeDefinitions(symbol, bindingUsages);
163163
AmbiguousSymbol ambiguousSymbol = AmbiguousSymbolImpl.create(alternativeDefinitions);
164164
// update symbol and usage to newly created ambiguous symbol
165165
symbol.usages().forEach(usage -> ((SymbolImpl) ambiguousSymbol).addUsage(usage.tree(), usage.kind()));
@@ -171,7 +171,7 @@ private void createAmbiguousSymbols() {
171171
}
172172
}
173173

174-
private Set<Symbol> getAlternativeDefinitions(Symbol symbol, List<Usage> bindingUsages) {
174+
private Set<Symbol> createAlternativeDefinitions(Symbol symbol, List<Usage> bindingUsages) {
175175
Set<Symbol> alternativeDefinitions = new HashSet<>();
176176
for (Usage bindingUsage : bindingUsages) {
177177
switch (bindingUsage.kind()) {

0 commit comments

Comments
 (0)