4343import org .sonar .plugins .python .api .caching .PythonReadCache ;
4444import org .sonar .plugins .python .api .caching .PythonWriteCache ;
4545import 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 ;
4750import org .sonar .python .caching .DummyCache ;
51+ import org .sonar .python .project .config .ProjectConfigurationBuilder ;
4852import org .sonar .python .semantic .ProjectLevelSymbolTable ;
4953import 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 ());
0 commit comments