2222import org .sonar .plugins .python .api .SubscriptionContext ;
2323import org .sonar .plugins .python .api .tree .AnnotatedAssignment ;
2424import org .sonar .plugins .python .api .tree .Argument ;
25- import org .sonar .plugins .python .api .tree .BinaryExpression ;
2625import org .sonar .plugins .python .api .tree .CallExpression ;
2726import org .sonar .plugins .python .api .tree .ClassDef ;
2827import org .sonar .plugins .python .api .tree .Expression ;
@@ -43,8 +42,6 @@ public class PydanticOptionalFieldDefaultCheck extends PythonSubscriptionCheck {
4342 private static final TypeMatcher IS_PYDANTIC_FIELD = TypeMatchers .isType ("pydantic.Field" );
4443
4544 private static final TypeMatcher IS_TYPING_OPTIONAL = TypeMatchers .isType ("typing.Optional" );
46- private static final TypeMatcher IS_TYPING_UNION = TypeMatchers .isType ("typing.Union" );
47- private static final TypeMatcher IS_NONE_TYPE = TypeMatchers .isObjectOfType ("NoneType" );
4845
4946 @ Override
5047 public void initialize (Context context ) {
@@ -67,16 +64,12 @@ private static void checkField(SubscriptionContext ctx, AnnotatedAssignment anno
6764 TypeAnnotation annotation = annotatedAssignment .annotation ();
6865 Expression annotationExpr = annotation .expression ();
6966
67+ if (!isTypingOptional (annotationExpr , ctx )) {
68+ return ;
69+ }
70+
7071 Expression assignedValue = annotatedAssignment .assignedValue ();
71- boolean hasFieldWithEllipsis = assignedValue != null && isFieldCallWithEllipsis (ctx , assignedValue );
72-
73- if (isTypingOptional (annotationExpr , ctx )) {
74- // Optional[X]: raise when no default, or when Field(...) with ellipsis
75- if (assignedValue == null || hasFieldWithEllipsis ) {
76- ctx .addIssue (annotationExpr , MESSAGE );
77- }
78- } else if (isNullableNonOptional (annotationExpr , ctx ) && hasFieldWithEllipsis ) {
79- // X | None or Union[X, None]: only raise when Field(...) with ellipsis
72+ if (assignedValue == null || isFieldCallWithEllipsis (ctx , assignedValue )) {
8073 ctx .addIssue (annotationExpr , MESSAGE );
8174 }
8275 }
@@ -88,36 +81,6 @@ private static boolean isTypingOptional(Expression annotationExpr, SubscriptionC
8881 return false ;
8982 }
9083
91- private static boolean isNullableNonOptional (Expression annotationExpr , SubscriptionContext ctx ) {
92- // Case 1: T | None (BinaryExpression with BITWISE_OR)
93- if (annotationExpr .is (Tree .Kind .BITWISE_OR )) {
94- return containsNone ((BinaryExpression ) annotationExpr , ctx );
95- }
96-
97- // Case 2: Union[T, None] (SubscriptionExpression)
98- if (annotationExpr instanceof SubscriptionExpression subscriptionExpr ) {
99- return IS_TYPING_UNION .isTrueFor (subscriptionExpr .object (), ctx ) && subscriptsContainNone (subscriptionExpr , ctx );
100- }
101-
102- return false ;
103- }
104-
105- private static boolean containsNone (BinaryExpression binaryExpr , SubscriptionContext ctx ) {
106- return isNoneExpression (binaryExpr .leftOperand (), ctx ) ||
107- isNoneExpression (binaryExpr .rightOperand (), ctx ) ||
108- (binaryExpr .leftOperand ().is (Tree .Kind .BITWISE_OR ) && containsNone ((BinaryExpression ) binaryExpr .leftOperand (), ctx )) ||
109- (binaryExpr .rightOperand ().is (Tree .Kind .BITWISE_OR ) && containsNone ((BinaryExpression ) binaryExpr .rightOperand (), ctx ));
110- }
111-
112- private static boolean isNoneExpression (Expression expr , SubscriptionContext ctx ) {
113- return IS_NONE_TYPE .isTrueFor (expr , ctx );
114- }
115-
116- private static boolean subscriptsContainNone (SubscriptionExpression subscriptionExpr , SubscriptionContext ctx ) {
117- return subscriptionExpr .subscripts ().expressions ().stream ()
118- .anyMatch (expr -> isNoneExpression (expr , ctx ));
119- }
120-
12184 private static boolean isFieldCallWithEllipsis (SubscriptionContext ctx , Expression assignedValue ) {
12285 if (!(assignedValue instanceof CallExpression callExpr )) {
12386 return false ;
0 commit comments