Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@
import org.sonar.plugins.python.api.symbols.Usage;
import org.sonar.plugins.python.api.symbols.v2.SymbolV2;
import org.sonar.plugins.python.api.symbols.v2.UsageV2;
import org.sonar.plugins.python.api.tree.AnnotatedAssignment;
import org.sonar.plugins.python.api.tree.AssignmentExpression;
import org.sonar.plugins.python.api.tree.AssignmentStatement;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.ConditionalExpression;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.Parameter;
import org.sonar.plugins.python.api.tree.SubscriptionExpression;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.types.v2.PythonType;
import org.sonar.python.checks.utils.Expressions;
import org.sonar.python.checks.utils.MarimoUtils;
import org.sonar.python.semantic.SymbolUtils;
import org.sonar.python.tree.TreeUtils;
Expand Down Expand Up @@ -107,7 +112,8 @@ private void checkName(SymbolV2 symbol, SubscriptionContext ctx) {
private boolean isType(SymbolV2 symbolV2) {
// TypeV1 and TypeV2 can detect different cases and work complementary to find more issues
Symbol symbolV1 = SymbolUtils.symbolV2ToSymbolV1(symbolV2).orElse(null);
return symbolV1 != null && (isExtendingType(symbolV1) || isAssignedFromTyping(symbolV2) || isPythonTypeAClassType(symbolV2));
return symbolV1 != null && (isExtendingType(symbolV1) || isAssignedFromTyping(symbolV2) || isAssignedFromBuiltinType(symbolV2)
|| isPythonTypeAClassType(symbolV2));
}

private static boolean isExtendingType(Symbol symbol) {
Expand Down Expand Up @@ -136,18 +142,69 @@ private static boolean isAssignedFromTyping(SymbolV2 symbol) {
return false;
}

/**
* Determines whether a local variable is assigned from the built-in {@code type} function.
*
* @param symbol the local variable symbol
* @return {@code true} when an assignment directly produces a class object
*/
private static boolean isAssignedFromBuiltinType(SymbolV2 symbol) {
return symbol.usages().stream()
.filter(u -> u.kind() == UsageV2.Kind.ASSIGNMENT_LHS)
.flatMap(usage -> getAssignedValue(usage.tree()))
.anyMatch(LocalVariableAndParameterNameConventionCheck::isBuiltinTypeAssignment);
}

/**
* Determines whether an assignment directly produces a class object with {@code type}.
*
* @param assignedValue the expression assigned to the local variable
* @return {@code true} when the expression is a built-in {@code type} call or its {@code None} conditional branch
*/
private static boolean isBuiltinTypeAssignment(Expression assignedValue) {
Expression unwrappedValue = Expressions.removeParentheses(assignedValue);
if (isBuiltinTypeCall(unwrappedValue)) {
return true;
}
if (unwrappedValue instanceof ConditionalExpression conditionalExpression) {
return isBuiltinTypeCall(conditionalExpression.trueExpression()) && conditionalExpression.falseExpression().is(Tree.Kind.NONE)
|| isBuiltinTypeCall(conditionalExpression.falseExpression()) && conditionalExpression.trueExpression().is(Tree.Kind.NONE);
}
return false;
}

/**
* Checks whether an expression is a call to the resolved built-in {@code type} function.
*
* @param expression the expression to inspect
* @return {@code true} when the expression calls the built-in {@code type} function
*/
private static boolean isBuiltinTypeCall(Expression expression) {
Expression unwrappedExpression = Expressions.removeParentheses(expression);
if (unwrappedExpression instanceof CallExpression callExpression) {
Symbol calleeSymbol = callExpression.calleeSymbol();
return calleeSymbol != null && "type".equals(calleeSymbol.fullyQualifiedName());
}
return false;
}

private boolean isPythonTypeAClassType(SymbolV2 symbol) {
PythonType type = SymbolUtils.getPythonType(symbol);
return isDjangoModelTypeCheck.check(type).isTrue();
}

private static Stream<Expression> getAssignedValue(Tree assignmentName) {
var assignmentStmt = TreeUtils.firstAncestorOfClass(assignmentName, AssignmentStatement.class);
if (assignmentStmt != null) {
return Stream.of(assignmentStmt.assignedValue());
} else {
return Stream.empty();
Tree assignment = TreeUtils.firstAncestor(assignmentName,
t -> t.is(Tree.Kind.ASSIGNMENT_STMT, Tree.Kind.ANNOTATED_ASSIGNMENT, Tree.Kind.ASSIGNMENT_EXPRESSION));
Expression assignedValue = null;
if (assignment instanceof AssignmentStatement assignmentStatement) {
assignedValue = assignmentStatement.assignedValue();
} else if (assignment instanceof AnnotatedAssignment annotatedAssignment) {
assignedValue = annotatedAssignment.assignedValue();
} else if (assignment instanceof AssignmentExpression assignmentExpression) {
assignedValue = assignmentExpression.expression();
}
return assignedValue != null ? Stream.of(assignedValue) : Stream.empty();
}

private static @Nullable Symbol getTypingSymbol(Expression expr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,29 @@ class MyClass:
MyClassAlias = MyClass # OK
MyTypeVariable: type = unknown_call() # Noncompliant

def builtin_type_assignments(role, roles):
RoleType = type(role)
OptionalRoleType = type(roles[0]) if roles else None
ParenthesizedRoleType = (type(role))
BadName = wrapper(type(role)) # Noncompliant

def annotated_builtin_type_assignments(role, roles):
AnnotatedRoleType: type = type(role)
AnnotatedOptionalRoleType: type | None = type(roles[0]) if roles else None
AnnotatedBadName: type = wrapper(type(role)) # Noncompliant

def walrus_builtin_type_assignments(role):
if (WalrusRoleType := type(role)):
pass
while (WalrusBadName := wrapper(type(role))): # Noncompliant
pass

def shadowed_type_assignment():
def type(value):
return value

BadName = type("role") # Noncompliant

def ml_names():
X = [1, 2, 3]
Y = [0, 1, 0]
Expand Down