1616 */
1717package org .sonar .python .checks ;
1818
19+ import java .util .HashMap ;
20+ import java .util .List ;
21+ import java .util .Map ;
1922import java .util .Optional ;
23+
2024import org .sonar .check .Rule ;
2125import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
2226import org .sonar .plugins .python .api .SubscriptionContext ;
27+ import org .sonar .plugins .python .api .quickfix .PythonQuickFix ;
28+ import org .sonar .plugins .python .api .tree .AliasedName ;
2329import org .sonar .plugins .python .api .tree .CallExpression ;
2430import org .sonar .plugins .python .api .tree .Expression ;
31+ import org .sonar .plugins .python .api .tree .ImportName ;
32+ import org .sonar .plugins .python .api .tree .Name ;
2533import org .sonar .plugins .python .api .tree .RegularArgument ;
2634import org .sonar .plugins .python .api .tree .Token ;
2735import org .sonar .plugins .python .api .tree .Tree ;
2836import org .sonar .plugins .python .api .types .v2 .TriBool ;
2937import org .sonar .python .checks .hotspots .CommonValidationUtils ;
38+ import org .sonar .python .quickfix .TextEditUtils ;
3039import org .sonar .python .tree .TreeUtils ;
3140import org .sonar .python .types .v2 .TypeCheckBuilder ;
3241
3342@ Rule (key = "S7486" )
3443public class AsyncLongSleepCheck extends PythonSubscriptionCheck {
3544
3645 private static final String MESSAGE = "Replace this call with \" %s.sleep_forever()\" as the sleep duration exceeds 24 hours." ;
46+ private static final String SECONDARY_MESSAGE = "This function is async." ;
47+ private static final String QUICK_FIX_MESSAGE = "Replace with %s" ;
3748 private static final int SECONDS_IN_DAY = 86400 ;
3849
3950 private TypeCheckBuilder isTrioSleepCall ;
4051 private TypeCheckBuilder isAnyioSleepCall ;
52+ private final Map <String , String > asyncLibraryAliases = new HashMap <>();
4153
4254 @ Override
4355 public void initialize (Context context ) {
4456 context .registerSyntaxNodeConsumer (Tree .Kind .FILE_INPUT , this ::setupCheck );
57+ context .registerSyntaxNodeConsumer (Tree .Kind .IMPORT_NAME , this ::checkImportName );
4558 context .registerSyntaxNodeConsumer (Tree .Kind .CALL_EXPR , this ::checkAsyncLongSleep );
4659 }
4760
4861 private void setupCheck (SubscriptionContext ctx ) {
4962 isTrioSleepCall = ctx .typeChecker ().typeCheckBuilder ().isTypeWithFqn ("trio.sleep" );
5063 isAnyioSleepCall = ctx .typeChecker ().typeCheckBuilder ().isTypeWithName ("anyio.sleep" );
64+ asyncLibraryAliases .clear ();
65+ }
66+
67+ private void checkImportName (SubscriptionContext ctx ) {
68+ ImportName importName = (ImportName ) ctx .syntaxNode ();
69+ for (AliasedName module : importName .modules ()) {
70+ List <Name > names = module .dottedName ().names ();
71+ if (names .size () > 1 ) {
72+ continue ;
73+ }
74+ String moduleName = names .get (0 ).name ();
75+ Name aliasName = module .alias ();
76+ String alias = aliasName != null ? aliasName .name () : moduleName ;
77+ if ("trio" .equals (moduleName ) || "anyio" .equals (moduleName )) {
78+ asyncLibraryAliases .put (moduleName , alias );
79+ }
80+ }
5181 }
5282
5383 private void checkAsyncLongSleep (SubscriptionContext context ) {
5484 CallExpression callExpression = (CallExpression ) context .syntaxNode ();
5585 Expression callee = callExpression .callee ();
56-
86+
5787 String libraryName ;
5888 if (isTrioSleepCall .check (callee .typeV2 ()) == TriBool .TRUE ) {
5989 libraryName = "trio" ;
@@ -74,11 +104,27 @@ private void checkAsyncLongSleep(SubscriptionContext context) {
74104 }
75105 if (CommonValidationUtils .isMoreThan (durationExpr , SECONDS_IN_DAY )) {
76106 String message = String .format (MESSAGE , libraryName );
77- context .addIssue (callExpression , message ).secondary (asyncToken , "This function is async." );
107+ var issue = context .addIssue (callExpression , message )
108+ .secondary (asyncToken , SECONDARY_MESSAGE );
109+ createQuickFix (libraryName , callExpression )
110+ .ifPresent (issue ::addQuickFix );
78111 }
79112 }
80113
81114 private static Optional <Expression > extractDurationExpression (CallExpression callExpression , String paramName ) {
82115 return Optional .ofNullable (TreeUtils .nthArgumentOrKeyword (0 , paramName , callExpression .arguments ())).map (RegularArgument ::expression );
83116 }
117+
118+ private Optional <PythonQuickFix > createQuickFix (String libraryName , CallExpression callExpression ) {
119+ String alias = asyncLibraryAliases .get (libraryName );
120+ if (alias == null ) {
121+ return Optional .empty ();
122+ }
123+ String replacement = alias + ".sleep_forever()" ;
124+ String quickFixMsg = String .format (QUICK_FIX_MESSAGE , replacement );
125+ return Optional .of (
126+ PythonQuickFix .newQuickFix (quickFixMsg )
127+ .addTextEdit (TextEditUtils .replace (callExpression , replacement ))
128+ .build ());
129+ }
84130}
0 commit comments