diff --git a/tests/Fixtures/src/Completion/WrappedImplementsCompletion.php b/tests/Fixtures/src/Completion/WrappedImplementsCompletion.php deleted file mode 100644 index 5c464ca6..00000000 --- a/tests/Fixtures/src/Completion/WrappedImplementsCompletion.php +++ /dev/null @@ -1,12 +0,0 @@ -documents = new DocumentManager(); - $production = ProductionSyntaxSource::create(); - $this->parser = $production->source; - $this->counter = $production->counter; - - $fixturesRoot = __DIR__ . '/../Fixtures'; - $knowledge = $this->knowledgeStackForProjectRoot($fixturesRoot, $production); - - $memberResolver = new MemberResolver($knowledge->source); - $typeSource = new NativeTypeSource($knowledge->source, $memberResolver); - $symbolResolver = new SymbolResolver( - $this->parser, - $knowledge->source, - $memberResolver, - $typeSource, - ); - $capabilities = self::createStub(SessionCapabilitiesProviderInterface::class); - $capabilities->method('getSessionCapabilities')->willReturn(new SessionCapabilities()); - $this->handler = new CompletionHandler( - $this->documents, - self::completionSourceFor($knowledge->source, $symbolResolver, $capabilities), - ); - $this->syncHandler = new TextDocumentSyncHandler($this->documents, $knowledge->sink, $knowledge->invalidator); - } - - /** - * One parse per handled message. Completion is the fan-out - * that made this matter — its sources each call a different CodeResolverInterface - * method, and every one of them re-parsed the same unchanged document. - * - * These fixtures are chosen to resolve nothing from disk, so the total parse - * count *is* the open document's count. Parses of other documents are a - * separate cost that dedup does not remove and is not meant to: they are - * memoized per class by the repository. - */ - #[DataProvider('singleParseCompletions')] - public function testCompletionParsesTheDocumentOnce(string $fixture, string $marker): void - { - $cursor = $this->openFixtureAtCursor($fixture, $marker); - // didOpen is a message of its own; the server discards its parses before - // the completion request is handled. - $this->parser->endMessage(); - $before = $this->counter->parseCount; - - $this->handler->handle($this->completionRequestAt($cursor)); - - $parsesForRequest = $this->counter->parseCount - $before; - self::assertSame(1, $parsesForRequest, 'the whole request costs one parse'); - } - - /** - * Distinct completion kinds, so more than one source fan-out is pinned. - * - * @return iterable - * - * @codeCoverageIgnore - */ - public static function singleParseCompletions(): iterable - { - yield 'variable prefix' => ['src/Completion/Variables.php', 'param_prefix']; - yield 'member access' => ['src/Completion/MethodAccess.php', 'this_empty']; - yield 'static access' => ['src/Completion/StaticAccess.php', 'self_empty']; - } - - public function testImplementsAcrossMultipleLinesOffersInterfaces(): void - { - self::markTestSkipped( - 'Wrapped/multi-line implements is not yet handled: the classifier is single-line, ' - . 'so continuation lines fall through to Expression completion. See issue #310.', - ); - - // @phpstan-ignore-next-line deadCode.unreachable (documents the target behavior; unskip with #310) - $cursor = $this->openFixtureAtCursor('src/Completion/WrappedImplementsCompletion.php', 'wrapped_implements'); - $result = $this->handler->handle($this->completionRequestAt($cursor)); - - self::assertIsArray($result); - $labels = array_column($result['items'], 'label'); - self::assertContains('Entity', $labels, 'Interfaces are valid in a wrapped implements list'); - - $kinds = array_column($result['items'], 'kind'); - self::assertNotContains( - CompletionItemKind::Function->value, - $kinds, - 'Functions must not leak into a wrapped implements list (issue #310)', - ); - } -} diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 2efdde6b..4080710f 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -259,6 +259,9 @@ public function testTheSyncPathParsesATextOnce(): void * A request needs the tree, and trees are kept for one message only. Two * identical completion requests separate that from a standing tree cache: * the second re-parses only if the first message's memo was discarded. + * + * A variable prefix asks the resolver several questions, so it also shows + * each request costs one parse; a member or static access asks only one. */ public function testParsesAreScopedToOneMessageOnTheRequestPath(): void {