Skip to content

Commit 2efd265

Browse files
joke1196sonartech
authored andcommitted
SONARPY-3233: ProjectLevelTypeTable has to be reset when updating files in SQ:IDE (#435)
GitOrigin-RevId: 43ff74f2658d9a64a7e9b887cfbb6a85d257c3ec
1 parent aa7f057 commit 2efd265

8 files changed

Lines changed: 86 additions & 9 deletions

File tree

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public abstract class PythonIndexer {
5757
private final Supplier<PythonParser> parserSupplier = PythonParser::create;
5858

5959
private final ProjectLevelSymbolTable projectLevelSymbolTable = ProjectLevelSymbolTable.empty();
60-
private final ProjectLevelTypeTable projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable);
60+
private ProjectLevelTypeTable projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable);
6161

6262
private final SignatureBasedAwsLambdaHandlersCollector signatureBasedAwsLambdaHandlersCollector = new SignatureBasedAwsLambdaHandlersCollector();
6363
private final ProjectConfigurationBuilder projectConfigurationBuilder;
@@ -78,6 +78,10 @@ public TypeTable projectLevelTypeTable() {
7878
return projectLevelTypeTable;
7979
}
8080

81+
protected void recreateProjectLevelTypeTable(){
82+
projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable);
83+
}
84+
8185
public String packageName(PythonInputFile inputFile) {
8286
if (!packageNames.containsKey(inputFile.wrappedFile().uri())) {
8387
String name = pythonPackageName(inputFile.wrappedFile().file(), projectBaseDirAbsolutePath);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,14 @@ private static List<PythonInputFile> getInputFiles(ModuleFileSystem moduleFileSy
118118
void addFile(PythonInputFile inputFile) throws IOException {
119119
super.addFile(inputFile);
120120
indexedFiles.put(inputFile.wrappedFile().absolutePath(), inputFile.wrappedFile());
121+
recreateProjectLevelTypeTable();
121122
}
122123

123124
@Override
124125
void removeFile(PythonInputFile inputFile) {
125126
super.removeFile(inputFile);
126127
indexedFiles.remove(inputFile.wrappedFile().absolutePath());
128+
recreateProjectLevelTypeTable();
127129
}
128130

129131
@Override

python-commons/src/test/java/org/sonar/plugins/python/indexer/SonarLintPythonIndexerTest.java

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@
4343
import org.sonar.plugins.python.api.caching.PythonReadCache;
4444
import org.sonar.plugins.python.api.caching.PythonWriteCache;
4545
import org.sonar.plugins.python.api.symbols.Symbol;
46-
import org.sonar.python.project.config.ProjectConfigurationBuilder;
46+
import org.sonar.plugins.python.api.types.v2.FunctionType;
47+
import org.sonar.plugins.python.api.types.v2.ModuleType;
48+
import org.sonar.plugins.python.api.types.v2.PythonType;
49+
import org.sonar.plugins.python.api.types.v2.UnknownType;
4750
import org.sonar.python.caching.DummyCache;
51+
import org.sonar.python.project.config.ProjectConfigurationBuilder;
4852
import org.sonar.python.semantic.ProjectLevelSymbolTable;
4953
import org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileEvent;
5054

@@ -130,7 +134,7 @@ void test_indexer_file_removed_twice() {
130134
}
131135

132136
@Test
133-
void test_indexer_added_file() throws IOException {
137+
void test_indexer_added_file() {
134138
PythonInputFile file3 = createInputFile("added.py");
135139
ModuleFileEvent moduleFileEvent = mock(ModuleFileEvent.class);
136140
when(moduleFileEvent.getType()).thenReturn(ModuleFileEvent.Type.CREATED);
@@ -145,7 +149,7 @@ void test_indexer_added_file() throws IOException {
145149
}
146150

147151
@Test
148-
void test_indexer_added_nonexistent_file() throws IOException {
152+
void test_indexer_added_nonexistent_file() {
149153
InputFile nonExistentFile = TestInputFileBuilder.create("moduleKey", "nonexistent.py")
150154
.setModuleBaseDir(baseDir.toPath())
151155
.setCharset(StandardCharsets.UTF_8)
@@ -163,7 +167,7 @@ void test_indexer_added_nonexistent_file() throws IOException {
163167
}
164168

165169
@Test
166-
void test_indexer_modified_file() throws IOException {
170+
void test_indexer_modified_file() {
167171
ModuleFileEvent moduleFileEvent = mock(ModuleFileEvent.class);
168172
when(moduleFileEvent.getType()).thenReturn(ModuleFileEvent.Type.MODIFIED);
169173
when(moduleFileEvent.getTarget()).thenReturn(file2.wrappedFile());
@@ -181,6 +185,67 @@ void test_indexer_non_python_file() {
181185
testNonPythonFile(null);
182186
}
183187

188+
@Test
189+
void test_indexer_added_file_updates_type_table() {
190+
191+
// Verify that the "added" module type is initially unknown
192+
PythonType addedModuleTypeBefore = pythonIndexer.projectLevelTypeTable().getModuleType(List.of("added_type"));
193+
assertThat(addedModuleTypeBefore).isInstanceOf(UnknownType.class);
194+
195+
// Create and add a new file
196+
PythonInputFile file = inputFile("added_type.py");
197+
ModuleFileEvent moduleFileEvent = mock(ModuleFileEvent.class);
198+
when(moduleFileEvent.getType()).thenReturn(ModuleFileEvent.Type.CREATED);
199+
when(moduleFileEvent.getTarget()).thenReturn(file.wrappedFile());
200+
pythonIndexer.process(moduleFileEvent);
201+
202+
PythonType addedModuleTypeAfter = pythonIndexer.projectLevelTypeTable().getModuleType(List.of("added_type"));
203+
assertThat(addedModuleTypeAfter).isInstanceOf(ModuleType.class);
204+
assertThat(addedModuleTypeAfter.toString()).contains("added_type");
205+
206+
PythonType addedFunction = pythonIndexer.projectLevelTypeTable().getType("added_type.A.foo");
207+
assertThat(addedFunction).isInstanceOf(FunctionType.class);
208+
assertThat(addedFunction.toString()).contains("FunctionType[foo]");
209+
210+
}
211+
212+
@Test
213+
void test_indexer_removed_file_updates_type_table() {
214+
// Create and add a new file
215+
PythonInputFile file = inputFile("removed_type.py");
216+
ModuleFileEvent moduleFileEvent = mock(ModuleFileEvent.class);
217+
when(moduleFileEvent.getType()).thenReturn(ModuleFileEvent.Type.CREATED);
218+
when(moduleFileEvent.getTarget()).thenReturn(file.wrappedFile());
219+
pythonIndexer.process(moduleFileEvent);
220+
221+
assertThat(projectLevelSymbolTable.getSymbolsFromModule("removed_type")).isNotNull();
222+
PythonType addedModule = pythonIndexer.projectLevelTypeTable().getModuleType(List.of("removed_type"));
223+
assertThat(addedModule).isInstanceOf(ModuleType.class);
224+
assertThat(addedModule.toString()).contains("removed_type");
225+
226+
PythonType functionBar = pythonIndexer.projectLevelTypeTable().getType("removed_type.B.bar");
227+
assertThat(functionBar).isInstanceOf(FunctionType.class);
228+
assertThat(functionBar.toString()).contains("FunctionType[bar]");
229+
230+
when(moduleFileEvent.getType()).thenReturn(ModuleFileEvent.Type.DELETED);
231+
when(moduleFileEvent.getTarget()).thenReturn(file.wrappedFile());
232+
pythonIndexer.process(moduleFileEvent);
233+
234+
PythonType removedModule = pythonIndexer.projectLevelTypeTable().getModuleType(List.of("removed_type"));
235+
assertThat(removedModule).isInstanceOf(UnknownType.class);
236+
PythonType removedClass = pythonIndexer.projectLevelTypeTable().getType("removed_type.B");
237+
assertThat(removedClass).isInstanceOf(UnknownType.class);
238+
239+
// Modifying the file here would act a simply recreating it
240+
when(moduleFileEvent.getType()).thenReturn(ModuleFileEvent.Type.MODIFIED);
241+
when(moduleFileEvent.getTarget()).thenReturn(file.wrappedFile());
242+
pythonIndexer.process(moduleFileEvent);
243+
244+
PythonType reAddedFun = pythonIndexer.projectLevelTypeTable().getType("removed_type.B.bar");
245+
assertThat(reAddedFun).isInstanceOf(FunctionType.class);
246+
assertThat(reAddedFun.toString()).contains("FunctionType[bar]");
247+
}
248+
184249
@Test
185250
void test_sonarlint_cache() throws IOException {
186251
PythonIndexer indexer = new SonarLintPythonIndexer(moduleFileSystem, new ProjectConfigurationBuilder());
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class A:
2+
def foo():
3+
...
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class B:
2+
def bar():
3+
...

python-frontend/src/main/java/org/sonar/python/semantic/v2/ProjectLevelTypeTable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class ProjectLevelTypeTable implements TypeTable {
3939
public ProjectLevelTypeTable(ProjectLevelSymbolTable projectLevelSymbolTable) {
4040
this.lazyTypesContext = new LazyTypesContext(this);
4141
this.symbolsModuleTypeProvider = new SymbolsModuleTypeProvider(projectLevelSymbolTable, lazyTypesContext);
42-
this.rootModule = this.symbolsModuleTypeProvider.createBuiltinModule();
42+
this.rootModule = this.symbolsModuleTypeProvider.getRootModule();
4343
this.createOrResolveSubModuleLock = new ReentrantLock();
4444
}
4545

python-frontend/src/main/java/org/sonar/python/semantic/v2/SymbolsModuleTypeProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public SymbolsModuleTypeProvider(ProjectLevelSymbolTable projectLevelSymbolTable
5656
this.rootModule = new ModuleType(null, null, null, rootModuleMembers);
5757
}
5858

59-
public ModuleType createBuiltinModule() {
59+
public ModuleType getRootModule() {
6060
return rootModule;
6161
}
6262

python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3006,7 +3006,7 @@ void resolveIncorrectLazyType2() {
30063006
ProjectLevelTypeTable projectLevelTypeTable = new ProjectLevelTypeTable(empty);
30073007
LazyTypesContext lazyTypesContext = projectLevelTypeTable.lazyTypesContext();
30083008
SymbolsModuleTypeProvider symbolsModuleTypeProvider = new SymbolsModuleTypeProvider(empty, lazyTypesContext);
3009-
ModuleType builtinModule = symbolsModuleTypeProvider.createBuiltinModule();
3009+
ModuleType builtinModule = symbolsModuleTypeProvider.getRootModule();
30103010
symbolsModuleTypeProvider.convertModuleType(List.of("typing"), builtinModule);
30113011

30123012
ClassSymbol symbol = Mockito.mock(ClassSymbolImpl.class);
@@ -3020,7 +3020,7 @@ void convertTypeshedModuleWithAliases() {
30203020
ProjectLevelTypeTable projectLevelTypeTable = new ProjectLevelTypeTable(empty);
30213021
LazyTypesContext lazyTypesContext = projectLevelTypeTable.lazyTypesContext();
30223022
SymbolsModuleTypeProvider symbolsModuleTypeProvider = new SymbolsModuleTypeProvider(empty, lazyTypesContext);
3023-
ModuleType builtinModule = symbolsModuleTypeProvider.createBuiltinModule();
3023+
ModuleType builtinModule = symbolsModuleTypeProvider.getRootModule();
30243024
PythonType responses = symbolsModuleTypeProvider.convertModuleType(List.of("fastapi", "responses"), builtinModule);
30253025
assertThat(responses.resolveMember("FileResponse")).containsInstanceOf(ClassType.class);
30263026
PythonType concurrency = symbolsModuleTypeProvider.convertModuleType(List.of("fastapi", "concurrency"), builtinModule);

0 commit comments

Comments
 (0)