Skip to content

Commit 83dbc83

Browse files
SONARPY-862 S117 (LocalVariableAndParameterNameConventionCheck) shouldn't raise on type aliases (#2064)
1 parent c0e14aa commit 83dbc83

File tree

2 files changed

+52
-3
lines changed

2 files changed

+52
-3
lines changed

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

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,20 @@
2121

2222
import java.util.Comparator;
2323
import java.util.EnumSet;
24+
import java.util.List;
2425
import java.util.regex.Pattern;
2526
import org.sonar.check.Rule;
2627
import org.sonar.check.RuleProperty;
2728
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2829
import org.sonar.plugins.python.api.SubscriptionContext;
30+
import org.sonar.plugins.python.api.symbols.Symbol;
31+
import org.sonar.plugins.python.api.symbols.Usage;
32+
import org.sonar.plugins.python.api.tree.AssignmentStatement;
2933
import org.sonar.plugins.python.api.tree.Expression;
3034
import org.sonar.plugins.python.api.tree.FunctionDef;
35+
import org.sonar.plugins.python.api.tree.Name;
36+
import org.sonar.plugins.python.api.tree.SubscriptionExpression;
3137
import org.sonar.plugins.python.api.tree.Tree;
32-
import org.sonar.plugins.python.api.symbols.Symbol;
33-
import org.sonar.plugins.python.api.symbols.Usage;
3438

3539
@Rule(key = "S117")
3640
public class LocalVariableAndParameterNameConventionCheck extends PythonSubscriptionCheck {
@@ -78,7 +82,42 @@ private void checkName(Symbol symbol, SubscriptionContext ctx) {
7882
}
7983

8084
private static boolean isType(Symbol symbol) {
81-
return symbol.usages().stream().map(Usage::tree).filter(Expression.class::isInstance).map(Expression.class::cast).anyMatch(e -> e.type().mustBeOrExtend("type"));
85+
return isExtendingType(symbol) || isAssignedFromTyping(symbol);
86+
}
87+
88+
private static boolean isExtendingType(Symbol symbol) {
89+
return symbol.usages().stream().map(Usage::tree).filter(Expression.class::isInstance).map(Expression.class::cast).anyMatch(e -> e.type().mustBeOrExtend("type")) ||
90+
(symbol.annotatedTypeName() != null && symbol.annotatedTypeName().startsWith("typing."));
91+
}
92+
93+
private static boolean isAssignedFromTyping(Symbol symbol) {
94+
List<Tree> assignmentNames = symbol.usages().stream().filter(u -> u.kind() == Usage.Kind.ASSIGNMENT_LHS).map(Usage::tree).toList();
95+
for (Tree assignmentName : assignmentNames) {
96+
Expression assignedValue = getAssignedValue(assignmentName);
97+
if (assignedValue == null) {
98+
continue;
99+
}
100+
if (assignedValue.is(Tree.Kind.SUBSCRIPTION)) {
101+
SubscriptionExpression subscriptionExpression = (SubscriptionExpression) assignedValue;
102+
if (subscriptionExpression.object().is(Tree.Kind.NAME)) {
103+
Symbol assignedSymbol = ((Name) subscriptionExpression.object()).symbol();
104+
if (assignedSymbol != null && isExtendingType(assignedSymbol)) {
105+
return true;
106+
}
107+
}
108+
}
109+
}
110+
return false;
111+
}
112+
113+
private static Expression getAssignedValue(Tree assignmentName) {
114+
while (assignmentName != null && !assignmentName.is(Tree.Kind.ASSIGNMENT_STMT)) {
115+
assignmentName = assignmentName.parent();
116+
}
117+
if (assignmentName == null) {
118+
return null;
119+
}
120+
return ((AssignmentStatement) assignmentName).assignedValue();
82121
}
83122

84123
private void raiseIssueForNameAndUsage(SubscriptionContext ctx, String name, Usage usage) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ def type_variables_ok(MyTypeParameter: type):
7474
from typing import NamedTuple
7575
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
7676

77+
def type_aliases_from_typing_special_form():
78+
from typing import Type, Union
79+
class A: ...
80+
MyType = Type[A] # OK
81+
AliasType = Union[str, int] # OK
82+
83+
# Assigned from a subscription check, but not from a typing_SpecialForm
84+
array = [1,2,3]
85+
MyInt = array[1] # Noncompliant
86+
MyOtherInt = [1,2,3][1] # Noncompliant
7787

7888
def type_variables_fp():
7989
class MyClass:

0 commit comments

Comments
 (0)