Skip to content

Commit ed45cb1

Browse files
maksim-grebeniuk-sonarsourcesonartech
authored andcommitted
SONARPY-3189 Implement a collection of a project AWS lambda handlers based on function signatures (#401)
GitOrigin-RevId: 86b7ce2424c8d13adb8c2fb1a3592ae5443783f1
1 parent 10cd7b2 commit ed45cb1

File tree

21 files changed

+460
-75
lines changed

21 files changed

+460
-75
lines changed

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ public static void verify(String path, PythonCheck check) {
5858

5959
public static void verifyNoIssue(String path, PythonCheck check) {
6060
File file = new File(path);
61-
createVerifier(Collections.singletonList(file), check, ProjectLevelSymbolTable.empty(), null, new ProjectConfiguration())
61+
createVerifier(Collections.singletonList(file), check, ProjectLevelSymbolTable.empty(), null, null)
6262
.assertNoIssues();
6363
}
6464

6565
public static void verify(List<String> paths, PythonCheck check) {
66-
verify(paths, check, new ProjectConfiguration());
66+
verify(paths, check, null);
6767
}
6868

69-
public static void verify(List<String> paths, PythonCheck check, ProjectConfiguration projectConfiguration) {
69+
public static void verify(List<String> paths, PythonCheck check, @Nullable ProjectConfiguration projectConfiguration) {
7070
List<File> files = paths.stream().map(File::new).toList();
7171
File baseDirFile = new File(files.get(0).getParent());
7272
ProjectLevelSymbolTable projectLevelSymbolTable = TestPythonVisitorRunner.globalSymbols(files, baseDirFile);
@@ -77,20 +77,20 @@ public static void verifyNoIssue(List<String> paths, PythonCheck check) {
7777
List<File> files = paths.stream().map(File::new).toList();
7878
File baseDirFile = new File(files.get(0).getParent());
7979
ProjectLevelSymbolTable projectLevelSymbolTable = TestPythonVisitorRunner.globalSymbols(files, baseDirFile);
80-
createVerifier(files, check, projectLevelSymbolTable, baseDirFile, new ProjectConfiguration()).assertNoIssues();
80+
createVerifier(files, check, projectLevelSymbolTable, baseDirFile, null).assertNoIssues();
8181
}
8282

8383
public static List<PreciseIssue> issues(String path, PythonCheck check) {
8484
File file = new File(path);
85-
PythonVisitorContext context = createContext(file, ProjectLevelSymbolTable.empty(), new ProjectConfiguration(), null);
85+
PythonVisitorContext context = createContext(file, ProjectLevelSymbolTable.empty(), null, null);
8686
return scanFileForIssues(check, context);
8787
}
8888

8989
private static MultiFileVerifier createVerifier(List<File> files,
9090
PythonCheck check,
9191
ProjectLevelSymbolTable projectLevelSymbolTable,
9292
@Nullable File baseDir,
93-
ProjectConfiguration projectConfiguration) {
93+
@Nullable ProjectConfiguration projectConfiguration) {
9494
MultiFileVerifier multiFileVerifier = MultiFileVerifier.create(files.get(0).toPath(), UTF_8);
9595
for (File file : files) {
9696
PythonVisitorContext context = createContext(file, projectLevelSymbolTable, projectConfiguration, baseDir);
@@ -101,7 +101,7 @@ private static MultiFileVerifier createVerifier(List<File> files,
101101

102102
private static PythonVisitorContext createContext(File file,
103103
ProjectLevelSymbolTable projectLevelSymbolTable,
104-
ProjectConfiguration projectConfiguration,
104+
@Nullable ProjectConfiguration projectConfiguration,
105105
@Nullable File baseDir) {
106106
return baseDir != null
107107
? TestPythonVisitorRunner.createContext(file,
@@ -145,7 +145,8 @@ private static MultiFileVerifier.Issue addPreciseIssue(Path path, MultiFileVerif
145145
MultiFileVerifier.Issue issueBuilder = verifier.reportIssue(path, message)
146146
.onRange(location.startLine(), location.startLineOffset() + 1, location.endLine(), location.endLineOffset());
147147
for (IssueLocation secondary : preciseIssue.secondaryLocations()) {
148-
issueBuilder.addSecondary(path, secondary.startLine(), secondary.startLineOffset() + 1, secondary.endLine(), secondary.endLineOffset(), secondary.message());
148+
issueBuilder.addSecondary(path, secondary.startLine(), secondary.startLineOffset() + 1, secondary.endLine(),
149+
secondary.endLineOffset(), secondary.message());
149150
}
150151
return issueBuilder;
151152
}

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.sonar.plugins.python.indexer.PythonIndexer;
4040
import org.sonar.plugins.python.indexer.SonarQubePythonIndexer;
4141
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
42+
import org.sonar.python.project.config.ProjectConfigurationBuilder;
4243
import org.sonar.python.caching.CacheContextImpl;
4344
import org.sonar.python.parser.PythonParser;
4445

@@ -53,12 +54,20 @@ public final class IPynbSensor implements Sensor {
5354
private static final String FAIL_FAST_PROPERTY_NAME = "sonar.internal.analysis.failFast";
5455
private final SensorTelemetryStorage sensorTelemetryStorage;
5556
private final NoSonarLineInfoCollector noSonarLineInfoCollector;
57+
private final ProjectConfigurationBuilder projectConfigurationBuilder;
5658

5759
public IPynbSensor(FileLinesContextFactory fileLinesContextFactory,
5860
CheckFactory checkFactory,
5961
NoSonarFilter noSonarFilter,
60-
NoSonarLineInfoCollector noSonarLineInfoCollector) {
61-
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()}, noSonarLineInfoCollector);
62+
NoSonarLineInfoCollector noSonarLineInfoCollector,
63+
ProjectConfigurationBuilder projectConfigurationBuilder) {
64+
this(fileLinesContextFactory,
65+
checkFactory,
66+
noSonarFilter,
67+
null,
68+
new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()},
69+
noSonarLineInfoCollector,
70+
projectConfigurationBuilder);
6271
}
6372

6473
public IPynbSensor(
@@ -67,8 +76,10 @@ public IPynbSensor(
6776
NoSonarFilter noSonarFilter,
6877
@Nullable PythonIndexer indexer,
6978
RepositoryInfoProvider[] editionMetadataProviders,
70-
NoSonarLineInfoCollector noSonarLineInfoCollector) {
79+
NoSonarLineInfoCollector noSonarLineInfoCollector,
80+
ProjectConfigurationBuilder projectConfigurationBuilder) {
7181
this.noSonarLineInfoCollector = noSonarLineInfoCollector;
82+
this.projectConfigurationBuilder = projectConfigurationBuilder;
7283

7384
this.checks = createPythonChecks(checkFactory, editionMetadataProviders);
7485

@@ -119,7 +130,7 @@ private void processNotebooksFiles(List<PythonInputFile> pythonFiles, SensorCont
119130
pythonFiles = parseNotebooks(pythonFiles, context);
120131
// Disable caching for IPynb files for now see: SONARPY-2020
121132
CacheContext cacheContext = CacheContextImpl.dummyCache();
122-
PythonIndexer pythonIndexer = new SonarQubePythonIndexer(pythonFiles, cacheContext, context);
133+
PythonIndexer pythonIndexer = new SonarQubePythonIndexer(pythonFiles, cacheContext, context, projectConfigurationBuilder);
123134
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser::createIPythonParser,
124135
pythonIndexer, new DummyArchitectureCallback(), noSonarLineInfoCollector);
125136
scanner.execute(pythonFiles, context);

python-commons/src/main/java/org/sonar/plugins/python/PythonExtensions.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.sonar.plugins.python.mypy.MypySensor;
4242
import org.sonar.plugins.python.nosonar.NoSonarIssueFilter;
4343
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
44+
import org.sonar.python.project.config.ProjectConfigurationBuilder;
4445
import org.sonar.plugins.python.pylint.PylintRulesDefinition;
4546
import org.sonar.plugins.python.pylint.PylintSensor;
4647
import org.sonar.plugins.python.ruff.RuffRulesDefinition;
@@ -69,6 +70,7 @@ private PythonExtensions() {
6970

7071
public static void addCommonExtensions(Plugin.Context context) {
7172
context.addExtensions(
73+
ProjectConfigurationBuilder.class,
7274
NoSonarLineInfoCollector.class,
7375
NoSonarIssueFilter.class,
7476
buildPythonSuffix(),

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ private PythonVisitorContext createVisitorContext(PythonInputFile inputFile, Pyt
151151
PythonTreeMaker treeMaker = getTreeMaker(inputFile);
152152
FileInput parse = treeMaker.fileInput(astNode);
153153
visitorContext = new PythonVisitorContext.Builder(parse, pythonFile)
154+
.projectConfiguration(indexer.projectConfig())
154155
.workingDirectory(getWorkingDirectory(context))
155156
.packageName(indexer.packageName(inputFile))
156157
.projectLevelSymbolTable(indexer.projectLevelSymbolTable())

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.sonar.plugins.python.indexer.PythonIndexerWrapper;
4949
import org.sonar.plugins.python.indexer.SonarQubePythonIndexer;
5050
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
51+
import org.sonar.python.project.config.ProjectConfigurationBuilder;
5152
import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper;
5253
import org.sonar.python.caching.CacheContextImpl;
5354
import org.sonar.python.parser.PythonParser;
@@ -79,6 +80,7 @@ public final class PythonSensor implements Sensor {
7980

8081
private final SensorTelemetryStorage sensorTelemetryStorage;
8182
private final NoSonarLineInfoCollector noSonarLineInfoCollector;
83+
private final ProjectConfigurationBuilder projectConfigurationBuilder;
8284

8385
public PythonSensor(
8486
FileLinesContextFactory fileLinesContextFactory,
@@ -89,8 +91,11 @@ public PythonSensor(
8991
SonarLintCacheWrapper sonarLintCacheWrapper,
9092
AnalysisWarningsWrapper analysisWarnings,
9193
RepositoryInfoProviderWrapper editionMetadataProviderWrapper,
92-
ArchitectureCallbackWrapper architectureUDGBuilderWrapper, NoSonarLineInfoCollector noSonarLineInfoCollector) {
94+
ArchitectureCallbackWrapper architectureUDGBuilderWrapper,
95+
NoSonarLineInfoCollector noSonarLineInfoCollector,
96+
ProjectConfigurationBuilder projectConfigurationBuilder) {
9397
this.noSonarLineInfoCollector = noSonarLineInfoCollector;
98+
this.projectConfigurationBuilder = projectConfigurationBuilder;
9499

95100
this.checks = createPythonChecks(checkFactory, editionMetadataProviderWrapper.infoProviders())
96101
.addCustomChecks(customRuleRepositoriesWrapper.customRuleRepositories());
@@ -133,7 +138,7 @@ public void execute(SensorContext context) {
133138
}
134139
updatePythonVersionTelemetry(context, pythonVersionParameter);
135140
CacheContext cacheContext = CacheContextImpl.of(context);
136-
PythonIndexer pythonIndexer = this.indexer != null ? this.indexer : new SonarQubePythonIndexer(pythonFiles, cacheContext, context);
141+
PythonIndexer pythonIndexer = this.indexer != null ? this.indexer : new SonarQubePythonIndexer(pythonFiles, cacheContext, context, projectConfigurationBuilder);
137142
pythonIndexer.setSonarLintCache(sonarLintCache);
138143
TypeShed.setProjectLevelSymbolTable(pythonIndexer.projectLevelSymbolTable());
139144
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser::create,

python-commons/src/main/java/org/sonar/plugins/python/indexer/PythonIndexer.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
import org.sonar.plugins.python.api.PythonFile;
3636
import org.sonar.plugins.python.api.SonarLintCache;
3737
import org.sonar.plugins.python.api.caching.CacheContext;
38+
import org.sonar.plugins.python.api.project.configuration.ProjectConfiguration;
3839
import org.sonar.plugins.python.api.tree.FileInput;
40+
import org.sonar.python.project.config.ProjectConfigurationBuilder;
41+
import org.sonar.python.project.config.SignatureBasedAwsLambdaHandlersCollector;
3942
import org.sonar.python.parser.PythonParser;
4043
import org.sonar.python.semantic.ProjectLevelSymbolTable;
4144
import org.sonar.python.tree.PythonTreeMaker;
@@ -51,11 +54,21 @@ public abstract class PythonIndexer {
5154
private final Map<URI, String> packageNames = new ConcurrentHashMap<>();
5255
private final Supplier<PythonParser> parserSupplier = PythonParser::create;
5356
private final ProjectLevelSymbolTable projectLevelSymbolTable = ProjectLevelSymbolTable.empty();
57+
private final SignatureBasedAwsLambdaHandlersCollector signatureBasedAwsLambdaHandlersCollector = new SignatureBasedAwsLambdaHandlersCollector();
58+
private final ProjectConfigurationBuilder projectConfigurationBuilder;
59+
60+
protected PythonIndexer(ProjectConfigurationBuilder projectConfigurationBuilder) {
61+
this.projectConfigurationBuilder = projectConfigurationBuilder;
62+
}
5463

5564
public ProjectLevelSymbolTable projectLevelSymbolTable() {
5665
return projectLevelSymbolTable;
5766
}
5867

68+
public ProjectConfiguration projectConfig() {
69+
return projectConfigurationBuilder.build();
70+
}
71+
5972
public String packageName(PythonInputFile inputFile) {
6073
if (!packageNames.containsKey(inputFile.wrappedFile().uri())) {
6174
String name = pythonPackageName(inputFile.wrappedFile().file(), projectBaseDirAbsolutePath);
@@ -81,6 +94,7 @@ void removeFile(PythonInputFile inputFile) {
8194
}
8295
packageNames.remove(inputFile.wrappedFile().uri());
8396
projectLevelSymbolTable.removeModule(packageName, filename);
97+
projectConfigurationBuilder.removePackageAwsLambdaHandlers(packageName);
8498
}
8599

86100
void addFile(PythonInputFile inputFile) throws IOException {
@@ -91,6 +105,7 @@ void addFile(PythonInputFile inputFile) throws IOException {
91105
projectLevelSymbolTable.addProjectPackage(packageName);
92106
PythonFile pythonFile = SonarQubePythonFile.create(inputFile.wrappedFile());
93107
projectLevelSymbolTable.addModule(astRoot, packageName, pythonFile);
108+
signatureBasedAwsLambdaHandlersCollector.collect(projectConfigurationBuilder, astRoot, packageName);
94109
}
95110

96111
public abstract void buildOnce(SensorContext context);

python-commons/src/main/java/org/sonar/plugins/python/indexer/SonarLintPythonIndexer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.sonar.plugins.python.api.caching.CacheContext;
3333
import org.sonar.plugins.python.PythonInputFile;
3434
import org.sonar.plugins.python.PythonInputFileImpl;
35+
import org.sonar.python.project.config.ProjectConfigurationBuilder;
3536
import org.sonar.python.caching.CacheContextImpl;
3637
import org.sonar.python.caching.PythonReadCacheImpl;
3738
import org.sonar.python.caching.PythonWriteCacheImpl;
@@ -52,7 +53,8 @@ public class SonarLintPythonIndexer extends PythonIndexer implements ModuleFileL
5253
private static final long DEFAULT_MAX_LINES_FOR_INDEXING = 300_000;
5354
private static final String MAX_LINES_PROPERTY = "sonar.python.sonarlint.indexing.maxlines";
5455

55-
public SonarLintPythonIndexer(ModuleFileSystem moduleFileSystem) {
56+
public SonarLintPythonIndexer(ModuleFileSystem moduleFileSystem, ProjectConfigurationBuilder projectConfigurationBuilder) {
57+
super(projectConfigurationBuilder);
5658
this.moduleFileSystem = moduleFileSystem;
5759
}
5860

python-commons/src/main/java/org/sonar/plugins/python/indexer/SonarQubePythonIndexer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.sonar.plugins.python.PythonInputFile;
3434
import org.sonar.plugins.python.api.caching.CacheContext;
3535
import org.sonar.plugins.python.caching.Caching;
36+
import org.sonar.python.project.config.ProjectConfigurationBuilder;
3637
import org.sonar.python.index.Descriptor;
3738
import org.sonar.python.semantic.DependencyGraph;
3839
import org.sonar.python.semantic.SymbolUtils;
@@ -57,7 +58,8 @@ public class SonarQubePythonIndexer extends PythonIndexer {
5758
private final List<PythonInputFile> inputFiles = new ArrayList<>();
5859
private final Map<PythonInputFile, String> inputFileToFQN = new HashMap<>();
5960

60-
public SonarQubePythonIndexer(List<PythonInputFile> inputFiles, CacheContext cacheContext, SensorContext context) {
61+
public SonarQubePythonIndexer(List<PythonInputFile> inputFiles, CacheContext cacheContext, SensorContext context, ProjectConfigurationBuilder projectConfigurationBuilder) {
62+
super(projectConfigurationBuilder);
6163
this.projectBaseDirAbsolutePath = context.fileSystem().baseDir().getAbsolutePath();
6264
this.caching = new Caching(cacheContext, getCacheVersion(context));
6365
inputFiles.forEach(f -> {

python-commons/src/test/java/org/sonar/plugins/python/IPynbSensorTest.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.sonar.plugins.python.indexer.SonarLintPythonIndexer;
5454
import org.sonar.plugins.python.indexer.TestModuleFileSystem;
5555
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
56+
import org.sonar.python.project.config.ProjectConfigurationBuilder;
5657

5758
import static java.nio.charset.StandardCharsets.UTF_8;
5859
import static org.assertj.core.api.Assertions.assertThat;
@@ -133,7 +134,13 @@ private IPynbSensor sensor(PythonIndexer indexer) {
133134
FileLinesContext fileLinesContext = mock(FileLinesContext.class);
134135
when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext);
135136
CheckFactory checkFactory = new CheckFactory(activeRules);
136-
return new IPynbSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), indexer, new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()}, new NoSonarLineInfoCollector());
137+
return new IPynbSensor(fileLinesContextFactory,
138+
checkFactory,
139+
mock(NoSonarFilter.class),
140+
indexer,
141+
new RepositoryInfoProvider[]{new OpenSourceRepositoryInfoProvider()},
142+
new NoSonarLineInfoCollector(),
143+
new ProjectConfigurationBuilder());
137144
}
138145

139146
private PythonInputFile inputFile(String name) {
@@ -154,7 +161,7 @@ private PythonInputFile createInputFile(String name) {
154161
}
155162

156163
private SonarLintPythonIndexer pythonIndexer(List<PythonInputFile> files) {
157-
return new SonarLintPythonIndexer(new TestModuleFileSystem(files));
164+
return new SonarLintPythonIndexer(new TestModuleFileSystem(files), new ProjectConfigurationBuilder());
158165
}
159166

160167
@Test
@@ -176,7 +183,11 @@ private IPynbSensor notebookSensor() {
176183
FileLinesContext fileLinesContext = mock(FileLinesContext.class);
177184
when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext);
178185
CheckFactory checkFactory = new CheckFactory(activeRules);
179-
return new IPynbSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), new NoSonarLineInfoCollector());
186+
return new IPynbSensor(fileLinesContextFactory,
187+
checkFactory,
188+
mock(NoSonarFilter.class),
189+
new NoSonarLineInfoCollector(),
190+
new ProjectConfigurationBuilder());
180191
}
181192

182193
@Test

python-commons/src/test/java/org/sonar/plugins/python/PythonExtensionsTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ class PythonExtensionsTest {
4343
void testGetExtensions() {
4444
Version v79 = Version.create(7, 9);
4545
SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(v79, SonarQubeSide.SERVER, SonarEdition.DEVELOPER);
46-
assertThat(extensions(runtime)).hasSize(42);
46+
assertThat(extensions(runtime)).hasSize(43);
4747
assertThat(extensions(runtime)).contains(AnalysisWarningsWrapper.class);
4848
assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79)))
49-
.hasSize(22)
49+
.hasSize(23)
5050
.contains(SonarLintCache.class);
5151
}
5252

0 commit comments

Comments
 (0)