Skip to content

Commit ba231cf

Browse files
joke1196ghislainpiot
authored andcommitted
SONARPY-1988: Create an interface to wrap InputFile (#1855)
1 parent 2570bdc commit ba231cf

18 files changed

+390
-301
lines changed

sonar-python-plugin/src/main/java/org/sonar/plugins/python/IPynbSensor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public void describe(SensorDescriptor descriptor) {
6262

6363
@Override
6464
public void execute(SensorContext context) {
65-
List<InputFile> pythonFiles = getInputFiles(context);
65+
List<PythonInputFile> pythonFiles = getInputFiles(context);
6666
var pythonVersions = context.config().getStringArray(PYTHON_VERSION_KEY);
6767
if (pythonVersions.length != 0) {
6868
ProjectPythonVersion.setCurrentVersions(PythonVersionUtils.fromStringArray(pythonVersions));
@@ -71,11 +71,11 @@ public void execute(SensorContext context) {
7171
scanner.execute(pythonFiles, context);
7272
}
7373

74-
private static List<InputFile> getInputFiles(SensorContext context) {
74+
private static List<PythonInputFile> getInputFiles(SensorContext context) {
7575
FilePredicates p = context.fileSystem().predicates();
7676
Iterable<InputFile> it = context.fileSystem().inputFiles(p.and(p.hasLanguage(IPynb.KEY)));
77-
List<InputFile> list = new ArrayList<>();
78-
it.forEach(list::add);
77+
List<PythonInputFile> list = new ArrayList<>();
78+
it.forEach(f -> list.add(new PythonInputFileImpl(f)));
7979
return Collections.unmodifiableList(list);
8080
}
8181
}

sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonHighlighter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.HashSet;
2424
import java.util.Set;
2525
import javax.annotation.Nullable;
26-
import org.sonar.api.batch.fs.InputFile;
2726
import org.sonar.api.batch.sensor.SensorContext;
2827
import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
2928
import org.sonar.api.batch.sensor.highlighting.TypeOfText;
@@ -93,10 +92,10 @@ public class PythonHighlighter extends PythonSubscriptionCheck {
9392

9493
private Set<Token> docStringTokens;
9594

96-
public PythonHighlighter(SensorContext context, InputFile inputFile) {
95+
public PythonHighlighter(SensorContext context, PythonInputFile inputFile) {
9796
docStringTokens = new HashSet<>();
9897
newHighlighting = context.newHighlighting();
99-
newHighlighting.onFile(inputFile);
98+
newHighlighting.onFile(inputFile.wrappedFile());
10099
}
101100

102101
@Override
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2024 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.plugins.python;
21+
22+
import org.sonar.api.batch.fs.InputFile;
23+
24+
public interface PythonInputFile {
25+
26+
InputFile wrappedFile();
27+
28+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2024 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.plugins.python;
21+
22+
import org.sonar.api.batch.fs.InputFile;
23+
24+
public class PythonInputFileImpl implements PythonInputFile {
25+
InputFile wrappedFile;
26+
27+
public PythonInputFileImpl(InputFile wrappedFile) {
28+
this.wrappedFile = wrappedFile;
29+
}
30+
31+
public InputFile wrappedFile() {
32+
return this.wrappedFile;
33+
}
34+
35+
@Override
36+
public String toString() {
37+
return wrappedFile.toString();
38+
}
39+
40+
41+
@Override
42+
public boolean equals(Object obj) {
43+
return wrappedFile.equals(obj);
44+
}
45+
46+
47+
@Override
48+
public int hashCode() {
49+
return wrappedFile.hashCode();
50+
}
51+
}

sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public class PythonScanner extends Scanner {
7777
private final NoSonarFilter noSonarFilter;
7878
private final PythonCpdAnalyzer cpdAnalyzer;
7979
private final PythonIndexer indexer;
80-
private final Map<InputFile, Set<PythonCheck>> checksExecutedWithoutParsingByFiles = new HashMap<>();
80+
private final Map<PythonInputFile, Set<PythonCheck>> checksExecutedWithoutParsingByFiles = new HashMap<>();
8181

8282
public PythonScanner(
8383
SensorContext context, PythonChecks checks,
@@ -98,10 +98,10 @@ protected String name() {
9898
}
9999

100100
@Override
101-
protected void scanFile(InputFile inputFile) {
102-
PythonFile pythonFile = SonarQubePythonFile.create(inputFile);
101+
protected void scanFile(PythonInputFile inputFile) {
102+
PythonFile pythonFile = SonarQubePythonFile.create(inputFile.wrappedFile());
103103
PythonVisitorContext visitorContext;
104-
InputFile.Type fileType = inputFile.type();
104+
InputFile.Type fileType = inputFile.wrappedFile().type();
105105
try {
106106
AstNode astNode = parser.parse(pythonFile.content());
107107
PythonTreeMaker treeMaker = getTreeMaker(inputFile);
@@ -122,8 +122,8 @@ protected void scanFile(InputFile inputFile) {
122122
LOG.error("Unable to parse file: " + inputFile);
123123
LOG.error(e.getMessage());
124124
context.newAnalysisError()
125-
.onFile(inputFile)
126-
.at(inputFile.newPointer(e.getLine(), 0))
125+
.onFile(inputFile.wrappedFile())
126+
.at(inputFile.wrappedFile().newPointer(e.getLine(), 0))
127127
.message(e.getMessage())
128128
.save();
129129
}
@@ -143,18 +143,18 @@ protected void scanFile(InputFile inputFile) {
143143
saveIssues(inputFile, visitorContext.getIssues());
144144

145145
if (visitorContext.rootTree() != null && !isInSonarLint(context)) {
146-
new SymbolVisitor(context.newSymbolTable().onFile(inputFile)).visitFileInput(visitorContext.rootTree());
146+
new SymbolVisitor(context.newSymbolTable().onFile(inputFile.wrappedFile())).visitFileInput(visitorContext.rootTree());
147147
new PythonHighlighter(context, inputFile).scanFile(visitorContext);
148148
}
149149
}
150150

151-
private static PythonTreeMaker getTreeMaker(InputFile inputFile) {
152-
return Python.KEY.equals(inputFile.language()) ? new PythonTreeMaker() : new IPythonTreeMaker();
151+
private static PythonTreeMaker getTreeMaker(PythonInputFile inputFile) {
152+
return Python.KEY.equals(inputFile.wrappedFile().language()) ? new PythonTreeMaker() : new IPythonTreeMaker();
153153
}
154154

155155
@Override
156-
public boolean scanFileWithoutParsing(InputFile inputFile) {
157-
InputFile.Type fileType = inputFile.type();
156+
public boolean scanFileWithoutParsing(PythonInputFile inputFile) {
157+
InputFile.Type fileType = inputFile.wrappedFile().type();
158158
boolean result = true;
159159
for (PythonCheck check : checks.all()) {
160160
if (!isCheckApplicable(check, fileType)) {
@@ -166,7 +166,7 @@ public boolean scanFileWithoutParsing(InputFile inputFile) {
166166
result = false;
167167
continue;
168168
}
169-
PythonFile pythonFile = SonarQubePythonFile.create(inputFile);
169+
PythonFile pythonFile = SonarQubePythonFile.create(inputFile.wrappedFile());
170170
PythonInputFileContext inputFileContext = new PythonInputFileContext(pythonFile, context.fileSystem().workDir(), indexer.cacheContext(), context.runtime().getProduct());
171171
if (check.scanWithoutParsing(inputFileContext)) {
172172
Set<PythonCheck> executedChecks = checksExecutedWithoutParsingByFiles.getOrDefault(inputFile, new HashSet<>());
@@ -184,7 +184,7 @@ public boolean scanFileWithoutParsing(InputFile inputFile) {
184184
return restoreAndPushMeasuresIfApplicable(inputFile);
185185
}
186186

187-
private boolean checkRequiresParsingOfImpactedFile(InputFile inputFile, PythonCheck check) {
187+
private boolean checkRequiresParsingOfImpactedFile(PythonInputFile inputFile, PythonCheck check) {
188188
return !indexer.canBeFullyScannedWithoutParsing(inputFile) && !check.getClass().getPackageName().startsWith("org.sonar.python.checks");
189189
}
190190

@@ -214,12 +214,12 @@ private static boolean isInSonarLint(SensorContext context) {
214214
}
215215

216216
@Override
217-
protected void processException(Exception e, InputFile file) {
217+
protected void processException(Exception e, PythonInputFile file) {
218218
LOG.warn("Unable to analyze file: " + file, e);
219219
}
220220

221221
@Override
222-
public boolean canBeScannedWithoutParsing(InputFile inputFile) {
222+
public boolean canBeScannedWithoutParsing(PythonInputFile inputFile) {
223223
return this.indexer.canBePartiallyScannedWithoutParsing(inputFile);
224224
}
225225

@@ -229,7 +229,7 @@ protected void reportStatistics(int numSkippedFiles, int numTotalFiles) {
229229
numSkippedFiles, numTotalFiles);
230230
}
231231

232-
private void saveIssues(InputFile inputFile, List<PreciseIssue> issues) {
232+
private void saveIssues(PythonInputFile inputFile, List<PreciseIssue> issues) {
233233
for (PreciseIssue preciseIssue : issues) {
234234
RuleKey ruleKey = checks.ruleKey(preciseIssue.check());
235235
NewIssue newIssue = context
@@ -251,7 +251,7 @@ private void saveIssues(InputFile inputFile, List<PreciseIssue> issues) {
251251
if (fileId != null) {
252252
InputFile issueLocationFile = component(fileId, context);
253253
if (issueLocationFile != null) {
254-
secondaryLocationsFlow.addFirst(newLocation(issueLocationFile, newIssue, secondaryLocation));
254+
secondaryLocationsFlow.addFirst(newLocation(new PythonInputFileImpl(issueLocationFile), newIssue, secondaryLocation));
255255
}
256256
} else {
257257
newIssue.addLocation(newLocation(inputFile, newIssue, secondaryLocation));
@@ -264,7 +264,7 @@ private void saveIssues(InputFile inputFile, List<PreciseIssue> issues) {
264264
newIssue.addFlow(secondaryLocationsFlow);
265265
}
266266

267-
handleQuickFixes(inputFile, ruleKey, newIssue, preciseIssue);
267+
handleQuickFixes(inputFile.wrappedFile(), ruleKey, newIssue, preciseIssue);
268268

269269
newIssue.save();
270270
}
@@ -280,15 +280,15 @@ private InputFile component(String fileId, SensorContext sensorContext) {
280280
return inputFile;
281281
}
282282

283-
private static NewIssueLocation newLocation(InputFile inputFile, NewIssue issue, IssueLocation location) {
283+
private static NewIssueLocation newLocation(PythonInputFile inputFile, NewIssue issue, IssueLocation location) {
284284
NewIssueLocation newLocation = issue.newLocation()
285-
.on(inputFile);
285+
.on(inputFile.wrappedFile());
286286
if (location.startLine() != IssueLocation.UNDEFINED_LINE) {
287287
TextRange range;
288288
if (location.startLineOffset() == IssueLocation.UNDEFINED_OFFSET) {
289-
range = inputFile.selectLine(location.startLine());
289+
range = inputFile.wrappedFile().selectLine(location.startLine());
290290
} else {
291-
range = inputFile.newRange(location.startLine(), location.startLineOffset(), location.endLine(), location.endLineOffset());
291+
range = inputFile.wrappedFile().newRange(location.startLine(), location.startLineOffset(), location.endLine(), location.endLineOffset());
292292
}
293293
newLocation.at(range);
294294
}
@@ -300,14 +300,14 @@ private static NewIssueLocation newLocation(InputFile inputFile, NewIssue issue,
300300
return newLocation;
301301
}
302302

303-
private void saveMeasures(InputFile inputFile, PythonVisitorContext visitorContext) {
303+
private void saveMeasures(PythonInputFile inputFile, PythonVisitorContext visitorContext) {
304304
FileMetrics fileMetrics = new FileMetrics(visitorContext);
305305
FileLinesVisitor fileLinesVisitor = fileMetrics.fileLinesVisitor();
306306

307-
noSonarFilter.noSonarInFile(inputFile, fileLinesVisitor.getLinesWithNoSonar());
307+
noSonarFilter.noSonarInFile(inputFile.wrappedFile(), fileLinesVisitor.getLinesWithNoSonar());
308308

309309
if (!isInSonarLint(context)) {
310-
cpdAnalyzer.pushCpdTokens(inputFile, visitorContext);
310+
cpdAnalyzer.pushCpdTokens(inputFile.wrappedFile(), visitorContext);
311311

312312
Set<Integer> linesOfCode = fileLinesVisitor.getLinesOfCode();
313313
saveMetricOnFile(inputFile, CoreMetrics.NCLOC, linesOfCode.size());
@@ -318,7 +318,7 @@ private void saveMeasures(InputFile inputFile, PythonVisitorContext visitorConte
318318
saveMetricOnFile(inputFile, CoreMetrics.COGNITIVE_COMPLEXITY, fileMetrics.cognitiveComplexity());
319319
saveMetricOnFile(inputFile, CoreMetrics.COMMENT_LINES, fileLinesVisitor.getCommentLineCount());
320320

321-
FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile);
321+
FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile.wrappedFile());
322322
for (int line : linesOfCode) {
323323
fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1);
324324
}
@@ -329,19 +329,19 @@ private void saveMeasures(InputFile inputFile, PythonVisitorContext visitorConte
329329
}
330330
}
331331

332-
private boolean restoreAndPushMeasuresIfApplicable(InputFile inputFile) {
333-
if (inputFile.type() == InputFile.Type.TEST) {
332+
private boolean restoreAndPushMeasuresIfApplicable(PythonInputFile inputFile) {
333+
if (inputFile.wrappedFile().type() == InputFile.Type.TEST) {
334334
return true;
335335
}
336336

337-
return cpdAnalyzer.pushCachedCpdTokens(inputFile, indexer.cacheContext());
337+
return cpdAnalyzer.pushCachedCpdTokens(inputFile.wrappedFile(), indexer.cacheContext());
338338
}
339339

340-
private void saveMetricOnFile(InputFile inputFile, Metric<Integer> metric, Integer value) {
340+
private void saveMetricOnFile(PythonInputFile inputFile, Metric<Integer> metric, Integer value) {
341341
context.<Integer>newMeasure()
342342
.withValue(value)
343343
.forMetric(metric)
344-
.on(inputFile)
344+
.on(inputFile.wrappedFile())
345345
.save();
346346
}
347347

sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public void describe(SensorDescriptor descriptor) {
115115
@Override
116116
public void execute(SensorContext context) {
117117
PerformanceMeasure.Duration durationReport = createPerformanceMeasureReport(context);
118-
List<InputFile> pythonFiles = getInputFiles(context);
118+
List<PythonInputFile> pythonFiles = getInputFiles(context);
119119
String[] pythonVersionParameter = context.config().getStringArray(PYTHON_VERSION_KEY);
120120
if (pythonVersionParameter.length == 0 && context.runtime().getProduct() != SonarProduct.SONARLINT) {
121121
LOG.warn(UNSET_VERSION_WARNING);
@@ -133,11 +133,11 @@ public void execute(SensorContext context) {
133133
durationReport.stop();
134134
}
135135

136-
private static List<InputFile> getInputFiles(SensorContext context) {
136+
private static List<PythonInputFile> getInputFiles(SensorContext context) {
137137
FilePredicates p = context.fileSystem().predicates();
138138
Iterable<InputFile> it = context.fileSystem().inputFiles(p.and(p.hasLanguage(Python.KEY)));
139-
List<InputFile> list = new ArrayList<>();
140-
it.forEach(list::add);
139+
List<PythonInputFile> list = new ArrayList<>();
140+
it.forEach(f -> list.add(new PythonInputFileImpl(f)));
141141
return Collections.unmodifiableList(list);
142142
}
143143

sonar-python-plugin/src/main/java/org/sonar/plugins/python/Scanner.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ protected Scanner(SensorContext context) {
3838
this.context = context;
3939
}
4040

41-
public void execute(List<InputFile> files, SensorContext context) {
41+
public void execute(List<PythonInputFile> files, SensorContext context) {
4242
ProgressReport progressReport = new ProgressReport(this.name() + " progress", TimeUnit.SECONDS.toMillis(10));
4343
String name = this.name();
4444
LOG.info("Starting {}", name);
45-
List<String> filenames = files.stream().map(InputFile::toString).toList();
45+
List<String> filenames = files.stream().map(PythonInputFile::wrappedFile).map(InputFile::toString).toList();
4646

4747
int numScannedWithoutParsing = 0;
4848
progressReport.start(filenames);
49-
for (InputFile file : files) {
49+
for (PythonInputFile file : files) {
5050
if (context.isCancelled()) {
5151
progressReport.cancel();
5252
return;
@@ -77,28 +77,28 @@ public void execute(List<InputFile> files, SensorContext context) {
7777

7878
protected abstract String name();
7979

80-
protected abstract void scanFile(InputFile file) throws IOException;
80+
protected abstract void scanFile(PythonInputFile file) throws IOException;
8181

82-
protected boolean scanFileWithoutParsing(InputFile file) throws IOException {
82+
protected boolean scanFileWithoutParsing(PythonInputFile file) throws IOException {
8383
return false;
8484
}
8585

8686
protected void endOfAnalysis() {
8787
// no op
8888
}
8989

90-
protected abstract void processException(Exception e, InputFile file);
90+
protected abstract void processException(Exception e, PythonInputFile file);
9191

9292
protected void reportStatistics(int numSkippedFiles, int numTotalFiles) {
9393
// Intentionally empty. Subclasses can override this method to output logs containing some logs after the execution of the scanner.
9494
}
9595

96-
public boolean canBeScannedWithoutParsing(InputFile inputFile) {
96+
public boolean canBeScannedWithoutParsing(PythonInputFile inputFile) {
9797
return false;
9898
}
9999

100-
private static boolean isParseErrorOnTestFile(InputFile file, Exception e) {
100+
private static boolean isParseErrorOnTestFile(PythonInputFile file, Exception e) {
101101
// As test files may contain invalid syntax on purpose, we avoid failing the analysis when encountering parse errors on them
102-
return e instanceof RecognitionException && file.type() == InputFile.Type.TEST;
102+
return e instanceof RecognitionException && file.wrappedFile().type() == InputFile.Type.TEST;
103103
}
104104
}

0 commit comments

Comments
 (0)