1616 */
1717package org .sonar .python .checks ;
1818
19- import java .util .HashMap ;
19+ import java .util .ArrayList ;
20+ import java .util .LinkedHashMap ;
2021import java .util .List ;
2122import java .util .Map ;
23+ import java .util .Objects ;
24+ import java .util .stream .Collectors ;
2225
2326import org .sonar .check .Rule ;
2427import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
2528import org .sonar .plugins .python .api .SubscriptionContext ;
29+ import org .sonar .plugins .python .api .quickfix .PythonQuickFix ;
2630import org .sonar .plugins .python .api .tree .AliasedName ;
31+ import org .sonar .plugins .python .api .tree .Argument ;
2732import org .sonar .plugins .python .api .tree .CallExpression ;
2833import org .sonar .plugins .python .api .tree .Expression ;
2934import org .sonar .plugins .python .api .tree .ImportName ;
3035import org .sonar .plugins .python .api .tree .Name ;
3136import org .sonar .plugins .python .api .tree .Tree ;
3237import org .sonar .plugins .python .api .types .v2 .TriBool ;
38+ import org .sonar .python .quickfix .TextEditUtils ;
3339import org .sonar .python .tree .TreeUtils ;
3440import org .sonar .python .types .v2 .TypeCheckBuilder ;
3541
@@ -45,8 +51,11 @@ public class InputInAsyncCheck extends PythonSubscriptionCheck {
4551 private static final String LIB_TRIO = "trio" ;
4652 private static final String LIB_ANYIO = "anyio" ;
4753
54+ private static final String QUICK_FIX_TO_THREAD = "Wrap with await %s.to_thread(input%s)" ;
55+ private static final String QUICK_FIX_RUN_SYNC = "Wrap with await %s.to_thread.run_sync(input%s)" ;
56+
4857 private TypeCheckBuilder isInputCall ;
49- private final Map <String , String > asyncLibraryAliases = new HashMap <>();
58+ private final Map <String , String > asyncLibraryAliases = new LinkedHashMap <>();
5059
5160 @ Override
5261 public void initialize (Context context ) {
@@ -83,7 +92,8 @@ private void checkInputInAsync(SubscriptionContext context) {
8392 TreeUtils .asyncTokenOfEnclosingFunction (callExpression )
8493 .ifPresent (asyncKeyword -> {
8594 String message = getMessage ();
86- context .addIssue (callee , message ).secondary (asyncKeyword , SECONDARY_MESSAGE );
95+ var issue = context .addIssue (callee , message ).secondary (asyncKeyword , SECONDARY_MESSAGE );
96+ createQuickFixes (callExpression , callee ).forEach (issue ::addQuickFix );
8797 });
8898 }
8999
@@ -100,4 +110,30 @@ private String getMessage() {
100110 return String .format (MESSAGE_TO_THREAD_RUN_SYNC , asyncLibraryAliases .get (LIB_ANYIO ));
101111 }
102112 }
113+
114+ private List <PythonQuickFix > createQuickFixes (CallExpression callExpression , Expression inputCallee ) {
115+ if (inputCallee instanceof Name inputCalleeName && !"input" .equals (inputCalleeName .name ())) {
116+ return List .of ();
117+ }
118+
119+ List <PythonQuickFix > fixes = new ArrayList <>();
120+ List <Argument > args = callExpression .arguments ();
121+
122+ String argsString = args .stream ()
123+ .map (arg -> TreeUtils .treeToString (arg , false ))
124+ .filter (Objects ::nonNull )
125+ .collect (Collectors .joining (", " ));
126+
127+ var argsForTemplate = argsString .isEmpty () ? "" : (", " + argsString );
128+
129+ asyncLibraryAliases .forEach ((library , alias ) -> {
130+ var replacementCall = LIB_ASYNCIO .equals (library ) ? (alias + ".to_thread(input" + argsForTemplate + ")" ) : (alias + ".to_thread.run_sync(input" + argsForTemplate + ")" );
131+ var quickFixMsg = LIB_ASYNCIO .equals (library ) ? String .format (QUICK_FIX_TO_THREAD , alias , argsForTemplate ) : String .format (QUICK_FIX_RUN_SYNC , alias , argsForTemplate );
132+ var quickFix = PythonQuickFix .newQuickFix (quickFixMsg )
133+ .addTextEdit (TextEditUtils .replace (callExpression , "await " + replacementCall ))
134+ .build ();
135+ fixes .add (quickFix );
136+ });
137+ return fixes ;
138+ }
103139}
0 commit comments