file_provider = new \Psalm\Tests\Internal\Provider\FakeFileProvider(); $config = new TestConfig(); $config->throw_exception = false; $providers = new Providers( $this->file_provider, new \Psalm\Tests\Internal\Provider\ParserInstanceCacheProvider(), null, null, new Provider\FakeFileReferenceCacheProvider() ); $this->project_analyzer = new ProjectAnalyzer( $config, $providers ); $this->project_analyzer->setPhpVersion('7.3'); } /** * @dataProvider providerTestErrorFix * * @param array> $file_stages * @param array> $error_positions * @param array $error_levels * * @return void */ public function testErrorFix( array $file_stages, array $error_positions, array $error_levels = [], bool $test_save = true ) { $this->project_analyzer->getCodebase()->diff_methods = true; $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; foreach ($error_levels as $error_type => $error_level) { $config->setCustomErrorLevel($error_type, $error_level); } if (!$file_stages) { throw new \UnexpectedValueException('$file_stages should not be empty'); } $start_files = array_shift($file_stages); // first batch foreach ($start_files as $file_path => $contents) { $this->file_provider->registerFile($file_path, $contents); $codebase->addFilesToAnalyze([$file_path => $file_path]); } $codebase->scanFiles(); $codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); $data = \Psalm\IssueBuffer::clear(); $found_positions = array_map( /** @param array{from: int} $a */ function (array $a) : int { return $a['from']; }, $data ); $this->assertSame($error_positions[0], $found_positions); foreach ($file_stages as $i => $file_stage) { foreach ($file_stage as $file_path => $contents) { $codebase->addTemporaryFileChanges( $file_path, $contents ); } $codebase->reloadFiles($this->project_analyzer, array_keys($file_stage)); $codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); $data = \Psalm\IssueBuffer::clear(); $found_positions = array_map( /** @param array{from: int} $a */ function (array $a) : int { return $a['from']; }, $data ); $this->assertSame($error_positions[$i + 1], $found_positions); } if ($test_save) { $last_file_stage = end($file_stages); foreach ($last_file_stage as $file_path => $_) { $codebase->removeTemporaryFileChanges($file_path); } foreach ($last_file_stage as $file_path => $contents) { $this->file_provider->registerFile($file_path, $contents); } $codebase->reloadFiles($this->project_analyzer, array_keys($last_file_stage)); $codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); $data = \Psalm\IssueBuffer::clear(); $found_positions = array_map( /** @param array{from: int} $a */ function (array $a) : int { return $a['from']; }, $data ); $this->assertSame($error_positions[count($file_stages)], $found_positions); } } /** * @return array>,error_positions:array>, error_levels?:array, test_save?:bool}> */ public function providerTestErrorFix() { return [ 'fixMissingColonSyntaxError' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[], [230], []], ], 'addReturnTypesToSingleMethod' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo(); } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo(); } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo(); } }', ], ], 'error_positions' => [[136, 317, 273], [323, 279], [329]], ], 'addSpaceAffectingOffsets' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo(); } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo(); } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo(); } }', ], ], 'error_positions' => [[373], [374], [375]], [ 'MixedAssignment' => \Psalm\Config::REPORT_INFO, ], ], 'fixReturnType' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[196, 144, 339, 290], [345, 296], []], [ 'MissingReturnType' => \Psalm\Config::REPORT_INFO, ], ], 'resolveNamesInDifferentFunction' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[333], []], [ 'InvalidDocblock' => \Psalm\Config::REPORT_INFO, ], ], 'bridgeStatements' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[136, 273], [279], [193, 144]], [ 'MissingReturnType' => \Psalm\Config::REPORT_INFO, ], ], 'colonReturnType' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[136, 273], [144, 136, 275]], [ 'MissingReturnType' => \Psalm\Config::REPORT_INFO, ], false, ], 'noChangeJustWeirdDocblocks' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[127], [127]], ], 'removeUseShouldInvalidate' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], [197]], ], 'removeGroupUseShouldInvalidate' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], [197]], ], 'removeUseWithAliasShouldInvalidate' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], [197]], ], 'removeGroupUseWithAliasShouldInvalidate' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], [197]], ], 'removeUseShouldInvalidateNoNamespace' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], [147]], ], 'removeGroupUseShouldInvalidateNoNamespace' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], [197]], ], 'removeUseWithAliasShouldInvalidateNoNamespace' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], [153]], ], 'removeGroupUseWithAliasShouldInvalidateNoNamespace' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], [197]], ], 'addUseShouldValidate' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [[197], []], ], 'fixMissingProperty' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'bar; } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'bar; } }', ], ], 'error_positions' => [[192, 192], []], ], 'traitMethodRenameDifferentFiles' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'bar(); } }', getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' 'bar(); } }', getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' 'bat(); } }', getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' 'bat(); } }', getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' 'bar(); } }', getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' [[], [238], [], [238], []], ], 'traitMethodRenameSameFile' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'bar(); } } trait T { public function bar() : string { return "hello"; } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'bar(); } } trait T { public function bat() : string { return "hello"; } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'bat(); } } trait T { public function bat() : string { return "hello"; } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'bat(); } } trait T { public function bar() : string { return "hello"; } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'bar(); } } trait T { public function bar() : string { return "hello"; } }', ], ], 'error_positions' => [[], [238], [], [238], []], ], 'duplicateMethodThenRemove' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[], [381], []], ], 'classCopiesUse' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[], [122], []], ], 'addMissingArgs' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[79, 238, 238], []], ], 'fixClassRef' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[306], [306, 452, 452]], ], 'addPropertyDocblock' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' */ private $bar = []; private $baz = []; public static function get() : A { return new A(); } }', ], ], 'error_positions' => [[152, 203], [337]], ], 'fixNotNullProperty' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[230], []], ], 'dontFixNotNullProperty' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[230], [230]], ], 'updatePropertyInitialization' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo = $foo; $this->bar = $bar; } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo = $foo; $this->bar = $bar; } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo = $foo; $this->bar = $bar; } }', ], ], 'error_positions' => [[], [202], []], ], 'addPartialMethodWithSyntaxError' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[], [381], []], ], ]; } }