1616 */
1717package org .sonar .python .checks ;
1818
19+ import java .util .Optional ;
1920import org .sonar .check .Rule ;
2021import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
2122import org .sonar .plugins .python .api .SubscriptionContext ;
23+ import org .sonar .plugins .python .api .quickfix .PythonQuickFix ;
2224import org .sonar .plugins .python .api .tree .ComprehensionFor ;
25+ import org .sonar .plugins .python .api .tree .Expression ;
2326import org .sonar .plugins .python .api .tree .ForStatement ;
2427import org .sonar .plugins .python .api .tree .Tree ;
2528import org .sonar .plugins .python .api .tree .Tuple ;
2629import org .sonar .plugins .python .api .types .v2 .TriBool ;
30+ import org .sonar .python .quickfix .TextEditUtils ;
31+ import org .sonar .python .tree .TreeUtils ;
2732import org .sonar .python .types .v2 .TypeCheckBuilder ;
2833
2934@ Rule (key = "S7517" )
3035public class LoopOverDictKeyValuesCheck extends PythonSubscriptionCheck {
31- public static final String DICT_FQN = "dict" ;
32- public static final String MESSAGE = "Use items to iterate over key-value pairs" ;
36+ private static final String DICT_FQN = "dict" ;
37+ private static final String MESSAGE = "Use items to iterate over key-value pairs" ;
38+ private static final String QUICK_FIX_MESSAGE = "Replace with items method call" ;
3339 private TypeCheckBuilder dictTypeCheck ;
3440
3541
@@ -51,7 +57,9 @@ private void checkForStatement(SubscriptionContext ctx) {
5157 if (expressions .size () == 2
5258 && testExpressions .size () == 1
5359 && dictTypeCheck .check (testExpressions .get (0 ).typeV2 ()) == TriBool .TRUE ) {
54- ctx .addIssue (testExpressions .get (0 ), MESSAGE );
60+ var dict = testExpressions .get (0 );
61+ var issue = ctx .addIssue (dict , MESSAGE );
62+ createQuickFix (dict ).ifPresent (issue ::addQuickFix );
5563 }
5664 }
5765
@@ -60,7 +68,17 @@ private void checkComprehensionFor(SubscriptionContext ctx) {
6068 if (comprehensionFor .loopExpression () instanceof Tuple tuple
6169 && tuple .elements ().size () == 2
6270 && dictTypeCheck .check (comprehensionFor .iterable ().typeV2 ()) == TriBool .TRUE ) {
63- ctx .addIssue (comprehensionFor .iterable (), MESSAGE );
71+ var dict = comprehensionFor .iterable ();
72+ var issue = ctx .addIssue (dict , MESSAGE );
73+ createQuickFix (dict ).ifPresent (issue ::addQuickFix );
6474 }
6575 }
76+
77+ private static Optional <PythonQuickFix > createQuickFix (Expression dict ) {
78+ return Optional .ofNullable (TreeUtils .treeToString (dict , false ))
79+ .map ("%s.items()" ::formatted )
80+ .map (replacementText -> TextEditUtils .replace (dict , replacementText ))
81+ .map (textEdit -> PythonQuickFix .newQuickFix (QUICK_FIX_MESSAGE ).addTextEdit (textEdit ))
82+ .map (PythonQuickFix .Builder ::build );
83+ }
6684}
0 commit comments