1818
1919import java .util .HashSet ;
2020import java .util .List ;
21+ import java .util .Objects ;
2122import java .util .Optional ;
2223import java .util .Set ;
2324import java .util .regex .Matcher ;
2627import org .sonar .check .Rule ;
2728import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
2829import org .sonar .plugins .python .api .SubscriptionContext ;
30+ import org .sonar .plugins .python .api .symbols .v2 .SymbolV2 ;
31+ import org .sonar .plugins .python .api .symbols .v2 .UsageV2 ;
2932import org .sonar .plugins .python .api .tree .CallExpression ;
3033import org .sonar .plugins .python .api .tree .Decorator ;
3134import org .sonar .plugins .python .api .tree .Expression ;
3235import org .sonar .plugins .python .api .tree .FunctionDef ;
36+ import org .sonar .plugins .python .api .tree .Name ;
37+ import org .sonar .plugins .python .api .tree .Parameter ;
38+ import org .sonar .plugins .python .api .tree .ParameterList ;
39+ import org .sonar .plugins .python .api .tree .QualifiedExpression ;
40+ import org .sonar .plugins .python .api .tree .RegularArgument ;
41+ import org .sonar .plugins .python .api .tree .SubscriptionExpression ;
3342import org .sonar .plugins .python .api .tree .Tree ;
43+ import org .sonar .plugins .python .api .tree .TypeAnnotation ;
44+ import org .sonar .plugins .python .api .types .v2 .FunctionType ;
45+ import org .sonar .plugins .python .api .types .v2 .ParameterV2 ;
3446import org .sonar .plugins .python .api .types .v2 .matchers .TypeMatcher ;
3547import org .sonar .plugins .python .api .types .v2 .matchers .TypeMatchers ;
3648import org .sonar .python .checks .utils .Expressions ;
@@ -56,6 +68,9 @@ public class FastAPIPathParametersCheck extends PythonSubscriptionCheck {
5668 TypeMatchers .isType ("fastapi.APIRouter." + method )))
5769 );
5870
71+ private static final TypeMatcher FASTAPI_DEPENDS_MATCHER = TypeMatchers .isType ("fastapi.param_functions.Depends" );
72+ private static final TypeMatcher TYPING_ANNOTATED_MATCHER = TypeMatchers .isType ("typing.Annotated" );
73+
5974 @ Override
6075 public void initialize (Context context ) {
6176 context .registerSyntaxNodeConsumer (Tree .Kind .FUNCDEF , FastAPIPathParametersCheck ::checkFunction );
@@ -84,9 +99,89 @@ private static void checkDecorator(SubscriptionContext ctx, Decorator decorator,
8499 }
85100
86101 FunctionParameterInfo paramInfo = FunctionParameterUtils .extractFunctionParameters (functionDef );
102+ pathParams .removeAll (extractDependencyParameters (functionDef , ctx ));
87103 reportIssues (ctx , functionDef , pathParams , paramInfo );
88104 }
89105
106+ private static Set <String > extractDependencyParameters (FunctionDef functionDef , SubscriptionContext ctx ) {
107+ return extractDependencyParameters (functionDef , ctx , new HashSet <>());
108+ }
109+
110+ private static Set <String > extractDependencyParameters (FunctionDef functionDef , SubscriptionContext ctx , Set <FunctionDef > visited ) {
111+ if (!visited .add (functionDef )) {
112+ return Set .of ();
113+ }
114+ ParameterList parameterList = functionDef .parameters ();
115+ if (parameterList == null ) {
116+ return Set .of ();
117+ }
118+ Set <String > dependencyParams = new HashSet <>();
119+ parameterList .nonTuple ().stream ()
120+ .map (param -> getDependsCall (param , ctx ))
121+ .filter (Optional ::isPresent )
122+ .map (Optional ::get )
123+ .forEach (dependsCall -> collectDependencyParams (dependsCall , dependencyParams , visited , ctx ));
124+ return dependencyParams ;
125+ }
126+
127+ private static Optional <CallExpression > getDependsCall (Parameter param , SubscriptionContext ctx ) {
128+ Expression defaultValue = param .defaultValue ();
129+ if (defaultValue instanceof CallExpression callExpr && FASTAPI_DEPENDS_MATCHER .isTrueFor (callExpr .callee (), ctx )) {
130+ return Optional .of (callExpr );
131+ }
132+ TypeAnnotation typeAnnotation = param .typeAnnotation ();
133+ if (typeAnnotation != null && typeAnnotation .expression () instanceof SubscriptionExpression subscriptionExpr
134+ && TYPING_ANNOTATED_MATCHER .isTrueFor (subscriptionExpr .object (), ctx )) {
135+ return subscriptionExpr .subscripts ().expressions ().stream ()
136+ .filter (e -> e instanceof CallExpression ce && FASTAPI_DEPENDS_MATCHER .isTrueFor (ce .callee (), ctx ))
137+ .map (e -> (CallExpression ) e )
138+ .findFirst ();
139+ }
140+ return Optional .empty ();
141+ }
142+
143+ private static void collectDependencyParams (CallExpression dependsCall , Set <String > dependencyParams , Set <FunctionDef > visited , SubscriptionContext ctx ) {
144+ TreeUtils .nthArgumentOrKeywordOptional (0 , "dependency" , dependsCall .arguments ())
145+ .map (RegularArgument ::expression )
146+ .ifPresent (argExpr -> {
147+ if (argExpr .typeV2 () instanceof FunctionType funcType ) {
148+ funcType .parameters ().stream ()
149+ .filter (param -> !param .isVariadic ())
150+ .map (ParameterV2 ::name )
151+ .filter (Objects ::nonNull )
152+ .forEach (dependencyParams ::add );
153+ }
154+ getFunctionDef (argExpr ).ifPresent (depFuncDef ->
155+ dependencyParams .addAll (extractDependencyParameters (depFuncDef , ctx , visited ))
156+ );
157+ });
158+ }
159+
160+ private static Optional <FunctionDef > getFunctionDef (Expression expression ) {
161+ Name name ;
162+ if (expression instanceof Name n ) {
163+ name = n ;
164+ } else if (expression instanceof QualifiedExpression qe ) {
165+ name = qe .name ();
166+ } else {
167+ name = null ;
168+ }
169+ if (name == null ) {
170+ return Optional .empty ();
171+ }
172+ SymbolV2 symbol = name .symbolV2 ();
173+ if (symbol == null ) {
174+ return Optional .empty ();
175+ }
176+ return symbol .usages ().stream ()
177+ .filter (u -> u .kind () == UsageV2 .Kind .FUNC_DECLARATION )
178+ .map (UsageV2 ::tree )
179+ .map (tree -> TreeUtils .firstAncestorOfKind (tree , Tree .Kind .FUNCDEF ))
180+ .filter (Objects ::nonNull )
181+ .map (FunctionDef .class ::cast )
182+ .findFirst ();
183+ }
184+
90185 private static Set <String > extractPathParameters (CallExpression callExpr ) {
91186 Set <String > pathParams = new HashSet <>();
92187 String pathString = getPathArgument (callExpr ).orElse ("" );
0 commit comments