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 testCompletionOnThisWithNoAssignment(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', ' } }' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\A&static', '->', 213], $codebase->getCompletionDataAtPosition('somefile.php', new Position(8, 31))); } public function testCompletionOnThisWithAssignmentBelow(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', ' $a = "foo"; } }' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\A&static', '->', 220], $codebase->getCompletionDataAtPosition('somefile.php', new Position(8, 31))); } public function testCompletionOnThisWithIfBelow(): 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()); $codebase->addTemporaryFileChanges( 'somefile.php', ' if(rand(0, 1)) {} } }' ); $codebase->reloadFiles($this->project_analyzer, ['somefile.php']); $codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); $this->assertSame(['B\A&static', '->', 220], $codebase->getCompletionDataAtPosition('somefile.php', new Position(8, 31))); } public function testCompletionOnThisProperty(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'cee_me = new C(); } public function foo() : void { $this->cee_me-> } }' ); $codebase = $this->project_analyzer->getCodebase(); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\C', '->', 454], $codebase->getCompletionDataAtPosition('somefile.php', new Position(16, 39))); } public function testCompletionOnThisPropertyWithCharacter(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'cee_me = new C(); } public function foo() : void { $this->cee_me->o } }' ); $codebase = $this->project_analyzer->getCodebase(); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\C', '->', 455], $codebase->getCompletionDataAtPosition('somefile.php', new Position(16, 40))); } public function testCompletionOnThisPropertyWithAnotherCharacter(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'cee_me = new C(); } public function foo() : void { $this->cee_me->ot } }' ); $codebase = $this->project_analyzer->getCodebase(); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertNull($codebase->getCompletionDataAtPosition('somefile.php', new Position(16, 41))); } public function testCompletionOnTemplatedThisProperty(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 't = $t; } public function otherFunction() : void } class A { /** @var C */ protected $cee_me; public function __construct() { $this->cee_me = new C("hello"); } public function foo() : void { $this->cee_me-> } }' ); $codebase = $this->project_analyzer->getCodebase(); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $completion_data = $codebase->getCompletionDataAtPosition('somefile.php', new Position(25, 39)); $this->assertSame(['B\C', '->', 726], $completion_data); $completion_items = $codebase->getCompletionItemsForClassishThing($completion_data[0], $completion_data[1]); $this->assertCount(3, $completion_items); } public function testCompletionOnMethodReturnValue(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'foo()-> } ' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\A', '->', 259], $codebase->getCompletionDataAtPosition('somefile.php', new Position(9, 31))); } public function testCompletionOnMethodArgument(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'foo($c->) } ' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\C', '->', 298], $codebase->getCompletionDataAtPosition('somefile.php', new Position(11, 32))); } public function testCompletionOnMethodReturnValueWithArgument(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'foo($c)-> } ' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\A', '->', 299], $codebase->getCompletionDataAtPosition('somefile.php', new Position(11, 33))); } public function testCompletionOnVariableWithWhitespace(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', ' }' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\A', '->', 126], $codebase->getCompletionDataAtPosition('somefile.php', new Position(6, 25))); } public function testCompletionOnVariableWithWhitespaceAndReturn(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', ' } ' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\A', '->', 150], $codebase->getCompletionDataAtPosition('somefile.php', new Position(7, 26))); } public function testCompletionOnMethodReturnValueWithWhitespace(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'foo() -> } ' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\A', '->', 261], $codebase->getCompletionDataAtPosition('somefile.php', new Position(10, 32))); } public function testCompletionOnMethodReturnValueWithWhitespaceAndReturn(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'foo() -> } ' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\A', '->', 285], $codebase->getCompletionDataAtPosition('somefile.php', new Position(11, 26))); } public function testCompletionOnMethodReturnValueWhereParamIsClosure(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'map(function ($foo) {})-> }' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\Collection', '->', 312], $codebase->getCompletionDataAtPosition('somefile.php', new Position(10, 49))); } public function testCompletionOnMethodReturnValueWhereParamIsClosureWithStmt(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', 'map(function ($foo) {return $foo;})-> }' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame(['B\Collection', '->', 324], $codebase->getCompletionDataAtPosition('somefile.php', new Position(10, 61))); } public function testCursorPositionOnMethodCompletion(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', ' } public function baz() {} }' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $completion_data = $codebase->getCompletionDataAtPosition('somefile.php', new Position(5, 31)); $this->assertSame(['B\A&static', '->', 146], $completion_data); $completion_items = $codebase->getCompletionItemsForClassishThing($completion_data[0], $completion_data[1]); $this->assertCount(2, $completion_items); $this->assertSame('bar($0)', $completion_items[0]->insertText); $this->assertSame('baz()', $completion_items[1]->insertText); } public function testCompletionOnNewExceptionWithoutNamespace(): 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()); $this->assertSame(['*Ex', 'symbol', 78], $codebase->getCompletionDataAtPosition('somefile.php', new Position(2, 32))); } public function testCompletionOnNewExceptionWithNamespaceNoUse(): 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()); $completion_data = $codebase->getCompletionDataAtPosition('somefile.php', new Position(4, 32)); $this->assertSame( [ '*Ex', 'symbol', 110, ], $completion_data ); $completion_items = $codebase->getCompletionItemsForPartialSymbol($completion_data[0], $completion_data[2], 'somefile.php'); $this->assertNotEmpty($completion_items); $this->assertSame('Exception', $completion_items[0]->label); $this->assertSame('Exception', $completion_items[0]->insertText); $this->assertNotNull($completion_items[0]->additionalTextEdits); $this->assertCount(1, $completion_items[0]->additionalTextEdits); $this->assertSame('use Exception;' . "\n" . "\n", $completion_items[0]->additionalTextEdits[0]->newText); $this->assertSame(3, $completion_items[0]->additionalTextEdits[0]->range->start->line); $this->assertSame(16, $completion_items[0]->additionalTextEdits[0]->range->start->character); $this->assertSame(3, $completion_items[0]->additionalTextEdits[0]->range->end->line); $this->assertSame(16, $completion_items[0]->additionalTextEdits[0]->range->end->character); } public function testCompletionOnNewExceptionWithNamespaceAndUse(): 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()); $completion_data = $codebase->getCompletionDataAtPosition('somefile.php', new Position(9, 30)); $this->assertSame( [ '*ArrayO', 'symbol', 220, ], $completion_data ); $completion_items = $codebase->getCompletionItemsForPartialSymbol($completion_data[0], $completion_data[2], 'somefile.php'); $this->assertCount(1, $completion_items); $this->assertNotNull($completion_items[0]->additionalTextEdits); $this->assertCount(1, $completion_items[0]->additionalTextEdits); $this->assertSame("\n" . 'use ArrayObject;', $completion_items[0]->additionalTextEdits[0]->newText); $this->assertSame(3, $completion_items[0]->additionalTextEdits[0]->range->start->line); $this->assertSame(44, $completion_items[0]->additionalTextEdits[0]->range->start->character); $this->assertSame(3, $completion_items[0]->additionalTextEdits[0]->range->end->line); $this->assertSame(44, $completion_items[0]->additionalTextEdits[0]->range->end->character); } public function testCompletionOnInstanceofWithNamespaceAndUse(): 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()); $completion_data = $codebase->getCompletionDataAtPosition('somefile.php', new Position(10, 41)); $this->assertSame( [ '*Ant', 'symbol', 267, ], $completion_data ); $completion_items = $codebase->getCompletionItemsForPartialSymbol($completion_data[0], $completion_data[2], 'somefile.php'); $this->assertCount(2, $completion_items); } public function testCompletionOnClassReference(): 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()); $completion_data = $codebase->getCompletionDataAtPosition('somefile.php', new Position(8, 23)); $this->assertSame(['Bar\Alpha', '::', 221], $completion_data); $completion_items = $codebase->getCompletionItemsForClassishThing($completion_data[0], $completion_data[1]); $this->assertCount(2, $completion_items); } public function testNoCrashOnLoopId(): 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()); } public function testCompletionOnArrayKey(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $this->addFile( 'somefile.php', ' 1, "bar" => 2]; $my_array[] ' ); $codebase->file_provider->openFile('somefile.php'); $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $completion_data = $codebase->getCompletionDataAtPosition('somefile.php', new Position(2, 26)); $this->assertSame( [ 'array{bar: 2, foo: 1}', '[', 86, ], $completion_data ); $completion_items = $codebase->getCompletionItemsForArrayKeys($completion_data[0]); $this->assertCount(2, $completion_items); } public function testTypeContextForFunctionArgument(): 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 = $codebase->getTypeContextAtPosition('somefile.php', new Position(5, 24)); $this->assertSame('string', (string) $type); } public function testTypeContextForFunctionArgumentWithWhiteSpace(): 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 = $codebase->getTypeContextAtPosition('somefile.php', new Position(5, 32)); $this->assertSame('bool', (string) $type); } public function testCompletionsForType(): void { $codebase = $this->project_analyzer->getCodebase(); $config = $codebase->config; $config->throw_exception = false; $completion_items = $codebase->getCompletionItemsForType(Type::parseString('bool')); $this->assertCount(2, $completion_items); $completion_items = $codebase->getCompletionItemsForType(Type::parseString('true')); $this->assertCount(1, $completion_items); $completion_items = $codebase->getCompletionItemsForType(Type::parseString("'yes'|'no'")); $this->assertCount(2, $completion_items); $completion_items = $codebase->getCompletionItemsForType(Type::parseString("1|2|3")); $this->assertCount(3, $completion_items); // Floats not supported. $completion_items = $codebase->getCompletionItemsForType(Type::parseString("1.0")); $this->assertCount(0, $completion_items); $completion_items = $codebase->getCompletionItemsForType(Type::parseString("DateTime::RFC3339")); $this->assertCount(1, $completion_items); } }