├── tests ├── Validation │ ├── cases │ │ ├── namespaces4.php │ │ ├── namespaces6.php │ │ ├── namespaces7.php │ │ ├── scopedPropertyAccess4.php │ │ ├── namespaces3.php │ │ ├── ifStatement1.php │ │ ├── magicConstantsShouldBeGlobal.php │ │ ├── namespaces9.php │ │ ├── functionUse2.php │ │ ├── namespaces7.php.expected.json │ │ ├── namespaces9.php.expected.json │ │ ├── param1.php │ │ ├── staticInArray.php.expected.json │ │ ├── namespaces2.php │ │ ├── arrayValueShouldBeBoolean.php │ │ ├── caseStatement1.php │ │ ├── staticInArray.php │ │ ├── constantsInFunctionParamDefault.php │ │ ├── exceptions1.php │ │ ├── functionUse.php │ │ ├── interfaceProperty.php │ │ ├── nameToken.php │ │ ├── testQualifiedNameOutsideOfNamespace.php │ │ ├── scopedPropertyAccess3.php │ │ ├── scopedPropertyAccess2.php │ │ ├── constants5.php │ │ ├── docBlocksOnNamespaceDefinition.php │ │ ├── stringVariable.php │ │ ├── magicConsts.php │ │ ├── namespaces5.php │ │ ├── classDefinition1.php │ │ ├── namespaces8.php │ │ ├── static4.php │ │ ├── constants4.php │ │ ├── scopedPropertyAccess.php │ │ ├── objectCreation.php │ │ ├── constants3.php │ │ ├── memberAccess1.php │ │ ├── memberAccess2.php │ │ ├── objectCreation2.php │ │ ├── objectCreation3.php │ │ ├── scopedPropertyAccess4.php.expected.json │ │ ├── verifyFqsenOnClassProperty.php │ │ ├── anonymousClassMembersShouldNotBeSymbols.php │ │ ├── memberAccess5.php │ │ ├── constants.php │ │ ├── propertyName2.php │ │ ├── self5.php │ │ ├── constants2.php │ │ ├── methodReturnType.php │ │ ├── self2.php │ │ ├── self3.php │ │ ├── parent1.php │ │ ├── parent2.php │ │ ├── parent3.php │ │ ├── static1.php │ │ ├── static2.php │ │ ├── static3.php │ │ ├── newStatic.php │ │ ├── memberAccess4.php │ │ ├── propertyName1.php │ │ ├── namespaces4.php.expected.json │ │ ├── functionUse.php.expected.json │ │ ├── multiplePreceedingComments.php │ │ ├── staticMethodReturnType.php │ │ ├── multipleNamespaces.php │ │ ├── forLoopReference1.php │ │ ├── self1.php │ │ ├── self4.php │ │ ├── namespaces3.php.expected.json │ │ ├── scopedPropertyAccess5.php │ │ ├── classProperty1.php │ │ ├── memberAccess3.php │ │ ├── functionUse2.php.expected.json │ │ ├── parameterTypeResolution1.php │ │ ├── returnType.php │ │ ├── WithReturnTypehints.php │ │ ├── memberCall1.php │ │ ├── interfaceProperty.php.expected.json │ │ ├── namespaces6.php.expected.json │ │ ├── docBlocksOnNamespaceDefinition.php.expected.json │ │ ├── anonymousClassMembersShouldNotBeSymbols.php.expected.json │ │ ├── exceptions1.php.expected.json │ │ ├── scopedPropertyAccess2.php.expected.json │ │ ├── ifStatement1.php.expected.json │ │ ├── caseStatement1.php.expected.json │ │ ├── testQualifiedNameOutsideOfNamespace.php.expected.json │ │ ├── magicConstantsShouldBeGlobal.php.expected.json │ │ ├── namespaces2.php.expected.json │ │ ├── namespaces8.php.expected.json │ │ ├── namespaces5.php.expected.json │ │ ├── arrayValueShouldBeBoolean.php.expected.json │ │ ├── propertyName1.php.expected.json │ │ ├── propertyName2.php.expected.json │ │ ├── magicConsts.php.expected.json │ │ ├── scopedPropertyAccess3.php.expected.json │ │ ├── multiplePreceedingComments.php.expected.json │ │ ├── nameToken.php.expected.json │ │ ├── objectCreation3.php.expected.json │ │ ├── param1.php.expected.json │ │ ├── constantsInFunctionParamDefault.php.expected.json │ │ ├── scopedPropertyAccess5.php.expected.json │ │ ├── forLoopReference1.php.expected.json │ │ ├── parameterTypeResolution1.php.expected.json │ │ ├── stringVariable.php.expected.json │ │ ├── returnType.php.expected.json │ │ ├── constants5.php.expected.json │ │ ├── classDefinition1.php.expected.json │ │ ├── objectCreation.php.expected.json │ │ ├── self5.php.expected.json │ │ ├── newStatic.php.expected.json │ │ ├── constants4.php.expected.json │ │ ├── constants.php.expected.json │ │ ├── constants2.php.expected.json │ │ ├── constants3.php.expected.json │ │ ├── memberAccess1.php.expected.json │ │ ├── memberAccess2.php.expected.json │ │ ├── static4.php.expected.json │ │ ├── verifyFqsenOnClassProperty.php.expected.json │ │ ├── memberAccess5.php.expected.json │ │ ├── scopedPropertyAccess.php.expected.json │ │ ├── methodReturnType.php.expected.json │ │ ├── memberAccess4.php.expected.json │ │ ├── self4.php.expected.json │ │ ├── memberCall1.php.expected.json │ │ ├── objectCreation2.php.expected.json │ │ └── memberAccess3.php.expected.json │ └── disabled.json ├── MockPhpDocument.php ├── MockProtocolStream.php ├── ProtocolStreamWriterTest.php ├── Index │ └── IndexTest.php ├── Server │ ├── TextDocument │ │ ├── References │ │ │ ├── NamespacedTest.php │ │ │ └── GlobalFallbackTest.php │ │ ├── DidCloseTest.php │ │ ├── DidChangeTest.php │ │ └── Definition │ │ │ ├── NamespacedTest.php │ │ │ └── GlobalFallbackTest.php │ └── Workspace │ │ └── DidChangeWatchedFilesTest.php ├── Utils │ ├── StripStringOverlapTest.php │ └── FileUriTest.php ├── ProtocolStreamReaderTest.php ├── PhpDocumentLoaderTest.php ├── ClientHandlerTest.php ├── PhpDocumentTest.php ├── DefinitionResolverTest.php └── Diagnostics │ └── InvalidThisUsageTest.php ├── src ├── ProtocolReader.php ├── ProtocolWriter.php ├── IdGenerator.php ├── ContentRetriever │ ├── ContentRetriever.php │ ├── FileSystemContentRetriever.php │ └── ClientContentRetriever.php ├── FilesFinder │ ├── FilesFinder.php │ ├── FileSystemFilesFinder.php │ └── ClientFilesFinder.php ├── Index │ ├── StubsIndex.php │ ├── GlobalIndex.php │ ├── ProjectIndex.php │ ├── DependenciesIndex.php │ └── ReadableIndex.php ├── Cache │ ├── Cache.php │ ├── ClientCache.php │ └── FileSystemCache.php ├── StderrLogger.php ├── Factory │ ├── RangeFactory.php │ ├── LocationFactory.php │ └── CompletionItemFactory.php ├── Client │ ├── XCache.php │ ├── Workspace.php │ ├── Window.php │ └── TextDocument.php ├── LanguageClient.php ├── ContentTooLargeException.php ├── Message.php ├── ProtocolStreamWriter.php ├── ComposerScripts.php ├── ProtocolStreamReader.php ├── ClientHandler.php ├── SignatureInformationFactory.php └── FqnUtilities.php ├── LICENSE.txt ├── composer.json └── benchmarks ├── parsing.php └── completion.php /tests/Validation/cases/namespaces4.php: -------------------------------------------------------------------------------- 1 | TRUE]; 4 | } -------------------------------------------------------------------------------- /tests/Validation/cases/caseStatement1.php: -------------------------------------------------------------------------------- 1 | b(); 7 | }; -------------------------------------------------------------------------------- /tests/Validation/cases/interfaceProperty.php: -------------------------------------------------------------------------------- 1 | hi"; 8 | } 9 | } -------------------------------------------------------------------------------- /tests/Validation/cases/magicConsts.php: -------------------------------------------------------------------------------- 1 | true 6 | ); 7 | } -------------------------------------------------------------------------------- /tests/Validation/cases/namespaces5.php: -------------------------------------------------------------------------------- 1 | a; 8 | 9 | class A { 10 | public $a = 3; 11 | } -------------------------------------------------------------------------------- /tests/Validation/cases/namespaces8.php: -------------------------------------------------------------------------------- 1 | inline_diff_renderer; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Validation/cases/constants3.php: -------------------------------------------------------------------------------- 1 | a(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Validation/cases/memberAccess2.php: -------------------------------------------------------------------------------- 1 | a(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Validation/cases/objectCreation2.php: -------------------------------------------------------------------------------- 1 | hi(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/Validation/cases/objectCreation3.php: -------------------------------------------------------------------------------- 1 | textDocument = new class($this->args) 6 | { 7 | }; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Validation/cases/scopedPropertyAccess4.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "A": [ 4 | "./scopedPropertyAccess4.php" 5 | ] 6 | }, 7 | "definitions": [] 8 | } -------------------------------------------------------------------------------- /tests/Validation/cases/verifyFqsenOnClassProperty.php: -------------------------------------------------------------------------------- 1 | bar = 'hello'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Validation/cases/anonymousClassMembersShouldNotBeSymbols.php: -------------------------------------------------------------------------------- 1 | args) { }; 10 | } 11 | } -------------------------------------------------------------------------------- /tests/Validation/cases/constants.php: -------------------------------------------------------------------------------- 1 | BYE 11 | ]; 12 | } 13 | } -------------------------------------------------------------------------------- /tests/Validation/cases/propertyName2.php: -------------------------------------------------------------------------------- 1 | assertTrue("HI"); 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Validation/cases/constants2.php: -------------------------------------------------------------------------------- 1 | "hi" 11 | ]; 12 | } 13 | } -------------------------------------------------------------------------------- /tests/Validation/cases/methodReturnType.php: -------------------------------------------------------------------------------- 1 | b(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Validation/cases/parent1.php: -------------------------------------------------------------------------------- 1 | b(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Validation/cases/static1.php: -------------------------------------------------------------------------------- 1 | b(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Validation/cases/newStatic.php: -------------------------------------------------------------------------------- 1 | foo(); 8 | } 9 | 10 | private function foo() { 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Validation/cases/memberAccess4.php: -------------------------------------------------------------------------------- 1 | toString()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Validation/cases/propertyName1.php: -------------------------------------------------------------------------------- 1 | b()": [ 7 | "./functionUse.php" 8 | ] 9 | }, 10 | "definitions": [] 11 | } -------------------------------------------------------------------------------- /tests/Validation/cases/multiplePreceedingComments.php: -------------------------------------------------------------------------------- 1 | b(); 16 | } 17 | } -------------------------------------------------------------------------------- /tests/Validation/cases/forLoopReference1.php: -------------------------------------------------------------------------------- 1 | foo()) { 6 | } 7 | } 8 | 9 | public function foo() { 10 | return $this; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/Validation/cases/self1.php: -------------------------------------------------------------------------------- 1 | addTestFile(__DIR__ . DS . 'Database' . DS . 'ConnectionTest.php'); 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /tests/Validation/cases/namespaces3.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "a\\b": [ 4 | "./_cases/namespaces3.php" 5 | ], 6 | "b": [ 7 | "./_cases/namespaces3.php" 8 | ], 9 | "c": [ 10 | "./_cases/namespaces3.php" 11 | ] 12 | }, 13 | "definitions": [] 14 | } -------------------------------------------------------------------------------- /tests/Validation/cases/scopedPropertyAccess5.php: -------------------------------------------------------------------------------- 1 | testProperty; -------------------------------------------------------------------------------- /tests/MockPhpDocument.php: -------------------------------------------------------------------------------- 1 | prefixesPsr0 = ComposerStaticInitIncludePath::$prefixesPsr0; 10 | 11 | }, null, ClassLoader::class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Validation/cases/functionUse2.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "LanguageServer": [ 4 | "./functionUse2.php" 5 | ], 6 | "LanguageServer\\pathToUri()": [ 7 | "./functionUse2.php" 8 | ], 9 | "LanguageServer\\timeout()": [ 10 | "./functionUse2.php" 11 | ] 12 | }, 13 | "definitions": [] 14 | } -------------------------------------------------------------------------------- /tests/Validation/cases/parameterTypeResolution1.php: -------------------------------------------------------------------------------- 1 | getAccount(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ProtocolReader.php: -------------------------------------------------------------------------------- 1 | getAccount(); 13 | } 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /src/ProtocolWriter.php: -------------------------------------------------------------------------------- 1 | counter++; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ContentRetriever/ContentRetriever.php: -------------------------------------------------------------------------------- 1 | Resolved with the content as a string 18 | */ 19 | public function retrieve(string $uri): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /src/FilesFinder/FilesFinder.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | public function find(string $glob): Promise; 21 | } 22 | -------------------------------------------------------------------------------- /src/Index/StubsIndex.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | public function get(string $key): Promise; 20 | 21 | /** 22 | * Sets a value in the cache 23 | * 24 | * @param string $key 25 | * @param mixed $value 26 | * @return Promise 27 | */ 28 | public function set(string $key, $value): Promise; 29 | } 30 | -------------------------------------------------------------------------------- /src/StderrLogger.php: -------------------------------------------------------------------------------- 1 | Resolved with the content as a string 19 | */ 20 | public function retrieve(string $uri): Promise 21 | { 22 | return Promise\resolve(file_get_contents(uriToPath($uri))); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/MockProtocolStream.php: -------------------------------------------------------------------------------- 1 | emit('message', [Message::parse((string)$msg)]); 25 | }); 26 | return Promise\resolve(null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Validation/cases/interfaceProperty.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [], 3 | "definitions": { 4 | "A": { 5 | "fqn": "A", 6 | "extends": [], 7 | "isMember": false, 8 | "roamed": false, 9 | "isStatic": false, 10 | "canBeInstantiated": false, 11 | "symbolInformation": { 12 | "name": "A", 13 | "kind": 11, 14 | "location": { 15 | "uri": "./interfaceProperty.php" 16 | }, 17 | "containerName": "" 18 | }, 19 | "type": null, 20 | "declarationLine": "interface A {", 21 | "documentation": null, 22 | "signatureInformation": null 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Validation/cases/namespaces6.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [], 3 | "definitions": { 4 | "A\\B": { 5 | "fqn": "A\\B", 6 | "extends": [], 7 | "isMember": false, 8 | "roamed": false, 9 | "isStatic": false, 10 | "canBeInstantiated": false, 11 | "symbolInformation": { 12 | "name": "A\\B", 13 | "kind": 3, 14 | "location": { 15 | "uri": "./namespaces6.php" 16 | }, 17 | "containerName": "A" 18 | }, 19 | "type": null, 20 | "declarationLine": "namespace A \\ B;", 21 | "documentation": null, 22 | "signatureInformation": null 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2016, Felix Frederick Becker 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /tests/Validation/cases/docBlocksOnNamespaceDefinition.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [], 3 | "definitions": { 4 | "MyNamespace": { 5 | "fqn": "MyNamespace", 6 | "extends": [], 7 | "isMember": false, 8 | "roamed": false, 9 | "isStatic": false, 10 | "canBeInstantiated": false, 11 | "symbolInformation": { 12 | "name": "MyNamespace", 13 | "kind": 3, 14 | "location": { 15 | "uri": "./docBlocksOnNamespaceDefinition.php" 16 | }, 17 | "containerName": "" 18 | }, 19 | "type": null, 20 | "declarationLine": "namespace MyNamespace;", 21 | "documentation": null, 22 | "signatureInformation": null 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Factory/RangeFactory.php: -------------------------------------------------------------------------------- 1 | getStart(), 22 | $node->getWidth(), 23 | $node->getFileContents() 24 | ); 25 | 26 | return new Range( 27 | new Position($range->start->line, $range->start->character), 28 | new Position($range->end->line, $range->end->character) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Validation/cases/anonymousClassMembersShouldNotBeSymbols.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [], 3 | "definitions": { 4 | "MyNamespace": { 5 | "fqn": "MyNamespace", 6 | "extends": [], 7 | "isMember": false, 8 | "roamed": false, 9 | "isStatic": false, 10 | "canBeInstantiated": false, 11 | "symbolInformation": { 12 | "name": "MyNamespace", 13 | "kind": 3, 14 | "location": { 15 | "uri": "./anonymousClassMembersShouldNotBeSymbols.php" 16 | }, 17 | "containerName": "" 18 | }, 19 | "type": null, 20 | "declarationLine": "namespace MyNamespace;", 21 | "documentation": null, 22 | "signatureInformation": null 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Validation/cases/exceptions1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\Exception": [ 4 | "./exceptions1.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "MyNamespace": { 9 | "fqn": "MyNamespace", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "MyNamespace", 17 | "kind": 3, 18 | "location": { 19 | "uri": "./exceptions1.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "namespace MyNamespace;", 25 | "documentation": null, 26 | "signatureInformation": null 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /tests/Validation/cases/scopedPropertyAccess2.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\A": [ 4 | "./scopedPropertyAccess2.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "MyNamespace": { 9 | "fqn": "MyNamespace", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "MyNamespace", 17 | "kind": 3, 18 | "location": { 19 | "uri": "./scopedPropertyAccess2.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "namespace MyNamespace;", 25 | "documentation": null, 26 | "signatureInformation": null 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Factory/LocationFactory.php: -------------------------------------------------------------------------------- 1 | getStart(), 23 | $node->getWidth(), 24 | $node->getFileContents() 25 | ); 26 | 27 | return new Location($node->getUri(), new Range( 28 | new Position($range->start->line, $range->start->character), 29 | new Position($range->end->line, $range->end->character) 30 | )); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Index/GlobalIndex.php: -------------------------------------------------------------------------------- 1 | stubsIndex = $stubsIndex; 28 | $this->projectIndex = $projectIndex; 29 | parent::__construct(); 30 | } 31 | 32 | /** 33 | * @return ReadableIndex[] 34 | */ 35 | protected function getIndexes(): array 36 | { 37 | return [$this->stubsIndex, $this->projectIndex]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Validation/cases/ifStatement1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\A": [ 4 | "./ifStatement1.php" 5 | ], 6 | "A": [ 7 | "./ifStatement1.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./ifStatement1.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/Validation/cases/caseStatement1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\A": [ 4 | "./caseStatement1.php" 5 | ], 6 | "A": [ 7 | "./caseStatement1.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./caseStatement1.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/Validation/cases/testQualifiedNameOutsideOfNamespace.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "NameOutsideOfNamespace": [ 4 | "./testQualifiedNameOutsideOfNamespace.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "SomeNamespace": { 9 | "fqn": "SomeNamespace", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "SomeNamespace", 17 | "kind": 3, 18 | "location": { 19 | "uri": "./testQualifiedNameOutsideOfNamespace.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "namespace SomeNamespace { }", 25 | "documentation": null, 26 | "signatureInformation": null 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /tests/Validation/cases/magicConstantsShouldBeGlobal.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "B\\__FILE__": [ 4 | "./magicConstantsShouldBeGlobal.php" 5 | ], 6 | "__FILE__": [ 7 | "./magicConstantsShouldBeGlobal.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "B": { 12 | "fqn": "B", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "B", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./magicConstantsShouldBeGlobal.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace B;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Client/XCache.php: -------------------------------------------------------------------------------- 1 | handler = $handler; 22 | } 23 | 24 | /** 25 | * @param string $key 26 | * @return Promise 27 | */ 28 | public function get(string $key): Promise 29 | { 30 | return $this->handler->request('xcache/get', ['key' => $key]); 31 | } 32 | 33 | /** 34 | * @param string $key 35 | * @param mixed $value 36 | * @return Promise 37 | */ 38 | public function set(string $key, $value): Promise 39 | { 40 | return $this->handler->notify('xcache/set', ['key' => $key, 'value' => $value]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/ProtocolStreamWriterTest.php: -------------------------------------------------------------------------------- 1 | str_repeat('X', 100000)])); 23 | $msgString = (string)$msg; 24 | 25 | $promise = $writer->write($msg); 26 | 27 | Loop\tick(); 28 | 29 | $promise->wait(); 30 | 31 | fclose($writeHandle); 32 | 33 | $this->assertEquals(strlen($msgString), filesize($tmpfile)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Index/IndexTest.php: -------------------------------------------------------------------------------- 1 | setDefinition('SomeNamespace\SomeClass', new Definition); 15 | $methodDefinition = new Definition; 16 | $methodFqn = 'SomeNamespace\SomeClass->someMethod()'; 17 | $index->setDefinition($methodFqn, $methodDefinition); 18 | $index->setDefinition('SomeNamespace\SomeClass->someProperty', new Definition); 19 | $this->assertSame($methodDefinition, $index->getDefinition($methodFqn)); 20 | } 21 | 22 | public function testGetSetClassDefinition() 23 | { 24 | $index = new Index; 25 | $definition = new Definition; 26 | $fqn = 'SomeNamespace\SomeClass'; 27 | $index->setDefinition($fqn, $definition); 28 | $this->assertSame($definition, $index->getDefinition($fqn)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Validation/cases/namespaces2.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "NS1\\C": [ 4 | "./namespaces2.php" 5 | ], 6 | "NS1": [ 7 | "./namespaces2.php" 8 | ], 9 | "NS1\\I": [ 10 | "./namespaces2.php" 11 | ], 12 | "NS1\\T": [ 13 | "./namespaces2.php" 14 | ] 15 | }, 16 | "definitions": { 17 | "MyNamespace1": { 18 | "fqn": "MyNamespace1", 19 | "extends": [], 20 | "isMember": false, 21 | "roamed": false, 22 | "isStatic": false, 23 | "canBeInstantiated": false, 24 | "symbolInformation": { 25 | "name": "MyNamespace1", 26 | "kind": 3, 27 | "location": { 28 | "uri": "./namespaces2.php" 29 | }, 30 | "containerName": "" 31 | }, 32 | "type": null, 33 | "declarationLine": "namespace MyNamespace1;", 34 | "documentation": null, 35 | "signatureInformation": null 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/FilesFinder/FileSystemFilesFinder.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | public function find(string $glob): Promise 21 | { 22 | return coroutine(function () use ($glob) { 23 | $uris = []; 24 | foreach (new GlobIterator($glob) as $path) { 25 | // Exclude any directories that also match the glob pattern 26 | if (!is_dir($path)) { 27 | $uris[] = pathToUri($path); 28 | } 29 | 30 | yield timeout(); 31 | } 32 | return $uris; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/LanguageClient.php: -------------------------------------------------------------------------------- 1 | textDocument = new Client\TextDocument($handler, $mapper); 44 | $this->window = new Client\Window($handler); 45 | $this->workspace = new Client\Workspace($handler, $mapper); 46 | $this->xcache = new Client\XCache($handler); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Validation/cases/namespaces8.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "LanguageServer": [ 4 | "./namespaces8.php" 5 | ], 6 | "LanguageServer\\pathToUri()": [ 7 | "./namespaces8.php" 8 | ], 9 | "LanguageServer\\uriToPath()": [ 10 | "./namespaces8.php" 11 | ] 12 | }, 13 | "definitions": { 14 | "LanguageServer\\Tests\\Utils": { 15 | "fqn": "LanguageServer\\Tests\\Utils", 16 | "extends": [], 17 | "isMember": false, 18 | "roamed": false, 19 | "isStatic": false, 20 | "canBeInstantiated": false, 21 | "symbolInformation": { 22 | "name": "LanguageServer\\Tests\\Utils", 23 | "kind": 3, 24 | "location": { 25 | "uri": "./namespaces8.php" 26 | }, 27 | "containerName": "LanguageServer\\Tests" 28 | }, 29 | "type": null, 30 | "declarationLine": "namespace LanguageServer\\Tests\\Utils;", 31 | "documentation": null, 32 | "signatureInformation": null 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/ContentRetriever/ClientContentRetriever.php: -------------------------------------------------------------------------------- 1 | client = $client; 21 | } 22 | 23 | /** 24 | * Retrieves the content of a text document identified by the URI through a textDocument/xcontent request 25 | * 26 | * @param string $uri The URI of the document 27 | * @return Promise Resolved with the content as a string 28 | */ 29 | public function retrieve(string $uri): Promise 30 | { 31 | return $this->client->textDocument->xcontent(new TextDocumentIdentifier($uri)) 32 | ->then(function (TextDocumentItem $textDocument) { 33 | return $textDocument->text; 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Server/TextDocument/References/NamespacedTest.php: -------------------------------------------------------------------------------- 1 | textDocument->references( 27 | new ReferenceContext, 28 | new TextDocumentIdentifier($definition->uri), 29 | $definition->range->end 30 | )->wait(); 31 | $this->assertEquals(parent::getReferenceLocations('TestNamespace'), $result); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Utils/StripStringOverlapTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('assertEquals('?php', stripStringOverlap('bla<', 'assertEquals('php', stripStringOverlap('blaassertEquals('', stripStringOverlap('blaassertEquals('assertEquals('', stripStringOverlap('bla', '')); 39 | } 40 | 41 | public function testBothEmpty() 42 | { 43 | $this->assertEquals('', stripStringOverlap('', '')); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Factory/CompletionItemFactory.php: -------------------------------------------------------------------------------- 1 | label = $def->symbolInformation->name; 22 | $item->kind = CompletionItemKind::fromSymbolKind($def->symbolInformation->kind); 23 | if ($def->type) { 24 | $item->detail = (string)$def->type; 25 | } else if ($def->symbolInformation->containerName) { 26 | $item->detail = $def->symbolInformation->containerName; 27 | } 28 | if ($def->documentation) { 29 | $item->documentation = $def->documentation; 30 | } 31 | if ($def->isStatic && $def->symbolInformation->kind === SymbolKind::PROPERTY) { 32 | $item->insertText = '$' . $def->symbolInformation->name; 33 | } 34 | return $item; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Cache/ClientCache.php: -------------------------------------------------------------------------------- 1 | client = $client; 25 | } 26 | 27 | /** 28 | * Gets a value from the cache 29 | * 30 | * @param string $key 31 | * @return Promise 32 | */ 33 | public function get(string $key): Promise 34 | { 35 | return $this->client->xcache->get($key)->then('unserialize')->otherwise(function () { 36 | // Ignore 37 | }); 38 | } 39 | 40 | /** 41 | * Sets a value in the cache 42 | * 43 | * @param string $key 44 | * @param mixed $value 45 | * @return Promise 46 | */ 47 | public function set(string $key, $value): Promise 48 | { 49 | return $this->client->xcache->set($key, serialize($value))->otherwise(function () { 50 | // Ignore 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Client/Workspace.php: -------------------------------------------------------------------------------- 1 | handler = $handler; 29 | $this->mapper = $mapper; 30 | } 31 | 32 | /** 33 | * Returns a list of all files in a directory 34 | * 35 | * @param string $base The base directory (defaults to the workspace) 36 | * @return Promise Array of documents 37 | */ 38 | public function xfiles(string $base = null): Promise 39 | { 40 | return $this->handler->request( 41 | 'workspace/xfiles', 42 | ['base' => $base] 43 | )->then(function (array $textDocuments) { 44 | return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ContentTooLargeException.php: -------------------------------------------------------------------------------- 1 | uri = $uri; 41 | $this->size = $size; 42 | $this->limit = $limit; 43 | parent::__construct("$uri exceeds size limit of $limit bytes ($size)", 0, $previous); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/ProtocolStreamReaderTest.php: -------------------------------------------------------------------------------- 1 | on('message', function (Message $message) use (&$msg) { 21 | $msg = $message; 22 | }); 23 | $ret = fwrite($writeHandle, (string)new Message(new RequestBody(1, 'aMethod', ['arg' => 'Hello World']))); 24 | Loop\tick(); 25 | $this->assertNotNull($msg); 26 | $this->assertInstanceOf(Message::class, $msg); 27 | $this->assertInstanceOf(RequestBody::class, $msg->body); 28 | $this->assertEquals(1, $msg->body->id); 29 | $this->assertEquals('aMethod', $msg->body->method); 30 | $this->assertEquals((object)['arg' => 'Hello World'], $msg->body->params); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Validation/cases/namespaces5.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "LanguageServerProtocol": [ 4 | "./namespaces5.php" 5 | ], 6 | "LanguageServerProtocol\\TextDocumentIdentifier": [ 7 | "./namespaces5.php" 8 | ], 9 | "LanguageServerProtocol\\Position": [ 10 | "./namespaces5.php" 11 | ], 12 | "LanguageServerProtocol\\ReferenceContext": [ 13 | "./namespaces5.php" 14 | ], 15 | "LanguageServerProtocol\\Location": [ 16 | "./namespaces5.php" 17 | ], 18 | "LanguageServerProtocol\\Range": [ 19 | "./namespaces5.php" 20 | ] 21 | }, 22 | "definitions": { 23 | "B": { 24 | "fqn": "B", 25 | "extends": [], 26 | "isMember": false, 27 | "roamed": false, 28 | "isStatic": false, 29 | "canBeInstantiated": false, 30 | "symbolInformation": { 31 | "name": "B", 32 | "kind": 3, 33 | "location": { 34 | "uri": "./namespaces5.php" 35 | }, 36 | "containerName": "" 37 | }, 38 | "type": null, 39 | "declarationLine": "namespace B;", 40 | "documentation": null, 41 | "signatureInformation": null 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Client/Window.php: -------------------------------------------------------------------------------- 1 | handler = $handler; 22 | } 23 | 24 | /** 25 | * The show message notification is sent from a server to a client 26 | * to ask the client to display a particular message in the user interface. 27 | * 28 | * @param int $type 29 | * @param string $message 30 | * @return Promise 31 | */ 32 | public function showMessage(int $type, string $message): Promise 33 | { 34 | return $this->handler->notify('window/showMessage', ['type' => $type, 'message' => $message]); 35 | } 36 | 37 | /** 38 | * The log message notification is sent from the server to the client to ask the client to log a particular message. 39 | * 40 | * @param int $type 41 | * @param string $message 42 | * @return Promise 43 | */ 44 | public function logMessage(int $type, string $message): Promise 45 | { 46 | return $this->handler->notify('window/logMessage', ['type' => $type, 'message' => $message]); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/FilesFinder/ClientFilesFinder.php: -------------------------------------------------------------------------------- 1 | client = $client; 27 | } 28 | 29 | /** 30 | * Returns all files in the workspace that match a glob. 31 | * If the client does not support workspace/files, it falls back to searching the file system directly. 32 | * 33 | * @param string $glob 34 | * @return Promise The URIs 35 | */ 36 | public function find(string $glob): Promise 37 | { 38 | return $this->client->workspace->xfiles()->then(function (array $textDocuments) use ($glob) { 39 | $uris = []; 40 | foreach ($textDocuments as $textDocument) { 41 | $path = Uri\parse($textDocument->uri)['path']; 42 | if (Glob::match($path, $glob)) { 43 | $uris[] = $textDocument->uri; 44 | } 45 | } 46 | return $uris; 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/PhpDocumentLoaderTest.php: -------------------------------------------------------------------------------- 1 | loader = new PhpDocumentLoader( 27 | new FileSystemContentRetriever, 28 | $projectIndex, 29 | new DefinitionResolver($projectIndex) 30 | ); 31 | } 32 | 33 | public function testGetOrLoadLoadsDocument() 34 | { 35 | $document = $this->loader->getOrLoad(pathToUri(__FILE__))->wait(); 36 | 37 | $this->assertNotNull($document); 38 | $this->assertInstanceOf(PhpDocument::class, $document); 39 | } 40 | 41 | public function testGetReturnsOpenedInstance() 42 | { 43 | $document1 = $this->loader->open(pathToUri(__FILE__), file_get_contents(__FILE__)); 44 | $document2 = $this->loader->get(pathToUri(__FILE__)); 45 | 46 | $this->assertSame($document1, $document2); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Index/ProjectIndex.php: -------------------------------------------------------------------------------- 1 | sourceIndex = $sourceIndex; 30 | $this->dependenciesIndex = $dependenciesIndex; 31 | $this->composerJson = $composerJson; 32 | parent::__construct(); 33 | } 34 | 35 | /** 36 | * @return ReadableIndex[] 37 | */ 38 | protected function getIndexes(): array 39 | { 40 | return [$this->sourceIndex, $this->dependenciesIndex]; 41 | } 42 | 43 | /** 44 | * @param string $uri 45 | * @return Index 46 | */ 47 | public function getIndexForUri(string $uri): Index 48 | { 49 | $packageName = getPackageName($uri, $this->composerJson); 50 | if ($packageName) { 51 | return $this->dependenciesIndex->getDependencyIndex($packageName); 52 | } 53 | return $this->sourceIndex; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Validation/cases/arrayValueShouldBeBoolean.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [], 3 | "definitions": { 4 | "A": { 5 | "fqn": "A", 6 | "extends": [], 7 | "isMember": false, 8 | "roamed": false, 9 | "isStatic": false, 10 | "canBeInstantiated": true, 11 | "symbolInformation": { 12 | "name": "A", 13 | "kind": 5, 14 | "location": { 15 | "uri": "./arrayValueShouldBeBoolean.php" 16 | }, 17 | "containerName": "" 18 | }, 19 | "type": null, 20 | "declarationLine": "class A {", 21 | "documentation": null, 22 | "signatureInformation": null 23 | }, 24 | "A->foo": { 25 | "fqn": "A->foo", 26 | "extends": [], 27 | "isMember": true, 28 | "roamed": false, 29 | "isStatic": false, 30 | "canBeInstantiated": false, 31 | "symbolInformation": { 32 | "name": "foo", 33 | "kind": 7, 34 | "location": { 35 | "uri": "./arrayValueShouldBeBoolean.php" 36 | }, 37 | "containerName": "A" 38 | }, 39 | "type__tostring": "bool[]", 40 | "type": {}, 41 | "declarationLine": "protected $foo;", 42 | "documentation": null, 43 | "signatureInformation": null 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/ClientHandlerTest.php: -------------------------------------------------------------------------------- 1 | once('message', function (Message $msg) use ($reader) { 20 | // Respond to request 21 | Loop\setTimeout(function () use ($reader, $msg) { 22 | $reader->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, 'pong'))); 23 | }, 0); 24 | }); 25 | $handler->request('testMethod', ['ping'])->then(function ($result) { 26 | $this->assertEquals('pong', $result); 27 | })->wait(); 28 | // No event listeners 29 | $this->assertEquals([], $reader->listeners('message')); 30 | $this->assertEquals([], $writer->listeners('message')); 31 | } 32 | 33 | public function testNotify() 34 | { 35 | $reader = new MockProtocolStream; 36 | $writer = new MockProtocolStream; 37 | $handler = new ClientHandler($reader, $writer); 38 | $handler->notify('testMethod', ['ping'])->wait(); 39 | // No event listeners 40 | $this->assertEquals([], $reader->listeners('message')); 41 | $this->assertEquals([], $writer->listeners('message')); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Validation/cases/propertyName1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [], 3 | "definitions": { 4 | "MyClass": { 5 | "fqn": "MyClass", 6 | "extends": [], 7 | "isMember": false, 8 | "roamed": false, 9 | "isStatic": false, 10 | "canBeInstantiated": true, 11 | "symbolInformation": { 12 | "name": "MyClass", 13 | "kind": 5, 14 | "location": { 15 | "uri": "./propertyName1.php" 16 | }, 17 | "containerName": "" 18 | }, 19 | "type": null, 20 | "declarationLine": "class MyClass", 21 | "documentation": null, 22 | "signatureInformation": null 23 | }, 24 | "MyClass->mainPropertyName": { 25 | "fqn": "MyClass->mainPropertyName", 26 | "extends": [], 27 | "isMember": true, 28 | "roamed": false, 29 | "isStatic": false, 30 | "canBeInstantiated": false, 31 | "symbolInformation": { 32 | "name": "mainPropertyName", 33 | "kind": 7, 34 | "location": { 35 | "uri": "./propertyName1.php" 36 | }, 37 | "containerName": "MyClass" 38 | }, 39 | "type__tostring": "string", 40 | "type": {}, 41 | "declarationLine": "protected $mainPropertyName;", 42 | "documentation": "The name of the main property, or NULL if there is none.", 43 | "signatureInformation": null 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/Validation/cases/propertyName2.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [], 3 | "definitions": { 4 | "MyClass": { 5 | "fqn": "MyClass", 6 | "extends": [], 7 | "isMember": false, 8 | "roamed": false, 9 | "isStatic": false, 10 | "canBeInstantiated": true, 11 | "symbolInformation": { 12 | "name": "MyClass", 13 | "kind": 5, 14 | "location": { 15 | "uri": "./propertyName2.php" 16 | }, 17 | "containerName": "" 18 | }, 19 | "type": null, 20 | "declarationLine": "class MyClass", 21 | "documentation": null, 22 | "signatureInformation": null 23 | }, 24 | "MyClass->mainPropertyName": { 25 | "fqn": "MyClass->mainPropertyName", 26 | "extends": [], 27 | "isMember": true, 28 | "roamed": false, 29 | "isStatic": false, 30 | "canBeInstantiated": false, 31 | "symbolInformation": { 32 | "name": "mainPropertyName", 33 | "kind": 7, 34 | "location": { 35 | "uri": "./propertyName2.php" 36 | }, 37 | "containerName": "MyClass" 38 | }, 39 | "type__tostring": "string", 40 | "type": {}, 41 | "declarationLine": "protected $mainPropertyName;", 42 | "documentation": "The name of the main property, or NULL if there is none.", 43 | "signatureInformation": null 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/Server/TextDocument/DidCloseTest.php: -------------------------------------------------------------------------------- 1 | open('whatever', "uri = 'whatever'; 28 | $textDocumentItem->languageId = 'php'; 29 | $textDocumentItem->version = 1; 30 | $textDocumentItem->text = 'hello world'; 31 | $textDocument->didOpen($textDocumentItem); 32 | 33 | $textDocument->didClose(new TextDocumentIdentifier($textDocumentItem->uri)); 34 | 35 | $this->assertFalse($loader->isOpen($textDocumentItem->uri)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Validation/cases/magicConsts.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "__CLASS__": [ 4 | "./magicConsts.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "A": { 9 | "fqn": "A", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": true, 15 | "symbolInformation": { 16 | "name": "A", 17 | "kind": 5, 18 | "location": { 19 | "uri": "./magicConsts.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "class A {", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "A::$deprecationsTriggered": { 29 | "fqn": "A::$deprecationsTriggered", 30 | "extends": [], 31 | "isMember": true, 32 | "roamed": false, 33 | "isStatic": true, 34 | "canBeInstantiated": false, 35 | "symbolInformation": { 36 | "name": "deprecationsTriggered", 37 | "kind": 7, 38 | "location": { 39 | "uri": "./magicConsts.php" 40 | }, 41 | "containerName": "A" 42 | }, 43 | "type__tostring": "bool[]", 44 | "type": {}, 45 | "declarationLine": "private static $deprecationsTriggered;", 46 | "documentation": null, 47 | "signatureInformation": null 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Index/DependenciesIndex.php: -------------------------------------------------------------------------------- 1 | indexes; 21 | } 22 | 23 | /** 24 | * @param string $packageName 25 | * @return Index 26 | */ 27 | public function getDependencyIndex(string $packageName): Index 28 | { 29 | if (!isset($this->indexes[$packageName])) { 30 | $index = new Index; 31 | $this->indexes[$packageName] = $index; 32 | $this->registerIndex($index); 33 | } 34 | return $this->indexes[$packageName]; 35 | } 36 | 37 | /** 38 | * @param string $packageName 39 | * @param Index $index 40 | * @return void 41 | */ 42 | public function setDependencyIndex(string $packageName, Index $index) 43 | { 44 | $this->indexes[$packageName] = $index; 45 | $this->registerIndex($index); 46 | } 47 | 48 | /** 49 | * @param string $packageName 50 | * @return void 51 | */ 52 | public function removeDependencyIndex(string $packageName) 53 | { 54 | unset($this->indexes[$packageName]); 55 | } 56 | 57 | /** 58 | * @param string $packageName 59 | * @return bool 60 | */ 61 | public function hasDependencyIndex(string $packageName): bool 62 | { 63 | return isset($this->indexes[$packageName]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Validation/cases/scopedPropertyAccess3.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "A": [ 4 | "./scopedPropertyAccess3.php" 5 | ], 6 | "A::$a": [ 7 | "./scopedPropertyAccess3.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "A": { 12 | "fqn": "A", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": true, 18 | "symbolInformation": { 19 | "name": "A", 20 | "kind": 5, 21 | "location": { 22 | "uri": "./scopedPropertyAccess3.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "class A {", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "A::$a": { 32 | "fqn": "A::$a", 33 | "extends": [], 34 | "isMember": true, 35 | "roamed": false, 36 | "isStatic": true, 37 | "canBeInstantiated": false, 38 | "symbolInformation": { 39 | "name": "a", 40 | "kind": 7, 41 | "location": { 42 | "uri": "./scopedPropertyAccess3.php" 43 | }, 44 | "containerName": "A" 45 | }, 46 | "type__tostring": "string", 47 | "type": {}, 48 | "declarationLine": "static $a;", 49 | "documentation": null, 50 | "signatureInformation": null 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /tests/Validation/cases/multiplePreceedingComments.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [], 3 | "definitions": { 4 | "Foo": { 5 | "fqn": "Foo", 6 | "extends": [], 7 | "isMember": false, 8 | "roamed": false, 9 | "isStatic": false, 10 | "canBeInstantiated": true, 11 | "symbolInformation": { 12 | "name": "Foo", 13 | "kind": 5, 14 | "location": { 15 | "uri": "./multiplePreceedingComments.php" 16 | }, 17 | "containerName": "" 18 | }, 19 | "type": null, 20 | "declarationLine": "class Foo", 21 | "documentation": null, 22 | "signatureInformation": null 23 | }, 24 | "Foo->fn()": { 25 | "fqn": "Foo->fn()", 26 | "extends": [], 27 | "isMember": true, 28 | "roamed": false, 29 | "isStatic": false, 30 | "canBeInstantiated": false, 31 | "symbolInformation": { 32 | "name": "fn", 33 | "kind": 6, 34 | "location": { 35 | "uri": "./multiplePreceedingComments.php" 36 | }, 37 | "containerName": "Foo" 38 | }, 39 | "type__tostring": "\\Iterator", 40 | "type": {}, 41 | "declarationLine": "public function fn()", 42 | "documentation": "Foo", 43 | "signatureInformation": { 44 | "label": "()", 45 | "documentation": "Foo", 46 | "parameters": [] 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /tests/Validation/cases/nameToken.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "A": [ 4 | "./nameToken.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "A": { 9 | "fqn": "A", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": true, 15 | "symbolInformation": { 16 | "name": "A", 17 | "kind": 5, 18 | "location": { 19 | "uri": "./nameToken.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "class A {", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "A->b()": { 29 | "fqn": "A->b()", 30 | "extends": [], 31 | "isMember": true, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": false, 35 | "symbolInformation": { 36 | "name": "b", 37 | "kind": 6, 38 | "location": { 39 | "uri": "./nameToken.php" 40 | }, 41 | "containerName": "A" 42 | }, 43 | "type__tostring": "mixed", 44 | "type": {}, 45 | "declarationLine": "function b() {", 46 | "documentation": null, 47 | "signatureInformation": { 48 | "label": "()", 49 | "documentation": null, 50 | "parameters": [] 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /tests/Validation/cases/objectCreation3.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "A->args": [ 4 | "./objectCreation3.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "A": { 9 | "fqn": "A", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": true, 15 | "symbolInformation": { 16 | "name": "A", 17 | "kind": 5, 18 | "location": { 19 | "uri": "./objectCreation3.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "class A {", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "A->a()": { 29 | "fqn": "A->a()", 30 | "extends": [], 31 | "isMember": true, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": false, 35 | "symbolInformation": { 36 | "name": "a", 37 | "kind": 6, 38 | "location": { 39 | "uri": "./objectCreation3.php" 40 | }, 41 | "containerName": "A" 42 | }, 43 | "type__tostring": "mixed", 44 | "type": {}, 45 | "declarationLine": "function a () {", 46 | "documentation": null, 47 | "signatureInformation": { 48 | "label": "()", 49 | "documentation": null, 50 | "parameters": [] 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /tests/Server/TextDocument/DidChangeTest.php: -------------------------------------------------------------------------------- 1 | open('whatever', "range = new Range(new Position(0, 0), new Position(9999, 9999)); 34 | $changeEvent->rangeLength = 9999; 35 | $changeEvent->text = "didChange($identifier, [$changeEvent]); 38 | 39 | $this->assertEquals("getContent()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Message.php: -------------------------------------------------------------------------------- 1 | body = MessageBody::parse(array_pop($parts)); 32 | foreach ($parts as $line) { 33 | if ($line) { 34 | $pair = explode(': ', $line); 35 | $obj->headers[$pair[0]] = $pair[1]; 36 | } 37 | } 38 | return $obj; 39 | } 40 | 41 | /** 42 | * @param \AdvancedJsonRpc\Message $body 43 | * @param string[] $headers 44 | */ 45 | public function __construct(MessageBody $body = null, array $headers = []) 46 | { 47 | $this->body = $body; 48 | if (!isset($headers['Content-Type'])) { 49 | $headers['Content-Type'] = 'application/vscode-jsonrpc; charset=utf8'; 50 | } 51 | $this->headers = $headers; 52 | } 53 | 54 | public function __toString(): string 55 | { 56 | $body = (string)$this->body; 57 | $contentLength = strlen($body); 58 | $this->headers['Content-Length'] = $contentLength; 59 | $headers = ''; 60 | foreach ($this->headers as $name => $value) { 61 | $headers .= "$name: $value\r\n"; 62 | } 63 | return $headers . "\r\n" . $body; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Cache/FileSystemCache.php: -------------------------------------------------------------------------------- 1 | cacheDir = getenv('LOCALAPPDATA') . '\\PHP Language Server\\'; 22 | } else if (getenv('XDG_CACHE_HOME')) { 23 | $this->cacheDir = getenv('XDG_CACHE_HOME') . '/phpls/'; 24 | } else { 25 | $this->cacheDir = getenv('HOME') . '/.phpls/'; 26 | } 27 | } 28 | 29 | /** 30 | * Gets a value from the cache 31 | * 32 | * @param string $key 33 | * @return Promise 34 | */ 35 | public function get(string $key): Promise 36 | { 37 | try { 38 | $file = $this->cacheDir . urlencode($key); 39 | if (!file_exists($file)) { 40 | return Promise\resolve(null); 41 | } 42 | return Promise\resolve(unserialize(file_get_contents($file))); 43 | } catch (\Exception $e) { 44 | return Promise\resolve(null); 45 | } 46 | } 47 | 48 | /** 49 | * Sets a value in the cache 50 | * 51 | * @param string $key 52 | * @param mixed $value 53 | * @return Promise 54 | */ 55 | public function set(string $key, $value): Promise 56 | { 57 | try { 58 | $file = $this->cacheDir . urlencode($key); 59 | if (!file_exists($this->cacheDir)) { 60 | mkdir($this->cacheDir); 61 | } 62 | file_put_contents($file, serialize($value)); 63 | } finally { 64 | return Promise\resolve(null); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Client/TextDocument.php: -------------------------------------------------------------------------------- 1 | handler = $handler; 29 | $this->mapper = $mapper; 30 | } 31 | 32 | /** 33 | * Diagnostics notification are sent from the server to the client to signal results of validation runs. 34 | * 35 | * @param string $uri 36 | * @param Diagnostic[] $diagnostics 37 | * @return Promise 38 | */ 39 | public function publishDiagnostics(string $uri, array $diagnostics): Promise 40 | { 41 | return $this->handler->notify('textDocument/publishDiagnostics', [ 42 | 'uri' => $uri, 43 | 'diagnostics' => $diagnostics 44 | ]); 45 | } 46 | 47 | /** 48 | * The content request is sent from a server to a client 49 | * to request the current content of a text document identified by the URI 50 | * 51 | * @param TextDocumentIdentifier $textDocument The document to get the content for 52 | * @return Promise The document's current content 53 | */ 54 | public function xcontent(TextDocumentIdentifier $textDocument): Promise 55 | { 56 | return $this->handler->request( 57 | 'textDocument/xcontent', 58 | ['textDocument' => $textDocument] 59 | )->then(function ($result) { 60 | return $this->mapper->map($result, new TextDocumentItem); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Validation/cases/param1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\Hi": [ 4 | "./param1.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "MyNamespace": { 9 | "fqn": "MyNamespace", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "MyNamespace", 17 | "kind": 3, 18 | "location": { 19 | "uri": "./param1.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "namespace MyNamespace;", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "MyNamespace\\init()": { 29 | "fqn": "MyNamespace\\init()", 30 | "extends": [], 31 | "isMember": false, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": false, 35 | "symbolInformation": { 36 | "name": "init", 37 | "kind": 12, 38 | "location": { 39 | "uri": "./param1.php" 40 | }, 41 | "containerName": "MyNamespace" 42 | }, 43 | "type__tostring": "mixed", 44 | "type": {}, 45 | "declarationLine": "function init(Hi $view)", 46 | "documentation": null, 47 | "signatureInformation": { 48 | "label": "(\\MyNamespace\\Hi $view)", 49 | "documentation": null, 50 | "parameters": [ 51 | { 52 | "label": "\\MyNamespace\\Hi $view", 53 | "documentation": null 54 | } 55 | ] 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /tests/Validation/cases/constantsInFunctionParamDefault.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MY_CONSTANT": [ 4 | "./constantsInFunctionParamDefault.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "A": { 9 | "fqn": "A", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "A", 17 | "kind": 11, 18 | "location": { 19 | "uri": "./constantsInFunctionParamDefault.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "interface A {", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "A->b()": { 29 | "fqn": "A->b()", 30 | "extends": [], 31 | "isMember": true, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": false, 35 | "symbolInformation": { 36 | "name": "b", 37 | "kind": 6, 38 | "location": { 39 | "uri": "./constantsInFunctionParamDefault.php" 40 | }, 41 | "containerName": "A" 42 | }, 43 | "type__tostring": "mixed", 44 | "type": {}, 45 | "declarationLine": "function b ($a = MY_CONSTANT);", 46 | "documentation": null, 47 | "signatureInformation": { 48 | "label": "(\\MY_CONSTANT $a = MY_CONSTANT)", 49 | "documentation": null, 50 | "parameters": [ 51 | { 52 | "label": "\\MY_CONSTANT $a = MY_CONSTANT", 53 | "documentation": null 54 | } 55 | ] 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /tests/Server/Workspace/DidChangeWatchedFilesTest.php: -------------------------------------------------------------------------------- 1 | on('message', function (Message $message) use ($fileEvent, &$isDiagnosticsCleared) { 30 | if ($message->body->method === "textDocument/publishDiagnostics") { 31 | $this->assertEquals($message->body->params->uri, $fileEvent->uri); 32 | $this->assertEquals($message->body->params->diagnostics, []); 33 | $isDiagnosticsCleared = true; 34 | } 35 | }); 36 | 37 | $workspace->didChangeWatchedFiles([$fileEvent]); 38 | Loop\tick(true); 39 | 40 | $this->assertTrue($isDiagnosticsCleared, "Deleting file should clear all diagnostics."); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Validation/cases/scopedPropertyAccess5.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "TestInterface": [ 4 | "./scopedPropertyAccess5.php" 5 | ], 6 | "TestClass": [ 7 | "./scopedPropertyAccess5.php" 8 | ], 9 | "TestClass::$testProperty": [ 10 | "./scopedPropertyAccess5.php" 11 | ], 12 | "TestClass::$staticTestProperty": [ 13 | "./scopedPropertyAccess5.php" 14 | ] 15 | }, 16 | "definitions": { 17 | "TestClass": { 18 | "fqn": "TestClass", 19 | "extends": [], 20 | "isMember": false, 21 | "roamed": false, 22 | "isStatic": false, 23 | "canBeInstantiated": true, 24 | "symbolInformation": { 25 | "name": "TestClass", 26 | "kind": 5, 27 | "location": { 28 | "uri": "./scopedPropertyAccess5.php" 29 | }, 30 | "containerName": "" 31 | }, 32 | "type": null, 33 | "declarationLine": "class TestClass implements TestInterface {", 34 | "documentation": null, 35 | "signatureInformation": null 36 | }, 37 | "TestClass::$testProperty": { 38 | "fqn": "TestClass::$testProperty", 39 | "extends": [], 40 | "isMember": true, 41 | "roamed": false, 42 | "isStatic": true, 43 | "canBeInstantiated": false, 44 | "symbolInformation": { 45 | "name": "testProperty", 46 | "kind": 7, 47 | "location": { 48 | "uri": "./scopedPropertyAccess5.php" 49 | }, 50 | "containerName": "TestClass" 51 | }, 52 | "type__tostring": "\\TestClass[]", 53 | "type": {}, 54 | "declarationLine": "public static $testProperty;", 55 | "documentation": "Lorem excepteur officia sit anim velit veniam enim.", 56 | "signatureInformation": null 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /tests/Validation/cases/forLoopReference1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "ForLoopReference1->foo()": [ 4 | "./_cases/forLoopReference1.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "ForLoopReference1": { 9 | "extends": [], 10 | "isGlobal": true, 11 | "isStatic": false, 12 | "canBeInstantiated": true, 13 | "symbolInformation": { 14 | "name": "ForLoopReference1", 15 | "kind": 5, 16 | "location": { 17 | "uri": "./_cases/forLoopReference1.php" 18 | }, 19 | "containerName": "" 20 | }, 21 | "type__class": "LanguageServer\\Tests\\ValidationTest", 22 | "type": null, 23 | "documentation": null 24 | }, 25 | "ForLoopReference1->getThat()": { 26 | "extends": [], 27 | "isGlobal": false, 28 | "isStatic": false, 29 | "canBeInstantiated": false, 30 | "symbolInformation": { 31 | "name": "getThat", 32 | "kind": 6, 33 | "location": { 34 | "uri": "./_cases/forLoopReference1.php" 35 | }, 36 | "containerName": "ForLoopReference1" 37 | }, 38 | "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", 39 | "type": {}, 40 | "documentation": null 41 | }, 42 | "ForLoopReference1->foo()": { 43 | "extends": [], 44 | "isGlobal": false, 45 | "isStatic": false, 46 | "canBeInstantiated": false, 47 | "symbolInformation": { 48 | "name": "foo", 49 | "kind": 6, 50 | "location": { 51 | "uri": "./_cases/forLoopReference1.php" 52 | }, 53 | "containerName": "ForLoopReference1" 54 | }, 55 | "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", 56 | "type": {}, 57 | "documentation": null 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Index/ReadableIndex.php: -------------------------------------------------------------------------------- 1 | Definition] 34 | * that maps fully qualified symbol names to Definitions (global or not) 35 | * 36 | * @return \Generator yields Definition 37 | */ 38 | public function getDefinitions(): \Generator; 39 | 40 | /** 41 | * Returns a Generator that yields all the direct child Definitions of a given FQN 42 | * 43 | * @param string $fqn 44 | * @return \Generator yields Definition 45 | */ 46 | public function getChildDefinitionsForFqn(string $fqn): \Generator; 47 | 48 | /** 49 | * Returns the Definition object by a specific FQN 50 | * 51 | * @param string $fqn 52 | * @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found 53 | * @return Definition|null 54 | */ 55 | public function getDefinition(string $fqn, bool $globalFallback = false); 56 | 57 | /** 58 | * Returns a Generator that yields all URIs in this index that reference a symbol 59 | * 60 | * @param string $fqn The fully qualified name of the symbol 61 | * @return \Generator yields string 62 | */ 63 | public function getReferenceUris(string $fqn): \Generator; 64 | } 65 | -------------------------------------------------------------------------------- /tests/Server/TextDocument/Definition/NamespacedTest.php: -------------------------------------------------------------------------------- 1 | getReferenceLocations('TEST_CONST')[0]; 26 | $result = $this->textDocument->definition( 27 | new TextDocumentIdentifier($reference->uri), 28 | $reference->range->start 29 | )->wait(); 30 | $this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result); 31 | } 32 | 33 | public function testDefinitionForClassLikeUseStatement() 34 | { 35 | // use TestNamespace\TestClass; 36 | // Get definition for TestClass 37 | $reference = $this->getReferenceLocations('TestClass')[7]; 38 | $result = $this->textDocument->definition( 39 | new TextDocumentIdentifier($reference->uri), 40 | $reference->range->start 41 | )->wait(); 42 | $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); 43 | } 44 | 45 | public function testDefinitionForClassLikeGroupUseStatement() 46 | { 47 | // use TestNamespace\{TestTrait, TestInterface}; 48 | // Get definition for TestInterface 49 | $reference = $this->getReferenceLocations('TestClass')[1]; 50 | $result = $this->textDocument->definition( 51 | new TextDocumentIdentifier($reference->uri), 52 | $reference->range->start 53 | )->wait(); 54 | $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "felixfbecker/language-server", 3 | "description": "PHP Implementation of the Visual Studio Code Language Server Protocol", 4 | "license": "ISC", 5 | "keywords": [ 6 | "php", 7 | "language", 8 | "server", 9 | "microsoft", 10 | "visual", 11 | "studio", 12 | "code", 13 | "intellisense", 14 | "autocompletion", 15 | "refactor" 16 | ], 17 | "authors": [ 18 | { 19 | "name": "Felix Becker", 20 | "email": "felix.b@outlook.com" 21 | } 22 | ], 23 | "require": { 24 | "php": "^7.0", 25 | "composer/xdebug-handler": "^1.0", 26 | "felixfbecker/advanced-json-rpc": "^3.0.0", 27 | "felixfbecker/language-server-protocol": "^1.0.1", 28 | "jetbrains/phpstorm-stubs": "dev-master", 29 | "microsoft/tolerant-php-parser": "0.0.*", 30 | "netresearch/jsonmapper": "^1.0", 31 | "phpdocumentor/reflection-docblock": "^4.0.0", 32 | "psr/log": "^1.0", 33 | "sabre/event": "^5.0", 34 | "sabre/uri": "^2.0", 35 | "webmozart/glob": "^4.1", 36 | "webmozart/path-util": "^2.3" 37 | }, 38 | "require-dev": { 39 | "phpunit/phpunit": "^6.3", 40 | "phan/phan": "1.1.4", 41 | "squizlabs/php_codesniffer": "^3.1" 42 | }, 43 | "autoload": { 44 | "psr-4": { 45 | "LanguageServer\\": "src/" 46 | }, 47 | "files" : [ 48 | "src/utils.php", 49 | "src/FqnUtilities.php", 50 | "src/ParserHelpers.php" 51 | ] 52 | }, 53 | "autoload-dev": { 54 | "psr-4": { 55 | "LanguageServer\\Tests\\": "tests/" 56 | } 57 | }, 58 | "bin": [ 59 | "bin/php-language-server.php" 60 | ], 61 | "scripts": { 62 | "parse-stubs": "LanguageServer\\ComposerScripts::parseStubs", 63 | "post-install-cmd": "@parse-stubs", 64 | "post-update-cmd": "@parse-stubs", 65 | "test": "vendor/bin/phpunit", 66 | "lint": "vendor/bin/phpcs" 67 | }, 68 | "config": { 69 | "sort-packages": true 70 | }, 71 | "minimum-stability": "dev", 72 | "prefer-stable": true 73 | } 74 | -------------------------------------------------------------------------------- /tests/Validation/cases/parameterTypeResolution1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "ParamType": [ 4 | "./_cases/parameterTypeResolution1.php" 5 | ], 6 | "static": [ 7 | "./_cases/parameterTypeResolution1.php" 8 | ], 9 | "ParamType->getAccount()": [ 10 | "./_cases/parameterTypeResolution1.php" 11 | ] 12 | }, 13 | "definitions": { 14 | "ParamType": { 15 | "extends": [], 16 | "isGlobal": true, 17 | "isStatic": false, 18 | "canBeInstantiated": true, 19 | "symbolInformation": { 20 | "name": "ParamType", 21 | "kind": 5, 22 | "location": { 23 | "uri": "./_cases/parameterTypeResolution1.php" 24 | }, 25 | "containerName": "" 26 | }, 27 | "type__class": "LanguageServer\\Tests\\ValidationTest", 28 | "type": null, 29 | "documentation": null 30 | }, 31 | "ParamType->setAccount()": { 32 | "extends": [], 33 | "isGlobal": false, 34 | "isStatic": false, 35 | "canBeInstantiated": false, 36 | "symbolInformation": { 37 | "name": "setAccount", 38 | "kind": 6, 39 | "location": { 40 | "uri": "./_cases/parameterTypeResolution1.php" 41 | }, 42 | "containerName": "ParamType" 43 | }, 44 | "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", 45 | "type": {}, 46 | "documentation": null 47 | }, 48 | "ParamType->getAccount()": { 49 | "extends": [], 50 | "isGlobal": false, 51 | "isStatic": false, 52 | "canBeInstantiated": false, 53 | "symbolInformation": { 54 | "name": "getAccount", 55 | "kind": 6, 56 | "location": { 57 | "uri": "./_cases/parameterTypeResolution1.php" 58 | }, 59 | "containerName": "ParamType" 60 | }, 61 | "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", 62 | "type": {}, 63 | "documentation": null 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/ProtocolStreamWriter.php: -------------------------------------------------------------------------------- 1 | output = $output; 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function write(Message $msg): Promise 36 | { 37 | // if the message queue is currently empty, register a write handler. 38 | if (empty($this->messages)) { 39 | Loop\addWriteStream($this->output, function () { 40 | $this->flush(); 41 | }); 42 | } 43 | 44 | $promise = new Promise(); 45 | $this->messages[] = [ 46 | 'message' => (string)$msg, 47 | 'promise' => $promise 48 | ]; 49 | return $promise; 50 | } 51 | 52 | /** 53 | * Writes pending messages to the output stream. 54 | * 55 | * @return void 56 | */ 57 | private function flush() 58 | { 59 | $keepWriting = true; 60 | while ($keepWriting) { 61 | $message = $this->messages[0]['message']; 62 | $promise = $this->messages[0]['promise']; 63 | 64 | $bytesWritten = @fwrite($this->output, $message); 65 | 66 | if ($bytesWritten > 0) { 67 | $message = substr($message, $bytesWritten); 68 | } 69 | 70 | // Determine if this message was completely sent 71 | if (strlen($message) === 0) { 72 | array_shift($this->messages); 73 | 74 | // This was the last message in the queue, remove the write handler. 75 | if (count($this->messages) === 0) { 76 | Loop\removeWriteStream($this->output); 77 | $keepWriting = false; 78 | } 79 | 80 | $promise->fulfill(); 81 | } else { 82 | $this->messages[0]['message'] = $message; 83 | $keepWriting = false; 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /benchmarks/parsing.php: -------------------------------------------------------------------------------- 1 | setLogger($logger); 20 | $xdebugHandler->check(); 21 | unset($xdebugHandler); 22 | 23 | $totalSize = 0; 24 | 25 | $frameworks = ["drupal", "wordpress", "php-language-server", "tolerant-php-parser", "math-php", "symfony", "codeigniter", "cakephp"]; 26 | 27 | foreach($frameworks as $framework) { 28 | $iterator = new RecursiveDirectoryIterator(__DIR__ . "/../validation/frameworks/$framework"); 29 | $testProviderArray = array(); 30 | 31 | foreach (new RecursiveIteratorIterator($iterator) as $file) { 32 | if (strpos((string)$file, ".php") !== false) { 33 | $totalSize += $file->getSize(); 34 | $testProviderArray[] = $file->getPathname(); 35 | } 36 | } 37 | 38 | if (count($testProviderArray) === 0) { 39 | throw new Exception("ERROR: Validation testsuite frameworks not found - run `git submodule update --init --recursive` to download."); 40 | } 41 | 42 | $start = microtime(true); 43 | 44 | foreach ($testProviderArray as $idx => $testCaseFile) { 45 | if (filesize($testCaseFile) > 10000) { 46 | continue; 47 | } 48 | if ($idx % 500 === 0) { 49 | echo $idx . '/' . count($testProviderArray) . PHP_EOL; 50 | } 51 | 52 | $fileContents = file_get_contents($testCaseFile); 53 | 54 | $docBlockFactory = DocBlockFactory::createInstance(); 55 | $index = new Index; 56 | $maxRecursion = []; 57 | $definitions = []; 58 | 59 | $definitionResolver = new DefinitionResolver($index); 60 | $parser = new PhpParser\Parser(); 61 | 62 | $document = new PhpDocument($testCaseFile, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver); 63 | } 64 | 65 | echo "------------------------------\n"; 66 | 67 | echo "Time [$framework]: " . (microtime(true) - $start) . PHP_EOL; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /tests/Validation/cases/stringVariable.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "B->hi": [ 4 | "./stringVariable.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "B": { 9 | "fqn": "B", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": true, 15 | "symbolInformation": { 16 | "name": "B", 17 | "kind": 5, 18 | "location": { 19 | "uri": "./stringVariable.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "class B", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "B->hi": { 29 | "fqn": "B->hi", 30 | "extends": [], 31 | "isMember": true, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": false, 35 | "symbolInformation": { 36 | "name": "hi", 37 | "kind": 7, 38 | "location": { 39 | "uri": "./stringVariable.php" 40 | }, 41 | "containerName": "B" 42 | }, 43 | "type__tostring": "int", 44 | "type": {}, 45 | "declarationLine": "public $hi;", 46 | "documentation": null, 47 | "signatureInformation": null 48 | }, 49 | "B->a()": { 50 | "fqn": "B->a()", 51 | "extends": [], 52 | "isMember": true, 53 | "roamed": false, 54 | "isStatic": false, 55 | "canBeInstantiated": false, 56 | "symbolInformation": { 57 | "name": "a", 58 | "kind": 6, 59 | "location": { 60 | "uri": "./stringVariable.php" 61 | }, 62 | "containerName": "B" 63 | }, 64 | "type__tostring": "mixed", 65 | "type": {}, 66 | "declarationLine": "function a () {", 67 | "documentation": null, 68 | "signatureInformation": { 69 | "label": "()", 70 | "documentation": null, 71 | "parameters": [] 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /tests/Validation/cases/returnType.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "TestNamespace\\TestClass": [ 4 | "./returnType.php" 5 | ], 6 | "TestNamespace\\TestClass2": [ 7 | "./returnType.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "TestNamespace": { 12 | "fqn": "TestNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "TestNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./returnType.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace TestNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "TestNamespace\\whatever()": { 32 | "fqn": "TestNamespace\\whatever()", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": false, 38 | "symbolInformation": { 39 | "name": "whatever", 40 | "kind": 12, 41 | "location": { 42 | "uri": "./returnType.php" 43 | }, 44 | "containerName": "TestNamespace" 45 | }, 46 | "type__tostring": "\\TestNamespace\\TestClass", 47 | "type": {}, 48 | "declarationLine": "function whatever(TestClass $param): TestClass2 {", 49 | "documentation": "Aute duis elit reprehenderit tempor cillum proident anim laborum eu laboris reprehenderit ea incididunt.", 50 | "signatureInformation": { 51 | "label": "(\\TestNamespace\\TestClass $param)", 52 | "documentation": "Aute duis elit reprehenderit tempor cillum proident anim laborum eu laboris reprehenderit ea incididunt.", 53 | "parameters": [ 54 | { 55 | "label": "\\TestNamespace\\TestClass $param", 56 | "documentation": "Adipisicing non non cillum sint incididunt cillum enim mollit." 57 | } 58 | ] 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /tests/Validation/cases/constants5.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\PHP_INT_MAX": [ 4 | "./constants5.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "MyNamespace": { 9 | "fqn": "MyNamespace", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "MyNamespace", 17 | "kind": 3, 18 | "location": { 19 | "uri": "./constants5.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "namespace MyNamespace;", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "MyNamespace\\Mbstring": { 29 | "fqn": "MyNamespace\\Mbstring", 30 | "extends": [], 31 | "isMember": false, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": true, 35 | "symbolInformation": { 36 | "name": "Mbstring", 37 | "kind": 5, 38 | "location": { 39 | "uri": "./constants5.php" 40 | }, 41 | "containerName": "MyNamespace" 42 | }, 43 | "type": null, 44 | "declarationLine": "class Mbstring", 45 | "documentation": null, 46 | "signatureInformation": null 47 | }, 48 | "MyNamespace\\Mbstring::MB_CASE_FOLD": { 49 | "fqn": "MyNamespace\\Mbstring::MB_CASE_FOLD", 50 | "extends": [], 51 | "isMember": true, 52 | "roamed": false, 53 | "isStatic": false, 54 | "canBeInstantiated": false, 55 | "symbolInformation": { 56 | "name": "MB_CASE_FOLD", 57 | "kind": 14, 58 | "location": { 59 | "uri": "./constants5.php" 60 | }, 61 | "containerName": "MyNamespace\\Mbstring" 62 | }, 63 | "type__tostring": "\\MyNamespace\\PHP_INT_MAX", 64 | "type": {}, 65 | "declarationLine": "const MB_CASE_FOLD = PHP_INT_MAX;", 66 | "documentation": null, 67 | "signatureInformation": null 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /tests/Validation/cases/classDefinition1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "TestNamespace\\A": [ 4 | "./classDefinition1.php" 5 | ], 6 | "TestNamespace\\A->a": [ 7 | "./classDefinition1.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "TestNamespace": { 12 | "fqn": "TestNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "TestNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./classDefinition1.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace TestNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "TestNamespace\\A": { 32 | "fqn": "TestNamespace\\A", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "A", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./classDefinition1.php" 43 | }, 44 | "containerName": "TestNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class A {", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "TestNamespace\\A->a": { 52 | "fqn": "TestNamespace\\A->a", 53 | "extends": [], 54 | "isMember": true, 55 | "roamed": false, 56 | "isStatic": false, 57 | "canBeInstantiated": false, 58 | "symbolInformation": { 59 | "name": "a", 60 | "kind": 7, 61 | "location": { 62 | "uri": "./classDefinition1.php" 63 | }, 64 | "containerName": "TestNamespace\\A" 65 | }, 66 | "type__tostring": "int", 67 | "type": {}, 68 | "declarationLine": "public $a;", 69 | "documentation": null, 70 | "signatureInformation": null 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /tests/Validation/cases/objectCreation.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\A->inline_diff_renderer": [ 4 | "./objectCreation.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "MyNamespace": { 9 | "fqn": "MyNamespace", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "MyNamespace", 17 | "kind": 3, 18 | "location": { 19 | "uri": "./objectCreation.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "namespace MyNamespace;", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "MyNamespace\\A": { 29 | "fqn": "MyNamespace\\A", 30 | "extends": [], 31 | "isMember": false, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": true, 35 | "symbolInformation": { 36 | "name": "A", 37 | "kind": 5, 38 | "location": { 39 | "uri": "./objectCreation.php" 40 | }, 41 | "containerName": "MyNamespace" 42 | }, 43 | "type": null, 44 | "declarationLine": "class A {", 45 | "documentation": null, 46 | "signatureInformation": null 47 | }, 48 | "MyNamespace\\A->a()": { 49 | "fqn": "MyNamespace\\A->a()", 50 | "extends": [], 51 | "isMember": true, 52 | "roamed": false, 53 | "isStatic": false, 54 | "canBeInstantiated": false, 55 | "symbolInformation": { 56 | "name": "a", 57 | "kind": 6, 58 | "location": { 59 | "uri": "./objectCreation.php" 60 | }, 61 | "containerName": "MyNamespace\\A" 62 | }, 63 | "type__tostring": "mixed", 64 | "type": {}, 65 | "declarationLine": "function a () {", 66 | "documentation": null, 67 | "signatureInformation": { 68 | "label": "()", 69 | "documentation": null, 70 | "parameters": [] 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /tests/Validation/cases/self5.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\A->assertTrue()": [ 4 | "./self5.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "MyNamespace": { 9 | "fqn": "MyNamespace", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "MyNamespace", 17 | "kind": 3, 18 | "location": { 19 | "uri": "./self5.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "namespace MyNamespace;", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "MyNamespace\\A": { 29 | "fqn": "MyNamespace\\A", 30 | "extends": [], 31 | "isMember": false, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": true, 35 | "symbolInformation": { 36 | "name": "A", 37 | "kind": 5, 38 | "location": { 39 | "uri": "./self5.php" 40 | }, 41 | "containerName": "MyNamespace" 42 | }, 43 | "type": null, 44 | "declarationLine": "class A", 45 | "documentation": null, 46 | "signatureInformation": null 47 | }, 48 | "MyNamespace\\A->typesProvider()": { 49 | "fqn": "MyNamespace\\A->typesProvider()", 50 | "extends": [], 51 | "isMember": true, 52 | "roamed": false, 53 | "isStatic": false, 54 | "canBeInstantiated": false, 55 | "symbolInformation": { 56 | "name": "typesProvider", 57 | "kind": 6, 58 | "location": { 59 | "uri": "./self5.php" 60 | }, 61 | "containerName": "MyNamespace\\A" 62 | }, 63 | "type__tostring": "mixed", 64 | "type": {}, 65 | "declarationLine": "public function typesProvider()", 66 | "documentation": null, 67 | "signatureInformation": { 68 | "label": "()", 69 | "documentation": null, 70 | "parameters": [] 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/ComposerScripts.php: -------------------------------------------------------------------------------- 1 | find("$stubsLocation/**/*.php"); 48 | 49 | foreach ($uris as $uri) { 50 | echo "Parsing $uri\n"; 51 | $content = yield $contentRetriever->retrieve($uri); 52 | 53 | // Change URI to phpstubs:// 54 | $parts = Uri\parse($uri); 55 | $parts['path'] = Path::makeRelative($parts['path'], $stubsLocation); 56 | $parts['scheme'] = 'phpstubs'; 57 | $uri = Uri\build($parts); 58 | 59 | // Create a new document and add it to $index 60 | new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver); 61 | } 62 | 63 | $index->setComplete(); 64 | 65 | echo "Saving Index\n"; 66 | 67 | $index->save(); 68 | 69 | echo "Finished\n"; 70 | })->wait(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/Validation/cases/newStatic.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "static": [ 4 | "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" 5 | ], 6 | "NewStatic->foo()": [ 7 | "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "NewStatic": { 12 | "extends": [], 13 | "isGlobal": true, 14 | "isStatic": false, 15 | "canBeInstantiated": true, 16 | "symbolInformation": { 17 | "name": "NewStatic", 18 | "kind": 5, 19 | "location": { 20 | "uri": "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" 21 | }, 22 | "containerName": "" 23 | }, 24 | "type__class": "LanguageServer\\Tests\\ValidationTest", 25 | "type": null, 26 | "documentation": null 27 | }, 28 | "NewStatic::main()": { 29 | "extends": [], 30 | "isGlobal": false, 31 | "isStatic": true, 32 | "canBeInstantiated": false, 33 | "symbolInformation": { 34 | "name": "main", 35 | "kind": 6, 36 | "location": { 37 | "uri": "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" 38 | }, 39 | "containerName": "NewStatic" 40 | }, 41 | "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", 42 | "type": {}, 43 | "documentation": null 44 | }, 45 | "NewStatic->foo()": { 46 | "extends": [], 47 | "isGlobal": false, 48 | "isStatic": false, 49 | "canBeInstantiated": false, 50 | "symbolInformation": { 51 | "name": "foo", 52 | "kind": 6, 53 | "location": { 54 | "uri": "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" 55 | }, 56 | "containerName": "NewStatic" 57 | }, 58 | "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", 59 | "type": {}, 60 | "documentation": null 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/ProtocolStreamReader.php: -------------------------------------------------------------------------------- 1 | input = $input; 27 | 28 | $this->on('close', function () { 29 | Loop\removeReadStream($this->input); 30 | }); 31 | 32 | Loop\addReadStream($this->input, function () { 33 | if (feof($this->input)) { 34 | // If stream_select reported a status change for this stream, 35 | // but the stream is EOF, it means it was closed. 36 | $this->emit('close'); 37 | return; 38 | } 39 | while (($c = fgetc($this->input)) !== false && $c !== '') { 40 | $this->buffer .= $c; 41 | switch ($this->parsingMode) { 42 | case self::PARSE_HEADERS: 43 | if ($this->buffer === "\r\n") { 44 | $this->parsingMode = self::PARSE_BODY; 45 | $this->contentLength = (int)$this->headers['Content-Length']; 46 | $this->buffer = ''; 47 | } else if (substr($this->buffer, -2) === "\r\n") { 48 | $parts = explode(':', $this->buffer); 49 | $this->headers[$parts[0]] = trim($parts[1]); 50 | $this->buffer = ''; 51 | } 52 | break; 53 | case self::PARSE_BODY: 54 | if (strlen($this->buffer) === $this->contentLength) { 55 | $msg = new Message(MessageBody::parse($this->buffer), $this->headers); 56 | $this->emit('message', [$msg]); 57 | $this->parsingMode = self::PARSE_HEADERS; 58 | $this->headers = []; 59 | $this->buffer = ''; 60 | } 61 | break; 62 | } 63 | } 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Validation/cases/constants4.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\HI": [ 4 | "./constants4.php" 5 | ], 6 | "HI": [ 7 | "./constants4.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./constants4.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\A": { 32 | "fqn": "MyNamespace\\A", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "A", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./constants4.php" 43 | }, 44 | "containerName": "MyNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class A", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "MyNamespace\\A->suite()": { 52 | "fqn": "MyNamespace\\A->suite()", 53 | "extends": [], 54 | "isMember": true, 55 | "roamed": false, 56 | "isStatic": false, 57 | "canBeInstantiated": false, 58 | "symbolInformation": { 59 | "name": "suite", 60 | "kind": 6, 61 | "location": { 62 | "uri": "./constants4.php" 63 | }, 64 | "containerName": "MyNamespace\\A" 65 | }, 66 | "type__tostring": "mixed", 67 | "type": {}, 68 | "declarationLine": "public function suite()", 69 | "documentation": null, 70 | "signatureInformation": { 71 | "label": "()", 72 | "documentation": null, 73 | "parameters": [] 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/PhpDocumentTest.php: -------------------------------------------------------------------------------- 1 | createDocument('whatever', "assertEquals([], $document->getDefinitions()); 37 | } 38 | 39 | public function testGetNodeAtPosition() 40 | { 41 | $document = $this->createDocument('whatever', "getNodeAtPosition(new Position(1, 13)); 43 | $this->assertQualifiedName($node); 44 | $this->assertEquals('SomeClass', (string)$node); 45 | } 46 | 47 | private function assertQualifiedName($node) 48 | { 49 | $this->assertInstanceOf(Node\QualifiedName::class, $node); 50 | } 51 | 52 | public function testIsVendored() 53 | { 54 | $document = $this->createDocument('file:///dir/vendor/x.php', "assertEquals(true, isVendored($document)); 56 | 57 | $document = $this->createDocument('file:///c:/dir/vendor/x.php', "assertEquals(true, isVendored($document)); 59 | 60 | $document = $this->createDocument('file:///vendor/x.php', "assertEquals(true, isVendored($document)); 62 | 63 | $document = $this->createDocument('file:///dir/vendor.php', "assertEquals(false, isVendored($document)); 65 | 66 | $document = $this->createDocument('file:///dir/x.php', "assertEquals(false, isVendored($document)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Validation/cases/constants.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\BYE": [ 4 | "./constants.php" 5 | ], 6 | "BYE": [ 7 | "./constants.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./constants.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\A": { 32 | "fqn": "MyNamespace\\A", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "A", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./constants.php" 43 | }, 44 | "containerName": "MyNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class A", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "MyNamespace\\A::suite()": { 52 | "fqn": "MyNamespace\\A::suite()", 53 | "extends": [], 54 | "isMember": true, 55 | "roamed": false, 56 | "isStatic": true, 57 | "canBeInstantiated": false, 58 | "symbolInformation": { 59 | "name": "suite", 60 | "kind": 6, 61 | "location": { 62 | "uri": "./constants.php" 63 | }, 64 | "containerName": "MyNamespace\\A" 65 | }, 66 | "type__tostring": "mixed", 67 | "type": {}, 68 | "declarationLine": "public static function suite()", 69 | "documentation": null, 70 | "signatureInformation": { 71 | "label": "()", 72 | "documentation": null, 73 | "parameters": [] 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/Validation/cases/constants2.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\BYE": [ 4 | "./constants2.php" 5 | ], 6 | "BYE": [ 7 | "./constants2.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./constants2.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\A": { 32 | "fqn": "MyNamespace\\A", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "A", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./constants2.php" 43 | }, 44 | "containerName": "MyNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class A", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "MyNamespace\\A::suite()": { 52 | "fqn": "MyNamespace\\A::suite()", 53 | "extends": [], 54 | "isMember": true, 55 | "roamed": false, 56 | "isStatic": true, 57 | "canBeInstantiated": false, 58 | "symbolInformation": { 59 | "name": "suite", 60 | "kind": 6, 61 | "location": { 62 | "uri": "./constants2.php" 63 | }, 64 | "containerName": "MyNamespace\\A" 65 | }, 66 | "type__tostring": "mixed", 67 | "type": {}, 68 | "declarationLine": "public static function suite()", 69 | "documentation": null, 70 | "signatureInformation": { 71 | "label": "()", 72 | "documentation": null, 73 | "parameters": [] 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/Validation/cases/constants3.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\T_NEW": [ 4 | "./constants3.php" 5 | ], 6 | "T_NEW": [ 7 | "./constants3.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./constants3.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\A": { 32 | "fqn": "MyNamespace\\A", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "A", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./constants3.php" 43 | }, 44 | "containerName": "MyNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class A", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "MyNamespace\\A::suite()": { 52 | "fqn": "MyNamespace\\A::suite()", 53 | "extends": [], 54 | "isMember": true, 55 | "roamed": false, 56 | "isStatic": true, 57 | "canBeInstantiated": false, 58 | "symbolInformation": { 59 | "name": "suite", 60 | "kind": 6, 61 | "location": { 62 | "uri": "./constants3.php" 63 | }, 64 | "containerName": "MyNamespace\\A" 65 | }, 66 | "type__tostring": "mixed", 67 | "type": {}, 68 | "declarationLine": "public static function suite()", 69 | "documentation": null, 70 | "signatureInformation": { 71 | "label": "()", 72 | "documentation": null, 73 | "parameters": [] 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/DefinitionResolverTest.php: -------------------------------------------------------------------------------- 1 | parseSourceFile("getUri()); 18 | 19 | $index = new Index; 20 | $definitionResolver = new DefinitionResolver($index); 21 | $def = $definitionResolver->createDefinitionFromNode($sourceFileNode->statementList[1]->expression, '\TEST_DEFINE'); 22 | 23 | $this->assertInstanceOf(\phpDocumentor\Reflection\Types\Boolean::class, $def->type); 24 | } 25 | 26 | public function testGetTypeFromNode() 27 | { 28 | $parser = new PhpParser\Parser; 29 | $doc = new MockPhpDocument; 30 | $sourceFileNode = $parser->parseSourceFile("getUri()); 31 | 32 | $index = new Index; 33 | $definitionResolver = new DefinitionResolver($index); 34 | $type = $definitionResolver->getTypeFromNode($sourceFileNode->statementList[1]->expression); 35 | 36 | $this->assertInstanceOf(\phpDocumentor\Reflection\Types\Boolean::class, $type); 37 | } 38 | 39 | public function testGetDefinedFqnForIncompleteDefine() 40 | { 41 | // define('XXX') (only one argument) must not introduce a new symbol 42 | $parser = new PhpParser\Parser; 43 | $doc = new MockPhpDocument; 44 | $sourceFileNode = $parser->parseSourceFile("getUri()); 45 | 46 | $index = new Index; 47 | $definitionResolver = new DefinitionResolver($index); 48 | $fqn = $definitionResolver->getDefinedFqn($sourceFileNode->statementList[1]->expression); 49 | 50 | $this->assertNull($fqn); 51 | } 52 | 53 | public function testGetDefinedFqnForDefine() 54 | { 55 | $parser = new PhpParser\Parser; 56 | $doc = new MockPhpDocument; 57 | $sourceFileNode = $parser->parseSourceFile("getUri()); 58 | 59 | $index = new Index; 60 | $definitionResolver = new DefinitionResolver($index); 61 | $fqn = $definitionResolver->getDefinedFqn($sourceFileNode->statementList[1]->expression); 62 | 63 | $this->assertEquals('TEST_DEFINE', $fqn); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Validation/cases/memberAccess1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\a": [ 4 | "./memberAccess1.php" 5 | ], 6 | "MyNamespace\\a->a()": [ 7 | "./memberAccess1.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./memberAccess1.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\A": { 32 | "fqn": "MyNamespace\\A", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "A", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./memberAccess1.php" 43 | }, 44 | "containerName": "MyNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class A {", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "MyNamespace\\A::a()": { 52 | "fqn": "MyNamespace\\A::a()", 53 | "extends": [], 54 | "isMember": true, 55 | "roamed": false, 56 | "isStatic": true, 57 | "canBeInstantiated": false, 58 | "symbolInformation": { 59 | "name": "a", 60 | "kind": 6, 61 | "location": { 62 | "uri": "./memberAccess1.php" 63 | }, 64 | "containerName": "MyNamespace\\A" 65 | }, 66 | "type__tostring": "mixed", 67 | "type": {}, 68 | "declarationLine": "static function a() {", 69 | "documentation": null, 70 | "signatureInformation": { 71 | "label": "()", 72 | "documentation": null, 73 | "parameters": [] 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/Validation/cases/memberAccess2.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\a": [ 4 | "./memberAccess2.php" 5 | ], 6 | "MyNamespace\\a->a()": [ 7 | "./memberAccess2.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./memberAccess2.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\A": { 32 | "fqn": "MyNamespace\\A", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "A", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./memberAccess2.php" 43 | }, 44 | "containerName": "MyNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class A {", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "MyNamespace\\A::a()": { 52 | "fqn": "MyNamespace\\A::a()", 53 | "extends": [], 54 | "isMember": true, 55 | "roamed": false, 56 | "isStatic": true, 57 | "canBeInstantiated": false, 58 | "symbolInformation": { 59 | "name": "a", 60 | "kind": 6, 61 | "location": { 62 | "uri": "./memberAccess2.php" 63 | }, 64 | "containerName": "MyNamespace\\A" 65 | }, 66 | "type__tostring": "mixed", 67 | "type": {}, 68 | "declarationLine": "static function a() {", 69 | "documentation": null, 70 | "signatureInformation": { 71 | "label": "()", 72 | "documentation": null, 73 | "parameters": [] 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/Validation/cases/static4.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\B": [ 4 | "./static4.php" 5 | ], 6 | "MyNamespace\\A": [ 7 | "./static4.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./static4.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\A": { 32 | "fqn": "MyNamespace\\A", 33 | "extends": [ 34 | "MyNamespace\\B" 35 | ], 36 | "isMember": false, 37 | "roamed": false, 38 | "isStatic": false, 39 | "canBeInstantiated": true, 40 | "symbolInformation": { 41 | "name": "A", 42 | "kind": 5, 43 | "location": { 44 | "uri": "./static4.php" 45 | }, 46 | "containerName": "MyNamespace" 47 | }, 48 | "type": null, 49 | "declarationLine": "class A extends B {", 50 | "documentation": null, 51 | "signatureInformation": null 52 | }, 53 | "MyNamespace\\A->a()": { 54 | "fqn": "MyNamespace\\A->a()", 55 | "extends": [], 56 | "isMember": true, 57 | "roamed": false, 58 | "isStatic": false, 59 | "canBeInstantiated": false, 60 | "symbolInformation": { 61 | "name": "a", 62 | "kind": 6, 63 | "location": { 64 | "uri": "./static4.php" 65 | }, 66 | "containerName": "MyNamespace\\A" 67 | }, 68 | "type__tostring": "mixed", 69 | "type": {}, 70 | "declarationLine": "function a () {", 71 | "documentation": null, 72 | "signatureInformation": { 73 | "label": "()", 74 | "documentation": null, 75 | "parameters": [] 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /tests/Validation/cases/verifyFqsenOnClassProperty.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "CURLAUTH_BASIC": [ 4 | "./verifyFqsenOnClassProperty.php" 5 | ], 6 | "Foo->bar": [ 7 | "./verifyFqsenOnClassProperty.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "Foo": { 12 | "fqn": "Foo", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": true, 18 | "symbolInformation": { 19 | "name": "Foo", 20 | "kind": 5, 21 | "location": { 22 | "uri": "./verifyFqsenOnClassProperty.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "class Foo {", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "Foo->bar": { 32 | "fqn": "Foo->bar", 33 | "extends": [], 34 | "isMember": true, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": false, 38 | "symbolInformation": { 39 | "name": "bar", 40 | "kind": 7, 41 | "location": { 42 | "uri": "./verifyFqsenOnClassProperty.php" 43 | }, 44 | "containerName": "Foo" 45 | }, 46 | "type__tostring": "\\", 47 | "type": {}, 48 | "declarationLine": "protected $bar;", 49 | "documentation": null, 50 | "signatureInformation": null 51 | }, 52 | "Foo->foo()": { 53 | "fqn": "Foo->foo()", 54 | "extends": [], 55 | "isMember": true, 56 | "roamed": false, 57 | "isStatic": false, 58 | "canBeInstantiated": false, 59 | "symbolInformation": { 60 | "name": "foo", 61 | "kind": 6, 62 | "location": { 63 | "uri": "./verifyFqsenOnClassProperty.php" 64 | }, 65 | "containerName": "Foo" 66 | }, 67 | "type__tostring": "mixed", 68 | "type": {}, 69 | "declarationLine": "public function foo () {", 70 | "documentation": null, 71 | "signatureInformation": { 72 | "label": "()", 73 | "documentation": null, 74 | "parameters": [] 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /tests/Validation/cases/memberAccess5.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\ParseErrorsTest->args": [ 4 | "./memberAccess5.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "MyNamespace": { 9 | "fqn": "MyNamespace", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": false, 15 | "symbolInformation": { 16 | "name": "MyNamespace", 17 | "kind": 3, 18 | "location": { 19 | "uri": "./memberAccess5.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "namespace MyNamespace;", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "MyNamespace\\ParseErrorsTest": { 29 | "fqn": "MyNamespace\\ParseErrorsTest", 30 | "extends": [], 31 | "isMember": false, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": true, 35 | "symbolInformation": { 36 | "name": "ParseErrorsTest", 37 | "kind": 5, 38 | "location": { 39 | "uri": "./memberAccess5.php" 40 | }, 41 | "containerName": "MyNamespace" 42 | }, 43 | "type": null, 44 | "declarationLine": "class ParseErrorsTest {", 45 | "documentation": null, 46 | "signatureInformation": null 47 | }, 48 | "MyNamespace\\ParseErrorsTest->setUp()": { 49 | "fqn": "MyNamespace\\ParseErrorsTest->setUp()", 50 | "extends": [], 51 | "isMember": true, 52 | "roamed": false, 53 | "isStatic": false, 54 | "canBeInstantiated": false, 55 | "symbolInformation": { 56 | "name": "setUp", 57 | "kind": 6, 58 | "location": { 59 | "uri": "./memberAccess5.php" 60 | }, 61 | "containerName": "MyNamespace\\ParseErrorsTest" 62 | }, 63 | "type__tostring": "mixed", 64 | "type": {}, 65 | "declarationLine": "public function setUp()", 66 | "documentation": null, 67 | "signatureInformation": { 68 | "label": "()", 69 | "documentation": null, 70 | "parameters": [] 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /tests/Validation/cases/scopedPropertyAccess.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\A::a()": [ 4 | "./scopedPropertyAccess.php" 5 | ], 6 | "MyNamespace\\A": [ 7 | "./scopedPropertyAccess.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./scopedPropertyAccess.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\A": { 32 | "fqn": "MyNamespace\\A", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "A", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./scopedPropertyAccess.php" 43 | }, 44 | "containerName": "MyNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class A {", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "MyNamespace\\A::a()": { 52 | "fqn": "MyNamespace\\A::a()", 53 | "extends": [], 54 | "isMember": true, 55 | "roamed": false, 56 | "isStatic": true, 57 | "canBeInstantiated": false, 58 | "symbolInformation": { 59 | "name": "a", 60 | "kind": 6, 61 | "location": { 62 | "uri": "./scopedPropertyAccess.php" 63 | }, 64 | "containerName": "MyNamespace\\A" 65 | }, 66 | "type__tostring": "mixed", 67 | "type": {}, 68 | "declarationLine": "static function a() {", 69 | "documentation": null, 70 | "signatureInformation": { 71 | "label": "()", 72 | "documentation": null, 73 | "parameters": [] 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/Utils/FileUriTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('file:///var/log', $uri); 16 | 17 | $uri = pathToUri('/usr/local/bin'); 18 | $this->assertEquals('file:///usr/local/bin', $uri); 19 | 20 | $uri = pathToUri('a/b/c/test.txt'); 21 | $this->assertEquals('file:///a/b/c/test.txt', $uri); 22 | 23 | $uri = pathToUri('/d/e/f'); 24 | $this->assertEquals('file:///d/e/f', $uri); 25 | 26 | // special chars are escaped 27 | $uri = pathToUri('c:/path/to/file/dürüm döner.php'); 28 | $this->assertEquals('file:///c:/path/to/file/d%C3%BCr%C3%BCm%20d%C3%B6ner.php', $uri); 29 | 30 | //backslashes are transformed 31 | $uri = pathToUri('c:\\foo\\bar.baz'); 32 | $this->assertEquals('file:///c:/foo/bar.baz', $uri); 33 | } 34 | 35 | public function testUriToPath() 36 | { 37 | $uri = 'file:///var/log'; 38 | $this->assertEquals('/var/log', uriToPath($uri)); 39 | 40 | $uri = 'file:///usr/local/bin'; 41 | $this->assertEquals('/usr/local/bin', uriToPath($uri)); 42 | 43 | $uri = 'file:///a/b/c/test.txt'; 44 | $this->assertEquals('/a/b/c/test.txt', uriToPath($uri)); 45 | 46 | $uri = 'file:///d/e/f'; 47 | $this->assertEquals('/d/e/f', uriToPath($uri)); 48 | 49 | $uri = 'file:///c:/path/to/file/d%C3%BCr%C3%BCm+d%C3%B6ner.php'; 50 | $this->assertEquals('c:\\path\\to\\file\\dürüm döner.php', uriToPath($uri)); 51 | 52 | $uri = 'file:///c:/foo/bar.baz'; 53 | $this->assertEquals('c:\\foo\\bar.baz', uriToPath($uri)); 54 | 55 | $uri = 'file:///c%3A/path/to/file/d%C3%BCr%C3%BCm+d%C3%B6ner.php'; 56 | $this->assertEquals('c:\\path\\to\\file\\dürüm döner.php', uriToPath($uri)); 57 | 58 | $uri = 'file:///c%3A/foo/bar.baz'; 59 | $this->assertEquals('c:\\foo\\bar.baz', uriToPath($uri)); 60 | } 61 | 62 | public function testUriToPathForUnknownProtocol() 63 | { 64 | $this->expectException(InvalidArgumentException::class); 65 | $uri = 'vfs:///whatever'; 66 | uriToPath($uri); 67 | } 68 | 69 | public function testUriToPathForInvalidProtocol() 70 | { 71 | $this->expectException(InvalidArgumentException::class); 72 | $uri = 'http://www.google.com'; 73 | uriToPath($uri); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Validation/cases/methodReturnType.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "FooClass": [ 4 | "./methodReturnType.php" 5 | ] 6 | }, 7 | "definitions": { 8 | "FooClass": { 9 | "fqn": "FooClass", 10 | "extends": [], 11 | "isMember": false, 12 | "roamed": false, 13 | "isStatic": false, 14 | "canBeInstantiated": true, 15 | "symbolInformation": { 16 | "name": "FooClass", 17 | "kind": 5, 18 | "location": { 19 | "uri": "./methodReturnType.php" 20 | }, 21 | "containerName": "" 22 | }, 23 | "type": null, 24 | "declarationLine": "class FooClass {", 25 | "documentation": null, 26 | "signatureInformation": null 27 | }, 28 | "FooClass->foo()": { 29 | "fqn": "FooClass->foo()", 30 | "extends": [], 31 | "isMember": true, 32 | "roamed": false, 33 | "isStatic": false, 34 | "canBeInstantiated": false, 35 | "symbolInformation": { 36 | "name": "foo", 37 | "kind": 6, 38 | "location": { 39 | "uri": "./methodReturnType.php" 40 | }, 41 | "containerName": "FooClass" 42 | }, 43 | "type__tostring": "\\FooClass", 44 | "type": {}, 45 | "declarationLine": "public function foo(): FooClass {", 46 | "documentation": null, 47 | "signatureInformation": { 48 | "label": "()", 49 | "documentation": null, 50 | "parameters": [] 51 | } 52 | }, 53 | "FooClass->bar()": { 54 | "fqn": "FooClass->bar()", 55 | "extends": [], 56 | "isMember": true, 57 | "roamed": false, 58 | "isStatic": false, 59 | "canBeInstantiated": false, 60 | "symbolInformation": { 61 | "name": "bar", 62 | "kind": 6, 63 | "location": { 64 | "uri": "./methodReturnType.php" 65 | }, 66 | "containerName": "FooClass" 67 | }, 68 | "type__tostring": "\\FooClass", 69 | "type": {}, 70 | "declarationLine": "public function bar() { }", 71 | "documentation": "", 72 | "signatureInformation": { 73 | "label": "()", 74 | "documentation": "", 75 | "parameters": [] 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /tests/Diagnostics/InvalidThisUsageTest.php: -------------------------------------------------------------------------------- 1 | getDiagnostics(); 38 | } 39 | 40 | /** 41 | * Assertions about a diagnostic. 42 | * 43 | * @param Diagnostic|null $diagnostic 44 | * @param int $message 45 | * @param string $severity 46 | * @param Range $range 47 | */ 48 | private function assertDiagnostic($diagnostic, $message, $severity, $range) 49 | { 50 | $this->assertInstanceOf(Diagnostic::class, $diagnostic); 51 | $this->assertEquals($message, $diagnostic->message); 52 | $this->assertEquals($severity, $diagnostic->severity); 53 | $this->assertEquals($range, $diagnostic->range); 54 | } 55 | 56 | public function testThisInStaticMethodProducesError() 57 | { 58 | $diagnostics = $this->collectDiagnostics( 59 | __DIR__ . '/../../fixtures/diagnostics/errors/this_in_static_method.php' 60 | ); 61 | 62 | $this->assertCount(1, $diagnostics); 63 | $this->assertDiagnostic( 64 | $diagnostics[0], 65 | '$this can not be used in static methods.', 66 | DiagnosticSeverity::ERROR, 67 | new Range( 68 | new Position(6, 15), 69 | new Position(6, 20) 70 | ) 71 | ); 72 | } 73 | 74 | public function testThisInMethodProducesNoError() 75 | { 76 | $diagnostics = $this->collectDiagnostics( 77 | __DIR__ . '/../../fixtures/diagnostics/baselines/this_in_method.php' 78 | ); 79 | 80 | $this->assertCount(0, $diagnostics); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/Server/TextDocument/Definition/GlobalFallbackTest.php: -------------------------------------------------------------------------------- 1 | setComplete(); 21 | $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); 22 | $definitionResolver = new DefinitionResolver($projectIndex); 23 | $contentRetriever = new FileSystemContentRetriever; 24 | $loader = new PhpDocumentLoader($contentRetriever, $projectIndex, $definitionResolver); 25 | $this->textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex); 26 | $loader->open('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php')); 27 | $loader->open('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php')); 28 | } 29 | 30 | public function testClassDoesNotFallback() 31 | { 32 | // $obj = new TestClass(); 33 | // Get definition for TestClass should not fall back to global 34 | $result = $this->textDocument->definition( 35 | new TextDocumentIdentifier('global_fallback'), 36 | new Position(9, 16) 37 | )->wait(); 38 | $this->assertEquals([], $result); 39 | } 40 | 41 | public function testFallsBackForConstants() 42 | { 43 | // echo TEST_CONST; 44 | // Get definition for TEST_CONST 45 | $result = $this->textDocument->definition( 46 | new TextDocumentIdentifier('global_fallback'), 47 | new Position(6, 10) 48 | )->wait(); 49 | $this->assertEquals(new Location('global_symbols', new Range(new Position(9, 6), new Position(9, 22))), $result); 50 | } 51 | 52 | public function testFallsBackForFunctions() 53 | { 54 | // test_function(); 55 | // Get definition for test_function 56 | $result = $this->textDocument->definition( 57 | new TextDocumentIdentifier('global_fallback'), 58 | new Position(5, 6) 59 | )->wait(); 60 | $this->assertEquals(new Location('global_symbols', new Range(new Position(78, 0), new Position(81, 1))), $result); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ClientHandler.php: -------------------------------------------------------------------------------- 1 | protocolReader = $protocolReader; 29 | $this->protocolWriter = $protocolWriter; 30 | $this->idGenerator = new IdGenerator; 31 | } 32 | 33 | /** 34 | * Sends a request to the client and returns a promise that is resolved with the result or rejected with the error 35 | * 36 | * @param string $method The method to call 37 | * @param array|object $params The method parameters 38 | * @return Promise Resolved with the result of the request or rejected with an error 39 | */ 40 | public function request(string $method, $params): Promise 41 | { 42 | $id = $this->idGenerator->generate(); 43 | return $this->protocolWriter->write( 44 | new Message( 45 | new AdvancedJsonRpc\Request($id, $method, (object)$params) 46 | ) 47 | )->then(function () use ($id) { 48 | $promise = new Promise; 49 | $listener = function (Message $msg) use ($id, $promise, &$listener) { 50 | if (AdvancedJsonRpc\Response::isResponse($msg->body) && $msg->body->id === $id) { 51 | // Received a response 52 | $this->protocolReader->removeListener('message', $listener); 53 | if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) { 54 | $promise->fulfill($msg->body->result); 55 | } else { 56 | $promise->reject($msg->body->error); 57 | } 58 | } 59 | }; 60 | $this->protocolReader->on('message', $listener); 61 | return $promise; 62 | }); 63 | } 64 | 65 | /** 66 | * Sends a notification to the client 67 | * 68 | * @param string $method The method to call 69 | * @param array|object $params The method parameters 70 | * @return Promise Will be resolved as soon as the notification has been sent 71 | */ 72 | public function notify(string $method, $params): Promise 73 | { 74 | return $this->protocolWriter->write( 75 | new Message( 76 | new AdvancedJsonRpc\Notification($method, (object)$params) 77 | ) 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Validation/cases/memberAccess4.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\Request::create()": [ 4 | "./memberAccess4.php" 5 | ], 6 | "MyNamespace\\Request": [ 7 | "./memberAccess4.php" 8 | ], 9 | "MyNamespace\\Url->toString()": [ 10 | "./memberAccess4.php" 11 | ], 12 | "MyNamespace\\Url": [ 13 | "./memberAccess4.php" 14 | ] 15 | }, 16 | "definitions": { 17 | "MyNamespace": { 18 | "fqn": "MyNamespace", 19 | "extends": [], 20 | "isMember": false, 21 | "roamed": false, 22 | "isStatic": false, 23 | "canBeInstantiated": false, 24 | "symbolInformation": { 25 | "name": "MyNamespace", 26 | "kind": 3, 27 | "location": { 28 | "uri": "./memberAccess4.php" 29 | }, 30 | "containerName": "" 31 | }, 32 | "type": null, 33 | "declarationLine": "namespace MyNamespace;", 34 | "documentation": null, 35 | "signatureInformation": null 36 | }, 37 | "MyNamespace\\A": { 38 | "fqn": "MyNamespace\\A", 39 | "extends": [], 40 | "isMember": false, 41 | "roamed": false, 42 | "isStatic": false, 43 | "canBeInstantiated": true, 44 | "symbolInformation": { 45 | "name": "A", 46 | "kind": 5, 47 | "location": { 48 | "uri": "./memberAccess4.php" 49 | }, 50 | "containerName": "MyNamespace" 51 | }, 52 | "type": null, 53 | "declarationLine": "class A {", 54 | "documentation": null, 55 | "signatureInformation": null 56 | }, 57 | "MyNamespace\\A->testRequest()": { 58 | "fqn": "MyNamespace\\A->testRequest()", 59 | "extends": [], 60 | "isMember": true, 61 | "roamed": false, 62 | "isStatic": false, 63 | "canBeInstantiated": false, 64 | "symbolInformation": { 65 | "name": "testRequest", 66 | "kind": 6, 67 | "location": { 68 | "uri": "./memberAccess4.php" 69 | }, 70 | "containerName": "MyNamespace\\A" 71 | }, 72 | "type__tostring": "mixed", 73 | "type": {}, 74 | "declarationLine": "public function testRequest()", 75 | "documentation": null, 76 | "signatureInformation": { 77 | "label": "()", 78 | "documentation": null, 79 | "parameters": [] 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /tests/Validation/cases/self4.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\A": [ 4 | "./self4.php" 5 | ], 6 | "MyNamespace\\A->addTestFile()": [ 7 | "./self4.php" 8 | ], 9 | "MyNamespace\\__DIR__": [ 10 | "./self4.php" 11 | ], 12 | "__DIR__": [ 13 | "./self4.php" 14 | ], 15 | "MyNamespace\\DS": [ 16 | "./self4.php" 17 | ], 18 | "DS": [ 19 | "./self4.php" 20 | ] 21 | }, 22 | "definitions": { 23 | "MyNamespace": { 24 | "fqn": "MyNamespace", 25 | "extends": [], 26 | "isMember": false, 27 | "roamed": false, 28 | "isStatic": false, 29 | "canBeInstantiated": false, 30 | "symbolInformation": { 31 | "name": "MyNamespace", 32 | "kind": 3, 33 | "location": { 34 | "uri": "./self4.php" 35 | }, 36 | "containerName": "" 37 | }, 38 | "type": null, 39 | "declarationLine": "namespace MyNamespace;", 40 | "documentation": null, 41 | "signatureInformation": null 42 | }, 43 | "MyNamespace\\A": { 44 | "fqn": "MyNamespace\\A", 45 | "extends": [], 46 | "isMember": false, 47 | "roamed": false, 48 | "isStatic": false, 49 | "canBeInstantiated": true, 50 | "symbolInformation": { 51 | "name": "A", 52 | "kind": 5, 53 | "location": { 54 | "uri": "./self4.php" 55 | }, 56 | "containerName": "MyNamespace" 57 | }, 58 | "type": null, 59 | "declarationLine": "class A", 60 | "documentation": null, 61 | "signatureInformation": null 62 | }, 63 | "MyNamespace\\A::suite()": { 64 | "fqn": "MyNamespace\\A::suite()", 65 | "extends": [], 66 | "isMember": true, 67 | "roamed": false, 68 | "isStatic": true, 69 | "canBeInstantiated": false, 70 | "symbolInformation": { 71 | "name": "suite", 72 | "kind": 6, 73 | "location": { 74 | "uri": "./self4.php" 75 | }, 76 | "containerName": "MyNamespace\\A" 77 | }, 78 | "type__tostring": "mixed", 79 | "type": {}, 80 | "declarationLine": "public static function suite()", 81 | "documentation": null, 82 | "signatureInformation": { 83 | "label": "()", 84 | "documentation": null, 85 | "parameters": [] 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/SignatureInformationFactory.php: -------------------------------------------------------------------------------- 1 | definitionResolver = $definitionResolver; 22 | } 23 | 24 | /** 25 | * Create a SignatureInformation from a FunctionLike node 26 | * 27 | * @param FunctionLike $node Node to create signature information from 28 | * 29 | * @return SignatureInformation 30 | */ 31 | public function create(FunctionLike $node): SignatureInformation 32 | { 33 | $params = $this->createParameters($node); 34 | $label = $this->createLabel($params); 35 | return new SignatureInformation( 36 | $label, 37 | $params, 38 | $this->definitionResolver->getDocumentationFromNode($node) 39 | ); 40 | } 41 | 42 | /** 43 | * Gets parameters from a FunctionLike node 44 | * 45 | * @param FunctionLike $node Node to get parameters from 46 | * 47 | * @return ParameterInformation[] 48 | */ 49 | private function createParameters(FunctionLike $node): array 50 | { 51 | $params = []; 52 | if ($node->parameters) { 53 | foreach ($node->parameters->getElements() as $element) { 54 | $param = (string) $this->definitionResolver->getTypeFromNode($element); 55 | $param .= ' '; 56 | if ($element->dotDotDotToken) { 57 | $param .= '...'; 58 | } 59 | $param .= '$' . $element->getName(); 60 | if ($element->default) { 61 | $param .= ' = ' . $element->default->getText(); 62 | } 63 | $params[] = new ParameterInformation( 64 | $param, 65 | $this->definitionResolver->getDocumentationFromNode($element) 66 | ); 67 | } 68 | } 69 | return $params; 70 | } 71 | 72 | /** 73 | * Creates a signature information label from parameters 74 | * 75 | * @param ParameterInformation[] $params Parameters to create the label from 76 | * 77 | * @return string 78 | */ 79 | private function createLabel(array $params): string 80 | { 81 | $label = '('; 82 | if ($params) { 83 | foreach ($params as $param) { 84 | $label .= $param->label . ', '; 85 | } 86 | $label = substr($label, 0, -2); 87 | } 88 | $label .= ')'; 89 | return $label; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/Server/TextDocument/References/GlobalFallbackTest.php: -------------------------------------------------------------------------------- 1 | setComplete(); 25 | $definitionResolver = new DefinitionResolver($projectIndex); 26 | $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); 27 | $this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver); 28 | $this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex); 29 | $this->documentLoader->open('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php')); 30 | $this->documentLoader->open('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php')); 31 | } 32 | 33 | public function testClassDoesNotFallback() 34 | { 35 | // class TestClass implements TestInterface 36 | // Get references for TestClass 37 | $result = $this->textDocument->references( 38 | new ReferenceContext, 39 | new TextDocumentIdentifier('global_symbols'), 40 | new Position(6, 9) 41 | )->wait(); 42 | $this->assertEquals([], $result); 43 | } 44 | 45 | public function testFallsBackForConstants() 46 | { 47 | // const TEST_CONST = 123; 48 | // Get references for TEST_CONST 49 | $result = $this->textDocument->references( 50 | new ReferenceContext, 51 | new TextDocumentIdentifier('global_symbols'), 52 | new Position(9, 13) 53 | )->wait(); 54 | $this->assertEquals([new Location('global_fallback', new Range(new Position(6, 5), new Position(6, 15)))], $result); 55 | } 56 | 57 | public function testFallsBackForFunctions() 58 | { 59 | // function test_function() 60 | // Get references for test_function 61 | $result = $this->textDocument->references( 62 | new ReferenceContext, 63 | new TextDocumentIdentifier('global_symbols'), 64 | new Position(78, 16) 65 | )->wait(); 66 | $this->assertEquals([new Location('global_fallback', new Range(new Position(5, 0), new Position(5, 13)))], $result); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Validation/cases/memberCall1.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\AccountInterface": [ 4 | "./memberCall1.php" 5 | ], 6 | "MyNamespace\\A": [ 7 | "./memberCall1.php" 8 | ], 9 | "MyNamespace\\AccountInterface->getAccount()": [ 10 | "./memberCall1.php" 11 | ] 12 | }, 13 | "definitions": { 14 | "MyNamespace": { 15 | "fqn": "MyNamespace", 16 | "extends": [], 17 | "isMember": false, 18 | "roamed": false, 19 | "isStatic": false, 20 | "canBeInstantiated": false, 21 | "symbolInformation": { 22 | "name": "MyNamespace", 23 | "kind": 3, 24 | "location": { 25 | "uri": "./memberCall1.php" 26 | }, 27 | "containerName": "" 28 | }, 29 | "type": null, 30 | "declarationLine": "namespace MyNamespace;", 31 | "documentation": null, 32 | "signatureInformation": null 33 | }, 34 | "MyNamespace\\ParseErrorsTest": { 35 | "fqn": "MyNamespace\\ParseErrorsTest", 36 | "extends": [], 37 | "isMember": false, 38 | "roamed": false, 39 | "isStatic": false, 40 | "canBeInstantiated": true, 41 | "symbolInformation": { 42 | "name": "ParseErrorsTest", 43 | "kind": 5, 44 | "location": { 45 | "uri": "./memberCall1.php" 46 | }, 47 | "containerName": "MyNamespace" 48 | }, 49 | "type": null, 50 | "declarationLine": "class ParseErrorsTest", 51 | "documentation": null, 52 | "signatureInformation": null 53 | }, 54 | "MyNamespace\\ParseErrorsTest->setAccount()": { 55 | "fqn": "MyNamespace\\ParseErrorsTest->setAccount()", 56 | "extends": [], 57 | "isMember": true, 58 | "roamed": false, 59 | "isStatic": false, 60 | "canBeInstantiated": false, 61 | "symbolInformation": { 62 | "name": "setAccount", 63 | "kind": 6, 64 | "location": { 65 | "uri": "./memberCall1.php" 66 | }, 67 | "containerName": "MyNamespace\\ParseErrorsTest" 68 | }, 69 | "type__tostring": "mixed", 70 | "type": {}, 71 | "declarationLine": "public function setAccount(AccountInterface $account)", 72 | "documentation": null, 73 | "signatureInformation": { 74 | "label": "(\\MyNamespace\\AccountInterface $account)", 75 | "documentation": null, 76 | "parameters": [ 77 | { 78 | "label": "\\MyNamespace\\AccountInterface $account", 79 | "documentation": null 80 | } 81 | ] 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/FqnUtilities.php: -------------------------------------------------------------------------------- 1 | getFqsen(); 18 | if ($fqsen !== null) { 19 | $fqns[] = substr((string)$fqsen, 1); 20 | } 21 | } 22 | if ($type instanceof Types\Compound) { 23 | for ($i = 0; $t = $type->get($i); $i++) { 24 | foreach (getFqnsFromType($t) as $fqn) { 25 | $fqns[] = $fqn; 26 | } 27 | } 28 | } 29 | return $fqns; 30 | } 31 | 32 | /** 33 | * Returns parent of an FQN. 34 | * 35 | * getFqnParent('') === '' 36 | * getFqnParent('\\') === '' 37 | * getFqnParent('\A') === '' 38 | * getFqnParent('A') === '' 39 | * getFqnParent('\A\') === '\A' // Empty trailing name is considered a name. 40 | * 41 | * @return string 42 | */ 43 | function nameGetParent(string $name): string 44 | { 45 | if ($name === '') { // Special-case handling for the root namespace. 46 | return ''; 47 | } 48 | $parts = explode('\\', $name); 49 | array_pop($parts); 50 | return implode('\\', $parts); 51 | } 52 | 53 | /** 54 | * Concatenates two names. 55 | * 56 | * nameConcat('\Foo\Bar', 'Baz') === '\Foo\Bar\Baz' 57 | * nameConcat('\Foo\Bar\\', '\Baz') === '\Foo\Bar\Baz' 58 | * nameConcat('\\', 'Baz') === '\Baz' 59 | * nameConcat('', 'Baz') === 'Baz' 60 | * 61 | * @return string 62 | */ 63 | function nameConcat(string $a, string $b): string 64 | { 65 | if ($a === '') { 66 | return $b; 67 | } 68 | $a = rtrim($a, '\\'); 69 | $b = ltrim($b, '\\'); 70 | return "$a\\$b"; 71 | } 72 | 73 | /** 74 | * Returns the first component of $name. 75 | * 76 | * nameGetFirstPart('Foo\Bar') === 'Foo' 77 | * nameGetFirstPart('\Foo\Bar') === 'Foo' 78 | * nameGetFirstPart('') === '' 79 | * nameGetFirstPart('\') === '' 80 | */ 81 | function nameGetFirstPart(string $name): string 82 | { 83 | $parts = explode('\\', $name, 3); 84 | if ($parts[0] === '' && count($parts) > 1) { 85 | return $parts[1]; 86 | } else { 87 | return $parts[0]; 88 | } 89 | } 90 | 91 | /** 92 | * Removes the first component of $name. 93 | * 94 | * nameWithoutFirstPart('Foo\Bar') === 'Bar' 95 | * nameWithoutFirstPart('\Foo\Bar') === 'Bar' 96 | * nameWithoutFirstPart('') === '' 97 | * nameWithoutFirstPart('\') === '' 98 | */ 99 | function nameWithoutFirstPart(string $name): string 100 | { 101 | $parts = explode('\\', $name, 3); 102 | if ($parts[0] === '') { 103 | array_shift($parts); 104 | } 105 | array_shift($parts); 106 | return implode('\\', $parts); 107 | } 108 | 109 | /** 110 | * @param string $name Name to match against 111 | * @param string $prefix Prefix $name has to starts with 112 | * @return bool 113 | */ 114 | function nameStartsWith(string $name, string $prefix): bool 115 | { 116 | return strlen($name) >= strlen($prefix) 117 | && strncmp($name, $prefix, strlen($prefix)) === 0; 118 | } 119 | -------------------------------------------------------------------------------- /benchmarks/completion.php: -------------------------------------------------------------------------------- 1 | setLogger($logger); 22 | $xdebugHandler->check(); 23 | unset($xdebugHandler); 24 | 25 | $totalSize = 0; 26 | 27 | $framework = "symfony"; 28 | 29 | $iterator = new RecursiveDirectoryIterator(__DIR__ . "/../validation/frameworks/$framework"); 30 | $testProviderArray = array(); 31 | 32 | foreach (new RecursiveIteratorIterator($iterator) as $file) { 33 | if (strpos((string)$file, ".php") !== false) { 34 | $totalSize += $file->getSize(); 35 | $testProviderArray[] = $file->getRealPath(); 36 | } 37 | } 38 | 39 | if (count($testProviderArray) === 0) { 40 | throw new Exception("ERROR: Validation testsuite frameworks not found - run `git submodule update --init --recursive` to download."); 41 | } 42 | 43 | $index = new Index; 44 | $definitionResolver = new DefinitionResolver($index); 45 | $completionProvider = new CompletionProvider($definitionResolver, $index); 46 | $docBlockFactory = DocBlockFactory::createInstance(); 47 | $completionFile = realpath(__DIR__ . '/../validation/frameworks/symfony/src/Symfony/Component/HttpFoundation/Request.php'); 48 | $parser = new PhpParser\Parser(); 49 | $completionDocument = null; 50 | 51 | echo "Indexing $framework" . PHP_EOL; 52 | 53 | foreach ($testProviderArray as $idx => $testCaseFile) { 54 | if (filesize($testCaseFile) > 100000) { 55 | continue; 56 | } 57 | if ($idx % 100 === 0) { 58 | echo $idx . '/' . count($testProviderArray) . PHP_EOL; 59 | } 60 | 61 | $fileContents = file_get_contents($testCaseFile); 62 | 63 | try { 64 | $d = new PhpDocument($testCaseFile, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver); 65 | if ($testCaseFile === $completionFile) { 66 | $completionDocument = $d; 67 | } 68 | } catch (\Throwable $e) { 69 | echo $e->getMessage() . PHP_EOL; 70 | continue; 71 | } 72 | } 73 | 74 | echo "Getting completion". PHP_EOL; 75 | 76 | // Completion in $this->|request = new ParameterBag($request); 77 | $start = microtime(true); 78 | $list = $completionProvider->provideCompletion($completionDocument, new Position(274, 15)); 79 | $end = microtime(true); 80 | echo 'Time ($this->|): ' . ($end - $start) . 's' . PHP_EOL; 81 | echo count($list->items) . ' completion items' . PHP_EOL; 82 | 83 | // Completion in $this->request = new| ParameterBag($request); 84 | // (this only finds ParameterBag though.) 85 | $start = microtime(true); 86 | $list = $completionProvider->provideCompletion($completionDocument, new Position(274, 28)); 87 | $end = microtime(true); 88 | echo 'Time (new|): ' . ($end - $start) . 's' . PHP_EOL; 89 | echo count($list->items) . ' completion items' . PHP_EOL; 90 | -------------------------------------------------------------------------------- /tests/Validation/cases/objectCreation2.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\B->hi()": [ 4 | "./objectCreation2.php" 5 | ], 6 | "MyNamespace\\B": [ 7 | "./objectCreation2.php" 8 | ] 9 | }, 10 | "definitions": { 11 | "MyNamespace": { 12 | "fqn": "MyNamespace", 13 | "extends": [], 14 | "isMember": false, 15 | "roamed": false, 16 | "isStatic": false, 17 | "canBeInstantiated": false, 18 | "symbolInformation": { 19 | "name": "MyNamespace", 20 | "kind": 3, 21 | "location": { 22 | "uri": "./objectCreation2.php" 23 | }, 24 | "containerName": "" 25 | }, 26 | "type": null, 27 | "declarationLine": "namespace MyNamespace;", 28 | "documentation": null, 29 | "signatureInformation": null 30 | }, 31 | "MyNamespace\\B": { 32 | "fqn": "MyNamespace\\B", 33 | "extends": [], 34 | "isMember": false, 35 | "roamed": false, 36 | "isStatic": false, 37 | "canBeInstantiated": true, 38 | "symbolInformation": { 39 | "name": "B", 40 | "kind": 5, 41 | "location": { 42 | "uri": "./objectCreation2.php" 43 | }, 44 | "containerName": "MyNamespace" 45 | }, 46 | "type": null, 47 | "declarationLine": "class B {", 48 | "documentation": null, 49 | "signatureInformation": null 50 | }, 51 | "MyNamespace\\A": { 52 | "fqn": "MyNamespace\\A", 53 | "extends": [], 54 | "isMember": false, 55 | "roamed": false, 56 | "isStatic": false, 57 | "canBeInstantiated": true, 58 | "symbolInformation": { 59 | "name": "A", 60 | "kind": 5, 61 | "location": { 62 | "uri": "./objectCreation2.php" 63 | }, 64 | "containerName": "MyNamespace" 65 | }, 66 | "type": null, 67 | "declarationLine": "class A {", 68 | "documentation": null, 69 | "signatureInformation": null 70 | }, 71 | "MyNamespace\\A->a()": { 72 | "fqn": "MyNamespace\\A->a()", 73 | "extends": [], 74 | "isMember": true, 75 | "roamed": false, 76 | "isStatic": false, 77 | "canBeInstantiated": false, 78 | "symbolInformation": { 79 | "name": "a", 80 | "kind": 6, 81 | "location": { 82 | "uri": "./objectCreation2.php" 83 | }, 84 | "containerName": "MyNamespace\\A" 85 | }, 86 | "type__tostring": "mixed", 87 | "type": {}, 88 | "declarationLine": "function a () {", 89 | "documentation": null, 90 | "signatureInformation": { 91 | "label": "()", 92 | "documentation": null, 93 | "parameters": [] 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /tests/Validation/cases/memberAccess3.php.expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": { 3 | "MyNamespace\\ClassLoader": [ 4 | "./memberAccess3.php" 5 | ], 6 | "Closure::bind()": [ 7 | "./memberAccess3.php" 8 | ], 9 | "Closure": [ 10 | "./memberAccess3.php" 11 | ], 12 | "MyNamespace\\ClassLoader->prefixesPsr0": [ 13 | "./memberAccess3.php" 14 | ], 15 | "MyNamespace\\ComposerStaticInitIncludePath": [ 16 | "./memberAccess3.php" 17 | ], 18 | "MyNamespace\\ComposerStaticInitIncludePath::$prefixesPsr0": [ 19 | "./memberAccess3.php" 20 | ], 21 | "MyNamespace\\ClassLoader::class": [ 22 | "./memberAccess3.php" 23 | ] 24 | }, 25 | "definitions": { 26 | "MyNamespace": { 27 | "fqn": "MyNamespace", 28 | "extends": [], 29 | "isMember": false, 30 | "roamed": false, 31 | "isStatic": false, 32 | "canBeInstantiated": false, 33 | "symbolInformation": { 34 | "name": "MyNamespace", 35 | "kind": 3, 36 | "location": { 37 | "uri": "./memberAccess3.php" 38 | }, 39 | "containerName": "" 40 | }, 41 | "type": null, 42 | "declarationLine": "namespace MyNamespace;", 43 | "documentation": null, 44 | "signatureInformation": null 45 | }, 46 | "MyNamespace\\A": { 47 | "fqn": "MyNamespace\\A", 48 | "extends": [], 49 | "isMember": false, 50 | "roamed": false, 51 | "isStatic": false, 52 | "canBeInstantiated": true, 53 | "symbolInformation": { 54 | "name": "A", 55 | "kind": 5, 56 | "location": { 57 | "uri": "./memberAccess3.php" 58 | }, 59 | "containerName": "MyNamespace" 60 | }, 61 | "type": null, 62 | "declarationLine": "class A {", 63 | "documentation": null, 64 | "signatureInformation": null 65 | }, 66 | "MyNamespace\\A::getInitializer()": { 67 | "fqn": "MyNamespace\\A::getInitializer()", 68 | "extends": [], 69 | "isMember": true, 70 | "roamed": false, 71 | "isStatic": true, 72 | "canBeInstantiated": false, 73 | "symbolInformation": { 74 | "name": "getInitializer", 75 | "kind": 6, 76 | "location": { 77 | "uri": "./memberAccess3.php" 78 | }, 79 | "containerName": "MyNamespace\\A" 80 | }, 81 | "type__tostring": "mixed", 82 | "type": {}, 83 | "declarationLine": "public static function getInitializer(ClassLoader $loader)", 84 | "documentation": null, 85 | "signatureInformation": { 86 | "label": "(\\MyNamespace\\ClassLoader $loader)", 87 | "documentation": null, 88 | "parameters": [ 89 | { 90 | "label": "\\MyNamespace\\ClassLoader $loader", 91 | "documentation": null 92 | } 93 | ] 94 | } 95 | } 96 | } 97 | } --------------------------------------------------------------------------------