getTestName(), 'SKIPPED-') !== false) { $this->markTestSkipped(); } $a_stmts = \Psalm\Internal\Provider\StatementsProvider::parseStatements($a, '7.4'); $b_stmts = \Psalm\Internal\Provider\StatementsProvider::parseStatements($b, '7.4'); $diff = \Psalm\Internal\Diff\FileStatementsDiffer::diff($a_stmts, $b_stmts, $a, $b); $this->assertSame( $same_methods, $diff[0] ); $this->assertSame( $same_signatures, $diff[1] ); $this->assertSame( $changed_methods, $diff[2] ); $this->assertSame(count($diff_map_offsets), count($diff[3])); $found_offsets = array_map( /** * @param array{0: int, 1: int, 2: int, 3: int} $arr * * @return array{0: int, 1: int} */ function (array $arr): array { return [$arr[2], $arr[3]]; }, $diff[3] ); $this->assertSame($diff_map_offsets, $found_offsets); } /** * @dataProvider getChanges * * @param string[] $same_methods * @param string[] $same_signatures * @param string[] $changed_methods * @param array $diff_map_offsets * */ public function testPartialAstDiff( string $a, string $b, array $same_methods, array $same_signatures, array $changed_methods, array $diff_map_offsets ): void { if (strpos($this->getTestName(), 'SKIPPED-') !== false) { $this->markTestSkipped(); } $file_changes = \Psalm\Internal\Diff\FileDiffer::getDiff($a, $b); $a_stmts = \Psalm\Internal\Provider\StatementsProvider::parseStatements($a, '7.4'); $traverser = new PhpParser\NodeTraverser; $traverser->addVisitor(new \Psalm\Internal\PhpVisitor\CloningVisitor); // performs a deep clone /** @var list */ $a_stmts_copy = $traverser->traverse($a_stmts); $this->assertTreesEqual($a_stmts, $a_stmts_copy); $b_stmts = \Psalm\Internal\Provider\StatementsProvider::parseStatements($b, '7.4', null, $a, $a_stmts_copy, $file_changes); $b_clean_stmts = \Psalm\Internal\Provider\StatementsProvider::parseStatements($b, '7.4'); $this->assertTreesEqual($b_clean_stmts, $b_stmts); $diff = \Psalm\Internal\Diff\FileStatementsDiffer::diff($a_stmts, $b_clean_stmts, $a, $b); $this->assertSame( $same_methods, $diff[0] ); $this->assertSame( $same_signatures, $diff[1] ); $this->assertSame( $changed_methods, $diff[2] ); $this->assertSame(count($diff_map_offsets), count($diff[3])); $found_offsets = array_map( /** * @param array{0: int, 1: int, 2: int, 3: int} $arr * * @return array{0: int, 1: int} */ function (array $arr): array { return [$arr[2], $arr[3]]; }, $diff[3] ); $this->assertSame($diff_map_offsets, $found_offsets); } /** * @param array $a * @param array $b * */ private function assertTreesEqual(array $a, array $b): void { $this->assertSame(count($a), count($b)); foreach ($a as $i => $a_stmt) { $b_stmt = $b[$i]; $this->assertNotSame($a_stmt, $b_stmt); $this->assertSame(get_class($a_stmt), get_class($b_stmt)); if ($a_stmt instanceof PhpParser\Node\Stmt\Expression && $b_stmt instanceof PhpParser\Node\Stmt\Expression ) { $this->assertSame(get_class($a_stmt->expr), get_class($b_stmt->expr)); } if ($a_doc = $a_stmt->getDocComment()) { $b_doc = $b_stmt->getDocComment(); $this->assertNotNull($b_doc, var_export($a_doc, true)); $this->assertNotSame($a_doc, $b_doc); $this->assertSame($a_doc->getStartLine(), $b_doc->getStartLine()); } $this->assertSame( $a_stmt->getAttribute('startFilePos'), $b_stmt->getAttribute('startFilePos') ); $this->assertSame( $a_stmt->getAttribute('endFilePos'), $b_stmt->getAttribute('endFilePos'), ($a_stmt instanceof PhpParser\Node\Stmt\Expression ? get_class($a_stmt->expr) : get_class($a_stmt)) . ' on line ' . $a_stmt->getLine() ); $this->assertSame($a_stmt->getLine(), $b_stmt->getLine()); if (isset($a_stmt->stmts)) { $this->assertTrue(isset($b_stmt->stmts)); /** * @psalm-suppress MixedArgument */ $this->assertTreesEqual($a_stmt->stmts, $b_stmt->stmts); } } } /** * @return array}> */ public function getChanges(): array { return [ 'sameFile' => [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ 'bar(); } } }', 'bar(); } } }', [], ['foo\c::foo'], [], [], ], 'vimeoDiff' => [ ' [ ' [ '