Compare commits
12 Commits
Author | SHA1 | Date |
---|---|---|
|
fb48c70ce2 | |
|
3fc105717d | |
|
9dc1656592 | |
|
7303143a60 | |
|
1705583e32 | |
|
1da3328bc2 | |
|
450116e2f3 | |
|
b1cc565d7e | |
|
ed2d8ddb1e | |
|
680f430453 | |
|
c7d25c7b44 | |
|
71390c9903 |
|
@ -10,6 +10,7 @@
|
||||||
/.gitignore export-ignore
|
/.gitignore export-ignore
|
||||||
/.gitmodules export-ignore
|
/.gitmodules export-ignore
|
||||||
/.npmrc export-ignore
|
/.npmrc export-ignore
|
||||||
|
/.phan export-ignore
|
||||||
/.travis.yml export-ignore
|
/.travis.yml export-ignore
|
||||||
/appveyor.yml export-ignore
|
/appveyor.yml export-ignore
|
||||||
/codecov.yml export-ignore
|
/codecov.yml export-ignore
|
||||||
|
|
|
@ -0,0 +1,308 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Phan\Issue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration file was automatically generated by 'phan --init --init-level=1'
|
||||||
|
*
|
||||||
|
* TODOs (added by 'phan --init'):
|
||||||
|
*
|
||||||
|
* - Go through this file and verify that there are no missing/unnecessary files/directories.
|
||||||
|
* (E.g. this only includes direct composer dependencies - You may have to manually add indirect composer dependencies to 'directory_list')
|
||||||
|
* - Look at 'plugins' and add or remove plugins if appropriate (see https://github.com/phan/phan/tree/master/.phan/plugins#plugins)
|
||||||
|
* - Add global suppressions for pre-existing issues to suppress_issue_types (https://github.com/phan/phan/wiki/Tutorial-for-Analyzing-a-Large-Sloppy-Code-Base)
|
||||||
|
*
|
||||||
|
* This configuration will be read and overlayed on top of the
|
||||||
|
* default configuration. Command line arguments will be applied
|
||||||
|
* after this file is read.
|
||||||
|
*
|
||||||
|
* @see src/Phan/Config.php
|
||||||
|
* See Config for all configurable options.
|
||||||
|
*
|
||||||
|
* A Note About Paths
|
||||||
|
* ==================
|
||||||
|
*
|
||||||
|
* Files referenced from this file should be defined as
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* Config::projectPath('relative_path/to/file')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* where the relative path is relative to the root of the
|
||||||
|
* project which is defined as either the working directory
|
||||||
|
* of the phan executable or a path passed in via the CLI
|
||||||
|
* '-d' flag.
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
|
||||||
|
// Supported values: '7.0', '7.1', '7.2', null.
|
||||||
|
// If this is set to null,
|
||||||
|
// then Phan assumes the PHP version which is closest to the minor version
|
||||||
|
// of the php executable used to execute phan.
|
||||||
|
// Automatically inferred from composer.json requirement for "php" of "^7.0"
|
||||||
|
'target_php_version' => '7.0',
|
||||||
|
|
||||||
|
// If enabled, missing properties will be created when
|
||||||
|
// they are first seen. If false, we'll report an
|
||||||
|
// error message if there is an attempt to write
|
||||||
|
// to a class property that wasn't explicitly
|
||||||
|
// defined.
|
||||||
|
'allow_missing_properties' => false,
|
||||||
|
|
||||||
|
// If enabled, null can be cast as any type and any
|
||||||
|
// type can be cast to null. Setting this to true
|
||||||
|
// will cut down on false positives.
|
||||||
|
'null_casts_as_any_type' => false,
|
||||||
|
|
||||||
|
// If enabled, allow null to be cast as any array-like type.
|
||||||
|
// This is an incremental step in migrating away from null_casts_as_any_type.
|
||||||
|
// If null_casts_as_any_type is true, this has no effect.
|
||||||
|
'null_casts_as_array' => false,
|
||||||
|
|
||||||
|
// If enabled, allow any array-like type to be cast to null.
|
||||||
|
// This is an incremental step in migrating away from null_casts_as_any_type.
|
||||||
|
// If null_casts_as_any_type is true, this has no effect.
|
||||||
|
'array_casts_as_null' => false,
|
||||||
|
|
||||||
|
// If enabled, scalars (int, float, bool, string, null)
|
||||||
|
// are treated as if they can cast to each other.
|
||||||
|
// This does not affect checks of array keys. See scalar_array_key_cast.
|
||||||
|
'scalar_implicit_cast' => false,
|
||||||
|
|
||||||
|
// If enabled, any scalar array keys (int, string)
|
||||||
|
// are treated as if they can cast to each other.
|
||||||
|
// E.g. array<int,stdClass> can cast to array<string,stdClass> and vice versa.
|
||||||
|
// Normally, a scalar type such as int could only cast to/from int and mixed.
|
||||||
|
'scalar_array_key_cast' => false,
|
||||||
|
|
||||||
|
// If this has entries, scalars (int, float, bool, string, null)
|
||||||
|
// are allowed to perform the casts listed.
|
||||||
|
// E.g. ['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]
|
||||||
|
// allows casting null to a string, but not vice versa.
|
||||||
|
// (subset of scalar_implicit_cast)
|
||||||
|
'scalar_implicit_partial' => [],
|
||||||
|
|
||||||
|
// If true, seemingly undeclared variables in the global
|
||||||
|
// scope will be ignored. This is useful for projects
|
||||||
|
// with complicated cross-file globals that you have no
|
||||||
|
// hope of fixing.
|
||||||
|
'ignore_undeclared_variables_in_global_scope' => false,
|
||||||
|
|
||||||
|
// Backwards Compatibility Checking. This is slow
|
||||||
|
// and expensive, but you should consider running
|
||||||
|
// it before upgrading your version of PHP to a
|
||||||
|
// new version that has backward compatibility
|
||||||
|
// breaks.
|
||||||
|
'backward_compatibility_checks' => false,
|
||||||
|
|
||||||
|
// If true, check to make sure the return type declared
|
||||||
|
// in the doc-block (if any) matches the return type
|
||||||
|
// declared in the method signature.
|
||||||
|
'check_docblock_signature_return_type_match' => true,
|
||||||
|
|
||||||
|
// (*Requires check_docblock_signature_param_type_match to be true*)
|
||||||
|
// If true, make narrowed types from phpdoc params override
|
||||||
|
// the real types from the signature, when real types exist.
|
||||||
|
// (E.g. allows specifying desired lists of subclasses,
|
||||||
|
// or to indicate a preference for non-nullable types over nullable types)
|
||||||
|
// Affects analysis of the body of the method and the param types passed in by callers.
|
||||||
|
'prefer_narrowed_phpdoc_param_type' => true,
|
||||||
|
|
||||||
|
// (*Requires check_docblock_signature_return_type_match to be true*)
|
||||||
|
// If true, make narrowed types from phpdoc returns override
|
||||||
|
// the real types from the signature, when real types exist.
|
||||||
|
// (E.g. allows specifying desired lists of subclasses,
|
||||||
|
// or to indicate a preference for non-nullable types over nullable types)
|
||||||
|
// Affects analysis of return statements in the body of the method and the return types passed in by callers.
|
||||||
|
'prefer_narrowed_phpdoc_return_type' => true,
|
||||||
|
|
||||||
|
'ensure_signature_compatibility' => true,
|
||||||
|
|
||||||
|
// Set to true in order to attempt to detect dead
|
||||||
|
// (unreferenced) code. Keep in mind that the
|
||||||
|
// results will only be a guess given that classes,
|
||||||
|
// properties, constants and methods can be referenced
|
||||||
|
// as variables (like `$class->$property` or
|
||||||
|
// `$class->$method()`) in ways that we're unable
|
||||||
|
// to make sense of.
|
||||||
|
'dead_code_detection' => false,
|
||||||
|
|
||||||
|
// If true, this run a quick version of checks that takes less
|
||||||
|
// time at the cost of not running as thorough
|
||||||
|
// an analysis. You should consider setting this
|
||||||
|
// to true only when you wish you had more **undiagnosed** issues
|
||||||
|
// to fix in your code base.
|
||||||
|
//
|
||||||
|
// In quick-mode the scanner doesn't rescan a function
|
||||||
|
// or a method's code block every time a call is seen.
|
||||||
|
// This means that the problem here won't be detected:
|
||||||
|
//
|
||||||
|
// ```php
|
||||||
|
// <?php
|
||||||
|
// function test($arg):int {
|
||||||
|
// return $arg;
|
||||||
|
// }
|
||||||
|
// test("abc");
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// This would normally generate:
|
||||||
|
//
|
||||||
|
// ```sh
|
||||||
|
// test.php:3 TypeError return string but `test()` is declared to return int
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// The initial scan of the function's code block has no
|
||||||
|
// type information for `$arg`. It isn't until we see
|
||||||
|
// the call and rescan test()'s code block that we can
|
||||||
|
// detect that it is actually returning the passed in
|
||||||
|
// `string` instead of an `int` as declared.
|
||||||
|
'quick_mode' => false,
|
||||||
|
|
||||||
|
// If true, then before analysis, try to simplify AST into a form
|
||||||
|
// which improves Phan's type inference in edge cases.
|
||||||
|
//
|
||||||
|
// This may conflict with 'dead_code_detection'.
|
||||||
|
// When this is true, this slows down analysis slightly.
|
||||||
|
//
|
||||||
|
// E.g. rewrites `if ($a = value() && $a > 0) {...}`
|
||||||
|
// into $a = value(); if ($a) { if ($a > 0) {...}}`
|
||||||
|
'simplify_ast' => true,
|
||||||
|
|
||||||
|
// Enable or disable support for generic templated
|
||||||
|
// class types.
|
||||||
|
'generic_types_enabled' => true,
|
||||||
|
|
||||||
|
// Override to hardcode existence and types of (non-builtin) globals in the global scope.
|
||||||
|
// Class names should be prefixed with '\\'.
|
||||||
|
// (E.g. ['_FOO' => '\\FooClass', 'page' => '\\PageClass', 'userId' => 'int'])
|
||||||
|
'globals_type_map' => [],
|
||||||
|
|
||||||
|
// The minimum severity level to report on. This can be
|
||||||
|
// set to Issue::SEVERITY_LOW, Issue::SEVERITY_NORMAL or
|
||||||
|
// Issue::SEVERITY_CRITICAL. Setting it to only
|
||||||
|
// critical issues is a good place to start on a big
|
||||||
|
// sloppy mature code base.
|
||||||
|
'minimum_severity' => Issue::SEVERITY_LOW,
|
||||||
|
|
||||||
|
// Add any issue types (such as 'PhanUndeclaredMethod')
|
||||||
|
// to this black-list to inhibit them from being reported.
|
||||||
|
'suppress_issue_types' => [
|
||||||
|
'PhanTypeMismatchDeclaredParamNullable',
|
||||||
|
'PhanUndeclaredProperty', // 66 occurence(s) (e.g. not being specific enough about the subclass)
|
||||||
|
'PhanUndeclaredMethod', // 32 occurence(s) (e.g. not being specific enough about the subclass of Node)
|
||||||
|
'PhanTypeMismatchArgument', // 21 occurence(s)
|
||||||
|
'PhanTypeMismatchProperty', // 13 occurence(s)
|
||||||
|
'PhanUnreferencedUseNormal', // 10 occurence(s) TODO: Fix
|
||||||
|
'PhanTypeMismatchDeclaredReturn', // 8 occurence(s)
|
||||||
|
'PhanUndeclaredTypeProperty', // 7 occurence(s)
|
||||||
|
'PhanTypeMismatchReturn', // 6 occurence(s)
|
||||||
|
'PhanUndeclaredVariable', // 4 occurence(s)
|
||||||
|
'PhanUndeclaredTypeReturnType', // 4 occurence(s)
|
||||||
|
'PhanParamTooMany', // 3 occurence(s)
|
||||||
|
'PhanUndeclaredTypeParameter', // 2 occurence(s)
|
||||||
|
'PhanUndeclaredClassProperty', // 2 occurence(s)
|
||||||
|
'PhanTypeSuspiciousStringExpression', // 2 occurence(s)
|
||||||
|
'PhanTypeMismatchArgumentInternal', // 2 occurence(s)
|
||||||
|
'PhanUnextractableAnnotationElementName', // 1 occurence(s)
|
||||||
|
'PhanUndeclaredClassMethod', // 1 occurence(s)
|
||||||
|
'PhanUndeclaredClassInstanceof', // 1 occurence(s)
|
||||||
|
'PhanTypeSuspiciousNonTraversableForeach', // 1 occurence(s)
|
||||||
|
'PhanTypeMismatchDimAssignment', // 1 occurence(s)
|
||||||
|
'PhanTypeMismatchDeclaredParam', // 1 occurence(s)
|
||||||
|
'PhanTypeInvalidDimOffset', // 1 occurence(s)
|
||||||
|
],
|
||||||
|
|
||||||
|
// A regular expression to match files to be excluded
|
||||||
|
// from parsing and analysis and will not be read at all.
|
||||||
|
//
|
||||||
|
// This is useful for excluding groups of test or example
|
||||||
|
// directories/files, unanalyzable files, or files that
|
||||||
|
// can't be removed for whatever reason.
|
||||||
|
// (e.g. '@Test\.php$@', or '@vendor/.*/(tests|Tests)/@')
|
||||||
|
'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
|
||||||
|
|
||||||
|
// A file list that defines files that will be excluded
|
||||||
|
// from parsing and analysis and will not be read at all.
|
||||||
|
//
|
||||||
|
// This is useful for excluding hopelessly unanalyzable
|
||||||
|
// files that can't be removed for whatever reason.
|
||||||
|
'exclude_file_list' => [],
|
||||||
|
|
||||||
|
// A directory list that defines files that will be excluded
|
||||||
|
// from static analysis, but whose class and method
|
||||||
|
// information should be included.
|
||||||
|
//
|
||||||
|
// Generally, you'll want to include the directories for
|
||||||
|
// third-party code (such as "vendor/") in this list.
|
||||||
|
//
|
||||||
|
// n.b.: If you'd like to parse but not analyze 3rd
|
||||||
|
// party code, directories containing that code
|
||||||
|
// should be added to the `directory_list` as
|
||||||
|
// to `excluce_analysis_directory_list`.
|
||||||
|
'exclude_analysis_directory_list' => [
|
||||||
|
'vendor/',
|
||||||
|
],
|
||||||
|
|
||||||
|
// The number of processes to fork off during the analysis
|
||||||
|
// phase.
|
||||||
|
'processes' => 1,
|
||||||
|
|
||||||
|
// List of case-insensitive file extensions supported by Phan.
|
||||||
|
// (e.g. php, html, htm)
|
||||||
|
'analyzed_file_extensions' => [
|
||||||
|
'php',
|
||||||
|
],
|
||||||
|
|
||||||
|
// You can put paths to stubs of internal extensions in this config option.
|
||||||
|
// If the corresponding extension is **not** loaded, then phan will use the stubs instead.
|
||||||
|
// Phan will continue using its detailed type annotations,
|
||||||
|
// but load the constants, classes, functions, and classes (and their Reflection types)
|
||||||
|
// from these stub files (doubling as valid php files).
|
||||||
|
// Use a different extension from php to avoid accidentally loading these.
|
||||||
|
// The 'tools/make_stubs' script can be used to generate your own stubs (compatible with php 7.0+ right now)
|
||||||
|
'autoload_internal_extension_signatures' => [],
|
||||||
|
|
||||||
|
// A list of plugin files to execute
|
||||||
|
// Plugins which are bundled with Phan can be added here by providing their name (e.g. 'AlwaysReturnPlugin')
|
||||||
|
// Alternately, you can pass in the full path to a PHP file with the plugin's implementation (e.g. 'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php')
|
||||||
|
'plugins' => [
|
||||||
|
'AlwaysReturnPlugin',
|
||||||
|
'DollarDollarPlugin',
|
||||||
|
'DuplicateArrayKeyPlugin',
|
||||||
|
'PregRegexCheckerPlugin',
|
||||||
|
'PrintfCheckerPlugin',
|
||||||
|
'UnreachableCodePlugin',
|
||||||
|
],
|
||||||
|
|
||||||
|
// A list of directories that should be parsed for class and
|
||||||
|
// method information. After excluding the directories
|
||||||
|
// defined in exclude_analysis_directory_list, the remaining
|
||||||
|
// files will be statically analyzed for errors.
|
||||||
|
//
|
||||||
|
// Thus, both first-party and third-party code being used by
|
||||||
|
// your application should be included in this list.
|
||||||
|
'directory_list' => [
|
||||||
|
'src',
|
||||||
|
'vendor/composer/xdebug-handler/src',
|
||||||
|
'vendor/felixfbecker/advanced-json-rpc/lib',
|
||||||
|
'vendor/felixfbecker/language-server-protocol/src/',
|
||||||
|
'vendor/microsoft/tolerant-php-parser/src',
|
||||||
|
'vendor/netresearch/jsonmapper/src',
|
||||||
|
'vendor/phpdocumentor/reflection-common/src',
|
||||||
|
'vendor/phpdocumentor/reflection-docblock/src',
|
||||||
|
'vendor/phpdocumentor/type-resolver/src',
|
||||||
|
'vendor/phpunit/phpunit/src',
|
||||||
|
'vendor/psr/log/Psr',
|
||||||
|
'vendor/sabre/event/lib',
|
||||||
|
'vendor/sabre/uri/lib',
|
||||||
|
'vendor/webmozart/glob/src',
|
||||||
|
'vendor/webmozart/path-util/src',
|
||||||
|
],
|
||||||
|
|
||||||
|
// A list of individual files to include in analysis
|
||||||
|
// with a path relative to the root directory of the
|
||||||
|
// project
|
||||||
|
'file_list' => [
|
||||||
|
'bin/php-language-server.php',
|
||||||
|
],
|
||||||
|
];
|
|
@ -16,8 +16,11 @@ cache:
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- composer install --prefer-dist --no-interaction
|
- composer install --prefer-dist --no-interaction
|
||||||
|
- pecl install ast-1.0.0
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- vendor/bin/phpcs -n
|
- vendor/bin/phpcs -n
|
||||||
|
- vendor/bin/phan
|
||||||
- vendor/bin/phpunit --coverage-clover=coverage.xml --colors=always
|
- vendor/bin/phpunit --coverage-clover=coverage.xml --colors=always
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
|
||||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -1,17 +1,19 @@
|
||||||
|
|
||||||
# Running this container will start a language server that listens for TCP connections on port 2088
|
# Running this container will start a language server that listens for TCP connections on port 2088
|
||||||
# Every connection will be run in a forked child process
|
# Every connection will be run in a forked child process
|
||||||
|
|
||||||
# Please note that before building the image, you have to install dependencies with `composer install`
|
FROM composer AS builder
|
||||||
|
|
||||||
|
COPY ./ /app
|
||||||
|
RUN composer install
|
||||||
|
|
||||||
FROM php:7-cli
|
FROM php:7-cli
|
||||||
MAINTAINER Felix Becker <felix.b@outlook.com>
|
LABEL maintainer="Felix Becker <felix.b@outlook.com>"
|
||||||
|
|
||||||
RUN docker-php-ext-configure pcntl --enable-pcntl
|
RUN docker-php-ext-configure pcntl --enable-pcntl
|
||||||
RUN docker-php-ext-install pcntl
|
RUN docker-php-ext-install pcntl
|
||||||
COPY ./php.ini /usr/local/etc/php/conf.d/
|
COPY ./php.ini /usr/local/etc/php/conf.d/
|
||||||
|
|
||||||
COPY ./ /srv/phpls
|
COPY --from=builder /app /srv/phpls
|
||||||
|
|
||||||
WORKDIR /srv/phpls
|
WORKDIR /srv/phpls
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "felixfbecker/language-server",
|
"name": "icedream/language-server",
|
||||||
"description": "PHP Implementation of the Visual Studio Code Language Server Protocol",
|
"description": "PHP Implementation of the Visual Studio Code Language Server Protocol",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -37,8 +37,12 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^6.3",
|
"phpunit/phpunit": "^6.3",
|
||||||
|
"phan/phan": "1.1.4",
|
||||||
"squizlabs/php_codesniffer": "^3.1"
|
"squizlabs/php_codesniffer": "^3.1"
|
||||||
},
|
},
|
||||||
|
"replace": {
|
||||||
|
"felixfbecker/language-server": "self.version"
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"LanguageServer\\": "src/"
|
"LanguageServer\\": "src/"
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
namespace RecursiveTest;
|
||||||
|
|
||||||
|
class A extends A {}
|
||||||
|
|
||||||
|
class B extends C {}
|
||||||
|
class C extends B {}
|
||||||
|
|
||||||
|
class D extends E {}
|
||||||
|
class E extends F {}
|
||||||
|
class F extends D {}
|
||||||
|
|
||||||
|
$a = new A;
|
||||||
|
$a->undef_prop = 1;
|
||||||
|
|
||||||
|
$b = new B;
|
||||||
|
$b->undef_prop = 1;
|
||||||
|
|
||||||
|
$d = new D;
|
||||||
|
$d->undef_prop = 1;
|
|
@ -1,8 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "php-language-server",
|
|
||||||
"version": "0.0.0-development",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gimenete/type-writer": {
|
"@gimenete/type-writer": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"prepare": [
|
"prepare": [
|
||||||
{
|
{
|
||||||
"path": "@semantic-release/exec",
|
"path": "@semantic-release/exec",
|
||||||
"cmd": "composer install --prefer-dist --no-interaction && docker build -t felixfbecker/php-language-server ."
|
"cmd": "docker build -t felixfbecker/php-language-server ."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"publish": [
|
"publish": [
|
||||||
|
|
|
@ -11,6 +11,11 @@ use Sabre\Event\Promise;
|
||||||
*/
|
*/
|
||||||
class ClientCache implements Cache
|
class ClientCache implements Cache
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var LanguageClient
|
||||||
|
*/
|
||||||
|
public $client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param LanguageClient $client
|
* @param LanguageClient $client
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4,7 +4,7 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer\Client;
|
namespace LanguageServer\Client;
|
||||||
|
|
||||||
use LanguageServer\ClientHandler;
|
use LanguageServer\ClientHandler;
|
||||||
use LanguageServerProtocol\{TextDocumentItem, TextDocumentIdentifier};
|
use LanguageServerProtocol\{Diagnostic, TextDocumentItem, TextDocumentIdentifier};
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use JsonMapper;
|
use JsonMapper;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use LanguageServer\Index\ReadableIndex;
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
use phpDocumentor\Reflection\{Types, Type, TypeResolver};
|
||||||
use LanguageServerProtocol\SymbolInformation;
|
use LanguageServerProtocol\SymbolInformation;
|
||||||
use Generator;
|
use Generator;
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ class Definition
|
||||||
* Can also be a compound type.
|
* Can also be a compound type.
|
||||||
* If it is unknown, will be Types\Mixed_.
|
* If it is unknown, will be Types\Mixed_.
|
||||||
*
|
*
|
||||||
* @var \phpDocumentor\Type|null
|
* @var Type|null
|
||||||
*/
|
*/
|
||||||
public $type;
|
public $type;
|
||||||
|
|
||||||
|
|
|
@ -438,6 +438,7 @@ class DefinitionResolver
|
||||||
|
|
||||||
// Find the right class that implements the member
|
// Find the right class that implements the member
|
||||||
$implementorFqns = [$classFqn];
|
$implementorFqns = [$classFqn];
|
||||||
|
$visitedFqns = [];
|
||||||
|
|
||||||
while ($implementorFqn = array_shift($implementorFqns)) {
|
while ($implementorFqn = array_shift($implementorFqns)) {
|
||||||
// If the member FQN exists, return it
|
// If the member FQN exists, return it
|
||||||
|
@ -450,10 +451,15 @@ class DefinitionResolver
|
||||||
if ($implementorDef === null) {
|
if ($implementorDef === null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Note the FQN as visited
|
||||||
|
$visitedFqns[] = $implementorFqn;
|
||||||
// Repeat for parent class
|
// Repeat for parent class
|
||||||
if ($implementorDef->extends) {
|
if ($implementorDef->extends) {
|
||||||
foreach ($implementorDef->extends as $extends) {
|
foreach ($implementorDef->extends as $extends) {
|
||||||
$implementorFqns[] = $extends;
|
// Don't add the parent FQN if it's already been visited
|
||||||
|
if (!\in_array($extends, $visitedFqns)) {
|
||||||
|
$implementorFqns[] = $extends;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer\FilesFinder;
|
namespace LanguageServer\FilesFinder;
|
||||||
|
|
||||||
use Webmozart\Glob\Iterator\GlobIterator;
|
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
use function LanguageServer\{pathToUri, timeout};
|
use function LanguageServer\{pathToUri, timeout};
|
||||||
|
@ -23,7 +22,7 @@ class FileSystemFilesFinder implements FilesFinder
|
||||||
$uris = [];
|
$uris = [];
|
||||||
foreach (new GlobIterator($glob) as $path) {
|
foreach (new GlobIterator($glob) as $path) {
|
||||||
// Exclude any directories that also match the glob pattern
|
// Exclude any directories that also match the glob pattern
|
||||||
if (!is_dir($path)) {
|
if (!is_dir($path) || !is_readable($path)) {
|
||||||
$uris[] = pathToUri($path);
|
$uris[] = pathToUri($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\FilesFinder;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
|
use EmptyIterator;
|
||||||
|
use IteratorIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use Webmozart\Glob\Glob;
|
||||||
|
use Webmozart\Glob\Iterator\GlobFilterIterator;
|
||||||
|
use Webmozart\Glob\Iterator\RecursiveDirectoryIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns filesystem paths matching a glob.
|
||||||
|
*
|
||||||
|
* @since 1.0
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*
|
||||||
|
* @see Glob
|
||||||
|
*/
|
||||||
|
class GlobIterator extends IteratorIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a new iterator.
|
||||||
|
*
|
||||||
|
* @param string $glob The glob pattern.
|
||||||
|
* @param int $flags A bitwise combination of the flag constants in
|
||||||
|
* {@link Glob}.
|
||||||
|
*/
|
||||||
|
public function __construct($glob, $flags = 0)
|
||||||
|
{
|
||||||
|
$basePath = Glob::getBasePath($glob, $flags);
|
||||||
|
if (!Glob::isDynamic($glob) && file_exists($glob)) {
|
||||||
|
// If the glob is a file path, return that path
|
||||||
|
$innerIterator = new ArrayIterator(array($glob));
|
||||||
|
} elseif (is_dir($basePath)) {
|
||||||
|
// Use the system's much more efficient glob() function where we can
|
||||||
|
if (
|
||||||
|
// glob() does not support /**/
|
||||||
|
false === strpos($glob, '/**/') &&
|
||||||
|
// glob() does not support stream wrappers
|
||||||
|
false === strpos($glob, '://') &&
|
||||||
|
// glob() does not support [^...] on Windows
|
||||||
|
('\\' !== DIRECTORY_SEPARATOR || false === strpos($glob, '[^'))
|
||||||
|
) {
|
||||||
|
$results = glob($glob, GLOB_BRACE);
|
||||||
|
|
||||||
|
// $results may be empty or false if $glob is invalid
|
||||||
|
if (empty($results)) {
|
||||||
|
// Parse glob and provoke errors if invalid
|
||||||
|
Glob::toRegEx($glob);
|
||||||
|
|
||||||
|
// Otherwise return empty result set
|
||||||
|
$innerIterator = new EmptyIterator();
|
||||||
|
} else {
|
||||||
|
$innerIterator = new ArrayIterator($results);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise scan the glob's base directory for matches
|
||||||
|
$innerIterator = new GlobFilterIterator(
|
||||||
|
$glob,
|
||||||
|
new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator(
|
||||||
|
$basePath,
|
||||||
|
RecursiveDirectoryIterator::CURRENT_AS_PATHNAME,
|
||||||
|
RecursiveDirectoryIterator::SKIP_DOTS
|
||||||
|
),
|
||||||
|
RecursiveIteratorIterator::SELF_FIRST,
|
||||||
|
RecursiveIteratorIterator::CATCH_GET_CHILD
|
||||||
|
),
|
||||||
|
GlobFilterIterator::FILTER_VALUE,
|
||||||
|
$flags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the glob's base directory does not exist, return nothing
|
||||||
|
$innerIterator = new EmptyIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($innerIterator);
|
||||||
|
}
|
||||||
|
}
|
|
@ -274,7 +274,6 @@ class Index implements ReadableIndex, \Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $serialized
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function serialize()
|
public function serialize()
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Indexer
|
||||||
/**
|
/**
|
||||||
* @var int The prefix for every cache item
|
* @var int The prefix for every cache item
|
||||||
*/
|
*/
|
||||||
const CACHE_VERSION = 2;
|
const CACHE_VERSION = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var FilesFinder
|
* @var FilesFinder
|
||||||
|
|
|
@ -165,8 +165,11 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
* @param int|null $processId The process Id of the parent process that started the server. Is null if the process has not been started by another process. If the parent process is not alive then the server should exit (see exit notification) its process.
|
* @param int|null $processId The process Id of the parent process that started the server. Is null if the process has not been started by another process. If the parent process is not alive then the server should exit (see exit notification) its process.
|
||||||
* @return Promise <InitializeResult>
|
* @return Promise <InitializeResult>
|
||||||
*/
|
*/
|
||||||
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null): Promise
|
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null, string $rootUri = null): Promise
|
||||||
{
|
{
|
||||||
|
if ($rootPath === null && $rootUri !== null) {
|
||||||
|
$rootPath = uriToPath($rootUri);
|
||||||
|
}
|
||||||
return coroutine(function () use ($capabilities, $rootPath, $processId) {
|
return coroutine(function () use ($capabilities, $rootPath, $processId) {
|
||||||
|
|
||||||
if ($capabilities->xfilesProvider) {
|
if ($capabilities->xfilesProvider) {
|
||||||
|
|
|
@ -63,7 +63,7 @@ class PhpDocument
|
||||||
/**
|
/**
|
||||||
* Map from fully qualified name (FQN) to Node
|
* Map from fully qualified name (FQN) to Node
|
||||||
*
|
*
|
||||||
* @var Node
|
* @var Node[]
|
||||||
*/
|
*/
|
||||||
private $definitionNodes;
|
private $definitionNodes;
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,7 @@ namespace LanguageServer;
|
||||||
use LanguageServer\Index\ReadableIndex;
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use LanguageServerProtocol\{
|
use LanguageServerProtocol\{
|
||||||
Position,
|
Position,
|
||||||
SignatureHelp,
|
SignatureHelp
|
||||||
ParameterInformation
|
|
||||||
};
|
};
|
||||||
use Microsoft\PhpParser\Node;
|
use Microsoft\PhpParser\Node;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
|
|
|
@ -38,7 +38,7 @@ function pathToUri(string $filepath): string
|
||||||
function uriToPath(string $uri)
|
function uriToPath(string $uri)
|
||||||
{
|
{
|
||||||
$fragments = parse_url($uri);
|
$fragments = parse_url($uri);
|
||||||
if ($fragments === null || !isset($fragments['scheme']) || $fragments['scheme'] !== 'file') {
|
if ($fragments === false || !isset($fragments['scheme']) || $fragments['scheme'] !== 'file') {
|
||||||
throw new InvalidArgumentException("Not a valid file URI: $uri");
|
throw new InvalidArgumentException("Not a valid file URI: $uri");
|
||||||
}
|
}
|
||||||
$filepath = urldecode($fragments['path']);
|
$filepath = urldecode($fragments['path']);
|
||||||
|
|
Loading…
Reference in New Issue