file_provider = new FakeFileProvider(); $config = new TestConfig(); $config->throw_exception = false; $providers = new Providers( $this->file_provider, new ParserInstanceCacheProvider(), null, null, new FakeFileReferenceCacheProvider(), new ProjectCacheProvider(), ); $this->codebase = new Codebase($config, $providers); $this->project_analyzer = new ProjectAnalyzer( $config, $providers, null, [], 1, null, $this->codebase, ); $this->project_analyzer->setPhpVersion('7.3', 'tests'); } /** * @dataProvider providerTestErrorFix * @param array> $file_stages * @param array> $error_positions * @param array $ignored_issues */ public function testErrorFix( array $file_stages, array $error_positions, array $ignored_issues = [], bool $test_save = true, bool $check_unused_code = false, ): void { $codebase = $this->codebase; $codebase->diff_methods = true; if ($check_unused_code) { $codebase->reportUnusedCode(); } $config = $codebase->config; foreach ($ignored_issues 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, true); $data = IssueBuffer::clear(); $found_positions = []; foreach ($data as $file_issues) { foreach ($file_issues as $issue_data) { $found_positions[] = $issue_data->from; } } $this->assertSame($error_positions[0], $found_positions); $i = 0; 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, true); $data = IssueBuffer::clear(); $found_positions = []; foreach ($data as $file_issues) { foreach ($file_issues as $issue_data) { $found_positions[] = $issue_data->from; } } $this->assertSame($error_positions[$i + 1], $found_positions, 'stage ' . ($i + 2)); } if ($test_save && $file_stages) { $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, true); $data = IssueBuffer::clear(); $found_positions = []; foreach ($data as $file_issues) { foreach ($file_issues as $issue_data) { $found_positions[] = $issue_data->from; } } $this->assertSame($error_positions[count($file_stages)], $found_positions, 'stage ' . ($i + 2)); } } /** * @return array>,error_positions:array>, ignored_issues?:array, test_save?:bool, check_unused_code?: bool}> */ public function providerTestErrorFix(): array { 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]], 'ignored_issues' => [ 'MixedAssignment' => Config::REPORT_INFO, ], ], 'fixReturnType' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[196, 144, 339, 290], [345, 296], []], 'ignored_issues' => [ 'MissingReturnType' => Config::REPORT_INFO, ], ], 'resolveNamesInDifferentFunction' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[333], []], 'ignored_issues' => [ 'InvalidDocblock' => Config::REPORT_INFO, ], ], 'bridgeStatements' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[136, 273], [279], [193, 144]], 'ignored_issues' => [ 'MissingReturnType' => Config::REPORT_INFO, ], ], 'colonReturnType' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[136, 273], [144, 136, 275]], 'ignored_issues' => [ 'MissingReturnType' => Config::REPORT_INFO, ], 'test_save' => 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], []], ], 'changeUseShouldInvalidateBadNew' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[247], []], ], 'changeUseShouldInvalidateBadReturn' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[196], []], ], 'changeUseShouldInvalidateBadDocblockReturn' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[184], []], ], 'changeUseShouldInvalidateBadParam' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[192], []], ], 'changeUseShouldInvalidateBadDocblockParam' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[183], []], ], 'changeUseShouldInvalidateBadExtends' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[142], []], ], '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, 231], [], [238, 231], []], ], '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, 231], [], [238, 231], []], ], 'duplicateMethodThenRemove' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[], [381], []], ], 'classCopiesUse' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[], [122], []], ], 'addMissingArgs' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[79, 238, 280, 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]], ], 'requiredFileWithConstructorInitialisation' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo = "hello"; } }', ], [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo = "hello"; } }', ], ], 'error_positions' => [[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], []], ], 'reformat' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[], []], ], 'dontForgetErrorInTraitMethod' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => 'foo();', getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' 'foo();', getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' [[192, 192], [192, 192]], ], 'stillUnusedClass' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [[84], [84]], 'ignored_issues' => [], 'test_save' => false, 'check_unused_code' => true, ], 'stillUnusedMethod' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo();', ], [ getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo();', ], ], 'error_positions' => [[201], [234]], 'ignored_issues' => [], 'test_save' => false, 'check_unused_code' => true, ], 'usedMethodWithNoAffectedConstantChanges' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' 'doFoo();', ], [ getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' 'doFoo();', ], ], 'error_positions' => [[], []], 'ignored_issues' => [], 'test_save' => false, 'check_unused_code' => true, ], 'syntaxErrorFixed' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [[347, 452, 538], [500, 500]], ], 'updateExampleWithSyntaxErrorThen' => [ [ [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' [[500, 500], [347, 452, 538], [500, 500]], ], ]; } }