file_provider = new Provider\FakeFileProvider(); $this->project_analyzer = new \Psalm\Internal\Analyzer\ProjectAnalyzer( new TestConfig(), new \Psalm\Internal\Provider\Providers( $this->file_provider, new Provider\FakeParserCacheProvider() ) ); $this->project_analyzer->getCodebase()->collectLocations(); $this->project_analyzer->setPhpVersion('7.3'); } /** * @dataProvider providerReferenceLocations * * @param string $input_code * @param string $symbol * @param array $expected_locations * * @return void */ public function testReferenceLocations($input_code, $symbol, $expected_locations) { $test_name = $this->getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { $this->markTestSkipped('Skipped due to a bug.'); } $context = new Context(); $file_path = self::$src_dir_path . 'somefile.php'; $this->addFile($file_path, $input_code); $this->analyzeFile($file_path, $context); $found_references = $this->project_analyzer->getCodebase()->findReferencesToSymbol($symbol); if (!$found_references) { throw new \UnexpectedValueException('No file references found in this file'); } $this->assertSame(count($found_references), count($expected_locations)); foreach ($expected_locations as $i => $expected_location) { $actual_location = $found_references[$i]; $this->assertSame( $expected_location, $actual_location->getLineNumber() . ':' . $actual_location->getColumn() . ':' . $actual_location->getSelectedText() ); } } /** * @dataProvider providerReferencedMethods * * @param string $input_code * @param array> $expected_method_references_to_members * @param array> $expected_file_references_to_members * @param array> $expected_method_references_to_missing_members * @param array> $expected_file_references_to_missing_members * * @return void */ public function testReferencedMethods( string $input_code, array $expected_method_references_to_members, array $expected_method_references_to_missing_members, array $expected_file_references_to_members, array $expected_file_references_to_missing_members ) { $test_name = $this->getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { $this->markTestSkipped('Skipped due to a bug.'); } $context = new Context(); $file_path = '/var/www/somefile.php'; $this->addFile($file_path, $input_code); $this->analyzeFile($file_path, $context); $referenced_members = $this->project_analyzer->getCodebase()->file_reference_provider->getAllMethodReferencesToClassMembers(); $this->assertSame($expected_method_references_to_members, $referenced_members); $referenced_missing_members = $this->project_analyzer->getCodebase()->file_reference_provider->getAllMethodReferencesToMissingClassMembers(); $this->assertSame($expected_method_references_to_missing_members, $referenced_missing_members); $referenced_files = $this->project_analyzer->getCodebase()->file_reference_provider->getAllFileReferencesToClassMembers(); $this->assertSame($expected_file_references_to_members, $referenced_files); $referenced_missing_files = $this->project_analyzer->getCodebase()->file_reference_provider->getAllFileReferencesToMissingClassMembers(); $this->assertSame($expected_file_references_to_missing_members, $referenced_missing_files); } /** * @return array}> */ public function providerReferenceLocations() { return [ 'getClassLocation' => [ ' [ 'foo();', 'A::foo', ['6:32:foo'], ], ]; } /** * @return array>, * 2: array>, * 3: array>, * 4: array> * }> */ public function providerReferencedMethods() { return [ 'getClassReferences' => [ 'foo(); } } class C { public function foo() : void { new A(); } } class D { /** @var ?string */ public $foo; public function __construct() {} } $d = new D(); $d->foo = "bar"; $a = new A();', [ 'use:A:d7863b8594fe57f85cb8183fe55a6c15' => [ 'foo\b::__construct' => true, 'foo\c::foo' => true, ], 'foo\a::bat' => [ 'foo\b::__construct' => true, ], 'use:C:d7863b8594fe57f85cb8183fe55a6c15' => [ 'foo\b::bar' => true, ], 'foo\c::foo' => [ 'foo\b::bar' => true, ], ], [ 'foo\a::__construct' => [ 'foo\b::__construct' => true, 'foo\c::foo' => true, ], 'foo\c::__construct' => [ 'foo\b::bar' => true, ], ], [ 'foo\d::__construct' => [ '/var/www/somefile.php' => true, ], 'foo\d::$foo' => [ '/var/www/somefile.php' => true, ], ], [ 'foo\a::__construct' => [ '/var/www/somefile.php' => true, ], ], ], 'interpolateClassCalls' => [ 'bar(); } }', [ 'use:C:d7863b8594fe57f85cb8183fe55a6c15' => [ 'foo\d::bat' => true, ], 'foo\b::__construct' => [ 'foo\d::bat' => true, ], 'foo\a::__construct' => [ 'foo\d::bat' => true, ], 'foo\c::__construct' => [ 'foo\d::bat' => true, ], 'foo\b::bar' => [ 'foo\d::bat' => true, ], 'foo\a::bar' => [ 'foo\d::bat' => true, ], 'foo\c::bar' => [ 'foo\d::bat' => true, ], ], [], [], [] ], 'constantRefs' => [ ' [ 'foo\b::__construct' => true, 'foo\c::foo' => true, ], ], [], [], [], [], [] ], 'staticPropertyRefs' => [ ' [ 'foo\b::__construct' => true, 'foo\c::foo' => true, ], 'foo\a::$fooBar' => [ 'foo\b::__construct' => true, 'foo\c::foo' => true, ], ], [], [], [] ], 'instancePropertyRefs' => [ 'fooBar; } } class C { public function foo() : void { echo (new A)->fooBar; } }', [ 'use:A:d7863b8594fe57f85cb8183fe55a6c15' => [ 'foo\b::__construct' => true, 'foo\c::foo' => true, ], 'foo\a::$fooBar' => [ 'foo\b::__construct' => true, 'foo\c::foo' => true, ], ], [ 'foo\a::__construct' => [ 'foo\b::__construct' => true, 'foo\c::foo' => true, ], ], [], [] ], ]; } }