From c837535c9d0512773744e91db541cf183ff25e28 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Fri, 22 Jan 2021 16:05:28 -0500 Subject: [PATCH] Remove file map cache when re-analyzing files (#5084) * Remove file map cache when re-analyzing files When the project is re-analyzed under the single thread condition (not using a process pool), the filemaps are not cleared before re-analyzing files. This means that file maps only get appended to. If you delete the contents of a file via the LSP, the file map will still be populated with all the old values for example. In doing this I had to write a few more tests to check my assumptions too, so adding those additional tests. * Rename test * Formatting * Formatting again! --- src/Psalm/Internal/Codebase/Analyzer.php | 5 + tests/FileUpdates/AnalyzedMethodTest.php | 36 +++++++ tests/Internal/Provider/FakeFileProvider.php | 7 ++ tests/LanguageServer/FileMapTest.php | 103 +++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 tests/LanguageServer/FileMapTest.php diff --git a/src/Psalm/Internal/Codebase/Analyzer.php b/src/Psalm/Internal/Codebase/Analyzer.php index ea0806383..2688c3229 100644 --- a/src/Psalm/Internal/Codebase/Analyzer.php +++ b/src/Psalm/Internal/Codebase/Analyzer.php @@ -576,6 +576,11 @@ class Analyzer $i = 0; foreach ($this->files_to_analyze as $file_path => $_) { + // Remove all current maps for the file, so new analysis doesn't + // only append to existing data. + unset($this->reference_map[$file_path]); + unset($this->type_map[$file_path]); + unset($this->argument_map[$file_path]); $analysis_worker($i, $file_path); ++$i; diff --git a/tests/FileUpdates/AnalyzedMethodTest.php b/tests/FileUpdates/AnalyzedMethodTest.php index 682d18837..2736b4244 100644 --- a/tests/FileUpdates/AnalyzedMethodTest.php +++ b/tests/FileUpdates/AnalyzedMethodTest.php @@ -1318,4 +1318,40 @@ class AnalyzedMethodTest extends \Psalm\Tests\TestCase ], ]; } + + public function testFileMapsUpdated(): void + { + $codebase = $this->project_analyzer->getCodebase(); + + $config = $codebase->config; + $config->throw_exception = false; + + $this->file_provider->registerFile('somefile.php', ' + addFilesToAnalyze(['somefile.php' => 'somefile.php']); + $codebase->scanFiles(); + $codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); + + $maps = $codebase->analyzer->getMapsForFile('somefile.php'); + + $this->assertNotEmpty($maps[0]); + + $this->file_provider->setOpenContents('somefile.php', ''); + + $codebase->reloadFiles($this->project_analyzer, ['somefile.php']); + $codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); + + $updated_maps = $codebase->analyzer->getMapsForFile('somefile.php'); + + $this->assertSame([], $updated_maps[0]); + $this->assertSame([], $updated_maps[1]); + $this->assertSame([], $updated_maps[2]); + } } diff --git a/tests/Internal/Provider/FakeFileProvider.php b/tests/Internal/Provider/FakeFileProvider.php index 94b403a21..45cac5a8a 100644 --- a/tests/Internal/Provider/FakeFileProvider.php +++ b/tests/Internal/Provider/FakeFileProvider.php @@ -40,6 +40,13 @@ class FakeFileProvider extends \Psalm\Internal\Provider\FileProvider $this->fake_files[$file_path] = $file_contents; } + public function setOpenContents(string $file_path, string $file_contents): void + { + if (isset($this->fake_files[strtolower($file_path)])) { + $this->fake_files[strtolower($file_path)] = $file_contents; + } + } + public function getModifiedTime(string $file_path): int { if (isset($this->fake_file_times[$file_path])) { diff --git a/tests/LanguageServer/FileMapTest.php b/tests/LanguageServer/FileMapTest.php new file mode 100644 index 000000000..5166da894 --- /dev/null +++ b/tests/LanguageServer/FileMapTest.php @@ -0,0 +1,103 @@ +file_provider = new \Psalm\Tests\Internal\Provider\FakeFileProvider(); + + $config = new TestConfig(); + + $providers = new Providers( + $this->file_provider, + new \Psalm\Tests\Internal\Provider\ParserInstanceCacheProvider(), + null, + null, + new Provider\FakeFileReferenceCacheProvider(), + new \Psalm\Tests\Internal\Provider\ProjectCacheProvider() + ); + + $this->project_analyzer = new ProjectAnalyzer( + $config, + $providers + ); + $this->project_analyzer->setPhpVersion('7.3'); + $this->project_analyzer->getCodebase()->store_node_types = true; + } + + public function testMapIsUpdatedOnReloadFiles(): void + { + $codebase = $this->project_analyzer->getCodebase(); + $config = $codebase->config; + $config->throw_exception = false; + + $this->addFile( + 'somefile.php', + 'file_provider->openFile('somefile.php'); + $codebase->scanFiles(); + $this->analyzeFile('somefile.php', new Context()); + [ $type_map ] = $codebase->analyzer->getMapsForFile('somefile.php'); + + $this->assertTrue(!empty($type_map)); + + $codebase->file_provider->setOpenContents('somefile.php', ''); + $codebase->reloadFiles($this->project_analyzer, ['somefile.php']); + [ $type_map ] = $codebase->analyzer->getMapsForFile('somefile.php'); + + $this->assertSame([], $type_map); + } + + public function testGetTypeMap(): void + { + $codebase = $this->project_analyzer->getCodebase(); + $config = $codebase->config; + $config->throw_exception = false; + + $this->addFile( + 'somefile.php', + 'file_provider->openFile('somefile.php'); + $codebase->scanFiles(); + $this->analyzeFile('somefile.php', new Context()); + [ $type_map ] = $codebase->analyzer->getMapsForFile('somefile.php'); + + $this->assertSame( + [ + 155 => [ + 156, + 'A', + ], + 146 => [ + 148, + '146-147:A', + ], + ], + $type_map + ); + } +}