Skip to content

Commit a81ba42

Browse files
SONARPY-1023 : Add support for SonarLint quick fixes (#1135)
1 parent 03c960c commit a81ba42

File tree

16 files changed

+679
-41
lines changed

16 files changed

+679
-41
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
<sonar.version>9.3.0.51899</sonar.version>
9494
<sonar.orchestrator.version>3.36.0.63</sonar.orchestrator.version>
9595
<sonar-analyzer-commons.version>1.24.0.965</sonar-analyzer-commons.version>
96-
<sonarlint-core.version>6.0.0.32513</sonarlint-core.version>
96+
<sonarlint-core.version>6.3.0.36253</sonarlint-core.version>
9797
<sslr.version>1.23</sslr.version>
9898
<protobuf.version>3.17.3</protobuf.version>
9999
<woodstox.version>6.2.7</woodstox.version>

python-checks-testkit/src/main/java/org/sonar/python/checks/utils/PythonCheckVerifier.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import static org.sonar.python.semantic.SymbolUtils.pythonPackageName;
4444

4545
public class PythonCheckVerifier {
46-
4746
private PythonCheckVerifier() {
4847
}
4948

@@ -55,7 +54,6 @@ private static List<PreciseIssue> scanFileForIssues(PythonCheck check, PythonVis
5554
return context.getIssues();
5655
}
5756

58-
5957
public static void verify(String path, PythonCheck check) {
6058
verify(Collections.singletonList(path), check);
6159
}
@@ -119,7 +117,6 @@ private static MultiFileVerifier.Issue addPreciseIssue(Path path, MultiFileVerif
119117
return verifier.reportIssue(path, message).onLine(location.startLine());
120118
}
121119

122-
123120
MultiFileVerifier.Issue issueBuilder = verifier.reportIssue(path, message)
124121
.onRange(location.startLine(), location.startLineOffset() + 1, location.endLine(), location.endLineOffset());
125122
for (IssueLocation secondary : preciseIssue.secondaryLocations()) {

python-checks/src/main/java/org/sonar/python/checks/ClassMethodFirstArgumentNameCheck.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
import org.sonar.plugins.python.api.tree.Parameter;
3434
import org.sonar.plugins.python.api.tree.ParameterList;
3535
import org.sonar.plugins.python.api.tree.Tree;
36+
import org.sonar.python.quickfix.IssueWithQuickFix;
37+
import org.sonar.python.quickfix.PythonQuickFix;
38+
import org.sonar.python.quickfix.PythonTextEdit;
3639
import org.sonar.python.tree.TreeUtils;
3740

3841
@Rule(key = "S2710")
@@ -89,7 +92,16 @@ private void checkFirstParameterName(FunctionDef functionDef, SubscriptionContex
8992
return;
9093
}
9194
if (!classParameterNames().contains(parameterName.name())) {
92-
ctx.addIssue(parameterName, String.format("Rename \"%s\" to a valid class parameter name or add the missing class parameter.", parameterName.name()));
95+
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(parameterName,
96+
String.format("Rename \"%s\" to a valid class parameter name or add the missing class parameter.", parameterName.name()));
97+
98+
PythonTextEdit text = PythonTextEdit
99+
.insertBefore(parameterName, "cls, ");
100+
PythonQuickFix quickFix = PythonQuickFix.newQuickFix("Add 'cls' as the first argument.")
101+
.addTextEdit(text)
102+
.build();
103+
issue.addQuickFix(quickFix);
93104
}
94105
}
106+
95107
}

python-frontend/src/main/java/org/sonar/python/SubscriptionVisitor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.sonar.plugins.python.api.tree.Token;
4545
import org.sonar.plugins.python.api.tree.Tree;
4646
import org.sonar.plugins.python.api.tree.Tree.Kind;
47+
import org.sonar.python.quickfix.IssueWithQuickFix;
4748
import org.sonar.python.regex.PythonAnalyzerRegexSource;
4849
import org.sonar.python.regex.PythonRegexIssueLocation;
4950
import org.sonar.python.regex.RegexContext;
@@ -147,7 +148,7 @@ public PythonCheck.PreciseIssue addLineIssue(String message, int lineNumber) {
147148
}
148149

149150
private PythonCheck.PreciseIssue addIssue(IssueLocation issueLocation) {
150-
PythonCheck.PreciseIssue newIssue = new PythonCheck.PreciseIssue(check, issueLocation);
151+
PythonCheck.PreciseIssue newIssue = new IssueWithQuickFix(check, issueLocation);
151152
pythonVisitorContext.addIssue(newIssue);
152153
return newIssue;
153154
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.quickfix;
21+
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import org.sonar.plugins.python.api.IssueLocation;
25+
import org.sonar.plugins.python.api.PythonCheck;
26+
27+
public class IssueWithQuickFix extends PythonCheck.PreciseIssue {
28+
29+
private final List<PythonQuickFix> quickFixes = new ArrayList<>();
30+
31+
public IssueWithQuickFix(PythonCheck check, IssueLocation primaryLocation) {
32+
super(check, primaryLocation);
33+
}
34+
35+
public void addQuickFix(PythonQuickFix quickFix){
36+
this.quickFixes.add(quickFix);
37+
}
38+
39+
public List<PythonQuickFix> getQuickFixes() {
40+
return quickFixes;
41+
}
42+
43+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.quickfix;
21+
22+
import java.util.ArrayList;
23+
import java.util.Arrays;
24+
import java.util.List;
25+
26+
public class PythonQuickFix {
27+
private final String description;
28+
private final List<PythonTextEdit> textEdits;
29+
30+
private PythonQuickFix(String description, List<PythonTextEdit> textEdits) {
31+
this.description = description;
32+
this.textEdits = textEdits;
33+
}
34+
35+
public String getDescription() {
36+
return description;
37+
}
38+
39+
public List<PythonTextEdit> getTextEdits() {
40+
return textEdits;
41+
}
42+
43+
public static Builder newQuickFix(String description) {
44+
return new Builder(description);
45+
}
46+
47+
public static class Builder {
48+
private final String description;
49+
private final List<PythonTextEdit> textEdits = new ArrayList<>();
50+
51+
private Builder(String description) {
52+
this.description = description;
53+
}
54+
55+
public Builder addTextEdit(PythonTextEdit... textEdit) {
56+
textEdits.addAll(Arrays.asList(textEdit));
57+
return this;
58+
}
59+
60+
public PythonQuickFix build() {
61+
return new PythonQuickFix(description, textEdits);
62+
}
63+
}
64+
65+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.quickfix;
21+
22+
import org.sonar.plugins.python.api.tree.Token;
23+
import org.sonar.plugins.python.api.tree.Tree;
24+
25+
public class PythonTextEdit {
26+
27+
private final String message;
28+
private final int startLine;
29+
private final int startLineOffset;
30+
private final int endLine;
31+
private final int endLineOffset;
32+
33+
public PythonTextEdit(String message, int startLine, int startLineOffset, int endLine, int endLineOffset) {
34+
this.message = message;
35+
this.startLine = startLine;
36+
this.startLineOffset = startLineOffset;
37+
this.endLine = endLine;
38+
this.endLineOffset = endLineOffset;
39+
}
40+
41+
public static PythonTextEdit insertBefore(Tree tree, String textToInsert) {
42+
Token token = tree.firstToken();
43+
return new PythonTextEdit(textToInsert, token.line(), token.column(), token.line(), token.column());
44+
}
45+
46+
public String replacementText() {
47+
return message;
48+
}
49+
50+
public int startLine() {
51+
return startLine;
52+
}
53+
54+
public int startLineOffset() {
55+
return startLineOffset;
56+
}
57+
58+
public int endLine() {
59+
return endLine;
60+
}
61+
62+
public int endLineOffset() {
63+
return endLineOffset;
64+
}
65+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
@ParametersAreNonnullByDefault
21+
package org.sonar.python.quickfix;
22+
23+
import javax.annotation.ParametersAreNonnullByDefault;
24+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.quickfix;
21+
22+
import org.junit.Test;
23+
import org.mockito.Mockito;
24+
import org.sonar.plugins.python.api.IssueLocation;
25+
import org.sonar.plugins.python.api.LocationInFile;
26+
import org.sonar.plugins.python.api.PythonCheck;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
public class IssueWithQuickFixTest {
31+
32+
@Test
33+
public void test() {
34+
PythonCheck check = Mockito.mock(PythonCheck.class);
35+
LocationInFile loc1 = new LocationInFile("null", 1, 7, 10, 10);
36+
IssueLocation issueLocation = IssueLocation.preciseLocation(loc1, "location");
37+
38+
IssueWithQuickFix issue = new IssueWithQuickFix(check, issueLocation);
39+
40+
assertThat(issue.getQuickFixes()).isEmpty();
41+
42+
PythonTextEdit textEdit = Mockito.mock(PythonTextEdit.class);
43+
PythonQuickFix quickFix = PythonQuickFix.newQuickFix("New Quickfix")
44+
.addTextEdit(textEdit)
45+
.build();
46+
47+
issue.addQuickFix(quickFix);
48+
issue.addQuickFix(quickFix);
49+
50+
assertThat(issue.getQuickFixes()).containsExactly(quickFix, quickFix);
51+
}
52+
53+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.quickfix;
21+
22+
import org.junit.Test;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
26+
public class PythonQuickFixTest {
27+
28+
@Test
29+
public void test() {
30+
PythonTextEdit textEdit = new PythonTextEdit("This is a replacement text", 1, 2, 3, 4);
31+
32+
PythonQuickFix quickFix = PythonQuickFix.newQuickFix("New quickfix").addTextEdit(textEdit).build();
33+
34+
assertThat(quickFix.getTextEdits()).containsExactly(textEdit);
35+
assertThat(quickFix.getDescription()).isEqualTo("New quickfix");
36+
}
37+
}

0 commit comments

Comments
 (0)