diff --git a/.dockerignore b/.dockerignore index a839c69..29f27b6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,4 @@ fixtures/ coverage/ coverage.xml images/ +node_modules/ diff --git a/.editorconfig b/.editorconfig index d162066..2954e68 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ trim_trailing_whitespace = true indent_style = space indent_size = 4 -[*.json,*.yml] +[*.{json,yml}] indent_size = 2 [composer.json] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..63babab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,23 @@ +* text=auto + +/.vscode export-ignore +/fixtures export-ignore +/images export-ignore +/validation export-ignore +/.dockerignore export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.gitmodules export-ignore +/.npmrc export-ignore +/.travis.yml export-ignore +/appveyor.yml export-ignore +/codecov.yml export-ignore +/dependencies.yml export-ignore +/Dockerfile export-ignore +/package.json export-ignore +/Performance.php export-ignore +/php.ini export-ignore +/phpcs.xml.dist export-ignore +/phpunit.xml.dist export-ignore +/release-docker.php export-ignore diff --git a/.gitignore b/.gitignore index 4791ea2..6476dbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .DS_Store -.vscode .idea vendor/ .phpls/ composer.lock stubs +*.ast +node_modules/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..06d05cb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,27 @@ +[submodule "validation/frameworks/php-language-server"] + path = validation/frameworks/php-language-server + url = https://github.com/felixfbecker/php-language-server +[submodule "validation/frameworks/wordpress"] + path = validation/frameworks/wordpress + url = https://github.com/wordpress/wordpress +[submodule "validation/frameworks/drupal"] + path = validation/frameworks/drupal + url = https://github.com/drupal/drupal +[submodule "validation/frameworks/tolerant-php-parser"] + path = validation/frameworks/tolerant-php-parser + url = https://github.com/microsoft/tolerant-php-parser +[submodule "validation/frameworks/symfony"] + path = validation/frameworks/symfony + url = https://github.com/symfony/symfony +[submodule "validation/frameworks/math-php"] + path = validation/frameworks/math-php + url = https://github.com/markrogoyski/math-php +[submodule "validation/frameworks/codeigniter"] + path = validation/frameworks/codeigniter + url = https://github.com/bcit-ci/codeigniter +[submodule "validation/frameworks/cakephp"] + path = validation/frameworks/cakephp + url = https://github.com/cakephp/cakephp +[submodule "validation/frameworks/phpunit"] + path = validation/frameworks/phpunit + url = https://github.com/sebastianbergmann/phpunit diff --git a/.travis.yml b/.travis.yml index 7fb9c9a..3d03dcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,57 @@ language: php php: - - '7.0' + - '7.0' + - '7.2' -services: - - docker +git: + depth: 10 + submodules: false cache: directories: - - $HOME/.composer/cache + - $HOME/Library/Caches/Homebrew + - $HOME/.composer/cache + - $HOME/.npm install: - - composer install - - composer run-script parse-stubs - + - composer install --prefer-dist --no-interaction script: - - vendor/bin/phpcs -n - - vendor/bin/phpunit --coverage-clover=coverage.xml + - vendor/bin/phpcs -n + - vendor/bin/phpunit --coverage-clover=coverage.xml --colors=always + - bash <(curl -s https://codecov.io/bash) -after_success: - - bash <(curl -s https://codecov.io/bash) - - | - if [[ $TRAVIS_TAG == v* ]]; then - docker build -t felixfbecker/php-language-server:${TRAVIS_TAG:1} . - docker login -e="$DOCKER_EMAIL" -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" - docker push felixfbecker/php-language-server:${TRAVIS_TAG:1} - fi +jobs: + include: + - stage: test + os: osx + osx_image: xcode9.1 + language: generic + before_install: + # Fix ruby error https://github.com/Homebrew/brew/issues/3299 + - brew update + - brew install php@7.1 + - brew link --force --overwrite php@7.1 + - pecl install xdebug-2.6.0 + - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + - php composer-setup.php + - ln -s "`pwd`/composer.phar" /usr/local/bin/composer + - stage: release + php: '7.0' + services: + - docker + install: + - nvm install 8 + - nvm use 8 + - npm install + script: + - ./node_modules/.bin/semantic-release + +stages: + - test + - name: release + if: branch = master AND type = push AND fork = false + +branches: + only: + - master diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1e4b234 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "PHPUnit", + "type": "php", + "request": "launch", + "program": "${workspaceRoot}/vendor/phpunit/phpunit/phpunit", + // "args": ["--filter", "testDefinitionForSelfKeyword"], + "cwd": "${workspaceRoot}" + }, + { + "name": "Listen for XDebug", + "type": "php", + "request": "launch", + "port": 9000 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 9000 + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..38150bd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "search.exclude": { + "**/validation": true, + "**/tests/Validation/cases": true + } +} diff --git a/Dockerfile b/Dockerfile index f4291db..549a28f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,11 +7,6 @@ FROM php:7-cli MAINTAINER Felix Becker -RUN apt-get update \ - # Needed for CodeSniffer - && apt-get install -y libxml2 libxml2-dev \ - && rm -rf /var/lib/apt/lists/* - RUN docker-php-ext-configure pcntl --enable-pcntl RUN docker-php-ext-install pcntl COPY ./php.ini /usr/local/etc/php/conf.d/ diff --git a/Performance.php b/Performance.php new file mode 100644 index 0000000..4d76d38 --- /dev/null +++ b/Performance.php @@ -0,0 +1,61 @@ +getSize(); + $testProviderArray[] = $file->getPathname(); + } + } + + if (count($testProviderArray) === 0) { + throw new Exception("ERROR: Validation testsuite frameworks not found - run `git submodule update --init --recursive` to download."); + } + + $start = microtime(true); + + foreach ($testProviderArray as $idx => $testCaseFile) { + if (filesize($testCaseFile) > 10000) { + continue; + } + if ($idx % 1000 === 0) { + echo "$idx\n"; + } + + $fileContents = file_get_contents($testCaseFile); + + $docBlockFactory = DocBlockFactory::createInstance(); + $index = new Index; + $maxRecursion = []; + $definitions = []; + + $definitionResolver = new DefinitionResolver($index); + $parser = new PhpParser\Parser(); + + $document = new PhpDocument($testCaseFile, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver); + } + + echo "------------------------------\n"; + + echo "Time [$framework]: " . (microtime(true) - $start) . PHP_EOL; +} + diff --git a/README.md b/README.md index fecd6c7..e608a77 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # PHP Language Server [![Version](https://img.shields.io/packagist/v/felixfbecker/language-server.svg)](https://packagist.org/packages/felixfbecker/language-server) -[![Build Status](https://travis-ci.org/felixfbecker/php-language-server.svg?branch=master)](https://travis-ci.org/felixfbecker/php-language-server) +[![Linux Build Status](https://travis-ci.org/felixfbecker/php-language-server.svg?branch=master)](https://travis-ci.org/felixfbecker/php-language-server) +[![Windows Build status](https://ci.appveyor.com/api/projects/status/2sp5ll052wdjqmdm/branch/master?svg=true)](https://ci.appveyor.com/project/felixfbecker/php-language-server/branch/master) [![Coverage](https://codecov.io/gh/felixfbecker/php-language-server/branch/master/graph/badge.svg)](https://codecov.io/gh/felixfbecker/php-language-server) [![Dependency Status](https://gemnasium.com/badges/github.com/felixfbecker/php-language-server.svg)](https://gemnasium.com/github.com/felixfbecker/php-language-server) +[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg)](https://php.net/) [![License](https://img.shields.io/packagist/l/felixfbecker/language-server.svg)](https://github.com/felixfbecker/php-language-server/blob/master/LICENSE.txt) [![Gitter](https://badges.gitter.im/felixfbecker/php-language-server.svg)](https://gitter.im/felixfbecker/php-language-server?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) @@ -11,12 +13,28 @@ A pure PHP implementation of the open [Language Server Protocol](https://github.com/Microsoft/language-server-protocol). Provides static code analysis for PHP for any IDE. -Uses the great [PHP-Parser](https://github.com/nikic/PHP-Parser), +Uses the great [Tolerant PHP Parser](https://github.com/Microsoft/tolerant-php-parser), [phpDocumentor's DocBlock reflection](https://github.com/phpDocumentor/ReflectionDocBlock) and an [event loop](http://sabre.io/event/loop/) for concurrency. +**Table of Contents** + - [Features](#features) + - [Performance](#performance) + - [Versioning](#versioning) + - [Installation](#installation) + - [Running](#running) + - [Used by](#used-by) + - [Contributing](#contributing) + + ## Features +### [Completion](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#textDocument_completion) +![Completion search demo](images/completion.gif) + +### [Signature Help](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#textDocument_signatureHelp) +![Signature help demo](images/signatureHelp.gif) + ### [Go To Definition](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#goto-definition-request) ![Go To Definition demo](images/definition.gif) @@ -40,9 +58,6 @@ For Parameters, it will return the `@param` tag. The query is matched case-insensitively against the fully qualified name of the symbol. Non-Standard: An empty query will return _all_ symbols found in the workspace. -### [Document Formatting](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#document-formatting-request) -![Document Formatting demo](images/formatDocument.gif) - ### Error reporting through [Publish Diagnostics](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#publishdiagnostics-notification) ![Error reporting demo](images/publishDiagnostics.png) @@ -170,7 +185,7 @@ Example: #### `--memory-limit=integer` (optional) Sets memory limit for language server. Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) php.ini directive. -By default there is no memory limit. +The default is 4GB (which is way more than needed). Example: @@ -180,7 +195,8 @@ Example: - [VS Code PHP IntelliSense](https://github.com/felixfbecker/vscode-php-intellisense) - [Eclipse Che](https://eclipse.org/che/) - [Eclipse IDE (LSP4E-PHP)](https://github.com/eclipselabs/lsp4e-php) - - [Neovim (nvim-cm-php-language-server)](https://github.com/roxma/nvim-cm-php-language-server) + - NeoVim: [LanguageServer-php-neovim](https://github.com/roxma/LanguageServer-php-neovim) with [LanguageClient neovim](https://github.com/autozimu/LanguageClient-neovim) + - Atom: [ide-php](https://github.com/atom/ide-php) ## Contributing @@ -190,14 +206,21 @@ Clone the repository and run composer install to install dependencies. -Then parse the stubs with - - composer run-script parse-stubs Run the tests with - vendor/bin/phpunit + composer test Lint with - vendor/bin/phpcs + composer lint + +The project parses PHPStorm's PHP stubs to get support for PHP builtins. It re-parses them as needed after Composer processes, but after some code changes (such as ones involving the index or parsing) you may have to explicitly re-parse them: + + composer run-script parse-stubs + +To debug with xDebug ensure that you have this set as an environment variable + + PHPLS_ALLOW_XDEBUG=1 + +This tells the Language Server to not restart without XDebug if it detects that XDebug is enabled (XDebug has a high performance impact). diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..7d0525c --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,54 @@ +version: '{build}' + +image: Visual Studio 2017 +platform: + - x64 + +skip_tags: true +skip_branch_with_pr: true +clone_depth: 1 +max_jobs: 3 + +cache: + - '%LOCALAPPDATA%\Composer' + - '%LOCALAPPDATA%\Temp\Chocolatey' + +environment: + ANSICON: 121x90 (121x90) + matrix: + - { PHP_VERSION: '7.1.11', VC_VERSION: '14', XDEBUG_VERSION: '2.5.5' } + +install: + # Enable Windows Update service, needed to install vcredist2015 (dependency of php) + - ps: Set-Service wuauserv -StartupType Manual + - choco config set cacheLocation %LOCALAPPDATA%\Temp\Chocolatey + - choco install -y php --version %PHP_VERSION% + - choco install -y composer + - refreshenv + - composer install --no-interaction --no-progress --prefer-dist + # Install XDebug for code coverage + - ps: | + $client = New-Object System.Net.WebClient + $phpMinorVersion = $env:PHP_VERSION -replace '\.\d+$' + $xdebugUrl = "https://xdebug.org/files/php_xdebug-$env:XDEBUG_VERSION-$phpMinorVersion-vc14-nts-x86_64.dll" + $phpDir = (Get-Item (Get-Command php).Source).Directory.FullName + $xdebugPath = Join-Path $phpDir ext\xdebug.dll + $client.DownloadFile($xdebugUrl, $xdebugPath) + $phpIniPath = Join-Path $phpDir php.ini + Add-Content $phpIniPath @" + zend_extension=$xdebugPath + "@ + +build: off + +test_script: + - vendor\bin\phpunit --coverage-clover=coverage/coverage.xml + +after_test: + - ps: | + # Delete vendor because it causes problems with codecovs report search + # https://github.com/codecov/codecov-bash/issues/96 + Remove-Item -Recurse -Force vendor + $env:PATH = 'C:\msys64\usr\bin;' + $env:PATH + Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh + bash codecov.sh -f 'coverage/coverage.xml' diff --git a/bin/php-language-server.php b/bin/php-language-server.php index 0e3de2a..8e5d348 100644 --- a/bin/php-language-server.php +++ b/bin/php-language-server.php @@ -1,12 +1,12 @@ critical((string)$e); }); @cli_set_process_title('PHP Language Server'); // If XDebug is enabled, restart without it -(new XdebugHandler(Factory::createOutput()))->check(); +$xdebugHandler = new XdebugHandler('PHPLS'); +$xdebugHandler->setLogger($logger); +$xdebugHandler->check(); +unset($xdebugHandler); if (!empty($options['tcp'])) { // Connect to a TCP server $address = $options['tcp']; $socket = stream_socket_client('tcp://' . $address, $errno, $errstr); if ($socket === false) { - fwrite(STDERR, "Could not connect to language client. Error $errno\n$errstr"); + $logger->critical("Could not connect to language client. Error $errno\n$errstr"); exit(1); } stream_set_blocking($socket, false); @@ -53,29 +58,30 @@ if (!empty($options['tcp'])) { $address = $options['tcp-server']; $tcpServer = stream_socket_server('tcp://' . $address, $errno, $errstr); if ($tcpServer === false) { - fwrite(STDERR, "Could not listen on $address. Error $errno\n$errstr"); + $logger->critical("Could not listen on $address. Error $errno\n$errstr"); exit(1); } - fwrite(STDOUT, "Server listening on $address\n"); - if (!extension_loaded('pcntl')) { - fwrite(STDERR, "PCNTL is not available. Only a single connection will be accepted\n"); + $logger->debug("Server listening on $address"); + $pcntlAvailable = extension_loaded('pcntl'); + if (!$pcntlAvailable) { + $logger->notice('PCNTL is not available. Only a single connection will be accepted'); } while ($socket = stream_socket_accept($tcpServer, -1)) { - fwrite(STDOUT, "Connection accepted\n"); + $logger->debug('Connection accepted'); stream_set_blocking($socket, false); - if (extension_loaded('pcntl')) { + if ($pcntlAvailable) { // If PCNTL is available, fork a child process for the connection // An exit notification will only terminate the child process $pid = pcntl_fork(); if ($pid === -1) { - fwrite(STDERR, "Could not fork\n"); + $logger->critical('Could not fork'); exit(1); } else if ($pid === 0) { // Child process $reader = new ProtocolStreamReader($socket); $writer = new ProtocolStreamWriter($socket); - $reader->on('close', function () { - fwrite(STDOUT, "Connection closed\n"); + $reader->on('close', function () use ($logger) { + $logger->debug('Connection closed'); }); $ls = new LanguageServer($reader, $writer); Loop\run(); @@ -94,6 +100,7 @@ if (!empty($options['tcp'])) { } } else { // Use STDIO + $logger->debug('Listening on STDIN'); stream_set_blocking(STDIN, false); $ls = new LanguageServer( new ProtocolStreamReader(STDIN), diff --git a/composer.json b/composer.json index b29c1ee..085f189 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,7 @@ { "name": "felixfbecker/language-server", "description": "PHP Implementation of the Visual Studio Code Language Server Protocol", - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], "license": "ISC", - "type": "library", "keywords": [ "php", "language", @@ -21,33 +14,38 @@ "autocompletion", "refactor" ], - "bin": ["bin/php-language-server.php"], - "scripts": { - "parse-stubs": "LanguageServer\\ComposerScripts::parseStubs", - "post-install-cmd": "@parse-stubs" - }, + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], "require": { - "php": ">=7.0", - "nikic/php-parser": "^3.0.4", - "phpdocumentor/reflection-docblock": "^3.0", - "sabre/event": "^5.0", - "felixfbecker/advanced-json-rpc": "^2.0", - "squizlabs/php_codesniffer" : "3.0.0RC3", - "netresearch/jsonmapper": "^1.0", - "webmozart/path-util": "^2.3", - "webmozart/glob": "^4.1", - "sabre/uri": "^2.0", + "php": "^7.0", + "composer/xdebug-handler": "^1.0", + "felixfbecker/advanced-json-rpc": "^3.0.0", "jetbrains/phpstorm-stubs": "dev-master", - "composer/composer": "^1.3" + "microsoft/tolerant-php-parser": "0.0.*", + "netresearch/jsonmapper": "^1.0", + "phpdocumentor/reflection-docblock": "^4.0.0", + "psr/log": "^1.0", + "sabre/event": "^5.0", + "sabre/uri": "^2.0", + "webmozart/glob": "^4.1", + "webmozart/path-util": "^2.3" + }, + "require-dev": { + "phpunit/phpunit": "^6.3", + "squizlabs/php_codesniffer": "^3.1" }, - "minimum-stability": "dev", - "prefer-stable": true, "autoload": { "psr-4": { "LanguageServer\\": "src/" }, "files" : [ - "src/utils.php" + "src/utils.php", + "src/FqnUtilities.php", + "src/ParserHelpers.php" ] }, "autoload-dev": { @@ -55,8 +53,19 @@ "LanguageServer\\Tests\\": "tests/" } }, - "require-dev": { - "phpunit/phpunit": "^5.5", - "phpunit/php-code-coverage": "^4.0" - } + "bin": [ + "bin/php-language-server.php" + ], + "scripts": { + "parse-stubs": "LanguageServer\\ComposerScripts::parseStubs", + "post-install-cmd": "@parse-stubs", + "post-update-cmd": "@parse-stubs", + "test": "vendor/bin/phpunit", + "lint": "vendor/bin/phpcs" + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/dependencies.yml b/dependencies.yml new file mode 100644 index 0000000..9df6f74 --- /dev/null +++ b/dependencies.yml @@ -0,0 +1,17 @@ +collectors: + +# pull requests for new major versions +- type: php-composer + path: / + actors: + - type: php-composer + versions: "Y.0.0" + settings: + commit_message_prefix: "chore: " +- type: js-npm + path: / + actors: + - type: js-npm + versions: "Y.0.0" + settings: + commit_message_prefix: "chore: " diff --git a/fixtures/completion/constant_with_namespace.php b/fixtures/completion/constant_with_namespace.php new file mode 100644 index 0000000..cade11f --- /dev/null +++ b/fixtures/completion/constant_with_namespace.php @@ -0,0 +1,23 @@ +test(); +$array1 = [new Bar(), new \stdClass()]; +$array2 = ['foo' => $bar, $bar]; +$array3 = ['foo' => $bar, 'baz' => $bar]; + +foreach ($bars as $value) { + $v + $value-> +} + +foreach ($array1 as $key => $value) { + $ +} + +foreach ($array2 as $key => $value) { + $ +} + +foreach ($array3 as $key => $value) { + $ +} + +foreach ($bar->test() as $value) { + $ +} + +foreach ($unknownArray as $member->access => $unknown) { + $unkno + +foreach ($loop as $loop) { +} + +foreach ($loop->getArray() as $loop) { +} diff --git a/fixtures/completion/html_no_completion.php b/fixtures/completion/html_no_completion.php new file mode 100644 index 0000000..9318418 --- /dev/null +++ b/fixtures/completion/html_no_completion.php @@ -0,0 +1 @@ +< diff --git a/fixtures/completion/inside_namespace_and_method.php b/fixtures/completion/inside_namespace_and_method.php new file mode 100644 index 0000000..698ac4a --- /dev/null +++ b/fixtures/completion/inside_namespace_and_method.php @@ -0,0 +1,11 @@ +foo(); +$foo-> \ No newline at end of file diff --git a/fixtures/completion/static_method_return_type.php b/fixtures/completion/static_method_return_type.php new file mode 100644 index 0000000..06cafdd --- /dev/null +++ b/fixtures/completion/static_method_return_type.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/fixtures/completion/this.php b/fixtures/completion/this.php new file mode 100644 index 0000000..b3d684c --- /dev/null +++ b/fixtures/completion/this.php @@ -0,0 +1,15 @@ + + } +} diff --git a/fixtures/completion/this_return_value.php b/fixtures/completion/this_return_value.php new file mode 100644 index 0000000..167d0b4 --- /dev/null +++ b/fixtures/completion/this_return_value.php @@ -0,0 +1,23 @@ +foo()->q + } + public function qux() + { + } +} diff --git a/fixtures/completion/this_with_prefix.php b/fixtures/completion/this_with_prefix.php new file mode 100644 index 0000000..6325b99 --- /dev/null +++ b/fixtures/completion/this_with_prefix.php @@ -0,0 +1,15 @@ +m + } +} diff --git a/fixtures/diagnostics/baselines/this_in_method.php b/fixtures/diagnostics/baselines/this_in_method.php new file mode 100644 index 0000000..0463b79 --- /dev/null +++ b/fixtures/diagnostics/baselines/this_in_method.php @@ -0,0 +1,9 @@ +foo(); +$t->foo(1, +$t->foo(1,); +$t->baz(); + +foo( + 1, + foo(1, 2, +); + +Test::bar(); + +new $foo(); +new $foo(1, ); + +new NotExist(); diff --git a/fixtures/symbols.php b/fixtures/symbols.php index d7700bc..4e9b0d4 100644 --- a/fixtures/symbols.php +++ b/fixtures/symbols.php @@ -98,3 +98,8 @@ new class { }; class ChildClass extends TestClass {} + +class Example { + public function __construct() {} + public function __destruct() {} +} diff --git a/images/completion.gif b/images/completion.gif new file mode 100644 index 0000000..b43d2f9 Binary files /dev/null and b/images/completion.gif differ diff --git a/images/formatDocument.gif b/images/formatDocument.gif deleted file mode 100644 index d8e49fe..0000000 Binary files a/images/formatDocument.gif and /dev/null differ diff --git a/images/signatureHelp.gif b/images/signatureHelp.gif new file mode 100644 index 0000000..9f921ff Binary files /dev/null and b/images/signatureHelp.gif differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fd26f34 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6662 @@ +{ + "name": "php-language-server", + "version": "0.0.0-development", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@gimenete/type-writer": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@gimenete/type-writer/-/type-writer-0.1.3.tgz", + "integrity": "sha512-vhpvVfM/fYqb1aAnkgOvtDKoOgU3ZYIvDnKSDAFSoBvallmGURMlHOE0/VG/gqunUZVXGCFBGHxI8swjBh+sIA==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "prettier": "^1.13.7" + }, + "dependencies": { + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + } + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.1.tgz", + "integrity": "sha512-KU/VDjC5RwtDUZiz3d+DHXJF2lp5hB9dn552TXIyptj8SH1vXmR40mG0JgGq03IlYsOgGfcv8xrLpSQ0YUMQdA==", + "dev": true + }, + "@octokit/rest": { + "version": "15.10.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.10.0.tgz", + "integrity": "sha512-xZ4ejCZoqvKrIN3tQOKZlJ6nDQxaOdLcjRsamDnbckU7V5YTn2xheIqFXnQ2vLvxqVwyI8+2dfsODYbHxtwtSw==", + "dev": true, + "requires": { + "@gimenete/type-writer": "^0.1.3", + "before-after-hook": "^1.1.0", + "btoa-lite": "^1.0.0", + "debug": "^3.1.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.0", + "lodash": "^4.17.4", + "node-fetch": "^2.1.1", + "url-template": "^2.0.8" + } + }, + "@semantic-release/commit-analyzer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-6.0.0.tgz", + "integrity": "sha512-LEBEg5Ek3PCVv8srHRA8uuwu0t9nAXuBQ9ixjBiMYCqCCUsCezUi5wRdmXnJkXs5/yQkd4Dzx8OJ1zIAL2Pqeg==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.0", + "debug": "^3.1.0", + "import-from": "^2.1.0", + "lodash": "^4.17.4" + } + }, + "@semantic-release/error": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", + "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", + "dev": true + }, + "@semantic-release/exec": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/exec/-/exec-3.1.0.tgz", + "integrity": "sha512-m+opdWgZ7ro4j9BD4yL2UIRom3RznE3sbx2Wj8CSCOXoSJ97Ar8Ci8/ziR4QF1SJcKVlrUUhj+7TP5FgziSDgg==", + "dev": true, + "requires": { + "@semantic-release/error": "^2.1.0", + "debug": "^3.1.0", + "execa": "^0.10.0", + "lodash": "^4.17.4", + "parse-json": "^4.0.0" + } + }, + "@semantic-release/github": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.0.2.tgz", + "integrity": "sha512-YySh2iBDXcsU5Mtaseo9L3wsCgTNj2wDMxi8L6hLxFayJy3YqiZ9Yym/jf+Fh8J/Afy8IxyVaz8Re5AfjnoyOg==", + "dev": true, + "requires": { + "@octokit/rest": "^15.2.0", + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^1.0.0", + "bottleneck": "^2.0.1", + "debug": "^3.1.0", + "dir-glob": "^2.0.0", + "fs-extra": "^7.0.0", + "globby": "^8.0.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "issue-parser": "^2.2.0", + "lodash": "^4.17.4", + "mime": "^2.0.3", + "p-filter": "^1.0.0", + "p-retry": "^2.0.0", + "parse-github-url": "^1.0.1", + "url-join": "^4.0.0" + } + }, + "@semantic-release/npm": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-5.0.2.tgz", + "integrity": "sha512-h0tNQGGd4pXO9AUVa3LlVLafMF2CJU7TRwl0KTqWLfmb5sFOxNRK4yIkoD/TCmMUst4vxgG57H1fPaD7GE17Fg==", + "dev": true, + "requires": { + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^1.0.0", + "detect-indent": "^5.0.0", + "detect-newline": "^2.1.0", + "execa": "^0.10.0", + "fs-extra": "^7.0.0", + "lodash": "^4.17.4", + "nerf-dart": "^1.0.0", + "normalize-url": "^3.0.0", + "npm": "^6.3.0", + "parse-json": "^4.0.0", + "rc": "^1.2.8", + "read-pkg": "^4.0.0", + "registry-auth-token": "^3.3.1" + }, + "dependencies": { + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + } + } + }, + "@semantic-release/release-notes-generator": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-7.0.1.tgz", + "integrity": "sha512-zMX2t+v0hjr8f+hfqJgvZ/7m9Bjt4RJ6qk52oo+bxwqEnwCm4b4u8nwGLXFfBg/Q7ph/E0MuzYIFrhECICxzBw==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-changelog-writer": "^4.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.0", + "debug": "^3.1.0", + "get-stream": "^4.0.0", + "git-url-parse": "^10.0.1", + "import-from": "^2.1.0", + "into-stream": "^3.1.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "get-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.0.0.tgz", + "integrity": "sha512-FneLKMENeOR7wOK0/ZXCh+lwqtnPwkeunJjRN28LPqzGvNAhYvrTAhXv6xDm4vsJ0M7lcRbIYHQudKsSy2RtSQ==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "JSONStream": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.4.tgz", + "integrity": "sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "aggregate-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz", + "integrity": "sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w=", + "dev": true, + "requires": { + "clean-stack": "^1.0.0", + "indent-string": "^3.0.0" + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansicolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", + "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "before-after-hook": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.1.0.tgz", + "integrity": "sha512-VOMDtYPwLbIncTxNoSzRyvaMxtXmLWLUqr8k5AfC1BzLk34HvBXaQX8snOwQZ4c0aX8aSERqtJSiI9/m2u5kuA==", + "dev": true + }, + "bottleneck": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.8.0.tgz", + "integrity": "sha512-yHJ9OeOgDWoYLjGKjee8N5qSC72VB/N79H1TmUc00vr99e/SXvzfrxkowFYZgTLmjWlDzLFCWaZ9wbfZm5Xl2Q==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "cardinal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-1.0.0.tgz", + "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=", + "dev": true, + "requires": { + "ansicolors": "~0.2.1", + "redeyed": "~1.0.0" + } + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-stack": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz", + "integrity": "sha1-noIVAa6XmYbEax1m0tQy2y/UrjE=", + "dev": true + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "compare-func": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", + "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" + } + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "conventional-changelog-angular": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.1.tgz", + "integrity": "sha512-q4ylJ68fWZDdrFC9z4zKcf97HW6hp7Mo2YlqD4owfXhecFKy/PJCU/1oVFF4TqochchChqmZ0Vb0e0g8/MKNlA==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "conventional-changelog-writer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.0.tgz", + "integrity": "sha512-hMZPe0AQ6Bi05epeK/7hz80xxk59nPA5z/b63TOHq2wigM0/akreOc8N4Jam5b9nFgKWX1e9PdPv2ewgW6bcfg==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "conventional-commits-filter": "^2.0.0", + "dateformat": "^3.0.0", + "handlebars": "^4.0.2", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "semver": "^5.5.0", + "split": "^1.0.0", + "through2": "^2.0.0" + } + }, + "conventional-commits-filter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.0.tgz", + "integrity": "sha512-Cfl0j1/NquB/TMVx7Wrmyq7uRM+/rPQbtVVGwzfkhZ6/yH6fcMmP0Q/9044TBZPTNdGzm46vXFXL14wbET0/Mg==", + "dev": true, + "requires": { + "is-subset": "^0.1.1", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.0.tgz", + "integrity": "sha512-GWh71U26BLWgMykCp+VghZ4s64wVbtseECcKQ/PvcPZR2cUnz+FUc2J9KjxNl7/ZbCxST8R03c9fc+Vi0umS9Q==", + "dev": true, + "requires": { + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.0", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0", + "trim-off-newlines": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", + "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + } + }, + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-ci": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-2.1.1.tgz", + "integrity": "sha512-7KVsNirTANngtXZkKzbO/9xtHB51rNzauhuKRKBj2itgATsD/N9ZjyfWNmfiUF9aZIaoqrZ8x0HS8M7Bsk7dBQ==", + "dev": true, + "requires": { + "execa": "^0.10.0", + "java-properties": "^0.2.9" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", + "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==", + "dev": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.0.1", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.1", + "micromatch": "^3.1.10" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "find-versions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-2.0.0.tgz", + "integrity": "sha1-KtkNSQ9oKMGqQCks9wmsMxghDDw=", + "dev": true, + "requires": { + "array-uniq": "^1.0.0", + "semver-regex": "^1.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-extra": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz", + "integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "git-log-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", + "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", + "dev": true, + "requires": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" + }, + "dependencies": { + "split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", + "dev": true, + "requires": { + "through2": "~2.0.0" + } + } + } + }, + "git-up": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-2.0.10.tgz", + "integrity": "sha512-2v4UN3qV2RGypD9QpmUjpk+4+RlYpW8GFuiZqQnKmvei08HsFPd0RfbDvEhnE4wBvnYs8ORVtYpOFuuCEmBVBw==", + "dev": true, + "requires": { + "is-ssh": "^1.3.0", + "parse-url": "^1.3.0" + } + }, + "git-url-parse": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-10.0.1.tgz", + "integrity": "sha512-Tq2u8UPXc/FawC/dO8bvh8jcck0Lkor5OhuZvmVSeyJGRucDBfw9y2zy/GNCx28lMYh1N12IzPwDexjUNFyAeg==", + "dev": true, + "requires": { + "git-up": "^2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, + "globby": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "dev": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hook-std": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-1.1.0.tgz", + "integrity": "sha512-aIyBZbZl3NS8XoSwIDQ+ZaiBuPOhhPWoBFA3QX0Q8hOMO8Tx4xGRTDnn/nl/LAtZWdieXzFC9ohAtTSnWrlHCQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "dev": true, + "requires": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-ssh": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.0.tgz", + "integrity": "sha1-6+oRaaJhTaOSpjdANmw84EnY3/Y=", + "dev": true, + "requires": { + "protocols": "^1.1.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "requires": { + "text-extensions": "^1.0.0" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "issue-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-2.2.0.tgz", + "integrity": "sha512-qBm9P//mcpIosDX0GU29TJkOcIIyF4PMnetfU6yfWsukLRQJPUWdJuYFjEkHlW5bxCbmEkpBnkaAPiTmCYCNDQ==", + "dev": true, + "requires": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1" + } + }, + "java-properties": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-0.2.10.tgz", + "integrity": "sha512-CpKJh9VRNhS+XqZtg1UMejETGEiqwCGDC/uwPEEQwc2nfdbSm73SIE29TplG2gLYuBOOTNDqxzG6A9NtEPLt0w==", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + } + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", + "dev": true + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "marked": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.0.tgz", + "integrity": "sha512-UhjmkCWKu1SS/BIePL2a59BMJ7V42EYtTfksodPRXzPEGEph3Inp5dylseqt+KbU9Jglsx8xcMKmlumfJMBXAA==", + "dev": true + }, + "marked-terminal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.0.0.tgz", + "integrity": "sha512-7gWHPxQlWNeqjVgW72gwxLeJBj0T/RmurVs2qHPm90f7kuu7CMcZVTmtqk1dogourkAtopZNnp2DUpTIJZKZ4w==", + "dev": true, + "requires": { + "cardinal": "^1.0.0", + "chalk": "^1.1.3", + "cli-table": "^0.3.1", + "lodash.assign": "^4.2.0", + "node-emoji": "^1.4.1" + } + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + }, + "dependencies": { + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + } + } + }, + "merge2": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", + "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", + "dev": true + }, + "nice-try": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "dev": true + }, + "node-emoji": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz", + "integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==", + "dev": true, + "requires": { + "lodash.toarray": "^4.4.0" + } + }, + "node-fetch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", + "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==", + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-url": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.2.0.tgz", + "integrity": "sha512-WvF3Myk0NhXkG8S9bygFM4IC1KOvnVJGq0QoGeoqOYOBeinBZp5ybW3QuYbTc89lkWBMM9ZBO4QGRoc0353kKA==", + "dev": true + }, + "npm": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.4.0.tgz", + "integrity": "sha512-k0VteQaxRuI1mREBxCtLUksesD2ZmX5gxjXNEjTmTrxQ3SHW22InkCKyX4NzoeGAYtgmDg5MuE7rcXYod7xgug==", + "dev": true, + "requires": { + "JSONStream": "^1.3.3", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "~1.2.0", + "archy": "~1.0.0", + "bin-links": "^1.1.2", + "bluebird": "~3.5.1", + "byte-size": "^4.0.3", + "cacache": "^11.1.0", + "call-limit": "~1.1.0", + "chownr": "~1.0.1", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.0", + "cmd-shim": "~2.0.2", + "columnify": "~1.5.4", + "config-chain": "~1.1.11", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.2.0", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.0.1", + "glob": "~7.1.2", + "graceful-fs": "~4.1.11", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.7.1", + "iferr": "^1.0.2", + "imurmurhash": "*", + "inflight": "~1.0.6", + "inherits": "~2.0.3", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^2.0.6", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^2.0.1", + "libnpmhook": "^4.0.1", + "libnpx": "^10.2.0", + "lock-verify": "^2.0.2", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^4.1.3", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "~0.5.1", + "move-concurrently": "^1.0.1", + "node-gyp": "^3.8.0", + "nopt": "~4.0.1", + "normalize-package-data": "~2.4.0", + "npm-audit-report": "^1.3.1", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "~3.0.0", + "npm-lifecycle": "^2.0.3", + "npm-package-arg": "^6.1.0", + "npm-packlist": "~1.1.10", + "npm-pick-manifest": "^2.1.0", + "npm-profile": "^3.0.2", + "npm-registry-client": "^8.5.1", + "npm-registry-fetch": "^1.1.0", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "~1.4.3", + "osenv": "^0.1.5", + "pacote": "^8.1.6", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.1.0", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "~1.0.1", + "read-installed": "~4.0.3", + "read-package-json": "^2.0.13", + "read-package-tree": "^5.2.1", + "readable-stream": "^2.3.6", + "readdir-scoped-modules": "*", + "request": "^2.87.0", + "retry": "^0.12.0", + "rimraf": "~2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "sha": "~2.0.1", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.0", + "stringify-package": "^1.0.0", + "tar": "^4.4.6", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "~1.1.0", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.2", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.6.0", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "agent-base": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.4.1", + "bundled": true, + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "asap": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true, + "dev": true + }, + "aws4": { + "version": "1.7.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "cmd-shim": "^2.0.2", + "gentle-fs": "^2.0.0", + "graceful-fs": "^4.1.11", + "write-file-atomic": "^2.3.0" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.1", + "bundled": true, + "dev": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "byline": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "byte-size": { + "version": "4.0.3", + "bundled": true, + "dev": true + }, + "cacache": { + "version": "11.1.0", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "figgy-pudding": "^3.1.0", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.0", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ci-info": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "cidr-regex": { + "version": "2.0.9", + "bundled": true, + "dev": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.0", + "bundled": true, + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "cmd-shim": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "colors": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "dev": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "config-chain": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "err-code": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es6-promise": { + "version": "4.2.4", + "bundled": true, + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "figgy-pudding": { + "version": "3.2.0", + "bundled": true, + "dev": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "gentle-fs": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "bundled": true, + "dev": true, + "requires": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true, + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true, + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "ip": { + "version": "1.1.5", + "bundled": true, + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "ci-info": "^1.0.0" + } + }, + "is-cidr": { + "version": "2.0.6", + "bundled": true, + "dev": true, + "requires": { + "cidr-regex": "^2.0.8" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-retry-allowed": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true, + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + } + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "libcipm": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^2.0.3", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^8.1.6", + "protoduck": "^5.0.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpmhook": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.1.0", + "npm-registry-fetch": "^3.0.0" + }, + "dependencies": { + "npm-registry-fetch": { + "version": "3.1.1", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^3.1.0", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^4.0.0", + "npm-package-arg": "^6.0.0" + } + } + } + }, + "libnpx": { + "version": "10.2.0", + "bundled": true, + "dev": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^5.1.2 || 6", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true, + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true, + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true, + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^11.0.1", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "mime-db": { + "version": "1.33.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "~1.33.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.3", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true, + "dev": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "3.8.0", + "bundled": true, + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + } + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-audit-report": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "npm-install-checks": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.11", + "node-gyp": "^3.6.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.0" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "npm-package-arg": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2 || 2", + "make-fetch-happen": "^2.5.0 || 3 || 4" + } + }, + "npm-registry-client": { + "version": "8.5.1", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true, + "dev": true + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "npm-registry-fetch": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^2.0.1", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^3.0.0", + "npm-package-arg": "^6.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "cacache": { + "version": "10.0.4", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + }, + "dependencies": { + "mississippi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + } + } + }, + "figgy-pudding": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "make-fetch-happen": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^10.0.4", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.0", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^3.0.1", + "ssri": "^5.2.4" + } + }, + "pump": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true, + "dev": true + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "requires": { + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "socks": "^1.1.10" + } + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.4.3", + "bundled": true, + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "8.1.6", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "cacache": "^11.0.2", + "get-stream": "^3.0.0", + "glob": "^7.1.2", + "lru-cache": "^4.1.3", + "make-fetch-happen": "^4.0.1", + "minimatch": "^3.0.4", + "minipass": "^2.3.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.10", + "npm-pick-manifest": "^2.1.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "ssri": "^6.0.0", + "tar": "^4.4.3", + "unique-filename": "^1.1.0", + "which": "^1.3.0" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true, + "dev": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true, + "dev": true + }, + "protoduck": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "genfun": "^4.0.1" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "qs": { + "version": "6.5.2", + "bundled": true, + "dev": true + }, + "query-string": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.0.13", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.2.1", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "once": "^1.3.0", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.87.0", + "bundled": true, + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "retry": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "sha": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "readable-stream": "^2.0.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slash": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "smart-buffer": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "socks": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" + } + }, + "socks-proxy-agent": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "~4.2.0", + "socks": "~2.2.0" + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "dev": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true, + "dev": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + } + } + }, + "ssri": { + "version": "6.0.0", + "bundled": true, + "dev": true + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "stringify-package": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.6", + "bundled": true, + "dev": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.3", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "through": { + "version": "2.3.8", + "bundled": true, + "dev": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "tough-cookie": { + "version": "2.3.4", + "bundled": true, + "dev": true, + "requires": { + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "umask": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "unique-filename": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.3.2", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + } + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.0.0", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, + "p-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-1.0.0.tgz", + "integrity": "sha1-Yp0xcVAgnI/VCLoTdxPvS7kg6ds=", + "dev": true, + "requires": { + "p-map": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + }, + "dependencies": { + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + } + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, + "p-retry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-2.0.0.tgz", + "integrity": "sha512-ZbCuzAmiwJ45q4evp/IG9D+5MUllGSUeCWwPt3j/tdYSi1KPkSD+46uqmAA1LhccDhOXv8kYZKNb8x78VflzfA==", + "dev": true, + "requires": { + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-github-url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", + "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse-url": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-1.3.11.tgz", + "integrity": "sha1-V8FUKKuKiSsfQ4aWRccR0OFEtVQ=", + "dev": true, + "requires": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prettier": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.2.tgz", + "integrity": "sha512-McHPg0n1pIke+A/4VcaS2en+pTNjy4xF+Uuq86u/5dyDO59/TtFZtQ708QIRkEZ3qwKz3GVkVa6mpxK/CpB8Rg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "protocols": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.6.tgz", + "integrity": "sha1-+LsmPqG1/Xp2BNJri+Ob13Z4v4o=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "redeyed": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-1.0.1.tgz", + "integrity": "sha1-6WwZO0DAgWsArshCaY5hGF5VSYo=", + "dev": true, + "requires": { + "esprima": "~3.0.0" + }, + "dependencies": { + "esprima": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.0.0.tgz", + "integrity": "sha1-U88kes2ncxPlUcOqLnM0LT+099k=", + "dev": true + } + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semantic-release": { + "version": "15.9.9", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-15.9.9.tgz", + "integrity": "sha512-d12aOwnpVNbI/G7ci63FWCmBlLsKHL5h7m0oWlc8ot4FIUslzhQtCtQ9nflWYhThhxy1taLsX+U+GRIkj72wuQ==", + "dev": true, + "requires": { + "@semantic-release/commit-analyzer": "^6.0.0", + "@semantic-release/error": "^2.2.0", + "@semantic-release/github": "^5.0.0", + "@semantic-release/npm": "^5.0.1", + "@semantic-release/release-notes-generator": "^7.0.0", + "aggregate-error": "^1.0.0", + "cosmiconfig": "^5.0.1", + "debug": "^3.1.0", + "env-ci": "^2.0.0", + "execa": "^0.10.0", + "figures": "^2.0.0", + "find-versions": "^2.0.0", + "get-stream": "^4.0.0", + "git-log-parser": "^1.2.0", + "git-url-parse": "^10.0.1", + "hook-std": "^1.1.0", + "hosted-git-info": "^2.7.1", + "lodash": "^4.17.4", + "marked": "^0.5.0", + "marked-terminal": "^3.0.0", + "p-locate": "^3.0.0", + "p-reduce": "^1.0.0", + "read-pkg-up": "^4.0.0", + "resolve-from": "^4.0.0", + "semver": "^5.4.1", + "signale": "^1.2.1", + "yargs": "^12.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.0.0.tgz", + "integrity": "sha512-FneLKMENeOR7wOK0/ZXCh+lwqtnPwkeunJjRN28LPqzGvNAhYvrTAhXv6xDm4vsJ0M7lcRbIYHQudKsSy2RtSQ==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "semantic-release-docker": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semantic-release-docker/-/semantic-release-docker-2.1.0.tgz", + "integrity": "sha512-jdXCfSuUVOH4GEYMyfmBTtH3VdJHEPUfhH4+1PI3rCplDbcfZ7pDmfuOySZhPlfkFHOJvOoVAHvXkdqOYoUxVg==", + "dev": true, + "requires": { + "@semantic-release/error": "^2.1.0", + "execa": "^0.10.0" + } + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + }, + "semver-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", + "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "signale": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.2.1.tgz", + "integrity": "sha512-yY7GbeTGqDLC2ggcXR9hyzcgZnNT+cooPAizWRpUOHYd0DtNVRXhMqM3+F6ZbKav9oCg1r/YtJaB250IAhn/Hg==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk=", + "dev": true + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "dev": true, + "requires": { + "through2": "^2.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "text-extensions": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.7.0.tgz", + "integrity": "sha512-AKXZeDq230UaSzaO5s3qQUZOaC7iKbzq0jOFL614R7d9R593HLqAOL0cYoqLdkNrjBSOdmoQI06yigq1TSBXAg==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + }, + "trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url-join": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", + "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=", + "dev": true + }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", + "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f4e461e --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server.git" + }, + "devDependencies": { + "@semantic-release/exec": "^3.1.0", + "semantic-release": "^15.9.9", + "semantic-release-docker": "^2.1.0" + }, + "release": { + "verifyConditions": [ + "@semantic-release/github", + "semantic-release-docker" + ], + "prepare": [ + { + "path": "@semantic-release/exec", + "cmd": "composer install --prefer-dist --no-interaction && docker build -t felixfbecker/php-language-server ." + } + ], + "publish": [ + "@semantic-release/github", + { + "path": "semantic-release-docker", + "name": "felixfbecker/php-language-server" + } + ] + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist index e67104b..ca324db 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -2,9 +2,12 @@ src tests + tests/Validation/cases + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4e3f6be..a6970f3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,30 @@ - + - ./tests + ./tests - - - ./src + + ./src + + + diff --git a/src/Cache/FileSystemCache.php b/src/Cache/FileSystemCache.php index 5e9f523..d6d4410 100644 --- a/src/Cache/FileSystemCache.php +++ b/src/Cache/FileSystemCache.php @@ -3,7 +3,6 @@ declare(strict_types = 1); namespace LanguageServer\Cache; -use LanguageServer\LanguageClient; use Sabre\Event\Promise; /** diff --git a/src/Client/TextDocument.php b/src/Client/TextDocument.php index 176c4fd..f163d60 100644 --- a/src/Client/TextDocument.php +++ b/src/Client/TextDocument.php @@ -4,7 +4,7 @@ declare(strict_types = 1); namespace LanguageServer\Client; use LanguageServer\ClientHandler; -use LanguageServer\Protocol\{Message, TextDocumentItem, TextDocumentIdentifier}; +use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier}; use Sabre\Event\Promise; use JsonMapper; diff --git a/src/Client/Window.php b/src/Client/Window.php index 053f306..c3558f5 100644 --- a/src/Client/Window.php +++ b/src/Client/Window.php @@ -4,7 +4,6 @@ declare(strict_types = 1); namespace LanguageServer\Client; use LanguageServer\ClientHandler; -use LanguageServer\Protocol\Message; use Sabre\Event\Promise; /** diff --git a/src/Client/XCache.php b/src/Client/XCache.php index 3004e58..b3ce5a5 100644 --- a/src/Client/XCache.php +++ b/src/Client/XCache.php @@ -4,7 +4,6 @@ declare(strict_types = 1); namespace LanguageServer\Client; use LanguageServer\ClientHandler; -use LanguageServer\Protocol\Message; use Sabre\Event\Promise; /** diff --git a/src/ClientHandler.php b/src/ClientHandler.php index 7b5a702..9fe921b 100644 --- a/src/ClientHandler.php +++ b/src/ClientHandler.php @@ -71,7 +71,6 @@ class ClientHandler */ public function notify(string $method, $params): Promise { - $id = $this->idGenerator->generate(); return $this->protocolWriter->write( new Protocol\Message( new AdvancedJsonRpc\Notification($method, (object)$params) diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 99a5d57..d8fefa8 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -3,7 +3,6 @@ declare(strict_types = 1); namespace LanguageServer; -use PhpParser\Node; use LanguageServer\Index\ReadableIndex; use LanguageServer\Protocol\{ TextEdit, @@ -11,8 +10,13 @@ use LanguageServer\Protocol\{ Position, CompletionList, CompletionItem, - CompletionItemKind + CompletionItemKind, + CompletionContext, + CompletionTriggerKind }; +use Microsoft\PhpParser; +use Microsoft\PhpParser\Node; +use Generator; class CompletionProvider { @@ -48,6 +52,7 @@ class CompletionProvider 'eval', 'exit', 'extends', + 'false', 'final', 'finally', 'for', @@ -66,6 +71,7 @@ class CompletionProvider 'list', 'namespace', 'new', + 'null', 'or', 'print', 'private', @@ -78,13 +84,30 @@ class CompletionProvider 'switch', 'throw', 'trait', + 'true', 'try', 'unset', 'use', 'var', 'while', 'xor', - 'yield' + 'yield', + + // List of other reserved words (http://php.net/manual/en/reserved.other-reserved-words.php) + // (the ones which do not occur as actual keywords above.) + 'int', + 'float', + 'bool', + 'string', + 'void', + 'iterable', + 'object', + + // Pseudo keywords + 'from', // As in yield from + 'strict_types', + 'ticks', // As in declare(ticks=1) + 'encoding', // As in declare(encoding='EBCDIC') ]; /** @@ -104,7 +127,7 @@ class CompletionProvider /** * @param DefinitionResolver $definitionResolver - * @param ReadableIndex $index + * @param ReadableIndex $index */ public function __construct(DefinitionResolver $definitionResolver, ReadableIndex $index) { @@ -117,159 +140,82 @@ class CompletionProvider * * @param PhpDocument $doc The opened document * @param Position $pos The cursor position + * @param CompletionContext $context The completion context * @return CompletionList */ - public function provideCompletion(PhpDocument $doc, Position $pos): CompletionList + public function provideCompletion(PhpDocument $doc, Position $pos, CompletionContext $context = null): CompletionList { + // This can be made much more performant if the tree follows specific invariants. $node = $doc->getNodeAtPosition($pos); - if ($node instanceof Node\Expr\Error) { - $node = $node->getAttribute('parentNode'); + // Get the node at the position under the cursor + $offset = $node === null ? -1 : $pos->toOffset($node->getFileContents()); + if ( + $node !== null + && $offset > $node->getEndPosition() + && $node->parent !== null + && $node->parent->getLastChild() instanceof PhpParser\MissingToken + ) { + $node = $node->parent; } $list = new CompletionList; $list->isIncomplete = true; - // A non-free node means we do NOT suggest global symbols - if ( - $node instanceof Node\Expr\MethodCall - || $node instanceof Node\Expr\PropertyFetch - || $node instanceof Node\Expr\StaticCall - || $node instanceof Node\Expr\StaticPropertyFetch - || $node instanceof Node\Expr\ClassConstFetch + if ($node instanceof Node\Expression\Variable && + $node->parent instanceof Node\Expression\ObjectCreationExpression && + $node->name instanceof PhpParser\MissingToken ) { - // If the name is an Error node, just filter by the class - if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) { - // For instances, resolve the variable type - $prefixes = DefinitionResolver::getFqnsFromType( - $this->definitionResolver->resolveExpressionNodeToType($node->var) - ); - } else { - // Static member reference - $prefixes = [$node->class instanceof Node\Name ? (string)$node->class : '']; - } - $prefixes = $this->expandParentFqns($prefixes); - // If we are just filtering by the class, add the appropiate operator to the prefix - // to filter the type of symbol - foreach ($prefixes as &$prefix) { - if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) { - $prefix .= '->'; - } else if ($node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\ClassConstFetch) { - $prefix .= '::'; - } else if ($node instanceof Node\Expr\StaticPropertyFetch) { - $prefix .= '::$'; - } - } - unset($prefix); + $node = $node->parent; + } - foreach ($this->index->getDefinitions() as $fqn => $def) { - foreach ($prefixes as $prefix) { - if (substr($fqn, 0, strlen($prefix)) === $prefix && !$def->isGlobal) { - $list->items[] = CompletionItem::fromDefinition($def); - } - } - } - } else if ( - // A ConstFetch means any static reference, like a class, interface, etc. or keyword - ($node instanceof Node\Name && $node->getAttribute('parentNode') instanceof Node\Expr\ConstFetch) - || $node instanceof Node\Expr\New_ - ) { - $prefix = ''; - $prefixLen = 0; - if ($node instanceof Node\Name) { - $isFullyQualified = $node->isFullyQualified(); - $prefix = (string)$node; - $prefixLen = strlen($prefix); - $namespacedPrefix = (string)$node->getAttribute('namespacedName'); - $namespacedPrefixLen = strlen($prefix); - } - // Find closest namespace - $namespace = getClosestNode($node, Node\Stmt\Namespace_::class); - /** Map from alias to Definition */ - $aliasedDefs = []; - if ($namespace) { - foreach ($namespace->stmts as $stmt) { - if ($stmt instanceof Node\Stmt\Use_ || $stmt instanceof Node\Stmt\GroupUse) { - foreach ($stmt->uses as $use) { - // Get the definition for the used namespace, class-like, function or constant - // And save it under the alias - $fqn = (string)Node\Name::concat($stmt->prefix ?? null, $use->name); - if ($def = $this->index->getDefinition($fqn)) { - $aliasedDefs[$use->alias] = $def; - } - } - } else { - // Use statements are always the first statements in a namespace - break; - } - } - } - // If there is a prefix that does not start with a slash, suggest `use`d symbols - if ($prefix && !$isFullyQualified) { - // Suggest symbols that have been `use`d - // Search the aliases for the typed-in name - foreach ($aliasedDefs as $alias => $def) { - if (substr($alias, 0, $prefixLen) === $prefix) { - $list->items[] = CompletionItem::fromDefinition($def); - } - } - } - // Additionally, suggest global symbols that either - // - start with the current namespace + prefix, if the Name node is not fully qualified - // - start with just the prefix, if the Name node is fully qualified - foreach ($this->index->getDefinitions() as $fqn => $def) { - if ( - $def->isGlobal // exclude methods, properties etc. + // Inspect the type of expression under the cursor + + $content = $doc->getContent(); + $offset = $pos->toOffset($content); + if ( + $node === null + || ( + $node instanceof Node\Statement\InlineHtml + && ( + $context !== null + // Make sure to not suggest on the > trigger character in HTML && ( - !$prefix - || ( - ((!$namespace || $isFullyQualified) && substr($fqn, 0, $prefixLen) === $prefix) - || ( - $namespace - && !$isFullyQualified - && substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix - ) - ) + $context->triggerKind === CompletionTriggerKind::INVOKED + || $context->triggerCharacter === '<' ) - // Only suggest classes for `new` - && (!($node instanceof Node\Expr\New_) || $def->canBeInstantiated) - ) { - $item = CompletionItem::fromDefinition($def); - // Find the shortest name to reference the symbol - if ($namespace && ($alias = array_search($def, $aliasedDefs, true)) !== false) { - // $alias is the name under which this definition is aliased in the current namespace - $item->insertText = $alias; - } else if ($namespace && !($prefix && $isFullyQualified)) { - // Insert the global FQN with trailing backslash - $item->insertText = '\\' . $fqn; - } else { - // Insert the FQN without trailing backlash - $item->insertText = $fqn; - } - $list->items[] = $item; - } - } - // Suggest keywords - if ($node instanceof Node\Name && $node->getAttribute('parentNode') instanceof Node\Expr\ConstFetch) { - foreach (self::KEYWORDS as $keyword) { - if (substr($keyword, 0, $prefixLen) === $prefix) { - $item = new CompletionItem($keyword, CompletionItemKind::KEYWORD); - $item->insertText = $keyword . ' '; - $list->items[] = $item; - } - } - } - } else if ( - $node instanceof Node\Expr\Variable - || ($node && $node->getAttribute('parentNode') instanceof Node\Expr\Variable) - ) { + ) + ) + || $pos == new Position(0, 0) + ) { + // HTML, beginning of file + + // Inside HTML and at the beginning of the file, propose textEdit = new TextEdit( + new Range($pos, $pos), + stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), 'items[] = $item; + + } elseif ( + $node instanceof Node\Expression\Variable + && !( + $node->parent instanceof Node\Expression\ScopedPropertyAccessExpression + && $node->parent->memberName === $node + ) + ) { + // Variables + // + // $| + // $a| + // Find variables, parameters and use statements in the scope - // If there was only a $ typed, $node will be instanceof Node\Error - $namePrefix = $node instanceof Node\Expr\Variable && is_string($node->name) ? $node->name : ''; + $namePrefix = $node->getName() ?? ''; foreach ($this->suggestVariablesAtNode($node, $namePrefix) as $var) { $item = new CompletionItem; $item->kind = CompletionItemKind::VARIABLE; - $item->label = '$' . ($var instanceof Node\Expr\ClosureUse ? $var->var : $var->name); + $item->label = '$' . $var->getName(); $item->documentation = $this->definitionResolver->getDocumentationFromNode($var); $item->detail = (string)$this->definitionResolver->getTypeFromNode($var); $item->textEdit = new TextEdit( @@ -278,36 +224,205 @@ class CompletionProvider ); $list->items[] = $item; } - } else if ($node instanceof Node\Stmt\InlineHTML || $pos == new Position(0, 0)) { - $item = new CompletionItem('textEdit = new TextEdit( - new Range($pos, $pos), - stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), 'c| + // $a->| + + // Multiple prefixes for all possible types + $fqns = FqnUtilities\getFqnsFromType( + $this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression) ); - $list->items[] = $item; + + // Add the object access operator to only get members of all parents + $prefixes = []; + foreach ($this->expandParentFqns($fqns) as $prefix) { + $prefixes[] = $prefix . '->'; + } + + // Collect all definitions that match any of the prefixes + foreach ($this->index->getDefinitions() as $fqn => $def) { + foreach ($prefixes as $prefix) { + if (substr($fqn, 0, strlen($prefix)) === $prefix && $def->isMember) { + $list->items[] = CompletionItem::fromDefinition($def); + } + } + } + + } elseif ( + ($scoped = $node->parent) instanceof Node\Expression\ScopedPropertyAccessExpression || + ($scoped = $node) instanceof Node\Expression\ScopedPropertyAccessExpression + ) { + // Static class members and constants + // + // A\B\C::$a| + // A\B\C::| + // A\B\C::$| + // A\B\C::foo| + // + // TODO: $a::| + + // Resolve all possible types to FQNs + $fqns = FqnUtilities\getFqnsFromType( + $classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier) + ); + + // Append :: operator to only get static members of all parents + $prefixes = []; + foreach ($this->expandParentFqns($fqns) as $prefix) { + $prefixes[] = $prefix . '::'; + } + + // Collect all definitions that match any of the prefixes + foreach ($this->index->getDefinitions() as $fqn => $def) { + foreach ($prefixes as $prefix) { + if (substr(strtolower($fqn), 0, strlen($prefix)) === strtolower($prefix) && $def->isMember) { + $list->items[] = CompletionItem::fromDefinition($def); + } + } + } + + } elseif ( + ParserHelpers\isConstantFetch($node) + // Creation gets set in case of an instantiation (`new` expression) + || ($creation = $node->parent) instanceof Node\Expression\ObjectCreationExpression + || (($creation = $node) instanceof Node\Expression\ObjectCreationExpression) + ) { + // Class instantiations, function calls, constant fetches, class names + // + // new MyCl| + // my_func| + // MY_CONS| + // MyCla| + + // The name Node under the cursor + $nameNode = isset($creation) ? $creation->classTypeDesignator : $node; + + /** The typed name */ + $prefix = $nameNode instanceof Node\QualifiedName + ? (string)PhpParser\ResolvedName::buildName($nameNode->nameParts, $nameNode->getFileContents()) + : $nameNode->getText($node->getFileContents()); + $prefixLen = strlen($prefix); + + /** Whether the prefix is qualified (contains at least one backslash) */ + $isQualified = $nameNode instanceof Node\QualifiedName && $nameNode->isQualifiedName(); + + /** Whether the prefix is fully qualified (begins with a backslash) */ + $isFullyQualified = $nameNode instanceof Node\QualifiedName && $nameNode->isFullyQualifiedName(); + + /** The closest NamespaceDefinition Node */ + $namespaceNode = $node->getNamespaceDefinition(); + + /** @var string The name of the namespace */ + $namespacedPrefix = null; + if ($namespaceNode) { + $namespacedPrefix = (string)PhpParser\ResolvedName::buildName($namespaceNode->name->nameParts, $node->getFileContents()) . '\\' . $prefix; + $namespacedPrefixLen = strlen($namespacedPrefix); + } + + // Get the namespace use statements + // TODO: use function statements, use const statements + + /** @var string[] $aliases A map from local alias to fully qualified name */ + list($aliases,,) = $node->getImportTablesForCurrentScope(); + + foreach ($aliases as $alias => $name) { + $aliases[$alias] = (string)$name; + } + + // If there is a prefix that does not start with a slash, suggest `use`d symbols + if ($prefix && !$isFullyQualified) { + foreach ($aliases as $alias => $fqn) { + // Suggest symbols that have been `use`d and match the prefix + if (substr($alias, 0, $prefixLen) === $prefix && ($def = $this->index->getDefinition($fqn))) { + $list->items[] = CompletionItem::fromDefinition($def); + } + } + } + + // Suggest global symbols that either + // - start with the current namespace + prefix, if the Name node is not fully qualified + // - start with just the prefix, if the Name node is fully qualified + foreach ($this->index->getDefinitions() as $fqn => $def) { + + $fqnStartsWithPrefix = substr($fqn, 0, $prefixLen) === $prefix; + + if ( + // Exclude methods, properties etc. + !$def->isMember + && ( + !$prefix + || ( + // Either not qualified, but a matching prefix with global fallback + ($def->roamed && !$isQualified && $fqnStartsWithPrefix) + // Or not in a namespace or a fully qualified name or AND matching the prefix + || ((!$namespaceNode || $isFullyQualified) && $fqnStartsWithPrefix) + // Or in a namespace, not fully qualified and matching the prefix + current namespace + || ( + $namespaceNode + && !$isFullyQualified + && substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix + ) + ) + ) + // Only suggest classes for `new` + && (!isset($creation) || $def->canBeInstantiated) + ) { + $item = CompletionItem::fromDefinition($def); + // Find the shortest name to reference the symbol + if ($namespaceNode && ($alias = array_search($fqn, $aliases, true)) !== false) { + // $alias is the name under which this definition is aliased in the current namespace + $item->insertText = $alias; + } else if ($namespaceNode && !($prefix && $isFullyQualified)) { + // Insert the global FQN with leading backslash + $item->insertText = '\\' . $fqn; + } else { + // Insert the FQN without leading backlash + $item->insertText = $fqn; + } + // Don't insert the parenthesis for functions + // TODO return a snippet and put the cursor inside + if (substr($item->insertText, -2) === '()') { + $item->insertText = substr($item->insertText, 0, -2); + } + $list->items[] = $item; + } + } + + // If not a class instantiation, also suggest keywords + if (!isset($creation)) { + foreach (self::KEYWORDS as $keyword) { + if (substr($keyword, 0, $prefixLen) === $prefix) { + $item = new CompletionItem($keyword, CompletionItemKind::KEYWORD); + $item->insertText = $keyword; + $list->items[] = $item; + } + } + } } return $list; } /** - * Adds the FQNs of all parent classes to an array of FQNs of classes + * Yields FQNs from an array along with the FQNs of all parent classes * * @param string[] $fqns - * @return string[] + * @return Generator */ - private function expandParentFqns(array $fqns): array + private function expandParentFqns(array $fqns) : Generator { - $expanded = $fqns; foreach ($fqns as $fqn) { + yield $fqn; $def = $this->index->getDefinition($fqn); - if ($def) { - foreach ($this->expandParentFqns($def->extends) as $parent) { - $expanded[] = $parent; + if ($def !== null) { + foreach ($def->getAncestorDefinitions($this->index) as $name => $def) { + yield $name; } } } - return $expanded; } /** @@ -335,30 +450,34 @@ class CompletionProvider // Walk the AST upwards until a scope boundary is met $level = $node; - while ($level && !($level instanceof Node\FunctionLike)) { + while ($level && !($level instanceof PhpParser\FunctionLike)) { // Walk siblings before the node $sibling = $level; - while ($sibling = $sibling->getAttribute('previousSibling')) { + while ($sibling = $sibling->getPreviousSibling()) { // Collect all variables inside the sibling node foreach ($this->findVariableDefinitionsInNode($sibling, $namePrefix) as $var) { - $vars[$var->name] = $var; + $vars[$var->getName()] = $var; } } - $level = $level->getAttribute('parentNode'); + $level = $level->parent; } // If the traversal ended because a function was met, // also add its parameters and closure uses to the result list - if ($level instanceof Node\FunctionLike) { - foreach ($level->params as $param) { - if (!isset($vars[$param->name]) && substr($param->name, 0, strlen($namePrefix)) === $namePrefix) { - $vars[$param->name] = $param; + if ($level && $level instanceof PhpParser\FunctionLike && $level->parameters !== null) { + foreach ($level->parameters->getValues() as $param) { + $paramName = $param->getName(); + if (empty($namePrefix) || strpos($paramName, $namePrefix) !== false) { + $vars[$paramName] = $param; } } - if ($level instanceof Node\Expr\Closure) { - foreach ($level->uses as $use) { - if (!isset($vars[$use->var]) && substr($use->var, 0, strlen($namePrefix)) === $namePrefix) { - $vars[$use->var] = $use; + + if ($level instanceof Node\Expression\AnonymousFunctionCreationExpression && $level->anonymousFunctionUseClause !== null && + $level->anonymousFunctionUseClause->useVariableNameList !== null) { + foreach ($level->anonymousFunctionUseClause->useVariableNameList->getValues() as $use) { + $useName = $use->getName(); + if (empty($namePrefix) || strpos($useName, $namePrefix) !== false) { + $vars[$useName] = $use; } } } @@ -372,38 +491,44 @@ class CompletionProvider * * @param Node $node * @param string $namePrefix Prefix to filter - * @return Node\Expr\Variable[] + * @return Node\Expression\Variable[] */ private function findVariableDefinitionsInNode(Node $node, string $namePrefix = ''): array { $vars = []; // If the child node is a variable assignment, save it - $parent = $node->getAttribute('parentNode'); - if ( - $node instanceof Node\Expr\Variable - && ($parent instanceof Node\Expr\Assign || $parent instanceof Node\Expr\AssignOp) - && is_string($node->name) // Variable variables are of no use - && substr($node->name, 0, strlen($namePrefix)) === $namePrefix - ) { - $vars[] = $node; - } - // Iterate over subnodes - foreach ($node->getSubNodeNames() as $attr) { - if (!isset($node->$attr)) { - continue; - } - $children = is_array($node->$attr) ? $node->$attr : [$node->$attr]; - foreach ($children as $child) { - // Dont try to traverse scalars - // Dont traverse functions, the contained variables are in a different scope - if (!($child instanceof Node) || $child instanceof Node\FunctionLike) { - continue; + + $isAssignmentToVariable = function ($node) { + return $node instanceof Node\Expression\AssignmentExpression; + }; + + if ($this->isAssignmentToVariableWithPrefix($node, $namePrefix)) { + $vars[] = $node->leftOperand; + } elseif ($node instanceof Node\ForeachKey || $node instanceof Node\ForeachValue) { + foreach ($node->getDescendantNodes() as $descendantNode) { + if ($descendantNode instanceof Node\Expression\Variable + && ($namePrefix === '' || strpos($descendantNode->getName(), $namePrefix) !== false) + ) { + $vars[] = $descendantNode; } - foreach ($this->findVariableDefinitionsInNode($child, $namePrefix) as $var) { - $vars[] = $var; + } + } else { + // Get all descendent variables, then filter to ones that start with $namePrefix. + // Avoiding closure usage in tight loop + foreach ($node->getDescendantNodes($isAssignmentToVariable) as $descendantNode) { + if ($this->isAssignmentToVariableWithPrefix($descendantNode, $namePrefix)) { + $vars[] = $descendantNode->leftOperand; } } } + return $vars; } + + private function isAssignmentToVariableWithPrefix(Node $node, string $namePrefix): bool + { + return $node instanceof Node\Expression\AssignmentExpression + && $node->leftOperand instanceof Node\Expression\Variable + && ($namePrefix === '' || strpos($node->leftOperand->getName(), $namePrefix) !== false); + } } diff --git a/src/ComposerScripts.php b/src/ComposerScripts.php index c84a176..2a584d2 100644 --- a/src/ComposerScripts.php +++ b/src/ComposerScripts.php @@ -10,6 +10,7 @@ use phpDocumentor\Reflection\DocBlockFactory; use Webmozart\PathUtil\Path; use Sabre\Uri; use function Sabre\Event\coroutine; +use Microsoft\PhpParser; foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) { if (file_exists($file)) { @@ -29,7 +30,7 @@ class ComposerScripts $finder = new FileSystemFilesFinder; $contentRetriever = new FileSystemContentRetriever; $docBlockFactory = DocBlockFactory::createInstance(); - $parser = new Parser; + $parser = new PhpParser\Parser(); $definitionResolver = new DefinitionResolver($index); $stubsLocation = null; @@ -55,7 +56,8 @@ class ComposerScripts $parts['scheme'] = 'phpstubs'; $uri = Uri\build($parts); - $document = new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver); + // Create a new document and add it to $index + new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver); } $index->setComplete(); diff --git a/src/Definition.php b/src/Definition.php index d4b59cb..1600408 100644 --- a/src/Definition.php +++ b/src/Definition.php @@ -3,10 +3,10 @@ declare(strict_types = 1); namespace LanguageServer; -use PhpParser\Node; +use LanguageServer\Index\ReadableIndex; use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver}; use LanguageServer\Protocol\SymbolInformation; -use Exception; +use Generator; /** * Class used to represent symbols @@ -38,12 +38,20 @@ class Definition public $extends; /** - * Only true for classes, interfaces, traits, functions and non-class constants + * False for classes, interfaces, traits, functions and non-class constants + * True for methods, properties and class constants * This is so methods and properties are not suggested in the global scope * * @var bool */ - public $isGlobal; + public $isMember; + + /** + * True if this definition is affected by global namespace fallback (global function or global constant) + * + * @var bool + */ + public $roamed; /** * False for instance methods and properties @@ -60,7 +68,7 @@ class Definition public $canBeInstantiated; /** - * @var Protocol\SymbolInformation + * @var SymbolInformation */ public $symbolInformation; @@ -70,7 +78,7 @@ class Definition * For functions and methods, this is the return type. * For any other declaration it will be null. * 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 */ @@ -89,4 +97,40 @@ class Definition * @var string */ public $documentation; + + /** + * Signature information if this definition is for a FunctionLike, for use in textDocument/signatureHelp + * + * @var SignatureInformation + */ + public $signatureInformation; + + /** + * Yields the definitons of all ancestor classes (the Definition fqn is yielded as key) + * + * @param ReadableIndex $index the index to search for needed definitions + * @param bool $includeSelf should the first yielded value be the current definition itself + * @return Generator + */ + public function getAncestorDefinitions(ReadableIndex $index, bool $includeSelf = false): Generator + { + if ($includeSelf) { + yield $this->fqn => $this; + } + if ($this->extends !== null) { + // iterating once, storing the references and iterating again + // guarantees that closest definitions are yielded first + $definitions = []; + foreach ($this->extends as $fqn) { + $def = $index->getDefinition($fqn); + if ($def !== null) { + yield $def->fqn => $def; + $definitions[] = $def; + } + } + foreach ($definitions as $def) { + yield from $def->getAncestorDefinitions($index); + } + } + } } diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index ec295a5..990c196 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -3,28 +3,44 @@ declare(strict_types = 1); namespace LanguageServer; -use PhpParser\Node; -use PhpParser\PrettyPrinter\Standard as PrettyPrinter; -use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver}; -use LanguageServer\Protocol\SymbolInformation; use LanguageServer\Index\ReadableIndex; +use LanguageServer\Protocol\SymbolInformation; +use Microsoft\PhpParser; +use Microsoft\PhpParser\Node; +use Microsoft\PhpParser\FunctionLike; +use phpDocumentor\Reflection\{ + DocBlock, DocBlockFactory, Fqsen, Type, TypeResolver, Types +}; class DefinitionResolver { /** + * The current project index (for retrieving existing definitions) + * * @var \LanguageServer\Index\ReadableIndex */ private $index; /** + * Resolves strings to a type object. + * * @var \phpDocumentor\Reflection\TypeResolver */ private $typeResolver; /** - * @var \PhpParser\PrettyPrinterAbstract + * Parses Doc Block comments given the DocBlock text and import tables at a position. + * + * @var DocBlockFactory */ - private $prettyPrinter; + private $docBlockFactory; + + /** + * Creates SignatureInformation + * + * @var SignatureInformationFactory + */ + private $signatureInformationFactory; /** * @param ReadableIndex $index @@ -33,35 +49,44 @@ class DefinitionResolver { $this->index = $index; $this->typeResolver = new TypeResolver; - $this->prettyPrinter = new PrettyPrinter; + $this->docBlockFactory = DocBlockFactory::createInstance(); + $this->signatureInformationFactory = new SignatureInformationFactory($this); } /** - * Builds the declaration line for a given node + * Builds the declaration line for a given node. Declarations with multiple lines are trimmed. * * @param Node $node * @return string */ - public function getDeclarationLineFromNode(Node $node): string + public function getDeclarationLineFromNode($node): string { - if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) { - // Properties and constants can have multiple declarations - // Use the parent node (that includes the modifiers), but only render the requested declaration - $child = $node; - /** @var Node */ - $node = $node->getAttribute('parentNode'); - $defLine = clone $node; - $defLine->props = [$child]; + // If node is part of a declaration list, build a declaration line that discludes other elements in the list + // - [PropertyDeclaration] // public $a, [$b = 3], $c; => public $b = 3; + // - [ConstDeclaration|ClassConstDeclaration] // "const A = 3, [B = 4];" => "const B = 4;" + if ( + ($declaration = ParserHelpers\tryGetPropertyDeclaration($node)) && ($elements = $declaration->propertyElements) || + ($declaration = ParserHelpers\tryGetConstOrClassConstDeclaration($node)) && ($elements = $declaration->constElements) + ) { + $defLine = $declaration->getText(); + $defLineStart = $declaration->getStart(); + + $defLine = \substr_replace( + $defLine, + $node->getFullText(), + $elements->getFullStart() - $defLineStart, + $elements->getFullWidth() + ); } else { - $defLine = clone $node; + $defLine = $node->getText(); } - // Don't include the docblock in the declaration string - $defLine->setAttribute('comments', []); - if (isset($defLine->stmts)) { - $defLine->stmts = []; - } - $defText = $this->prettyPrinter->prettyPrint([$defLine]); - return strstr($defText, "\n", true) ?: $defText; + + // Trim string to only include first line + $defLine = \rtrim(\strtok($defLine, "\n"), "\r"); + + // TODO - pretty print rather than getting text + + return $defLine; } /** @@ -70,28 +95,79 @@ class DefinitionResolver * @param Node $node * @return string|null */ - public function getDocumentationFromNode(Node $node) + public function getDocumentationFromNode($node) { - if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) { - $node = $node->getAttribute('parentNode'); + // Any NamespaceDefinition comments likely apply to the file, not the declaration itself. + if ($node instanceof Node\Statement\NamespaceDefinition) { + return null; } - if ($node instanceof Node\Param) { - $fn = $node->getAttribute('parentNode'); - $docBlock = $fn->getAttribute('docBlock'); - if ($docBlock !== null) { - $tags = $docBlock->getTagsByName('param'); - foreach ($tags as $tag) { - if ($tag->getVariableName() === $node->name) { - return $tag->getDescription()->render(); - } - } - } - } else { - $docBlock = $node->getAttribute('docBlock'); - if ($docBlock !== null) { + + // For properties and constants, set the node to the declaration node, rather than the individual property. + // This is because they get defined as part of a list. + $constOrPropertyDeclaration = ParserHelpers\tryGetPropertyDeclaration($node) ?? ParserHelpers\tryGetConstOrClassConstDeclaration($node); + if ($constOrPropertyDeclaration !== null) { + $node = $constOrPropertyDeclaration; + } + + // For parameters, parse the function-like declaration to get documentation for a parameter + if ($node instanceof Node\Parameter) { + $variableName = $node->getName(); + + $functionLikeDeclaration = ParserHelpers\getFunctionLikeDeclarationFromParameter($node); + $docBlock = $this->getDocBlock($functionLikeDeclaration); + + $parameterDocBlockTag = $this->tryGetDocBlockTagForParameter($docBlock, $variableName); + return $parameterDocBlockTag !== null ? $parameterDocBlockTag->getDescription()->render() : null; + } + + // For everything else, get the doc block summary corresponding to the current node. + $docBlock = $this->getDocBlock($node); + if ($docBlock !== null) { + // check whether we have a description, when true, add a new paragraph + // with the description + $description = $docBlock->getDescription()->render(); + + if (empty($description)) { return $docBlock->getSummary(); } + + return $docBlock->getSummary() . "\n\n" . $description; } + return null; + } + + /** + * Gets Doc Block with resolved names for a Node + * + * @param Node $node + * @return DocBlock|null + */ + private function getDocBlock(Node $node) + { + // TODO make more efficient (caching, ensure import table is in right format to begin with) + $docCommentText = $node->getDocCommentText(); + if ($docCommentText !== null) { + list($namespaceImportTable,,) = $node->getImportTablesForCurrentScope(); + foreach ($namespaceImportTable as $alias => $name) { + $namespaceImportTable[$alias] = (string)$name; + } + $namespaceDefinition = $node->getNamespaceDefinition(); + if ($namespaceDefinition !== null && $namespaceDefinition->name !== null) { + $namespaceName = (string)$namespaceDefinition->name->getNamespacedName(); + } else { + $namespaceName = 'global'; + } + $context = new Types\Context($namespaceName, $namespaceImportTable); + + try { + // create() throws when it thinks the doc comment has invalid fields. + // For example, a @see tag that is followed by something that doesn't look like a valid fqsen will throw. + return $this->docBlockFactory->create($docCommentText, $context); + } catch (\InvalidArgumentException $e) { + return null; + } + } + return null; } /** @@ -103,35 +179,72 @@ class DefinitionResolver */ public function createDefinitionFromNode(Node $node, string $fqn = null): Definition { - $parent = $node->getAttribute('parentNode'); $def = new Definition; - $def->canBeInstantiated = $node instanceof Node\Stmt\Class_; - $def->isGlobal = ( - $node instanceof Node\Stmt\ClassLike - || ($node instanceof Node\Name && $parent instanceof Node\Stmt\Namespace_) - || $node instanceof Node\Stmt\Function_ - || $parent instanceof Node\Stmt\Const_ - ); - $def->isStatic = ( - ($node instanceof Node\Stmt\ClassMethod && $node->isStatic()) - || ($node instanceof Node\Stmt\PropertyProperty && $parent->isStatic()) - ); $def->fqn = $fqn; - if ($node instanceof Node\Stmt\Class_) { + + // Determines whether the suggestion will show after "new" + $def->canBeInstantiated = ( + $node instanceof Node\Statement\ClassDeclaration && + // check whether it is not an abstract class + ($node->abstractOrFinalModifier === null || $node->abstractOrFinalModifier->kind !== PhpParser\TokenKind::AbstractKeyword) + ); + + // Interfaces, classes, traits, namespaces, functions, and global const elements + $def->isMember = !( + $node instanceof PhpParser\ClassLike || + + ($node instanceof Node\Statement\NamespaceDefinition && $node->name !== null) || + + $node instanceof Node\Statement\FunctionDeclaration || + + ($node instanceof Node\ConstElement && $node->parent->parent instanceof Node\Statement\ConstDeclaration) + ); + + // Definition is affected by global namespace fallback if it is a global constant or a global function + $def->roamed = ( + $fqn !== null + && strpos($fqn, '\\') === false + && ( + ($node instanceof Node\ConstElement && $node->parent->parent instanceof Node\Statement\ConstDeclaration) + || $node instanceof Node\Statement\FunctionDeclaration + ) + ); + + // Static methods and static property declarations + $def->isStatic = ( + ($node instanceof Node\MethodDeclaration && $node->isStatic()) || + + (($propertyDeclaration = ParserHelpers\tryGetPropertyDeclaration($node)) !== null + && $propertyDeclaration->isStatic()) + ); + + if ($node instanceof Node\Statement\ClassDeclaration && + // TODO - this should be better represented in the parser API + $node->classBaseClause !== null && $node->classBaseClause->baseClass !== null) { + $def->extends = [(string)$node->classBaseClause->baseClass->getResolvedName()]; + } elseif ( + $node instanceof Node\Statement\InterfaceDeclaration && + // TODO - this should be better represented in the parser API + $node->interfaceBaseClause !== null && $node->interfaceBaseClause->interfaceNameList !== null + ) { $def->extends = []; - if ($node->extends) { - $def->extends[] = (string)$node->extends; - } - } else if ($node instanceof Node\Stmt\Interface_) { - $def->extends = []; - foreach ($node->extends as $n) { - $def->extends[] = (string)$n; + foreach ($node->interfaceBaseClause->interfaceNameList->getValues() as $n) { + $def->extends[] = (string)$n->getResolvedName(); } } + $def->symbolInformation = SymbolInformation::fromNode($node, $fqn); - $def->type = $this->getTypeFromNode($node); - $def->declarationLine = $this->getDeclarationLineFromNode($node); - $def->documentation = $this->getDocumentationFromNode($node); + + if ($def->symbolInformation !== null) { + $def->type = $this->getTypeFromNode($node); + $def->declarationLine = $this->getDeclarationLineFromNode($node); + $def->documentation = $this->getDocumentationFromNode($node); + } + + if ($node instanceof FunctionLike) { + $def->signatureInformation = $this->signatureInformationFactory->create($node); + } + return $def; } @@ -143,14 +256,19 @@ class DefinitionResolver */ public function resolveReferenceNodeToDefinition(Node $node) { - // Variables are not indexed globally, as they stay in the file scope anyway - if ($node instanceof Node\Expr\Variable) { - // Resolve $this - if ($node->name === 'this' && $fqn = $this->getContainingClassFqn($node)) { + $parent = $node->parent; + // Variables are not indexed globally, as they stay in the file scope anyway. + // Ignore variable nodes that are part of ScopedPropertyAccessExpression, + // as the scoped property access expression node is handled separately. + if ($node instanceof Node\Expression\Variable && + !($parent instanceof Node\Expression\ScopedPropertyAccessExpression)) { + // Resolve $this to the containing class definition. + if ($node->getName() === 'this' && $fqn = $this->getContainingClassFqn($node)) { return $this->index->getDefinition($fqn, false); } + // Resolve the variable to a definition node (assignment, param or closure use) - $defNode = self::resolveVariableToNode($node); + $defNode = $this->resolveVariableToNode($node); if ($defNode === null) { return null; } @@ -159,186 +277,234 @@ class DefinitionResolver // Other references are references to a global symbol that have an FQN // Find out the FQN $fqn = $this->resolveReferenceNodeToFqn($node); - if ($fqn === null) { + if (!$fqn) { return null; } + + if ($fqn === 'self' || $fqn === 'static') { + // Resolve self and static keywords to the containing class + // (This is not 100% correct for static but better than nothing) + $classNode = $node->getFirstAncestor(Node\Statement\ClassDeclaration::class); + if (!$classNode) { + return; + } + $fqn = (string)$classNode->getNamespacedName(); + if (!$fqn) { + return; + } + } else if ($fqn === 'parent') { + // Resolve parent keyword to the base class FQN + $classNode = $node->getFirstAncestor(Node\Statement\ClassDeclaration::class); + if (!$classNode || !$classNode->classBaseClause || !$classNode->classBaseClause->baseClass) { + return; + } + $fqn = (string)$classNode->classBaseClause->baseClass->getResolvedName(); + if (!$fqn) { + return; + } + } + // If the node is a function or constant, it could be namespaced, but PHP falls back to global // http://php.net/manual/en/language.namespaces.fallback.php - $parent = $node->getAttribute('parentNode'); - $globalFallback = $parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall; + // TODO - verify that this is not a method + $globalFallback = ParserHelpers\isConstantFetch($node) || $parent instanceof Node\Expression\CallExpression; + // Return the Definition object from the index index return $this->index->getDefinition($fqn, $globalFallback); } - /** - * Returns all possible FQNs in a type - * - * @param Type $type - * @return string[] - */ - public static function getFqnsFromType(Type $type): array - { - $fqns = []; - if ($type instanceof Types\Object_) { - $fqsen = $type->getFqsen(); - if ($fqsen !== null) { - $fqns[] = substr((string)$fqsen, 1); - } - } - if ($type instanceof Types\Compound) { - for ($i = 0; $t = $type->get($i); $i++) { - foreach (self::getFqnsFromType($type) as $fqn) { - $fqns[] = $fqn; - } - } - } - return $fqns; - } - /** * Given any node, returns the FQN of the symbol that is referenced * Returns null if the FQN could not be resolved or the reference node references a variable + * May also return "static", "self" or "parent" * * @param Node $node * @return string|null */ public function resolveReferenceNodeToFqn(Node $node) { - $parent = $node->getAttribute('parentNode'); - - if ( - $node instanceof Node\Name && ( - $parent instanceof Node\Stmt\ClassLike - || $parent instanceof Node\Param - || $parent instanceof Node\FunctionLike - || $parent instanceof Node\Stmt\GroupUse - || $parent instanceof Node\Expr\New_ - || $parent instanceof Node\Expr\StaticCall - || $parent instanceof Node\Expr\ClassConstFetch - || $parent instanceof Node\Expr\StaticPropertyFetch - || $parent instanceof Node\Expr\Instanceof_ - ) - ) { - // For extends, implements, type hints and classes of classes of static calls use the name directly - $name = (string)$node; - // Only the name node should be considered a reference, not the UseUse node itself - } else if ($parent instanceof Node\Stmt\UseUse) { - $name = (string)$parent->name; - $grandParent = $parent->getAttribute('parentNode'); - if ($grandParent instanceof Node\Stmt\GroupUse) { - $name = $grandParent->prefix . '\\' . $name; - } else if ($grandParent instanceof Node\Stmt\Use_ && $grandParent->type === Node\Stmt\Use_::TYPE_FUNCTION) { - $name .= '()'; - } - } else if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) { - if ($node->name instanceof Node\Expr) { - // Cannot get definition if right-hand side is expression - return null; - } - // Get the type of the left-hand expression - $varType = $this->resolveExpressionNodeToType($node->var); - if ($varType instanceof Types\Compound) { - // For compound types, use the first FQN we find - // (popular use case is ClassName|null) - for ($i = 0; $t = $varType->get($i); $i++) { - if ( - $t instanceof Types\This - || $t instanceof Types\Object_ - || $t instanceof Types\Static_ - || $t instanceof Types\Self_ - ) { - $varType = $t; - break; - } - } - } - if ( - $varType instanceof Types\This - || $varType instanceof Types\Static_ - || $varType instanceof Types\Self_ - ) { - // $this/static/self is resolved to the containing class - $classFqn = self::getContainingClassFqn($node); - } else if (!($varType instanceof Types\Object_) || $varType->getFqsen() === null) { - // Left-hand expression could not be resolved to a class - return null; - } else { - $classFqn = substr((string)$varType->getFqsen(), 1); - } - $memberSuffix = '->' . (string)$node->name; - if ($node instanceof Node\Expr\MethodCall) { - $memberSuffix .= '()'; - } - // Find the right class that implements the member - $implementorFqns = [$classFqn]; - while ($implementorFqn = array_shift($implementorFqns)) { - // If the member FQN exists, return it - if ($this->index->getDefinition($implementorFqn . $memberSuffix)) { - return $implementorFqn . $memberSuffix; - } - // Get Definition of implementor class - $implementorDef = $this->index->getDefinition($implementorFqn); - // If it doesn't exist, return the initial guess - if ($implementorDef === null) { - break; - } - // Repeat for parent class - if ($implementorDef->extends) { - foreach ($implementorDef->extends as $extends) { - $implementorFqns[] = $extends; - } - } - } - return $classFqn . $memberSuffix; - } else if ($parent instanceof Node\Expr\FuncCall && $node instanceof Node\Name) { - if ($parent->name instanceof Node\Expr) { - return null; - } - $name = (string)($node->getAttribute('namespacedName') ?? $parent->name); - } else if ($parent instanceof Node\Expr\ConstFetch && $node instanceof Node\Name) { - $name = (string)($node->getAttribute('namespacedName') ?? $parent->name); + // TODO all name tokens should be a part of a node + if ($node instanceof Node\QualifiedName) { + return $this->resolveQualifiedNameNodeToFqn($node); + } else if ($node instanceof Node\Expression\MemberAccessExpression) { + return $this->resolveMemberAccessExpressionNodeToFqn($node); + } else if (ParserHelpers\isConstantFetch($node)) { + return (string)($node->getNamespacedName()); } else if ( - $node instanceof Node\Expr\ClassConstFetch - || $node instanceof Node\Expr\StaticPropertyFetch - || $node instanceof Node\Expr\StaticCall + // A\B::C - constant access expression + $node instanceof Node\Expression\ScopedPropertyAccessExpression + && !($node->memberName instanceof Node\Expression\Variable) ) { - if ($node->class instanceof Node\Expr || $node->name instanceof Node\Expr) { - // Cannot get definition of dynamic names - return null; - } - $className = (string)$node->class; - if ($className === 'self' || $className === 'static' || $className === 'parent') { - // self and static are resolved to the containing class - $classNode = getClosestNode($node, Node\Stmt\Class_::class); - if ($classNode === null) { + return $this->resolveScopedPropertyAccessExpressionNodeToFqn($node); + } else if ( + // A\B::$c - static property access expression + $node->parent instanceof Node\Expression\ScopedPropertyAccessExpression + ) { + return $this->resolveScopedPropertyAccessExpressionNodeToFqn($node->parent); + } + + return null; + } + + private function resolveQualifiedNameNodeToFqn(Node\QualifiedName $node) + { + $parent = $node->parent; + + if ($parent instanceof Node\TraitSelectOrAliasClause) { + return null; + } + // Add use clause references + if (($useClause = $parent) instanceof Node\NamespaceUseGroupClause + || $useClause instanceof Node\NamespaceUseClause + ) { + $contents = $node->getFileContents(); + if ($useClause instanceof Node\NamespaceUseGroupClause) { + $prefix = $useClause->parent->parent->namespaceName; + if ($prefix === null) { return null; } - if ($className === 'parent') { - // parent is resolved to the parent class - if (!isset($n->extends)) { - return null; + $name = PhpParser\ResolvedName::buildName($prefix->nameParts, $contents); + $name->addNameParts($node->nameParts, $contents); + $name = (string)$name; + + if ($useClause->functionOrConst === null) { + $useClause = $node->getFirstAncestor(Node\Statement\NamespaceUseDeclaration::class); + if ($useClause->functionOrConst !== null && $useClause->functionOrConst->kind === PhpParser\TokenKind::FunctionKeyword) { + $name .= '()'; } - $className = (string)$classNode->extends; - } else { - $className = (string)$classNode->namespacedName; + } + return $name; + } else { + $name = (string) PhpParser\ResolvedName::buildName($node->nameParts, $contents); + if ($useClause->groupClauses === null && $useClause->parent->parent->functionOrConst !== null && $useClause->parent->parent->functionOrConst->kind === PhpParser\TokenKind::FunctionKeyword) { + $name .= '()'; } } - if ($node instanceof Node\Expr\StaticPropertyFetch) { - $name = (string)$className . '::$' . $node->name; - } else { - $name = (string)$className . '::' . $node->name; + + return $name; + } + + // For extends, implements, type hints and classes of classes of static calls use the name directly + $name = (string) ($node->getResolvedName() ?? $node->getNamespacedName()); + + if ($node->parent instanceof Node\Expression\CallExpression) { + $name .= '()'; + } + return $name; + } + + private function resolveMemberAccessExpressionNodeToFqn(Node\Expression\MemberAccessExpression $access) + { + if ($access->memberName instanceof Node\Expression) { + // Cannot get definition if right-hand side is expression + return null; + } + // Get the type of the left-hand expression + $varType = $this->resolveExpressionNodeToType($access->dereferencableExpression); + + if ($varType instanceof Types\Compound) { + // For compound types, use the first FQN we find + // (popular use case is ClassName|null) + for ($i = 0; $t = $varType->get($i); $i++) { + if ( + $t instanceof Types\This + || $t instanceof Types\Object_ + || $t instanceof Types\Static_ + || $t instanceof Types\Self_ + ) { + $varType = $t; + break; + } } + } + if ( + $varType instanceof Types\This + || $varType instanceof Types\Static_ + || $varType instanceof Types\Self_ + ) { + // $this/static/self is resolved to the containing class + $classFqn = self::getContainingClassFqn($access); + } else if (!($varType instanceof Types\Object_) || $varType->getFqsen() === null) { + // Left-hand expression could not be resolved to a class + return null; + } else { + $classFqn = substr((string)$varType->getFqsen(), 1); + } + $memberSuffix = '->' . (string)($access->memberName->getText() ?? $access->memberName->getText($access->getFileContents())); + if ($access->parent instanceof Node\Expression\CallExpression) { + $memberSuffix .= '()'; + } + + // Find the right class that implements the member + $implementorFqns = [$classFqn]; + + while ($implementorFqn = array_shift($implementorFqns)) { + // If the member FQN exists, return it + if ($this->index->getDefinition($implementorFqn . $memberSuffix)) { + return $implementorFqn . $memberSuffix; + } + // Get Definition of implementor class + $implementorDef = $this->index->getDefinition($implementorFqn); + // If it doesn't exist, return the initial guess + if ($implementorDef === null) { + break; + } + // Repeat for parent class + if ($implementorDef->extends) { + foreach ($implementorDef->extends as $extends) { + $implementorFqns[] = $extends; + } + } + } + + return $classFqn . $memberSuffix; + } + + private function resolveScopedPropertyAccessExpressionNodeToFqn(Node\Expression\ScopedPropertyAccessExpression $scoped) + { + if ($scoped->scopeResolutionQualifier instanceof Node\Expression\Variable) { + $varType = $this->getTypeFromNode($scoped->scopeResolutionQualifier); + if ($varType === null) { + return null; + } + $className = substr((string)$varType->getFqsen(), 1); + } elseif ($scoped->scopeResolutionQualifier instanceof Node\QualifiedName) { + $className = (string)$scoped->scopeResolutionQualifier->getResolvedName(); } else { return null; } - if (!isset($name)) { - return null; + + if ($className === 'self' || $className === 'static' || $className === 'parent') { + // self and static are resolved to the containing class + $classNode = $scoped->getFirstAncestor(Node\Statement\ClassDeclaration::class); + if ($classNode === null) { + return null; + } + if ($className === 'parent') { + // parent is resolved to the parent class + if (!isset($classNode->extends)) { + return null; + } + $className = (string)$classNode->extends->getResolvedName(); + } else { + $className = (string)$classNode->getNamespacedName(); + } + } elseif ($scoped->scopeResolutionQualifier instanceof Node\QualifiedName) { + $className = $scoped->scopeResolutionQualifier->getResolvedName(); } - if ( - $node instanceof Node\Expr\MethodCall - || $node instanceof Node\Expr\StaticCall - || $parent instanceof Node\Expr\FuncCall - ) { + if ($scoped->memberName instanceof Node\Expression\Variable) { + if ($scoped->parent instanceof Node\Expression\CallExpression) { + return null; + } + $memberName = $scoped->memberName->getName(); + if (empty($memberName)) { + return null; + } + $name = (string)$className . '::$' . $memberName; + } else { + $name = (string)$className . '::' . $scoped->memberName->getText($scoped->getFileContents()); + } + if ($scoped->parent instanceof Node\Expression\CallExpression) { $name .= '()'; } return $name; @@ -353,118 +519,234 @@ class DefinitionResolver */ private static function getContainingClassFqn(Node $node) { - $classNode = getClosestNode($node, Node\Stmt\Class_::class); - if ($classNode === null || $classNode->isAnonymous()) { + $classNode = $node->getFirstAncestor(Node\Statement\ClassDeclaration::class); + if ($classNode === null) { return null; } - return (string)$classNode->namespacedName; + return (string)$classNode->getNamespacedName(); + } + + /** + * Returns the type of the class a node is contained in + * Returns null if the class is anonymous or the node is not contained in a class + * + * @param Node $node The node used to find the containing class + * + * @return Types\Object_|null + */ + private function getContainingClassType(Node $node) + { + $classFqn = $this->getContainingClassFqn($node); + return $classFqn ? new Types\Object_(new Fqsen('\\' . $classFqn)) : null; } /** * Returns the assignment or parameter node where a variable was defined * - * @param Node\Expr\Variable|Node\Expr\ClosureUse $var The variable access - * @return Node\Expr\Assign|Node\Expr\AssignOp|Node\Param|Node\Expr\ClosureUse|null + * @param Node\Expression\Variable|Node\Expression\ClosureUse $var The variable access + * @return Node\Expression\Assign|Node\Expression\AssignOp|Node\Param|Node\Expression\ClosureUse|null */ - public static function resolveVariableToNode(Node\Expr $var) + public function resolveVariableToNode($var) { $n = $var; - // When a use is passed, start outside the closure to not return immediatly - if ($var instanceof Node\Expr\ClosureUse) { - $n = $var->getAttribute('parentNode')->getAttribute('parentNode'); - $name = $var->var; - } else if ($var instanceof Node\Expr\Variable || $var instanceof Node\Param) { - $name = $var->name; + // When a use is passed, start outside the closure to not return immediately + // Use variable vs variable parsing? + if ($var instanceof Node\UseVariableName) { + $n = $var->getFirstAncestor(Node\Expression\AnonymousFunctionCreationExpression::class)->parent; + $name = $var->getName(); + } else if ($var instanceof Node\Expression\Variable || $var instanceof Node\Parameter) { + $name = $var->getName(); } else { throw new \InvalidArgumentException('$var must be Variable, Param or ClosureUse, not ' . get_class($var)); } + if (empty($name)) { + return null; + } + + $shouldDescend = function ($nodeToDescand) { + // Make sure not to decend into functions or classes (they represent a scope boundary) + return !($nodeToDescand instanceof PhpParser\FunctionLike || $nodeToDescand instanceof PhpParser\ClassLike); + }; + // Traverse the AST up do { // If a function is met, check the parameters and use statements - if ($n instanceof Node\FunctionLike) { - foreach ($n->getParams() as $param) { - if ($param->name === $name) { - return $param; + if ($n instanceof PhpParser\FunctionLike) { + if ($n->parameters !== null) { + foreach ($n->parameters->getElements() as $param) { + if ($param->getName() === $name) { + return $param; + } } } // If it is a closure, also check use statements - if ($n instanceof Node\Expr\Closure) { - foreach ($n->uses as $use) { - if ($use->var === $name) { + if ($n instanceof Node\Expression\AnonymousFunctionCreationExpression && + $n->anonymousFunctionUseClause !== null && + $n->anonymousFunctionUseClause->useVariableNameList !== null) { + foreach ($n->anonymousFunctionUseClause->useVariableNameList->getElements() as $use) { + if ($use->getName() === $name) { return $use; } } } break; } - // Check each previous sibling node for a variable assignment to that variable - while ($n->getAttribute('previousSibling') && $n = $n->getAttribute('previousSibling')) { - if ( - ($n instanceof Node\Expr\Assign || $n instanceof Node\Expr\AssignOp) - && $n->var instanceof Node\Expr\Variable && $n->var->name === $name - ) { + + // Check each previous sibling node and their descendents for a variable assignment to that variable + // Each previous sibling could contain a declaration of the variable + while (($prevSibling = $n->getPreviousSibling()) !== null && $n = $prevSibling) { + + // Check the sibling itself + if (self::isVariableDeclaration($n, $name)) { return $n; } + + // Check descendant of this sibling (e.g. the children of a previous if block) + foreach ($n->getDescendantNodes($shouldDescend) as $descendant) { + if (self::isVariableDeclaration($descendant, $name)) { + return $descendant; + } + } } - } while (isset($n) && $n = $n->getAttribute('parentNode')); + } while (isset($n) && $n = $n->parent); // Return null if nothing was found return null; } /** - * Given an expression node, resolves that expression recursively to a type. - * If the type could not be resolved, returns Types\Mixed. + * Checks whether the given Node declares the given variable name * - * @param \PhpParser\Node\Expr $expr - * @return \phpDocumentor\Reflection\Type + * @param Node $n The Node to check + * @param string $name The name of the wanted variable + * @return bool */ - public function resolveExpressionNodeToType(Node\Expr $expr): Type + private static function isVariableDeclaration(Node $n, string $name) { - if ($expr instanceof Node\Expr\Variable || $expr instanceof Node\Expr\ClosureUse) { - if ($expr instanceof Node\Expr\Variable && $expr->name === 'this') { - return new Types\This; + if ( + // TODO - clean this up + ($n instanceof Node\Expression\AssignmentExpression && $n->operator->kind === PhpParser\TokenKind::EqualsToken) + && $n->leftOperand instanceof Node\Expression\Variable && $n->leftOperand->getName() === $name + ) { + return true; + } + + if ( + ($n instanceof Node\ForeachValue || $n instanceof Node\ForeachKey) + && $n->expression instanceof Node\Expression\Variable + && $n->expression->getName() === $name + ) { + return true; + } + + return false; + } + + /** + * Given an expression node, resolves that expression recursively to a type. + * If the type could not be resolved, returns Types\Mixed_. + * + * @param Node\Expression $expr + * @return \phpDocumentor\Reflection\Type|null + */ + public function resolveExpressionNodeToType($expr) + { + // PARENTHESIZED EXPRESSION + // Retrieve inner expression from parenthesized expression + while ($expr instanceof Node\Expression\ParenthesizedExpression) { + $expr = $expr->expression; + } + + if ($expr == null || $expr instanceof PhpParser\MissingToken || $expr instanceof PhpParser\SkippedToken) { + // TODO some members are null or Missing/SkippedToken + // How do we handle this more generally? + return new Types\Mixed_; + } + + // VARIABLE + // $this -> Type\this + // $myVariable -> type of corresponding assignment expression + if ($expr instanceof Node\Expression\Variable || $expr instanceof Node\UseVariableName) { + if ($expr->getName() === 'this') { + return new Types\Object_(new Fqsen('\\' . $this->getContainingClassFqn($expr))); } - // Find variable definition + // Find variable definition (parameter or assignment expression) $defNode = $this->resolveVariableToNode($expr); - if ($defNode instanceof Node\Expr) { + if ($defNode instanceof Node\Expression\AssignmentExpression || $defNode instanceof Node\UseVariableName) { return $this->resolveExpressionNodeToType($defNode); } - if ($defNode instanceof Node\Param) { + if ($defNode instanceof Node\ForeachKey || $defNode instanceof Node\ForeachValue) { + return $this->getTypeFromNode($defNode); + } + if ($defNode instanceof Node\Parameter) { return $this->getTypeFromNode($defNode); } } - if ($expr instanceof Node\Expr\FuncCall) { + + // FUNCTION CALL + // Function calls are resolved to type corresponding to their FQN + if ($expr instanceof Node\Expression\CallExpression && + !( + $expr->callableExpression instanceof Node\Expression\ScopedPropertyAccessExpression || + $expr->callableExpression instanceof Node\Expression\MemberAccessExpression) + ) { // Find the function definition - if ($expr->name instanceof Node\Expr) { + if ($expr->callableExpression instanceof Node\Expression) { // Cannot get type for dynamic function call - return new Types\Mixed; + return new Types\Mixed_; } - $fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name); - $def = $this->index->getDefinition($fqn, true); - if ($def !== null) { - return $def->type; + + if ($expr->callableExpression instanceof Node\QualifiedName) { + $fqn = $expr->callableExpression->getResolvedName() ?? $expr->callableExpression->getNamespacedName(); + $fqn .= '()'; + $def = $this->index->getDefinition($fqn, true); + if ($def !== null) { + return $def->type; + } } } - if ($expr instanceof Node\Expr\ConstFetch) { - if (strtolower((string)$expr->name) === 'true' || strtolower((string)$expr->name) === 'false') { + + // TRUE / FALSE / NULL + // Resolve true and false reserved words to Types\Boolean + if ($expr instanceof Node\ReservedWord) { + $token = $expr->children->kind; + if ($token === PhpParser\TokenKind::TrueReservedWord || $token === PhpParser\TokenKind::FalseReservedWord) { return new Types\Boolean; } - if (strtolower((string)$expr->name) === 'null') { + + if ($token === PhpParser\TokenKind::NullReservedWord) { return new Types\Null_; } - // Resolve constant - $fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name); + } + + // CONSTANT FETCH + // Resolve constants by retrieving corresponding definition type from FQN + if (ParserHelpers\isConstantFetch($expr)) { + $fqn = (string)$expr->getNamespacedName(); $def = $this->index->getDefinition($fqn, true); if ($def !== null) { return $def->type; } } - if ($expr instanceof Node\Expr\MethodCall || $expr instanceof Node\Expr\PropertyFetch) { - if ($expr->name instanceof Node\Expr) { - return new Types\Mixed; + + // MEMBER CALL EXPRESSION/SCOPED PROPERTY CALL EXPRESSION + // The type of the member/scoped property call expression is the type of the method, so resolve the + // type of the callable expression. + if ($expr instanceof Node\Expression\CallExpression && ( + $expr->callableExpression instanceof Node\Expression\MemberAccessExpression || + $expr->callableExpression instanceof Node\Expression\ScopedPropertyAccessExpression) + ) { + return $this->resolveExpressionNodeToType($expr->callableExpression); + } + + // MEMBER ACCESS EXPRESSION + if ($expr instanceof Node\Expression\MemberAccessExpression) { + if ($expr->memberName instanceof Node\Expression) { + return new Types\Mixed_; } - // Resolve object - $objType = $this->resolveExpressionNodeToType($expr->var); + $var = $expr->dereferencableExpression; + + // Resolve object + $objType = $this->resolveExpressionNodeToType($var); if (!($objType instanceof Types\Compound)) { $objType = new Types\Compound([$objType]); } @@ -472,165 +754,211 @@ class DefinitionResolver if ($t instanceof Types\This) { $classFqn = self::getContainingClassFqn($expr); if ($classFqn === null) { - return new Types\Mixed; + return new Types\Mixed_; } } else if (!($t instanceof Types\Object_) || $t->getFqsen() === null) { - return new Types\Mixed; + return new Types\Mixed_; } else { $classFqn = substr((string)$t->getFqsen(), 1); } - $fqn = $classFqn . '->' . $expr->name; - if ($expr instanceof Node\Expr\MethodCall) { - $fqn .= '()'; + $add = '->' . $expr->memberName->getText($expr->getFileContents()); + if ($expr->parent instanceof Node\Expression\CallExpression) { + $add .= '()'; } - $def = $this->index->getDefinition($fqn); - if ($def !== null) { - return $def->type; + $classDef = $this->index->getDefinition($classFqn); + if ($classDef !== null) { + foreach ($classDef->getAncestorDefinitions($this->index, true) as $fqn => $def) { + $def = $this->index->getDefinition($fqn . $add); + if ($def !== null) { + if ($def->type instanceof Types\This || $def->type instanceof Types\Self_) { + return new Types\Object_(new Fqsen('\\' . $classFqn)); + } + return $def->type; + } + } } } } - if ( - $expr instanceof Node\Expr\StaticCall - || $expr instanceof Node\Expr\StaticPropertyFetch - || $expr instanceof Node\Expr\ClassConstFetch - ) { - $classType = self::resolveClassNameToType($expr->class); - if (!($classType instanceof Types\Object_) || $classType->getFqsen() === null || $expr->name instanceof Node\Expr) { - return new Types\Mixed; + + // SCOPED PROPERTY ACCESS EXPRESSION + if ($expr instanceof Node\Expression\ScopedPropertyAccessExpression) { + $classType = $this->resolveClassNameToType($expr->scopeResolutionQualifier); + if (!($classType instanceof Types\Object_) || $classType->getFqsen() === null) { + return new Types\Mixed_; } $fqn = substr((string)$classType->getFqsen(), 1) . '::'; - if ($expr instanceof Node\Expr\StaticPropertyFetch) { - $fqn .= '$'; - } - $fqn .= $expr->name; - if ($expr instanceof Node\Expr\StaticCall) { + + // TODO is there a cleaner way to do this? + $fqn .= $expr->memberName->getText() ?? $expr->memberName->getText($expr->getFileContents()); + if ($expr->parent instanceof Node\Expression\CallExpression) { $fqn .= '()'; } + $def = $this->index->getDefinition($fqn); if ($def === null) { - return new Types\Mixed; + return new Types\Mixed_; } return $def->type; } - if ($expr instanceof Node\Expr\New_) { - return self::resolveClassNameToType($expr->class); + + // OBJECT CREATION EXPRESSION + // new A() => resolves to the type of the class type designator (A) + // TODO: new $this->a => resolves to the string represented by "a" + if ($expr instanceof Node\Expression\ObjectCreationExpression) { + return $this->resolveClassNameToType($expr->classTypeDesignator); } - if ($expr instanceof Node\Expr\Clone_ || $expr instanceof Node\Expr\Assign) { - return $this->resolveExpressionNodeToType($expr->expr); + + // CLONE EXPRESSION + // clone($a) => resolves to the type of $a + if ($expr instanceof Node\Expression\CloneExpression) { + return $this->resolveExpressionNodeToType($expr->expression); } - if ($expr instanceof Node\Expr\Ternary) { + + // ASSIGNMENT EXPRESSION + // $a = $myExpression => resolves to the type of the right-hand operand + if ($expr instanceof Node\Expression\AssignmentExpression) { + return $this->resolveExpressionNodeToType($expr->rightOperand); + } + + // TERNARY EXPRESSION + // $condition ? $ifExpression : $elseExpression => reslves to type of $ifCondition or $elseExpression + // $condition ?: $elseExpression => resolves to type of $condition or $elseExpression + if ($expr instanceof Node\Expression\TernaryExpression) { // ?: - if ($expr->if === null) { + if ($expr->ifExpression === null) { return new Types\Compound([ - $this->resolveExpressionNodeToType($expr->cond), - $this->resolveExpressionNodeToType($expr->else) + $this->resolveExpressionNodeToType($expr->condition), // TODO: why? + $this->resolveExpressionNodeToType($expr->elseExpression) ]); } // Ternary is a compound of the two possible values return new Types\Compound([ - $this->resolveExpressionNodeToType($expr->if), - $this->resolveExpressionNodeToType($expr->else) + $this->resolveExpressionNodeToType($expr->ifExpression), + $this->resolveExpressionNodeToType($expr->elseExpression) ]); } - if ($expr instanceof Node\Expr\BinaryOp\Coalesce) { + + // NULL COALLESCE + // $rightOperand ?? $leftOperand => resolves to type of $rightOperand or $leftOperand + if ($expr instanceof Node\Expression\BinaryExpression && $expr->operator->kind === PhpParser\TokenKind::QuestionQuestionToken) { // ?? operator return new Types\Compound([ - $this->resolveExpressionNodeToType($expr->left), - $this->resolveExpressionNodeToType($expr->right) + $this->resolveExpressionNodeToType($expr->leftOperand), + $this->resolveExpressionNodeToType($expr->rightOperand) ]); } + + // BOOLEAN EXPRESSIONS: resolve to Types\Boolean + // (bool) $expression + // !$expression + // empty($var) + // isset($var) + // >, >=, <, <=, &&, ||, AND, OR, XOR, ==, ===, !=, !== if ( - $expr instanceof Node\Expr\Instanceof_ - || $expr instanceof Node\Expr\Cast\Bool_ - || $expr instanceof Node\Expr\BooleanNot - || $expr instanceof Node\Expr\Empty_ - || $expr instanceof Node\Expr\Isset_ - || $expr instanceof Node\Expr\BinaryOp\Greater - || $expr instanceof Node\Expr\BinaryOp\GreaterOrEqual - || $expr instanceof Node\Expr\BinaryOp\Smaller - || $expr instanceof Node\Expr\BinaryOp\SmallerOrEqual - || $expr instanceof Node\Expr\BinaryOp\BooleanAnd - || $expr instanceof Node\Expr\BinaryOp\BooleanOr - || $expr instanceof Node\Expr\BinaryOp\LogicalAnd - || $expr instanceof Node\Expr\BinaryOp\LogicalOr - || $expr instanceof Node\Expr\BinaryOp\LogicalXor - || $expr instanceof Node\Expr\BinaryOp\NotEqual - || $expr instanceof Node\Expr\BinaryOp\NotIdentical + ParserHelpers\isBooleanExpression($expr) + + || ($expr instanceof Node\Expression\CastExpression && $expr->castType->kind === PhpParser\TokenKind::BoolCastToken) + || ($expr instanceof Node\Expression\UnaryOpExpression && $expr->operator->kind === PhpParser\TokenKind::ExclamationToken) + || $expr instanceof Node\Expression\EmptyIntrinsicExpression + || $expr instanceof Node\Expression\IssetIntrinsicExpression ) { return new Types\Boolean; } + + // STRING EXPRESSIONS: resolve to Types\String + // [concatenation] .=, . + // [literals] "hello", \b"hello", \B"hello", 'hello', \b'hello', HEREDOC, NOWDOC + // [cast] (string) "hello" + // + // TODO: Magic constants (__CLASS__, __DIR__, __FUNCTION__, __METHOD__, __NAMESPACE__, __TRAIT__, __FILE__) if ( - $expr instanceof Node\Expr\Cast\String_ - || $expr instanceof Node\Expr\BinaryOp\Concat - || $expr instanceof Node\Expr\AssignOp\Concat - || $expr instanceof Node\Scalar\String_ - || $expr instanceof Node\Scalar\Encapsed - || $expr instanceof Node\Scalar\EncapsedStringPart - || $expr instanceof Node\Scalar\MagicConst\Class_ - || $expr instanceof Node\Scalar\MagicConst\Dir - || $expr instanceof Node\Scalar\MagicConst\Function_ - || $expr instanceof Node\Scalar\MagicConst\Method - || $expr instanceof Node\Scalar\MagicConst\Namespace_ - || $expr instanceof Node\Scalar\MagicConst\Trait_ + ($expr instanceof Node\Expression\BinaryExpression && + ($expr->operator->kind === PhpParser\TokenKind::DotToken || $expr->operator->kind === PhpParser\TokenKind::DotEqualsToken)) || + $expr instanceof Node\StringLiteral || + ($expr instanceof Node\Expression\CastExpression && $expr->castType->kind === PhpParser\TokenKind::StringCastToken) ) { return new Types\String_; } + + // BINARY EXPRESSIONS: + // Resolve to Types\Integer if both left and right operands are integer types, otherwise Types\Float + // [operator] +, -, *, ** + // [assignment] *=, **=, -=, += + // Resolve to Types\Float + // [assignment] /= if ( - $expr instanceof Node\Expr\BinaryOp\Minus - || $expr instanceof Node\Expr\BinaryOp\Plus - || $expr instanceof Node\Expr\BinaryOp\Pow - || $expr instanceof Node\Expr\BinaryOp\Mul + $expr instanceof Node\Expression\BinaryExpression && + ($operator = $expr->operator->kind) + && ($operator === PhpParser\TokenKind::PlusToken || + $operator === PhpParser\TokenKind::AsteriskAsteriskToken || + $operator === PhpParser\TokenKind::AsteriskToken || + $operator === PhpParser\TokenKind::MinusToken || + + // Assignment expressions (TODO: consider making this a type of AssignmentExpression rather than kind of BinaryExpression) + $operator === PhpParser\TokenKind::AsteriskEqualsToken|| + $operator === PhpParser\TokenKind::AsteriskAsteriskEqualsToken || + $operator === PhpParser\TokenKind::MinusEqualsToken || + $operator === PhpParser\TokenKind::PlusEqualsToken + ) ) { if ( - $this->resolveExpressionNodeToType($expr->left) instanceof Types\Integer - && $this->resolveExpressionNodeToType($expr->right) instanceof Types\Integer + $this->resolveExpressionNodeToType($expr->leftOperand) instanceof Types\Integer + && $this->resolveExpressionNodeToType($expr->rightOperand) instanceof Types\Integer ) { return new Types\Integer; } return new Types\Float_; - } - - if ( - $expr instanceof Node\Expr\AssignOp\Minus - || $expr instanceof Node\Expr\AssignOp\Plus - || $expr instanceof Node\Expr\AssignOp\Pow - || $expr instanceof Node\Expr\AssignOp\Mul + } else if ( + $expr instanceof Node\Expression\BinaryExpression && + $expr->operator->kind === PhpParser\TokenKind::SlashEqualsToken ) { - if ( - $this->resolveExpressionNodeToType($expr->var) instanceof Types\Integer - && $this->resolveExpressionNodeToType($expr->expr) instanceof Types\Integer - ) { - return new Types\Integer; - } return new Types\Float_; } + // INTEGER EXPRESSIONS: resolve to Types\Integer + // [literal] 1 + // [operator] <=>, &, ^, | + // TODO: Magic constants (__LINE__) if ( - $expr instanceof Node\Scalar\LNumber - || $expr instanceof Node\Expr\Cast\Int_ - || $expr instanceof Node\Scalar\MagicConst\Line - || $expr instanceof Node\Expr\BinaryOp\Spaceship - || $expr instanceof Node\Expr\BinaryOp\BitwiseAnd - || $expr instanceof Node\Expr\BinaryOp\BitwiseOr - || $expr instanceof Node\Expr\BinaryOp\BitwiseXor + // TODO: consider different Node types of float/int, also better property name (not "children") + ($expr instanceof Node\NumericLiteral && $expr->children->kind === PhpParser\TokenKind::IntegerLiteralToken) || + $expr instanceof Node\Expression\BinaryExpression && ( + ($operator = $expr->operator->kind) + && ($operator === PhpParser\TokenKind::LessThanEqualsGreaterThanToken || + $operator === PhpParser\TokenKind::AmpersandToken || + $operator === PhpParser\TokenKind::CaretToken || + $operator === PhpParser\TokenKind::BarToken) + ) ) { return new Types\Integer; } + + // FLOAT EXPRESSIONS: resolve to Types\Float + // [literal] 1.5 + // [operator] / + // [cast] (double) if ( - $expr instanceof Node\Expr\BinaryOp\Div - || $expr instanceof Node\Scalar\DNumber - || $expr instanceof Node\Expr\Cast\Double + $expr instanceof Node\NumericLiteral && $expr->children->kind === PhpParser\TokenKind::FloatingLiteralToken || + ($expr instanceof Node\Expression\CastExpression && $expr->castType->kind === PhpParser\TokenKind::DoubleCastToken) || + ($expr instanceof Node\Expression\BinaryExpression && $expr->operator->kind === PhpParser\TokenKind::SlashToken) ) { return new Types\Float_; } - if ($expr instanceof Node\Expr\Array_) { + + // ARRAY CREATION EXPRESSION: + // Resolve to Types\Array (Types\Compound of value and key types) + // [a, b, c] + // [1=>"hello", "hi"=>1, 4=>[]]s + if ($expr instanceof Node\Expression\ArrayCreationExpression) { $valueTypes = []; $keyTypes = []; - foreach ($expr->items as $item) { - $valueTypes[] = $this->resolveExpressionNodeToType($item->value); - $keyTypes[] = $item->key ? $this->resolveExpressionNodeToType($item->key) : new Types\Integer; + if ($expr->arrayElements !== null) { + foreach ($expr->arrayElements->getElements() as $item) { + $valueTypes[] = $this->resolveExpressionNodeToType($item->elementValue); + $keyTypes[] = $item->elementKey ? $this->resolveExpressionNodeToType($item->elementKey) : new Types\Integer; + } } - $valueTypes = array_unique($keyTypes); + $valueTypes = array_unique($valueTypes); $keyTypes = array_unique($keyTypes); if (empty($valueTypes)) { $valueType = null; @@ -648,54 +976,69 @@ class DefinitionResolver } return new Types\Array_($valueType, $keyType); } - if ($expr instanceof Node\Expr\ArrayDimFetch) { - $varType = $this->resolveExpressionNodeToType($expr->var); + + // SUBSCRIPT EXPRESSION + // $myArray[3] + // $myArray{"hello"} + if ($expr instanceof Node\Expression\SubscriptExpression) { + $varType = $this->resolveExpressionNodeToType($expr->postfixExpression); if (!($varType instanceof Types\Array_)) { - return new Types\Mixed; + return new Types\Mixed_; } return $varType->getValueType(); } - if ($expr instanceof Node\Expr\Include_) { + + // SCRIPT INCLUSION EXPRESSION + // include, require, include_once, require_once + if ($expr instanceof Node\Expression\ScriptInclusionExpression) { // TODO: resolve path to PhpDocument and find return statement - return new Types\Mixed; + return new Types\Mixed_; } - return new Types\Mixed; + + if ($expr instanceof Node\QualifiedName) { + return $this->resolveClassNameToType($expr); + } + + return new Types\Mixed_; } + /** * Takes any class name node (from a static method call, or new node) and returns a Type object * Resolves keywords like self, static and parent * - * @param Node $class + * @param Node|PhpParser\Token $class * @return Type */ - private static function resolveClassNameToType(Node $class): Type + public function resolveClassNameToType($class): Type { - if ($class instanceof Node\Expr) { - return new Types\Mixed; + if ($class instanceof Node\Expression) { + return new Types\Mixed_; } - if ($class instanceof Node\Stmt\Class_) { + if ($class instanceof PhpParser\Token && $class->kind === PhpParser\TokenKind::ClassKeyword) { // Anonymous class return new Types\Object_; } - $className = (string)$class; - if ($className === 'static') { + if ($class instanceof PhpParser\Token && $class->kind === PhpParser\TokenKind::StaticKeyword) { + // `new static` return new Types\Static_; } + $className = (string)$class->getResolvedName(); + if ($className === 'self' || $className === 'parent') { - $classNode = getClosestNode($class, Node\Stmt\Class_::class); + $classNode = $class->getFirstAncestor(Node\Statement\ClassDeclaration::class); if ($className === 'parent') { - if ($classNode === null || $classNode->extends === null) { + if ($classNode === null || $classNode->classBaseClause === null) { return new Types\Object_; } // parent is resolved to the parent class - $classFqn = (string)$classNode->extends; + $classFqn = (string)$classNode->classBaseClause->baseClass->getResolvedName(); } else { if ($classNode === null) { return new Types\Self_; } // self is resolved to the containing class - $classFqn = (string)$classNode->namespacedName; + $classFqn = (string)$classNode->getNamespacedName(); } return new Types\Object_(new Fqsen('\\' . $classFqn)); } @@ -710,109 +1053,163 @@ class DefinitionResolver * For classes and interfaces, this is the class type (object). * For variables / assignments, this is the documented type or type the assignment resolves to. * Can also be a compound type. - * If it is unknown, will be Types\Mixed. + * If it is unknown, will be Types\Mixed_. * Returns null if the node does not have a type. * * @param Node $node * @return \phpDocumentor\Reflection\Type|null */ - public function getTypeFromNode(Node $node) + public function getTypeFromNode($node) { - if ($node instanceof Node\Param) { + if (ParserHelpers\isConstDefineExpression($node)) { + // constants with define() like + // define('TEST_DEFINE_CONSTANT', false); + return $this->resolveExpressionNodeToType($node->argumentExpressionList->children[2]->expression); + } + + // PARAMETERS + // Get the type of the parameter: + // 1. Doc block + // 2. Parameter type and default + if ($node instanceof Node\Parameter) { // Parameters - $docBlock = $node->getAttribute('parentNode')->getAttribute('docBlock'); - if ($docBlock !== null) { - // Use @param tag - foreach ($docBlock->getTagsByName('param') as $paramTag) { - if ($paramTag->getVariableName() === $node->name) { - if ($paramTag->getType() === null) { - break; - } - return $paramTag->getType(); - } - } + // Get the doc block for the the function call + // /** + // * @param MyClass $myParam + // */ + // function foo($a) + $functionLikeDeclaration = ParserHelpers\getFunctionLikeDeclarationFromParameter($node); + $variableName = $node->getName(); + $docBlock = $this->getDocBlock($functionLikeDeclaration); + + $parameterDocBlockTag = $this->tryGetDocBlockTagForParameter($docBlock, $variableName); + if ($parameterDocBlockTag !== null && ($type = $parameterDocBlockTag->getType())) { + // Doc block comments supercede all other forms of type inference + return $type; } - $type = null; - if ($node->type !== null) { + + // function foo(MyClass $a) + if ($node->typeDeclaration !== null) { // Use PHP7 return type hint - if (is_string($node->type)) { + if ($node->typeDeclaration instanceof PhpParser\Token) { // Resolve a string like "bool" to a type object - $type = $this->typeResolver->resolve($node->type); + $type = $this->typeResolver->resolve($node->typeDeclaration->getText($node->getFileContents())); } else { - $type = new Types\Object_(new Fqsen('\\' . (string)$node->type)); + $type = new Types\Object_(new Fqsen('\\' . (string)$node->typeDeclaration->getResolvedName())); } } + // function foo($a = 3) if ($node->default !== null) { $defaultType = $this->resolveExpressionNodeToType($node->default); if (isset($type) && !is_a($type, get_class($defaultType))) { - $type = new Types\Compound([$type, $defaultType]); - } else { - $type = $defaultType; + // TODO - verify it is worth creating a compound type + return new Types\Compound([$type, $defaultType]); } + $type = $defaultType; } - return $type ?? new Types\Mixed; + return $type ?? new Types\Mixed_; } - if ($node instanceof Node\FunctionLike) { + + // FUNCTIONS AND METHODS + // Get the return type + // 1. doc block + // 2. return type hint + // 3. TODO: infer from return statements + if ($node instanceof PhpParser\FunctionLike) { // Functions/methods - $docBlock = $node->getAttribute('docBlock'); + $docBlock = $this->getDocBlock($node); if ( $docBlock !== null && !empty($returnTags = $docBlock->getTagsByName('return')) && $returnTags[0]->getType() !== null ) { // Use @return tag - return $returnTags[0]->getType(); - } - if ($node->returnType !== null) { - // Use PHP7 return type hint - if (is_string($node->returnType)) { - // Resolve a string like "bool" to a type object - return $this->typeResolver->resolve($node->returnType); + $returnType = $returnTags[0]->getType(); + if ($returnType instanceof Types\Self_) { + $selfType = $this->getContainingClassType($node); + if ($selfType) { + return $selfType; + } } - return new Types\Object_(new Fqsen('\\' . (string)$node->returnType)); + return $returnType; + } + if ($node->returnType !== null && !($node->returnType instanceof PhpParser\MissingToken)) { + // Use PHP7 return type hint + if ($node->returnType instanceof PhpParser\Token) { + // Resolve a string like "bool" to a type object + return $this->typeResolver->resolve($node->returnType->getText($node->getFileContents())); + } elseif ($node->returnType->getResolvedName() === 'self') { + $selfType = $this->getContainingClassType($node); + if ($selfType !== null) { + return $selfType; + } + } + return new Types\Object_(new Fqsen('\\' . (string)$node->returnType->getResolvedName())); } // Unknown return type - return new Types\Mixed; + return new Types\Mixed_; } - if ($node instanceof Node\Expr\Variable) { - $node = $node->getAttribute('parentNode'); - } - if ( - $node instanceof Node\Stmt\PropertyProperty - || $node instanceof Node\Const_ - || $node instanceof Node\Expr\Assign - || $node instanceof Node\Expr\AssignOp - ) { - if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) { - $docBlockHolder = $node->getAttribute('parentNode'); - } else { - $docBlockHolder = $node; + + // FOREACH KEY/VARIABLE + if ($node instanceof Node\ForeachKey || $node->parent instanceof Node\ForeachKey) { + $foreach = $node->getFirstAncestor(Node\Statement\ForeachStatement::class); + $collectionType = $this->resolveExpressionNodeToType($foreach->forEachCollectionName); + if ($collectionType instanceof Types\Array_) { + return $collectionType->getKeyType(); } + return new Types\Mixed_(); + } + + // FOREACH VALUE/VARIABLE + if ($node instanceof Node\ForeachValue + || ($node instanceof Node\Expression\Variable && $node->parent instanceof Node\ForeachValue) + ) { + $foreach = $node->getFirstAncestor(Node\Statement\ForeachStatement::class); + $collectionType = $this->resolveExpressionNodeToType($foreach->forEachCollectionName); + if ($collectionType instanceof Types\Array_) { + return $collectionType->getValueType(); + } + return new Types\Mixed_(); + } + + // PROPERTIES, CONSTS, CLASS CONSTS, ASSIGNMENT EXPRESSIONS + // Get the documented type the assignment resolves to. + if ( + ($declarationNode = + ParserHelpers\tryGetPropertyDeclaration($node) ?? + ParserHelpers\tryGetConstOrClassConstDeclaration($node) + ) !== null || + ($node = $node->parent) instanceof Node\Expression\AssignmentExpression) { + $declarationNode = $declarationNode ?? $node; + // Property, constant or variable // Use @var tag if ( - isset($docBlockHolder) - && ($docBlock = $docBlockHolder->getAttribute('docBlock')) + ($docBlock = $this->getDocBlock($declarationNode)) && !empty($varTags = $docBlock->getTagsByName('var')) && ($type = $varTags[0]->getType()) ) { return $type; } + // Resolve the expression - if ($node instanceof Node\Stmt\PropertyProperty) { - if ($node->default) { - return $this->resolveExpressionNodeToType($node->default); + if ($declarationNode instanceof Node\PropertyDeclaration) { + // TODO should have default + if (isset($node->parent->rightOperand)) { + return $this->resolveExpressionNodeToType($node->parent->rightOperand); } - } else if ($node instanceof Node\Const_) { - return $this->resolveExpressionNodeToType($node->value); - } else { - return $this->resolveExpressionNodeToType($node); + } else if ($node instanceof Node\ConstElement) { + return $this->resolveExpressionNodeToType($node->assignment); + } else if ($node instanceof Node\Expression\AssignmentExpression) { + return $this->resolveExpressionNodeToType($node->rightOperand); } // TODO: read @property tags of class // TODO: Try to infer the type from default value / constant value // Unknown - return new Types\Mixed; + return new Types\Mixed_; } + + // The node does not have a type return null; } @@ -823,58 +1220,131 @@ class DefinitionResolver * @param Node $node * @return string|null */ - public static function getDefinedFqn(Node $node) + public static function getDefinedFqn($node) { - $parent = $node->getAttribute('parentNode'); + $parent = $node->parent; // Anonymous classes don't count as a definition - if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) { - // Class, interface or trait declaration - return (string)$node->namespacedName; - } else if ($node instanceof Node\Name && $parent instanceof Node\Stmt\Namespace_) { - return (string)$node; - } else if ($node instanceof Node\Stmt\Function_) { + // INPUT OUTPUT: + // namespace A\B; + // class C { } A\B\C + // interface C { } A\B\C + // trait C { } A\B\C + if ( + $node instanceof PhpParser\ClassLike + ) { + return (string) $node->getNamespacedName(); + } + + // INPUT OUTPUT: + // namespace A\B; A\B + if ($node instanceof Node\Statement\NamespaceDefinition && $node->name instanceof Node\QualifiedName) { + $name = (string) PhpParser\ResolvedName::buildName($node->name->nameParts, $node->getFileContents()); + return $name === "" ? null : $name; + } + + // INPUT OUTPUT: + // namespace A\B; + // function a(); A\B\a(); + if ($node instanceof Node\Statement\FunctionDeclaration) { // Function: use functionName() as the name - return (string)$node->namespacedName . '()'; - } else if ($node instanceof Node\Stmt\ClassMethod) { + $name = (string)$node->getNamespacedName(); + return $name === "" ? null : $name . '()'; + } + + // INPUT OUTPUT + // namespace A\B; + // class C { + // function a () {} A\B\C->a() + // static function b() {} A\B\C::b() + // } + if ($node instanceof Node\MethodDeclaration) { // Class method: use ClassName->methodName() as name - $class = $node->getAttribute('parentNode'); + $class = $node->getFirstAncestor( + Node\Expression\ObjectCreationExpression::class, + PhpParser\ClassLike::class + ); if (!isset($class->name)) { // Ignore anonymous classes return null; } if ($node->isStatic()) { - return (string)$class->namespacedName . '::' . (string)$node->name . '()'; + return (string)$class->getNamespacedName() . '::' . $node->getName() . '()'; } else { - return (string)$class->namespacedName . '->' . (string)$node->name . '()'; - } - } else if ($node instanceof Node\Stmt\PropertyProperty) { - $property = $node->getAttribute('parentNode'); - $class = $property->getAttribute('parentNode'); - if (!isset($class->name)) { - // Ignore anonymous classes - return null; - } - if ($property->isStatic()) { - // Static Property: use ClassName::$propertyName as name - return (string)$class->namespacedName . '::$' . (string)$node->name; - } else { - // Instance Property: use ClassName->propertyName as name - return (string)$class->namespacedName . '->' . (string)$node->name; - } - } else if ($node instanceof Node\Const_) { - $parent = $node->getAttribute('parentNode'); - if ($parent instanceof Node\Stmt\Const_) { - // Basic constant: use CONSTANT_NAME as name - return (string)$node->namespacedName; - } - if ($parent instanceof Node\Stmt\ClassConst) { - // Class constant: use ClassName::CONSTANT_NAME as name - $class = $parent->getAttribute('parentNode'); - if (!isset($class->name) || $class->name instanceof Node\Expr) { - return null; - } - return (string)$class->namespacedName . '::' . $node->name; + return (string)$class->getNamespacedName() . '->' . $node->getName() . '()'; } } + + // INPUT OUTPUT + // namespace A\B; + // class C { + // static $a = 4, $b = 4 A\B\C::$a, A\B\C::$b + // $a = 4, $b = 4 A\B\C->$a, A\B\C->$b // TODO verify variable name + // } + if ( + ($propertyDeclaration = ParserHelpers\tryGetPropertyDeclaration($node)) !== null && + ($classDeclaration = + $node->getFirstAncestor( + Node\Expression\ObjectCreationExpression::class, + PhpParser\ClassLike::class + ) + ) !== null && isset($classDeclaration->name)) { + $name = $node->getName(); + if ($propertyDeclaration->isStatic()) { + // Static Property: use ClassName::$propertyName as name + return (string)$classDeclaration->getNamespacedName() . '::$' . $name; + } + + // Instance Property: use ClassName->propertyName as name + return (string)$classDeclaration->getNamespacedName() . '->' . $name; + } + + // INPUT OUTPUT + // namespace A\B; + // const FOO = 5; A\B\FOO + // class C { + // const $a, $b = 4 A\B\C::$a(), A\B\C::$b + // } + if (($constDeclaration = ParserHelpers\tryGetConstOrClassConstDeclaration($node)) !== null) { + if ($constDeclaration instanceof Node\Statement\ConstDeclaration) { + // Basic constant: use CONSTANT_NAME as name + return (string)$node->getNamespacedName(); + } + + // Class constant: use ClassName::CONSTANT_NAME as name + $classDeclaration = $constDeclaration->getFirstAncestor( + Node\Expression\ObjectCreationExpression::class, + PhpParser\ClassLike::class + ); + + if (!isset($classDeclaration->name)) { + return null; + } + return (string)$classDeclaration->getNamespacedName() . '::' . $node->getName(); + } + + if (ParserHelpers\isConstDefineExpression($node)) { + return $node->argumentExpressionList->children[0]->expression->getStringContentsText(); + } + + return null; + } + + /** + * @param DocBlock|null $docBlock + * @param string|null $variableName + * @return DocBlock\Tags\Param|null + */ + private function tryGetDocBlockTagForParameter($docBlock, $variableName) + { + if ($docBlock === null || $variableName === null) { + return null; + } + $tags = $docBlock->getTagsByName('param'); + foreach ($tags as $tag) { + if ($tag->getVariableName() === \ltrim($variableName, "$")) { + return $tag; + } + } + return null; } } diff --git a/src/FilesFinder/FileSystemFilesFinder.php b/src/FilesFinder/FileSystemFilesFinder.php index 52df4b6..a26b5d8 100644 --- a/src/FilesFinder/FileSystemFilesFinder.php +++ b/src/FilesFinder/FileSystemFilesFinder.php @@ -22,7 +22,11 @@ class FileSystemFilesFinder implements FilesFinder return coroutine(function () use ($glob) { $uris = []; foreach (new GlobIterator($glob) as $path) { - $uris[] = pathToUri($path); + // Exclude any directories that also match the glob pattern + if (!is_dir($path)) { + $uris[] = pathToUri($path); + } + yield timeout(); } return $uris; diff --git a/src/Formatter.php b/src/Formatter.php deleted file mode 100644 index 2787cb7..0000000 --- a/src/Formatter.php +++ /dev/null @@ -1,107 +0,0 @@ -standards = self::findConfiguration($path); - - // Autoload class to set up a bunch of PHP_CodeSniffer-specific token type constants - spl_autoload_call(Tokens::class); - - $file = new DummyFile($content, new Ruleset($config), $config); - $file->process(); - $fixed = $file->fixer->fixFile(); - if (!$fixed && $file->getErrorCount() > 0) { - throw new Exception('Unable to format file'); - } - - $new = $file->fixer->getContents(); - if ($content === $new) { - return []; - } - return [new TextEdit(new Range(new Position(0, 0), self::calculateEndPosition($content)), $new)]; - } - - /** - * Calculate position of last character. - * - * @param string $content document as string - * - * @return \LanguageServer\Protocol\Position - */ - private static function calculateEndPosition(string $content): Position - { - $lines = explode("\n", $content); - return new Position(count($lines) - 1, strlen(end($lines))); - } - - /** - * Search for PHP_CodeSniffer configuration file at given directory or its parents. - * If no configuration found then PSR2 standard is loaded by default. - * - * @param string $path path to file or directory - * @return string[] - */ - private static function findConfiguration(string $path) - { - if (is_dir($path)) { - $currentDir = $path; - } else { - $currentDir = dirname($path); - } - do { - $default = $currentDir . DIRECTORY_SEPARATOR . 'phpcs.xml'; - if (is_file($default)) { - return [$default]; - } - - $default = $currentDir . DIRECTORY_SEPARATOR . 'phpcs.xml.dist'; - if (is_file($default)) { - return [$default]; - } - - $lastDir = $currentDir; - $currentDir = dirname($currentDir); - } while ($currentDir !== '.' && $currentDir !== $lastDir); - - $standard = Config::getConfigData('default_standard') ?? 'PSR2'; - return explode(',', $standard); - } -} diff --git a/src/FqnUtilities.php b/src/FqnUtilities.php new file mode 100644 index 0000000..fcd6027 --- /dev/null +++ b/src/FqnUtilities.php @@ -0,0 +1,30 @@ +getFqsen(); + if ($fqsen !== null) { + $fqns[] = substr((string)$fqsen, 1); + } + } + if ($type instanceof Types\Compound) { + for ($i = 0; $t = $type->get($i); $i++) { + foreach (getFqnsFromType($t) as $fqn) { + $fqns[] = $fqn; + } + } + } + return $fqns; +} diff --git a/src/Index/Index.php b/src/Index/Index.php index 9879ced..a88c7ba 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -117,7 +117,7 @@ class Index implements ReadableIndex, \Serializable * Registers a definition * * @param string $fqn The fully qualified name of the symbol - * @param string $definition The Definition object + * @param Definition $definition The Definition object * @return void */ public function setDefinition(string $fqn, Definition $definition) @@ -150,6 +150,17 @@ class Index implements ReadableIndex, \Serializable return $this->references[$fqn] ?? []; } + /** + * For test use. + * Returns all references, keyed by fqn. + * + * @return string[][] + */ + public function getReferences(): array + { + return $this->references; + } + /** * Adds a document URI as a referencee of a specific symbol * diff --git a/src/Indexer.php b/src/Indexer.php index 4cd85d3..fd59aa3 100644 --- a/src/Indexer.php +++ b/src/Indexer.php @@ -8,16 +8,15 @@ use LanguageServer\FilesFinder\FilesFinder; use LanguageServer\Index\{DependenciesIndex, Index}; use LanguageServer\Protocol\MessageType; use Webmozart\PathUtil\Path; -use Composer\Semver\VersionParser; use Sabre\Event\Promise; use function Sabre\Event\coroutine; class Indexer { /** - * @var The prefix for every cache item + * @var int The prefix for every cache item */ - const CACHE_VERSION = 1; + const CACHE_VERSION = 2; /** * @var FilesFinder @@ -156,7 +155,7 @@ class Indexer $packageKey = null; $cacheKey = null; $index = null; - foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) { + foreach (array_merge($this->composerLock->packages, (array)$this->composerLock->{'packages-dev'}) as $package) { // Check if package name matches and version is absolute // Dynamic constraints are not cached, because they can change every time $packageVersion = ltrim($package->version, 'v'); diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 6eb68e4..193b0a1 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -8,23 +8,19 @@ use LanguageServer\Protocol\{ ClientCapabilities, TextDocumentSyncKind, Message, - MessageType, InitializeResult, - SymbolInformation, - TextDocumentIdentifier, - CompletionOptions + CompletionOptions, + SignatureHelpOptions }; use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder}; use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever}; use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex}; use LanguageServer\Cache\{FileSystemCache, ClientCache}; use AdvancedJsonRpc; -use Sabre\Event\{Loop, Promise}; +use Sabre\Event\Promise; use function Sabre\Event\coroutine; -use Exception; use Throwable; use Webmozart\PathUtil\Path; -use Sabre\Uri; class LanguageServer extends AdvancedJsonRpc\Dispatcher { @@ -111,7 +107,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher protected $definitionResolver; /** - * @param PotocolReader $reader + * @param ProtocolReader $reader * @param ProtocolWriter $writer */ public function __construct(ProtocolReader $reader, ProtocolWriter $writer) @@ -137,7 +133,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher // If a ResponseError is thrown, send it back in the Response $error = $e; } catch (Throwable $e) { - // If an unexpected error occured, send back an INTERNAL_ERROR error response + // If an unexpected error occurred, send back an INTERNAL_ERROR error response $error = new AdvancedJsonRpc\Error( (string)$e, AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR, @@ -275,8 +271,6 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher $serverCapabilities->documentSymbolProvider = true; // Support "Find all symbols in workspace" $serverCapabilities->workspaceSymbolProvider = true; - // Support "Format Code" - $serverCapabilities->documentFormattingProvider = true; // Support "Go to definition" $serverCapabilities->definitionProvider = true; // Support "Find all references" @@ -287,6 +281,10 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher $serverCapabilities->completionProvider = new CompletionOptions; $serverCapabilities->completionProvider->resolveProvider = false; $serverCapabilities->completionProvider->triggerCharacters = ['$', '>']; + + $serverCapabilities->signatureHelpProvider = new SignatureHelpOptions(); + $serverCapabilities->signatureHelpProvider->triggerCharacters = ['(', ',']; + // Support global references $serverCapabilities->xworkspaceReferencesProvider = true; $serverCapabilities->xdefinitionProvider = true; diff --git a/src/NodeVisitor/ColumnCalculator.php b/src/NodeVisitor/ColumnCalculator.php deleted file mode 100644 index 6f6b3bf..0000000 --- a/src/NodeVisitor/ColumnCalculator.php +++ /dev/null @@ -1,41 +0,0 @@ -code = $code; - $this->codeLength = strlen($code); - } - - public function enterNode(Node $node) - { - $startFilePos = $node->getAttribute('startFilePos'); - $endFilePos = $node->getAttribute('endFilePos'); - - if ($startFilePos > $this->codeLength || $endFilePos > $this->codeLength) { - throw new \RuntimeException('Invalid position information'); - } - - $startLinePos = strrpos($this->code, "\n", $startFilePos - $this->codeLength); - if ($startLinePos === false) { - $startLinePos = -1; - } - - $endLinePos = strrpos($this->code, "\n", $endFilePos - $this->codeLength); - if ($endLinePos === false) { - $endLinePos = -1; - } - - $node->setAttribute('startColumn', $startFilePos - $startLinePos); - $node->setAttribute('endColumn', $endFilePos - $endLinePos); - } -} diff --git a/src/NodeVisitor/DefinitionCollector.php b/src/NodeVisitor/DefinitionCollector.php deleted file mode 100644 index 5198139..0000000 --- a/src/NodeVisitor/DefinitionCollector.php +++ /dev/null @@ -1,47 +0,0 @@ -definitionResolver = $definitionResolver; - } - - public function enterNode(Node $node) - { - $fqn = DefinitionResolver::getDefinedFqn($node); - // Only index definitions with an FQN (no variables) - if ($fqn === null) { - return; - } - $this->nodes[$fqn] = $node; - $this->definitions[$fqn] = $this->definitionResolver->createDefinitionFromNode($node, $fqn); - } -} diff --git a/src/NodeVisitor/DocBlockParser.php b/src/NodeVisitor/DocBlockParser.php deleted file mode 100644 index 78f928e..0000000 --- a/src/NodeVisitor/DocBlockParser.php +++ /dev/null @@ -1,96 +0,0 @@ -docBlockFactory = $docBlockFactory; - } - - public function beforeTraverse(array $nodes) - { - $this->namespace = ''; - $this->prefix = ''; - $this->aliases = []; - } - - public function enterNode(Node $node) - { - if ($node instanceof Node\Stmt\Namespace_) { - $this->namespace = (string)$node->name; - } else if ($node instanceof Node\Stmt\GroupUse) { - $this->prefix = (string)$node->prefix . '\\'; - } else if ($node instanceof Node\Stmt\UseUse) { - $this->aliases[$node->alias] = $this->prefix . (string)$node->name; - } - $docComment = $node->getDocComment(); - if ($docComment === null) { - return; - } - $context = new Context($this->namespace, $this->aliases); - try { - $docBlock = $this->docBlockFactory->create($docComment->getText(), $context); - $node->setAttribute('docBlock', $docBlock); - } catch (Exception $e) { - $this->errors[] = new PhpParser\Error($e->getMessage(), [ - 'startFilePos' => $docComment->getFilePos(), - 'endFilePos' => $docComment->getFilePos() + strlen($docComment->getText()), - 'startLine' => $docComment->getLine(), - 'endLine' => $docComment->getLine() + preg_match_all('/[\\n\\r]/', $docComment->getText()) + 1 - ]); - } - } - - public function leaveNode(Node $node) - { - if ($node instanceof Node\Stmt\Namespace_) { - $this->namespace = ''; - $this->aliases = []; - } else if ($node instanceof Node\Stmt\GroupUse) { - $this->prefix = ''; - } - } -} diff --git a/src/NodeVisitor/NodeAtPositionFinder.php b/src/NodeVisitor/NodeAtPositionFinder.php deleted file mode 100644 index cb17801..0000000 --- a/src/NodeVisitor/NodeAtPositionFinder.php +++ /dev/null @@ -1,45 +0,0 @@ -position = $position; - } - - public function leaveNode(Node $node) - { - if ($this->node === null) { - $range = Range::fromNode($node); - if ($range->includes($this->position)) { - $this->node = $node; - return NodeTraverser::STOP_TRAVERSAL; - } - } - } -} diff --git a/src/NodeVisitor/ReferencesAdder.php b/src/NodeVisitor/ReferencesAdder.php deleted file mode 100644 index 9c6ac0f..0000000 --- a/src/NodeVisitor/ReferencesAdder.php +++ /dev/null @@ -1,54 +0,0 @@ -document = $document; - } - - public function enterNode(Node $node) - { - $node->setAttribute('ownerDocument', $this->document); - if (!empty($this->stack)) { - $node->setAttribute('parentNode', end($this->stack)); - } - if (isset($this->previous) && $this->previous->getAttribute('parentNode') === $node->getAttribute('parentNode')) { - $node->setAttribute('previousSibling', $this->previous); - $this->previous->setAttribute('nextSibling', $node); - } - $this->stack[] = $node; - } - - public function leaveNode(Node $node) - { - $this->previous = $node; - array_pop($this->stack); - } -} diff --git a/src/NodeVisitor/ReferencesCollector.php b/src/NodeVisitor/ReferencesCollector.php deleted file mode 100644 index e7b9f06..0000000 --- a/src/NodeVisitor/ReferencesCollector.php +++ /dev/null @@ -1,75 +0,0 @@ -definitionResolver = $definitionResolver; - } - - public function enterNode(Node $node) - { - // Check if the node references any global symbol - $fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node); - if ($fqn) { - $parent = $node->getAttribute('parentNode'); - $grandParent = $parent ? $parent->getAttribute('parentNode') : null; - $this->addReference($fqn, $node); - if ( - $node instanceof Node\Name - && $node->isQualified() - && !($parent instanceof Node\Stmt\Namespace_ && $parent->name === $node) - ) { - // Add references for each referenced namespace - $ns = $fqn; - while (($pos = strrpos($ns, '\\')) !== false) { - $ns = substr($ns, 0, $pos); - $this->addReference($ns, $node); - } - } - // Namespaced constant access and function calls also need to register a reference - // to the global version because PHP falls back to global at runtime - // http://php.net/manual/en/language.namespaces.fallback.php - if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) { - $parts = explode('\\', $fqn); - if (count($parts) > 1) { - $globalFqn = end($parts); - $this->addReference($globalFqn, $node); - } - } - } - } - - private function addReference(string $fqn, Node $node) - { - if (!isset($this->nodes[$fqn])) { - $this->nodes[$fqn] = []; - } - $this->nodes[$fqn][] = $node; - } -} diff --git a/src/NodeVisitor/VariableReferencesCollector.php b/src/NodeVisitor/VariableReferencesCollector.php deleted file mode 100644 index bb44bdc..0000000 --- a/src/NodeVisitor/VariableReferencesCollector.php +++ /dev/null @@ -1,50 +0,0 @@ -name = $name; - } - - public function enterNode(Node $node) - { - if ($node instanceof Node\Expr\Variable && $node->name === $this->name) { - $this->nodes[] = $node; - } else if ($node instanceof Node\FunctionLike) { - // If we meet a function node, dont traverse its statements, they are in another scope - // except it is a closure that has imported the variable through use - if ($node instanceof Node\Expr\Closure) { - foreach ($node->uses as $use) { - if ($use->var === $this->name) { - return; - } - } - } - return NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - } -} diff --git a/src/Parser.php b/src/Parser.php deleted file mode 100644 index d6d6d13..0000000 --- a/src/Parser.php +++ /dev/null @@ -1,25 +0,0 @@ - [ - 'comments', - 'startLine', - 'endLine', - 'startFilePos', - 'endFilePos' - ] - ]); - parent::__construct($lexer); - } -} diff --git a/src/ParserHelpers.php b/src/ParserHelpers.php new file mode 100644 index 0000000..f20fb1f --- /dev/null +++ b/src/ParserHelpers.php @@ -0,0 +1,116 @@ +parent; + return + ( + $node instanceof Node\QualifiedName && + ( + $parent instanceof Node\Expression || + $parent instanceof Node\DelimitedList\ExpressionList || + $parent instanceof Node\ArrayElement || + ($parent instanceof Node\Parameter && $node->parent->default === $node) || + $parent instanceof Node\StatementNode || + $parent instanceof Node\CaseStatementNode + ) && + !( + $parent instanceof Node\Expression\MemberAccessExpression || + $parent instanceof Node\Expression\CallExpression || + $parent instanceof Node\Expression\ObjectCreationExpression || + $parent instanceof Node\Expression\ScopedPropertyAccessExpression || + $parent instanceof PhpParser\FunctionLike || + ( + $parent instanceof Node\Expression\BinaryExpression && + $parent->operator->kind === PhpParser\TokenKind::InstanceOfKeyword + ) + )); +} + +function getFunctionLikeDeclarationFromParameter(Node\Parameter $node) +{ + return $node->parent->parent; +} + +function isBooleanExpression($expression) : bool +{ + if (!($expression instanceof Node\Expression\BinaryExpression)) { + return false; + } + switch ($expression->operator->kind) { + case PhpParser\TokenKind::InstanceOfKeyword: + case PhpParser\TokenKind::GreaterThanToken: + case PhpParser\TokenKind::GreaterThanEqualsToken: + case PhpParser\TokenKind::LessThanToken: + case PhpParser\TokenKind::LessThanEqualsToken: + case PhpParser\TokenKind::AndKeyword: + case PhpParser\TokenKind::AmpersandAmpersandToken: + case PhpParser\TokenKind::LessThanEqualsGreaterThanToken: + case PhpParser\TokenKind::OrKeyword: + case PhpParser\TokenKind::BarBarToken: + case PhpParser\TokenKind::XorKeyword: + case PhpParser\TokenKind::ExclamationEqualsEqualsToken: + case PhpParser\TokenKind::ExclamationEqualsToken: + case PhpParser\TokenKind::CaretToken: + case PhpParser\TokenKind::EqualsEqualsEqualsToken: + case PhpParser\TokenKind::EqualsToken: + return true; + } + return false; +} + + +/** + * Tries to get the parent property declaration given a Node + * @param Node $node + * @return Node\PropertyDeclaration|null $node + */ +function tryGetPropertyDeclaration(Node $node) +{ + if ($node instanceof Node\Expression\Variable && + (($propertyDeclaration = $node->parent->parent) instanceof Node\PropertyDeclaration || + ($propertyDeclaration = $propertyDeclaration->parent) instanceof Node\PropertyDeclaration) + ) { + return $propertyDeclaration; + } + return null; +} + +/** + * Tries to get the parent ConstDeclaration or ClassConstDeclaration given a Node + * @param Node $node + * @return Node\Statement\ConstDeclaration|Node\ClassConstDeclaration|null $node + */ +function tryGetConstOrClassConstDeclaration(Node $node) +{ + if ( + $node instanceof Node\ConstElement && ( + ($constDeclaration = $node->parent->parent) instanceof Node\ClassConstDeclaration || + $constDeclaration instanceof Node\Statement\ConstDeclaration ) + ) { + return $constDeclaration; + } + return null; +} + +/** + * Returns true if the node is a usage of `define`. + * e.g. define('TEST_DEFINE_CONSTANT', false); + * @param Node $node + * @return bool + */ +function isConstDefineExpression(Node $node): bool +{ + return $node instanceof Node\Expression\CallExpression + && $node->callableExpression instanceof Node\QualifiedName + && strtolower($node->callableExpression->getText()) === 'define' + && isset($node->argumentExpressionList->children[0]) + && $node->argumentExpressionList->children[0]->expression instanceof Node\StringLiteral + && isset($node->argumentExpressionList->children[2]); +} diff --git a/src/PhpDocument.php b/src/PhpDocument.php index 3a25c23..805bc08 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -3,27 +3,20 @@ declare(strict_types = 1); namespace LanguageServer; -use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, TextEdit}; -use LanguageServer\NodeVisitor\{ - NodeAtPositionFinder, - ReferencesAdder, - DocBlockParser, - DefinitionCollector, - ColumnCalculator, - ReferencesCollector -}; use LanguageServer\Index\Index; -use PhpParser\{Error, ErrorHandler, Node, NodeTraverser}; -use PhpParser\NodeVisitor\NameResolver; +use LanguageServer\Protocol\{ + Diagnostic, Position, Range +}; +use Microsoft\PhpParser; +use Microsoft\PhpParser\Node; use phpDocumentor\Reflection\DocBlockFactory; -use Sabre\Uri; class PhpDocument { /** * The PHPParser instance * - * @var Parser + * @var PhpParser\Parser */ private $parser; @@ -53,19 +46,12 @@ class PhpDocument */ private $uri; - /** - * The content of the document - * - * @var string - */ - private $content; - /** * The AST of the document * - * @var Node[] + * @var Node\SourceFileNode */ - private $stmts; + private $sourceFileNode; /** * Map from fully qualified name (FQN) to Definition @@ -77,7 +63,7 @@ class PhpDocument /** * Map from fully qualified name (FQN) to Node * - * @var Node[] + * @var Node */ private $definitionNodes; @@ -96,18 +82,18 @@ class PhpDocument private $diagnostics; /** - * @param string $uri The URI of the document - * @param string $content The content of the document - * @param Index $index The Index to register definitions and references to - * @param Parser $parser The PHPParser instance - * @param DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks + * @param string $uri The URI of the document + * @param string $content The content of the document + * @param Index $index The Index to register definitions and references to + * @param PhpParser\Parser $parser The PhpParser instance + * @param DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks * @param DefinitionResolver $definitionResolver The DefinitionResolver to resolve definitions to symbols in the workspace */ public function __construct( string $uri, string $content, Index $index, - Parser $parser, + $parser, DocBlockFactory $docBlockFactory, DefinitionResolver $definitionResolver ) { @@ -133,15 +119,13 @@ class PhpDocument /** * Updates the content on this document. * Re-parses a source file, updates symbols and reports parsing errors - * that may have occured as diagnostics. + * that may have occurred as diagnostics. * * @param string $content * @return void */ public function updateContent(string $content) { - $this->content = $content; - // Unregister old definitions if (isset($this->definitions)) { foreach ($this->definitions as $fqn => $definition) { @@ -160,77 +144,28 @@ class PhpDocument $this->definitions = null; $this->definitionNodes = null; - $errorHandler = new ErrorHandler\Collecting; - $stmts = $this->parser->parse($content, $errorHandler); + $treeAnalyzer = new TreeAnalyzer($this->parser, $content, $this->docBlockFactory, $this->definitionResolver, $this->uri); - $this->diagnostics = []; - foreach ($errorHandler->getErrors() as $error) { - $this->diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php'); + $this->diagnostics = $treeAnalyzer->getDiagnostics(); + + $this->definitions = $treeAnalyzer->getDefinitions(); + + $this->definitionNodes = $treeAnalyzer->getDefinitionNodes(); + + $this->referenceNodes = $treeAnalyzer->getReferenceNodes(); + + foreach ($this->definitions as $fqn => $definition) { + $this->index->setDefinition($fqn, $definition); } - // $stmts can be null in case of a fatal parsing error - if ($stmts) { - $traverser = new NodeTraverser; - - // Resolve aliased names to FQNs - $traverser->addVisitor(new NameResolver($errorHandler)); - - // Add parentNode, previousSibling, nextSibling attributes - $traverser->addVisitor(new ReferencesAdder($this)); - - // Add column attributes to nodes - $traverser->addVisitor(new ColumnCalculator($content)); - - // Parse docblocks and add docBlock attributes to nodes - $docBlockParser = new DocBlockParser($this->docBlockFactory); - $traverser->addVisitor($docBlockParser); - - $traverser->traverse($stmts); - - // Report errors from parsing docblocks - foreach ($docBlockParser->errors as $error) { - $this->diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::WARNING, 'php'); - } - - $traverser = new NodeTraverser; - - // Collect all definitions - $definitionCollector = new DefinitionCollector($this->definitionResolver); - $traverser->addVisitor($definitionCollector); - - // Collect all references - $referencesCollector = new ReferencesCollector($this->definitionResolver); - $traverser->addVisitor($referencesCollector); - - $traverser->traverse($stmts); - - // Register this document on the project for all the symbols defined in it - $this->definitions = $definitionCollector->definitions; - $this->definitionNodes = $definitionCollector->nodes; - foreach ($definitionCollector->definitions as $fqn => $definition) { - $this->index->setDefinition($fqn, $definition); - } - // Register this document on the project for references - $this->referenceNodes = $referencesCollector->nodes; - foreach ($referencesCollector->nodes as $fqn => $nodes) { - $this->index->addReferenceUri($fqn, $this->uri); - } - - $this->stmts = $stmts; + // Register this document on the project for references + foreach ($this->referenceNodes as $fqn => $nodes) { + // Cast the key to string. If (string)'2' is set as an array index, it will read out as (int)2. We must + // deal with incorrect code, so this is a valid scenario. + $this->index->addReferenceUri((string)$fqn, $this->uri); } - } - /** - * Returns array of TextEdit changes to format this document. - * - * @return \LanguageServer\Protocol\TextEdit[] - */ - public function getFormattedText() - { - if (empty($this->content)) { - return []; - } - return Formatter::format($this->content, $this->uri); + $this->sourceFileNode = $treeAnalyzer->getSourceFileNode(); } /** @@ -240,7 +175,7 @@ class PhpDocument */ public function getContent() { - return $this->content; + return $this->sourceFileNode->fileContents; } /** @@ -266,11 +201,11 @@ class PhpDocument /** * Returns the AST of the document * - * @return Node[] + * @return Node\SourceFileNode|null */ - public function getStmts(): array + public function getSourceFileNode() { - return $this->stmts; + return $this->sourceFileNode; } /** @@ -281,14 +216,16 @@ class PhpDocument */ public function getNodeAtPosition(Position $position) { - if ($this->stmts === null) { + if ($this->sourceFileNode === null) { return null; } - $traverser = new NodeTraverser; - $finder = new NodeAtPositionFinder($position); - $traverser->addVisitor($finder); - $traverser->traverse($this->stmts); - return $finder->node; + + $offset = $position->toOffset($this->sourceFileNode->getFileContents()); + $node = $this->sourceFileNode->getDescendantNodeAtPosition($offset); + if ($node !== null && $node->getStart() > $offset) { + return null; + } + return $node; } /** @@ -299,12 +236,10 @@ class PhpDocument */ public function getRange(Range $range) { - if ($this->content === null) { - return null; - } - $start = $range->start->toOffset($this->content); - $length = $range->end->toOffset($this->content) - $start; - return substr($this->content, $start, $length); + $content = $this->getContent(); + $start = $range->start->toOffset($content); + $length = $range->end->toOffset($content) - $start; + return substr($content, $start, $length); } /** diff --git a/src/PhpDocumentLoader.php b/src/PhpDocumentLoader.php index 728225d..57a7e9c 100644 --- a/src/PhpDocumentLoader.php +++ b/src/PhpDocumentLoader.php @@ -8,6 +8,7 @@ use LanguageServer\Index\ProjectIndex; use phpDocumentor\Reflection\DocBlockFactory; use Sabre\Event\Promise; use function Sabre\Event\coroutine; +use Microsoft\PhpParser; /** * Takes care of loading documents and managing "open" documents @@ -36,6 +37,11 @@ class PhpDocumentLoader */ private $parser; + /** + * @var PhpParser\Parser + */ + private $tolerantParser; + /** * @var DocBlockFactory */ @@ -47,9 +53,10 @@ class PhpDocumentLoader private $definitionResolver; /** - * @param ContentRetriever $contentRetriever - * @param ProjectIndex $project + * @param ContentRetriever $contentRetriever + * @param ProjectIndex $projectIndex * @param DefinitionResolver $definitionResolver + * @internal param ProjectIndex $project */ public function __construct( ContentRetriever $contentRetriever, @@ -59,7 +66,7 @@ class PhpDocumentLoader $this->contentRetriever = $contentRetriever; $this->projectIndex = $projectIndex; $this->definitionResolver = $definitionResolver; - $this->parser = new Parser; + $this->parser = new PhpParser\Parser(); $this->docBlockFactory = DocBlockFactory::createInstance(); } diff --git a/src/Protocol/CompletionContext.php b/src/Protocol/CompletionContext.php new file mode 100644 index 0000000..f538867 --- /dev/null +++ b/src/Protocol/CompletionContext.php @@ -0,0 +1,30 @@ +triggerKind = $triggerKind; + $this->triggerCharacter = $triggerCharacter; + } +} diff --git a/src/Protocol/CompletionTriggerKind.php b/src/Protocol/CompletionTriggerKind.php new file mode 100644 index 0000000..d36129d --- /dev/null +++ b/src/Protocol/CompletionTriggerKind.php @@ -0,0 +1,16 @@ +getRawMessage(), // Do not include "on line ..." in the error message - Range::fromError($error, $content), - $error->getCode(), - $severity, - $source - ); - } - /** * @param string $message The diagnostic's message * @param Range $range The range at which the message applies diff --git a/src/Protocol/DocumentHighlight.php b/src/Protocol/DocumentHighlight.php index e00c842..167d45a 100644 --- a/src/Protocol/DocumentHighlight.php +++ b/src/Protocol/DocumentHighlight.php @@ -7,7 +7,7 @@ namespace LanguageServer\Protocol; * special attention. Usually a document highlight is visualized by changing * the background color of its range. */ -class DocumentHighlightKind +class DocumentHighlight { /** * The range this highlight applies to. diff --git a/src/Protocol/Location.php b/src/Protocol/Location.php index a1d9861..50fedfa 100644 --- a/src/Protocol/Location.php +++ b/src/Protocol/Location.php @@ -2,7 +2,8 @@ namespace LanguageServer\Protocol; -use PhpParser\Node; +use Microsoft\PhpParser; +use Microsoft\PhpParser\Node; /** * Represents a location inside a resource, such as a line inside a text file. @@ -25,9 +26,13 @@ class Location * @param Node $node * @return self */ - public static function fromNode(Node $node) + public static function fromNode($node) { - return new self($node->getAttribute('ownerDocument')->getUri(), Range::fromNode($node)); + $range = PhpParser\PositionUtilities::getRangeFromPosition($node->getStart(), $node->getWidth(), $node->getFileContents()); + return new self($node->getUri(), new Range( + new Position($range->start->line, $range->start->character), + new Position($range->end->line, $range->end->character) + )); } public function __construct(string $uri = null, Range $range = null) diff --git a/src/Protocol/PackageDescriptor.php b/src/Protocol/PackageDescriptor.php new file mode 100644 index 0000000..e635740 --- /dev/null +++ b/src/Protocol/PackageDescriptor.php @@ -0,0 +1,25 @@ +name = $name; + } +} diff --git a/src/Protocol/ParameterInformation.php b/src/Protocol/ParameterInformation.php index 89b5e53..fa9b7bf 100644 --- a/src/Protocol/ParameterInformation.php +++ b/src/Protocol/ParameterInformation.php @@ -23,4 +23,17 @@ class ParameterInformation * @var string|null */ public $documentation; + + /** + * Create ParameterInformation + * + * @param string $label The label of this signature. Will be shown in the UI. + * @param string $documentation The human-readable doc-comment of this signature. Will be shown in the UI but can + * be omitted. + */ + public function __construct(string $label, string $documentation = null) + { + $this->label = $label; + $this->documentation = $documentation; + } } diff --git a/src/Protocol/Range.php b/src/Protocol/Range.php index e4fe527..1e19a5a 100644 --- a/src/Protocol/Range.php +++ b/src/Protocol/Range.php @@ -2,7 +2,8 @@ namespace LanguageServer\Protocol; -use PhpParser\{Error, Node}; +use Microsoft\PhpParser; +use Microsoft\PhpParser\Node; /** * A range in a text document expressed as (zero-based) start and end positions. @@ -31,26 +32,12 @@ class Range */ public static function fromNode(Node $node) { - return new self( - new Position($node->getAttribute('startLine') - 1, $node->getAttribute('startColumn') - 1), - new Position($node->getAttribute('endLine') - 1, $node->getAttribute('endColumn')) - ); - } + $range = PhpParser\PositionUtilities::getRangeFromPosition($node->getStart(), $node->getWidth(), $node->getFileContents()); - /** - * Returns the range where an error occured - * - * @param \PhpParser\Error $error - * @param string $content - * @return self - */ - public static function fromError(Error $error, string $content) - { - $startLine = max($error->getStartLine() - 1, 0); - $endLine = max($error->getEndLine() - 1, $startLine); - $startColumn = $error->hasColumnInfo() ? $error->getStartColumn($content) - 1 : 0; - $endColumn = $error->hasColumnInfo() ? $error->getEndColumn($content) : 0; - return new self(new Position($startLine, $startColumn), new Position($endLine, $endColumn)); + return new self( + new Position($range->start->line, $range->start->character), + new Position($range->end->line, $range->end->character) + ); } public function __construct(Position $start = null, Position $end = null) diff --git a/src/Protocol/SignatureHelp.php b/src/Protocol/SignatureHelp.php index 407b25a..f7aec6f 100644 --- a/src/Protocol/SignatureHelp.php +++ b/src/Protocol/SignatureHelp.php @@ -29,4 +29,18 @@ class SignatureHelp * @var int|null */ public $activeParameter; + + /** + * Create a SignatureHelp + * + * @param SignatureInformation[] $signatures List of signature information + * @param int|null $activeSignature The active signature, zero based + * @param int|null $activeParameter The active parameter, zero based + */ + public function __construct(array $signatures = [], $activeSignature = null, int $activeParameter = null) + { + $this->signatures = $signatures; + $this->activeSignature = $activeSignature; + $this->activeParameter = $activeParameter; + } } diff --git a/src/Protocol/SignatureInformation.php b/src/Protocol/SignatureInformation.php index 77e152c..d545f65 100644 --- a/src/Protocol/SignatureInformation.php +++ b/src/Protocol/SignatureInformation.php @@ -31,4 +31,19 @@ class SignatureInformation * @var ParameterInformation[]|null */ public $parameters; + + /** + * Create a SignatureInformation + * + * @param string $label The label of this signature. Will be shown in the UI. + * @param ParameterInformation[]|null The parameters of this signature + * @param string|null The human-readable doc-comment of this signature. Will be shown in the UI + * but can be omitted. + */ + public function __construct(string $label, array $parameters = null, string $documentation = null) + { + $this->label = $label; + $this->parameters = $parameters; + $this->documentation = $documentation; + } } diff --git a/src/Protocol/SymbolDescriptor.php b/src/Protocol/SymbolDescriptor.php index fa74bcb..4116864 100644 --- a/src/Protocol/SymbolDescriptor.php +++ b/src/Protocol/SymbolDescriptor.php @@ -3,7 +3,10 @@ declare(strict_types = 1); namespace LanguageServer\Protocol; -class SymbolDescriptor extends SymbolInformation +/** + * Uniquely identifies a symbol + */ +class SymbolDescriptor { /** * The fully qualified structural element name, a globally unique identifier for the symbol. @@ -13,19 +16,17 @@ class SymbolDescriptor extends SymbolInformation public $fqsen; /** - * A package from the composer.lock file or the contents of the composer.json - * Example: https://github.com/composer/composer/blob/master/composer.lock#L10 - * Available fields may differ + * Identifies the Composer package the symbol is defined in (if any) * - * @var object|null + * @var PackageDescriptor|null */ public $package; /** - * @param string $fqsen The fully qualified structural element name, a globally unique identifier for the symbol. - * @param object $package A package from the composer.lock file or the contents of the composer.json + * @param string $fqsen The fully qualified structural element name, a globally unique identifier for the symbol. + * @param PackageDescriptor $package Identifies the Composer package the symbol is defined in */ - public function __construct(string $fqsen = null, $package = null) + public function __construct(string $fqsen = null, PackageDescriptor $package = null) { $this->fqsen = $fqsen; $this->package = $package; diff --git a/src/Protocol/SymbolInformation.php b/src/Protocol/SymbolInformation.php index 5d442f0..499e417 100644 --- a/src/Protocol/SymbolInformation.php +++ b/src/Protocol/SymbolInformation.php @@ -2,8 +2,8 @@ namespace LanguageServer\Protocol; -use PhpParser\Node; -use Exception; +use Microsoft\PhpParser; +use Microsoft\PhpParser\Node; /** * Represents information about programming constructs like variables, classes, @@ -44,51 +44,70 @@ class SymbolInformation * * @param Node $node * @param string $fqn If given, $containerName will be extracted from it - * @return self|null + * @return SymbolInformation|null */ - public static function fromNode(Node $node, string $fqn = null) + public static function fromNode($node, string $fqn = null) { - $parent = $node->getAttribute('parentNode'); $symbol = new self; - if ($node instanceof Node\Stmt\Class_) { + if ($node instanceof Node\Statement\ClassDeclaration) { $symbol->kind = SymbolKind::CLASS_; - } else if ($node instanceof Node\Stmt\Trait_) { + } else if ($node instanceof Node\Statement\TraitDeclaration) { $symbol->kind = SymbolKind::CLASS_; - } else if ($node instanceof Node\Stmt\Interface_) { + } else if (\LanguageServer\ParserHelpers\isConstDefineExpression($node)) { + // constants with define() like + // define('TEST_DEFINE_CONSTANT', false); + $symbol->kind = SymbolKind::CONSTANT; + $symbol->name = $node->argumentExpressionList->children[0]->expression->getStringContentsText(); + } else if ($node instanceof Node\Statement\InterfaceDeclaration) { $symbol->kind = SymbolKind::INTERFACE; - } else if ($node instanceof Node\Name && $parent instanceof Node\Stmt\Namespace_) { + } else if ($node instanceof Node\Statement\NamespaceDefinition) { $symbol->kind = SymbolKind::NAMESPACE; - } else if ($node instanceof Node\Stmt\Function_) { + } else if ($node instanceof Node\Statement\FunctionDeclaration) { $symbol->kind = SymbolKind::FUNCTION; - } else if ($node instanceof Node\Stmt\ClassMethod) { - $symbol->kind = SymbolKind::METHOD; - } else if ($node instanceof Node\Stmt\PropertyProperty) { + } else if ($node instanceof Node\MethodDeclaration) { + $nameText = $node->getName(); + if ($nameText === '__construct' || $nameText === '__destruct') { + $symbol->kind = SymbolKind::CONSTRUCTOR; + } else { + $symbol->kind = SymbolKind::METHOD; + } + } else if ($node instanceof Node\Expression\Variable && $node->getFirstAncestor(Node\PropertyDeclaration::class) !== null) { $symbol->kind = SymbolKind::PROPERTY; - } else if ($node instanceof Node\Const_) { + } else if ($node instanceof Node\ConstElement) { $symbol->kind = SymbolKind::CONSTANT; } else if ( ( - ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignOp) - && $node->var instanceof Node\Expr\Variable + ($node instanceof Node\Expression\AssignmentExpression) + && $node->leftOperand instanceof Node\Expression\Variable ) - || $node instanceof Node\Expr\ClosureUse - || $node instanceof Node\Param + || $node instanceof Node\UseVariableName + || $node instanceof Node\Parameter ) { $symbol->kind = SymbolKind::VARIABLE; } else { return null; } - if ($node instanceof Node\Name) { - $symbol->name = (string)$node; - } else if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignOp) { - $symbol->name = $node->var->name; - } else if ($node instanceof Node\Expr\ClosureUse) { - $symbol->name = $node->var; + + if ($node instanceof Node\Expression\AssignmentExpression) { + if ($node->leftOperand instanceof Node\Expression\Variable) { + $symbol->name = $node->leftOperand->getName(); + } elseif ($node->leftOperand instanceof PhpParser\Token) { + $symbol->name = trim($node->leftOperand->getText($node->getFileContents()), "$"); + } + } else if ($node instanceof Node\UseVariableName) { + $symbol->name = $node->getName(); } else if (isset($node->name)) { - $symbol->name = (string)$node->name; - } else { + if ($node->name instanceof Node\QualifiedName) { + $symbol->name = (string)PhpParser\ResolvedName::buildName($node->name->nameParts, $node->getFileContents()); + } else { + $symbol->name = ltrim((string)$node->name->getText($node->getFileContents()), "$"); + } + } else if (isset($node->variableName)) { + $symbol->name = $node->variableName->getText($node); + } else if (!isset($symbol->name)) { return null; } + $symbol->location = Location::fromNode($node); if ($fqn !== null) { $parts = preg_split('/(::|->|\\\\)/', $fqn); diff --git a/src/ProtocolStreamWriter.php b/src/ProtocolStreamWriter.php index 3f51e14..f004a5f 100644 --- a/src/ProtocolStreamWriter.php +++ b/src/ProtocolStreamWriter.php @@ -8,7 +8,6 @@ use Sabre\Event\{ Loop, Promise }; -use RuntimeException; class ProtocolStreamWriter implements ProtocolWriter { diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index aa76ec2..e5657b3 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -3,34 +3,33 @@ declare(strict_types = 1); namespace LanguageServer\Server; -use PhpParser\PrettyPrinter\Standard as PrettyPrinter; -use PhpParser\{Node, NodeTraverser}; -use LanguageServer\{LanguageClient, PhpDocumentLoader, PhpDocument, DefinitionResolver, CompletionProvider}; -use LanguageServer\NodeVisitor\VariableReferencesCollector; -use LanguageServer\Protocol\{ - SymbolLocationInformation, - SymbolDescriptor, - TextDocumentItem, - TextDocumentIdentifier, - VersionedTextDocumentIdentifier, - Position, - Range, - FormattingOptions, - TextEdit, - Location, - SymbolInformation, - ReferenceContext, - Hover, - MarkedString, - SymbolKind, - CompletionItem, - CompletionItemKind +use LanguageServer\{ + CompletionProvider, SignatureHelpProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver }; use LanguageServer\Index\ReadableIndex; +use LanguageServer\Protocol\{ + FormattingOptions, + Hover, + Location, + MarkedString, + Position, + Range, + ReferenceContext, + SymbolDescriptor, + PackageDescriptor, + SymbolLocationInformation, + TextDocumentIdentifier, + TextDocumentItem, + VersionedTextDocumentIdentifier, + CompletionContext +}; +use Microsoft\PhpParser\Node; use Sabre\Event\Promise; use Sabre\Uri; +use function LanguageServer\{ + isVendored, waitForEvent, getPackageName +}; use function Sabre\Event\coroutine; -use function LanguageServer\{waitForEvent, isVendored}; /** * Provides method handlers for all textDocument/* methods @@ -49,11 +48,6 @@ class TextDocument */ protected $project; - /** - * @var PrettyPrinter - */ - protected $prettyPrinter; - /** * @var DefinitionResolver */ @@ -64,6 +58,11 @@ class TextDocument */ protected $completionProvider; + /** + * @var SignatureHelpProvider + */ + protected $signatureHelpProvider; + /** * @var ReadableIndex */ @@ -80,12 +79,12 @@ class TextDocument protected $composerLock; /** - * @param PhpDocumentLoader $documentLoader + * @param PhpDocumentLoader $documentLoader * @param DefinitionResolver $definitionResolver - * @param LanguageClient $client - * @param ReadableIndex $index - * @param \stdClass $composerJson - * @param \stdClass $composerLock + * @param LanguageClient $client + * @param ReadableIndex $index + * @param \stdClass $composerJson + * @param \stdClass $composerLock */ public function __construct( PhpDocumentLoader $documentLoader, @@ -97,9 +96,9 @@ class TextDocument ) { $this->documentLoader = $documentLoader; $this->client = $client; - $this->prettyPrinter = new PrettyPrinter(); $this->definitionResolver = $definitionResolver; $this->completionProvider = new CompletionProvider($this->definitionResolver, $index); + $this->signatureHelpProvider = new SignatureHelpProvider($this->definitionResolver, $index, $documentLoader); $this->index = $index; $this->composerJson = $composerJson; $this->composerLock = $composerLock; @@ -158,7 +157,7 @@ class TextDocument * The document's truth now exists where the document's uri points to (e.g. if the document's uri is a file uri the * truth now exists on disk). * - * @param \LanguageServer\Protocol\TextDocumentItem $textDocument The document that was closed + * @param \LanguageServer\Protocol\TextDocumentIdentifier $textDocument The document that was closed * @return void */ public function didClose(TextDocumentIdentifier $textDocument) @@ -166,20 +165,6 @@ class TextDocument $this->documentLoader->close($textDocument->uri); } - /** - * The document formatting request is sent from the server to the client to format a whole document. - * - * @param TextDocumentIdentifier $textDocument The document to format - * @param FormattingOptions $options The format options - * @return Promise - */ - public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options) - { - return $this->documentLoader->getOrLoad($textDocument->uri)->then(function (PhpDocument $document) { - return $document->getFormattedText(); - }); - } - /** * The references request is sent from the client to the server to resolve project-wide references for the symbol * denoted by the given text document position. @@ -202,31 +187,34 @@ class TextDocument // Variables always stay in the boundary of the file and need to be searched inside their function scope // by traversing the AST if ( - $node instanceof Node\Expr\Variable - || $node instanceof Node\Param - || $node instanceof Node\Expr\ClosureUse + + ($node instanceof Node\Expression\Variable && !($node->getParent()->getParent() instanceof Node\PropertyDeclaration)) + || $node instanceof Node\Parameter + || $node instanceof Node\UseVariableName ) { - if ($node->name instanceof Node\Expr) { + if (isset($node->name) && $node->name instanceof Node\Expression) { return null; } // Find function/method/closure scope $n = $node; - while (isset($n) && !($n instanceof Node\FunctionLike)) { - $n = $n->getAttribute('parentNode'); + + $n = $n->getFirstAncestor(Node\Statement\FunctionDeclaration::class, Node\MethodDeclaration::class, Node\Expression\AnonymousFunctionCreationExpression::class, Node\SourceFileNode::class); + + if ($n === null) { + $n = $node->getFirstAncestor(Node\Statement\ExpressionStatement::class)->getParent(); } - if (!isset($n)) { - $n = $node->getAttribute('ownerDocument'); - } - $traverser = new NodeTraverser; - $refCollector = new VariableReferencesCollector($node->name); - $traverser->addVisitor($refCollector); - $traverser->traverse($n->getStmts()); - foreach ($refCollector->nodes as $ref) { - $locations[] = Location::fromNode($ref); + + foreach ($n->getDescendantNodes() as $descendantNode) { + if ($descendantNode instanceof Node\Expression\Variable && + $descendantNode->getName() === $node->getName() + ) { + $locations[] = Location::fromNode($descendantNode); + } } } else { // Definition with a global FQN $fqn = DefinitionResolver::getDefinedFqn($node); + // Wait until indexing finished if (!$this->index->isComplete()) { yield waitForEvent($this->index, 'complete'); @@ -254,6 +242,23 @@ class TextDocument }); } + /** + * The signature help request is sent from the client to the server to request signature information at a given + * cursor position. + * + * @param TextDocumentIdentifier $textDocument The text document + * @param Position $position The position inside the text document + * + * @return Promise + */ + public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): Promise + { + return coroutine(function () use ($textDocument, $position) { + $document = yield $this->documentLoader->getOrLoad($textDocument->uri); + return $this->signatureHelpProvider->getSignatureHelp($document, $position); + }); + } + /** * The goto definition request is sent from the client to the server to resolve the definition location of a symbol * at a given text document position. @@ -331,6 +336,7 @@ class TextDocument if ($def === null) { return new Hover([], $range); } + $contents = []; if ($def->declarationLine) { $contents[] = new MarkedString('php', "declarationLine); } @@ -353,13 +359,14 @@ class TextDocument * * @param TextDocumentIdentifier The text document * @param Position $position The position + * @param CompletionContext|null $context The completion context * @return Promise */ - public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise + public function completion(TextDocumentIdentifier $textDocument, Position $position, CompletionContext $context = null): Promise { - return coroutine(function () use ($textDocument, $position) { + return coroutine(function () use ($textDocument, $position, $context) { $document = yield $this->documentLoader->getOrLoad($textDocument->uri); - return $this->completionProvider->provideCompletion($document, $position); + return $this->completionProvider->provideCompletion($document, $position, $context); }); } @@ -384,9 +391,10 @@ class TextDocument return []; } // Handle definition nodes + $fqn = DefinitionResolver::getDefinedFqn($node); while (true) { if ($fqn) { - $def = $this->index->getDefinition($definedFqn); + $def = $this->index->getDefinition($fqn); } else { // Handle reference nodes $def = $this->definitionResolver->resolveReferenceNodeToDefinition($node); @@ -404,25 +412,14 @@ class TextDocument ) { return []; } - $symbol = new SymbolDescriptor; - foreach (get_object_vars($def->symbolInformation) as $prop => $val) { - $symbol->$prop = $val; - } - $symbol->fqsen = $def->fqn; + // if Definition is inside a dependency, use the package name $packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson); - if ($packageName && $this->composerLock !== null) { - // Definition is inside a dependency - foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) { - if ($package->name === $packageName) { - $symbol->package = $package; - break; - } - } - } else if ($this->composerJson !== null) { - // Definition belongs to a root package - $symbol->package = $this->composerJson; + // else use the package name of the root package (if exists) + if (!$packageName && $this->composerJson !== null) { + $packageName = $this->composerJson->name; } - return [new SymbolLocationInformation($symbol, $symbol->location)]; + $descriptor = new SymbolDescriptor($def->fqn, new PackageDescriptor($packageName)); + return [new SymbolLocationInformation($descriptor, $def->symbolInformation->location)]; }); } } diff --git a/src/Server/Workspace.php b/src/Server/Workspace.php index 954e5e9..548197b 100644 --- a/src/Server/Workspace.php +++ b/src/Server/Workspace.php @@ -3,7 +3,7 @@ declare(strict_types = 1); namespace LanguageServer\Server; -use LanguageServer\{LanguageClient, Project, PhpDocumentLoader, Options, Indexer}; +use LanguageServer\{LanguageClient, PhpDocumentLoader}; use LanguageServer\Index\{ProjectIndex, DependenciesIndex, Index}; use LanguageServer\Protocol\{ FileChangeType, @@ -16,7 +16,7 @@ use LanguageServer\Protocol\{ }; use Sabre\Event\Promise; use function Sabre\Event\coroutine; -use function LanguageServer\{waitForEvent, getPackageName}; +use function LanguageServer\waitForEvent; /** * Provides method handlers for all workspace/* methods @@ -33,7 +33,7 @@ class Workspace * * @var ProjectIndex */ - private $index; + private $projectIndex; /** * @var DependenciesIndex @@ -45,16 +45,6 @@ class Workspace */ private $sourceIndex; - /** - * @var Options - */ - private $options; - - /** - * @var Indexer - */ - private $indexer; - /** * @var \stdClass */ @@ -67,25 +57,21 @@ class Workspace /** * @param LanguageClient $client LanguageClient instance used to signal updated results - * @param ProjectIndex $index Index that is searched on a workspace/symbol request + * @param ProjectIndex $projectIndex Index that is used to wait for full index completeness * @param DependenciesIndex $dependenciesIndex Index that is used on a workspace/xreferences request * @param DependenciesIndex $sourceIndex Index that is used on a workspace/xreferences request * @param \stdClass $composerLock The parsed composer.lock of the project, if any * @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents - * @param Indexer $indexer - * @param Options $options */ - public function __construct(LanguageClient $client, ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null, Indexer $indexer = null, Options $options = null) + public function __construct(LanguageClient $client, ProjectIndex $projectIndex, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null) { $this->client = $client; $this->sourceIndex = $sourceIndex; - $this->index = $index; + $this->projectIndex = $projectIndex; $this->dependenciesIndex = $dependenciesIndex; $this->composerLock = $composerLock; $this->documentLoader = $documentLoader; $this->composerJson = $composerJson; - $this->indexer = $indexer; - $this->options = $options; } /** @@ -98,11 +84,11 @@ class Workspace { return coroutine(function () use ($query) { // Wait until indexing for definitions finished - if (!$this->index->isStaticComplete()) { - yield waitForEvent($this->index, 'static-complete'); + if (!$this->sourceIndex->isStaticComplete()) { + yield waitForEvent($this->sourceIndex, 'static-complete'); } $symbols = []; - foreach ($this->index->getDefinitions() as $fqn => $definition) { + foreach ($this->sourceIndex->getDefinitions() as $fqn => $definition) { if ($query === '' || stripos($fqn, $query) !== false) { $symbols[] = $definition->symbolInformation; } @@ -135,13 +121,14 @@ class Workspace */ public function xreferences($query, array $files = null): Promise { + // TODO: $files is unused in the coroutine return coroutine(function () use ($query, $files) { if ($this->composerLock === null) { return []; } // Wait until indexing finished - if (!$this->index->isComplete()) { - yield waitForEvent($this->index, 'complete'); + if (!$this->projectIndex->isComplete()) { + yield waitForEvent($this->projectIndex, 'complete'); } /** Map from URI to array of referenced FQNs in dependencies */ $refs = []; @@ -160,38 +147,11 @@ class Workspace $refInfos = []; foreach ($refs as $uri => $fqns) { foreach ($fqns as $fqn) { - $def = $this->dependenciesIndex->getDefinition($fqn); - $symbol = new SymbolDescriptor; - $symbol->fqsen = $fqn; - foreach (get_object_vars($def->symbolInformation) as $prop => $val) { - $symbol->$prop = $val; - } - // Find out package name - $packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson); - foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) { - if ($package->name === $packageName) { - $symbol->package = $package; - break; - } - } - // If there was no FQSEN provided, check if query attributes match - if (!isset($query->fqsen)) { - $matches = true; - foreach (get_object_vars($query) as $prop => $val) { - if ($query->$prop != $symbol->$prop) { - $matches = false; - break; - } - } - if (!$matches) { - continue; - } - } $doc = yield $this->documentLoader->getOrLoad($uri); foreach ($doc->getReferenceNodesByFqn($fqn) as $node) { $refInfo = new ReferenceInformation; $refInfo->reference = Location::fromNode($node); - $refInfo->symbol = $symbol; + $refInfo->symbol = $query; $refInfos[] = $refInfo; } } @@ -209,80 +169,9 @@ class Workspace return []; } $dependencyReferences = []; - foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) { + foreach (array_merge($this->composerLock->packages, (array)$this->composerLock->{'packages-dev'}) as $package) { $dependencyReferences[] = new DependencyReference($package); } return $dependencyReferences; } - - /** - * A notification sent from the client to the server to signal the change of configuration settings. - * - * The default paramter type is Options but it also accepts different types - * which will be transformed on demand. - * - * Currently only the vscode format is supported - * - * @param mixed|null $settings - * @return bool - * @throws \Exception Settings format not valid - */ - public function didChangeConfiguration($settings = null): bool - { - // List of options that affect the indexer - $indexerOptions = ['fileTypes']; - - if ($settings === null) { - return false; - } - - // VSC sends the settings with the config section as main key - if ($settings instanceof \stdClass && $settings->phpIntelliSense) { - $mapper = new \JsonMapper(); - $settings = $mapper->map($settings->phpIntelliSense, new Options); - } - - if (!($settings instanceof Options)) { - throw new \Exception('Settings format not valid.'); - } - - $changedOptions = $this->getChangedOptions($settings); - - if (empty($changedOptions)) { - return false; - } - - foreach (get_object_vars($settings) as $prop => $val) { - $this->options->$prop = $val; - } - - if ($this->indexer && !empty(array_intersect($changedOptions, $indexerOptions))) { - // check list of options that changed since last time against the list of valid indexer options - - // wipe main index and start reindexing - $this->index->wipe(); - $this->indexer->index()->otherwise('\\LanguageServer\\crash'); - } - - return true; - } - - /** - * Get a list with all options that changed since last time - * - * @param Options $settings - * @return array List with changed options - */ - private function getChangedOptions(Options $settings): array - { - $old = get_object_vars($this->options); - $new = get_object_vars($settings); - $changed = array_udiff($old, $new, function ($a, $b) { - // custom callback since array_diff uses strings for comparison - - return $a <=> $b; - }); - - return array_keys($changed); - } } diff --git a/src/SignatureHelpProvider.php b/src/SignatureHelpProvider.php new file mode 100644 index 0000000..439be9c --- /dev/null +++ b/src/SignatureHelpProvider.php @@ -0,0 +1,187 @@ +definitionResolver = $definitionResolver; + $this->index = $index; + $this->documentLoader = $documentLoader; + } + + /** + * Finds signature help for a callable position + * + * @param PhpDocument $doc The document the position belongs to + * @param Position $position The position to detect a call from + * + * @return Promise + */ + public function getSignatureHelp(PhpDocument $doc, Position $position): Promise + { + return coroutine(function () use ($doc, $position) { + // Find the node under the cursor + $node = $doc->getNodeAtPosition($position); + + // Find the definition of the item being called + list($def, $argumentExpressionList) = yield $this->getCallingInfo($node); + + if (!$def || !$def->signatureInformation) { + return new SignatureHelp(); + } + + // Find the active parameter + $activeParam = $argumentExpressionList + ? $this->findActiveParameter($argumentExpressionList, $position, $doc) + : 0; + + return new SignatureHelp([$def->signatureInformation], 0, $activeParam); + }); + } + + /** + * Given a node that could be a callable, finds the definition of the call and the argument expression list of + * the node + * + * @param Node $node The node to find calling information from + * + * @return Promise + */ + private function getCallingInfo(Node $node) + { + return coroutine(function () use ($node) { + $fqn = null; + $callingNode = null; + if ($node instanceof Node\DelimitedList\ArgumentExpressionList) { + // Cursor is already inside a ( + $argumentExpressionList = $node; + if ($node->parent instanceof Node\Expression\ObjectCreationExpression) { + // Constructing something + $callingNode = $node->parent->classTypeDesignator; + if (!$callingNode instanceof Node\QualifiedName) { + // We only support constructing from a QualifiedName + return null; + } + $fqn = $this->definitionResolver->resolveReferenceNodeToFqn($callingNode); + $fqn = "{$fqn}->__construct()"; + } else { + $callingNode = $node->parent->getFirstChildNode( + Node\Expression\MemberAccessExpression::class, + Node\Expression\ScopedPropertyAccessExpression::class, + Node\QualifiedName::class + ); + } + } elseif ($node instanceof Node\Expression\CallExpression) { + $argumentExpressionList = $node->getFirstChildNode(Node\DelimitedList\ArgumentExpressionList::class); + $callingNode = $node->getFirstChildNode( + Node\Expression\MemberAccessExpression::class, + Node\Expression\ScopedPropertyAccessExpression::class, + Node\QualifiedName::class + ); + } elseif ($node instanceof Node\Expression\ObjectCreationExpression) { + $argumentExpressionList = $node->getFirstChildNode(Node\DelimitedList\ArgumentExpressionList::class); + $callingNode = $node->classTypeDesignator; + if (!$callingNode instanceof Node\QualifiedName) { + // We only support constructing from a QualifiedName + return null; + } + // Manually build the __construct fqn + $fqn = $this->definitionResolver->resolveReferenceNodeToFqn($callingNode); + $fqn = "{$fqn}->__construct()"; + } + + if (!$callingNode) { + return null; + } + + // Now find the definition of the call + $fqn = $fqn ?: DefinitionResolver::getDefinedFqn($callingNode); + while (true) { + if ($fqn) { + $def = $this->index->getDefinition($fqn); + } else { + $def = $this->definitionResolver->resolveReferenceNodeToDefinition($callingNode); + } + if ($def !== null || $this->index->isComplete()) { + break; + } + yield waitForEvent($this->index, 'definition-added'); + } + + if (!$def) { + return null; + } + + return [$def, $argumentExpressionList]; + }); + } + + /** + * Given a position and arguments, finds the "active" argument at the position + * + * @param Node\DelimitedList\ArgumentExpressionList $argumentExpressionList The argument expression list + * @param Position $position The position to detect the active argument from + * @param PhpDocument $doc The document that contains the expression + * + * @return int + */ + private function findActiveParameter( + Node\DelimitedList\ArgumentExpressionList $argumentExpressionList, + Position $position, + PhpDocument $doc + ): int { + $args = $argumentExpressionList->children; + $i = 0; + $found = null; + foreach ($args as $arg) { + if ($arg instanceof Node) { + $start = $arg->getFullStart(); + $end = $arg->getEndPosition(); + } else { + $start = $arg->fullStart; + $end = $start + $arg->length; + } + $offset = $position->toOffset($doc->getContent()); + if ($offset >= $start && $offset <= $end) { + $found = $i; + break; + } + if ($arg instanceof Node) { + ++$i; + } + } + if ($found === null) { + $found = $i; + } + return $found; + } +} diff --git a/src/SignatureInformationFactory.php b/src/SignatureInformationFactory.php new file mode 100644 index 0000000..6b8a1f0 --- /dev/null +++ b/src/SignatureInformationFactory.php @@ -0,0 +1,91 @@ +definitionResolver = $definitionResolver; + } + + /** + * Create a SignatureInformation from a FunctionLike node + * + * @param FunctionLike $node Node to create signature information from + * + * @return SignatureInformation + */ + public function create(FunctionLike $node): SignatureInformation + { + $params = $this->createParameters($node); + $label = $this->createLabel($params); + return new SignatureInformation( + $label, + $params, + $this->definitionResolver->getDocumentationFromNode($node) + ); + } + + /** + * Gets parameters from a FunctionLike node + * + * @param FunctionLike $node Node to get parameters from + * + * @return ParameterInformation[] + */ + private function createParameters(FunctionLike $node): array + { + $params = []; + if ($node->parameters) { + foreach ($node->parameters->getElements() as $element) { + $param = (string) $this->definitionResolver->getTypeFromNode($element); + $param .= ' '; + if ($element->dotDotDotToken) { + $param .= '...'; + } + $param .= '$' . $element->getName(); + if ($element->default) { + $param .= ' = ' . $element->default->getText(); + } + $params[] = new ParameterInformation( + $param, + $this->definitionResolver->getDocumentationFromNode($element) + ); + } + } + return $params; + } + + /** + * Creates a signature information label from parameters + * + * @param ParameterInformation[] $params Parameters to create the label from + * + * @return string + */ + private function createLabel(array $params): string + { + $label = '('; + if ($params) { + foreach ($params as $param) { + $label .= $param->label . ', '; + } + $label = substr($label, 0, -2); + } + $label .= ')'; + return $label; + } +} diff --git a/src/StderrLogger.php b/src/StderrLogger.php new file mode 100644 index 0000000..6f3bb38 --- /dev/null +++ b/src/StderrLogger.php @@ -0,0 +1,25 @@ +parser = $parser; + $this->docBlockFactory = $docBlockFactory; + $this->definitionResolver = $definitionResolver; + $this->sourceFileNode = $this->parser->parseSourceFile($content, $uri); + + // TODO - docblock errors + + $this->traverse($this->sourceFileNode); + } + + /** + * Collects Parser diagnostic messages for the Node/Token + * and transforms them into LSP Format + * + * @param Node|Token $node + * @return void + */ + private function collectDiagnostics($node) + { + // Get errors from the parser. + if (($error = PhpParser\DiagnosticsProvider::checkDiagnostics($node)) !== null) { + $range = PhpParser\PositionUtilities::getRangeFromPosition($error->start, $error->length, $this->sourceFileNode->fileContents); + + switch ($error->kind) { + case PhpParser\DiagnosticKind::Error: + $severity = DiagnosticSeverity::ERROR; + break; + case PhpParser\DiagnosticKind::Warning: + default: + $severity = DiagnosticSeverity::WARNING; + break; + } + + $this->diagnostics[] = new Diagnostic( + $error->message, + new Range( + new Position($range->start->line, $range->start->character), + new Position($range->end->line, $range->start->character) + ), + null, + $severity, + 'php' + ); + } + + // Check for invalid usage of $this. + if ($node instanceof Node\Expression\Variable && $node->getName() === 'this') { + // Find the first ancestor that's a class method. Return an error + // if there is none, or if the method is static. + $method = $node->getFirstAncestor(Node\MethodDeclaration::class); + if ($method && $method->isStatic()) { + $this->diagnostics[] = new Diagnostic( + "\$this can not be used in static methods.", + Range::fromNode($node), + null, + DiagnosticSeverity::ERROR, + 'php' + ); + } + } + } + + /** + * Recursive AST traversal to collect definitions/references and diagnostics + * + * @param Node|Token $currentNode The node/token to process + */ + private function traverse($currentNode) + { + $this->collectDiagnostics($currentNode); + + // Only update/descend into Nodes, Tokens are leaves + if ($currentNode instanceof Node) { + $this->collectDefinitionsAndReferences($currentNode); + + foreach ($currentNode::CHILD_NAMES as $name) { + $child = $currentNode->$name; + + if ($child === null) { + continue; + } + + if (\is_array($child)) { + foreach ($child as $actualChild) { + if ($actualChild !== null) { + $this->traverse($actualChild); + } + } + } else { + $this->traverse($child); + } + } + } + } + + /** + * Collect definitions and references for the given node + * + * @param Node $node + */ + private function collectDefinitionsAndReferences(Node $node) + { + $fqn = ($this->definitionResolver)::getDefinedFqn($node); + // Only index definitions with an FQN (no variables) + if ($fqn !== null) { + $this->definitionNodes[$fqn] = $node; + $this->definitions[$fqn] = $this->definitionResolver->createDefinitionFromNode($node, $fqn); + } else { + + $parent = $node->parent; + if ( + ( + // $node->parent instanceof Node\Expression\ScopedPropertyAccessExpression || + ($node instanceof Node\Expression\ScopedPropertyAccessExpression || + $node instanceof Node\Expression\MemberAccessExpression) + && !( + $node->parent instanceof Node\Expression\CallExpression || + $node->memberName instanceof PhpParser\Token + )) + || ($parent instanceof Node\Statement\NamespaceDefinition && $parent->name !== null && $parent->name->getStart() === $node->getStart()) + ) { + return; + } + + $fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node); + if (!$fqn) { + return; + } + + if ($fqn === 'self' || $fqn === 'static') { + // Resolve self and static keywords to the containing class + // (This is not 100% correct for static but better than nothing) + $classNode = $node->getFirstAncestor(Node\Statement\ClassDeclaration::class); + if (!$classNode) { + return; + } + $fqn = (string)$classNode->getNamespacedName(); + if (!$fqn) { + return; + } + } else if ($fqn === 'parent') { + // Resolve parent keyword to the base class FQN + $classNode = $node->getFirstAncestor(Node\Statement\ClassDeclaration::class); + if (!$classNode || !$classNode->classBaseClause || !$classNode->classBaseClause->baseClass) { + return; + } + $fqn = (string)$classNode->classBaseClause->baseClass->getResolvedName(); + if (!$fqn) { + return; + } + } + + $this->addReference($fqn, $node); + + if ( + $node instanceof Node\QualifiedName + && ($node->isQualifiedName() || $node->parent instanceof Node\NamespaceUseClause) + && !($parent instanceof Node\Statement\NamespaceDefinition && $parent->name->getStart() === $node->getStart() + ) + ) { + // Add references for each referenced namespace + $ns = $fqn; + while (($pos = strrpos($ns, '\\')) !== false) { + $ns = substr($ns, 0, $pos); + $this->addReference($ns, $node); + } + } + + // Namespaced constant access and function calls also need to register a reference + // to the global version because PHP falls back to global at runtime + // http://php.net/manual/en/language.namespaces.fallback.php + if (ParserHelpers\isConstantFetch($node) || + ($parent instanceof Node\Expression\CallExpression + && !( + $node instanceof Node\Expression\ScopedPropertyAccessExpression || + $node instanceof Node\Expression\MemberAccessExpression + ))) { + $parts = explode('\\', $fqn); + if (count($parts) > 1) { + $globalFqn = end($parts); + $this->addReference($globalFqn, $node); + } + } + } + } + + /** + * @return Diagnostic[] + */ + public function getDiagnostics(): array + { + return $this->diagnostics ?? []; + } + + /** + * @return void + */ + private function addReference(string $fqn, Node $node) + { + if (!isset($this->referenceNodes[$fqn])) { + $this->referenceNodes[$fqn] = []; + } + $this->referenceNodes[$fqn][] = $node; + } + + /** + * @return Definition[] + */ + public function getDefinitions() + { + return $this->definitions ?? []; + } + + /** + * @return Node[] + */ + public function getDefinitionNodes() + { + return $this->definitionNodes ?? []; + } + + /** + * @return Node[] + */ + public function getReferenceNodes() + { + return $this->referenceNodes ?? []; + } + + /** + * @return Node\SourceFileNode + */ + public function getSourceFileNode() + { + return $this->sourceFileNode; + } +} diff --git a/src/utils.php b/src/utils.php index c0c5bf7..97e091d 100644 --- a/src/utils.php +++ b/src/utils.php @@ -5,7 +5,6 @@ namespace LanguageServer; use Throwable; use InvalidArgumentException; -use PhpParser\Node; use Sabre\Event\{Loop, Promise, EmitterInterface}; use Sabre\Uri; @@ -94,23 +93,6 @@ function waitForEvent(EmitterInterface $emitter, string $event): Promise return $p; } -/** - * Returns the closest node of a specific type - * - * @param Node $node - * @param string $type The node class name - * @return Node|null $type - */ -function getClosestNode(Node $node, string $type) -{ - $n = $node; - while ($n = $n->getAttribute('parentNode')) { - if ($n instanceof $type) { - return $n; - } - } -} - /** * Returns the part of $b that is not overlapped by $a * Example: @@ -166,9 +148,8 @@ function isVendored(PhpDocument $document, \stdClass $composerJson = null): bool * Check a given URI against the composer.json to see if it * is a vendored URI * - * @param \stdClass|null $composerJson * @param string $uri - * @param array $matches + * @param \stdClass|null $composerJson * @return string|null */ function getPackageName(string $uri, \stdClass $composerJson = null) diff --git a/tests/DefinitionResolverTest.php b/tests/DefinitionResolverTest.php new file mode 100644 index 0000000..0397e71 --- /dev/null +++ b/tests/DefinitionResolverTest.php @@ -0,0 +1,65 @@ +parseSourceFile("getUri()); + + $index = new Index; + $definitionResolver = new DefinitionResolver($index); + $def = $definitionResolver->createDefinitionFromNode($sourceFileNode->statementList[1]->expression, '\TEST_DEFINE'); + + $this->assertInstanceOf(\phpDocumentor\Reflection\Types\Boolean::class, $def->type); + } + + public function testGetTypeFromNode() + { + $parser = new PhpParser\Parser; + $doc = new MockPhpDocument; + $sourceFileNode = $parser->parseSourceFile("getUri()); + + $index = new Index; + $definitionResolver = new DefinitionResolver($index); + $type = $definitionResolver->getTypeFromNode($sourceFileNode->statementList[1]->expression); + + $this->assertInstanceOf(\phpDocumentor\Reflection\Types\Boolean::class, $type); + } + + public function testGetDefinedFqnForIncompleteDefine() + { + // define('XXX') (only one argument) must not introduce a new symbol + $parser = new PhpParser\Parser; + $doc = new MockPhpDocument; + $sourceFileNode = $parser->parseSourceFile("getUri()); + + $index = new Index; + $definitionResolver = new DefinitionResolver($index); + $fqn = $definitionResolver->getDefinedFqn($sourceFileNode->statementList[1]->expression); + + $this->assertNull($fqn); + } + + public function testGetDefinedFqnForDefine() + { + $parser = new PhpParser\Parser; + $doc = new MockPhpDocument; + $sourceFileNode = $parser->parseSourceFile("getUri()); + + $index = new Index; + $definitionResolver = new DefinitionResolver($index); + $fqn = $definitionResolver->getDefinedFqn($sourceFileNode->statementList[1]->expression); + + $this->assertEquals('TEST_DEFINE', $fqn); + } +} diff --git a/tests/Diagnostics/InvalidThisUsageTest.php b/tests/Diagnostics/InvalidThisUsageTest.php new file mode 100644 index 0000000..b7e8196 --- /dev/null +++ b/tests/Diagnostics/InvalidThisUsageTest.php @@ -0,0 +1,82 @@ +getDiagnostics(); + } + + /** + * Assertions about a diagnostic. + * + * @param Diagnostic|null $diagnostic + * @param int $message + * @param string $severity + * @param Range $range + */ + private function assertDiagnostic($diagnostic, $message, $severity, $range) + { + $this->assertInstanceOf(Diagnostic::class, $diagnostic); + $this->assertEquals($message, $diagnostic->message); + $this->assertEquals($severity, $diagnostic->severity); + $this->assertEquals($range, $diagnostic->range); + } + + public function testThisInStaticMethodProducesError() + { + $diagnostics = $this->collectDiagnostics( + __DIR__ . '/../../fixtures/diagnostics/errors/this_in_static_method.php' + ); + + $this->assertCount(1, $diagnostics); + $this->assertDiagnostic( + $diagnostics[0], + '$this can not be used in static methods.', + DiagnosticSeverity::ERROR, + new Range( + new Position(6, 15), + new Position(6, 20) + ) + ); + } + + public function testThisInMethodProducesNoError() + { + $diagnostics = $this->collectDiagnostics( + __DIR__ . '/../../fixtures/diagnostics/baselines/this_in_method.php' + ); + + $this->assertCount(0, $diagnostics); + } +} diff --git a/tests/FormatterTest.php b/tests/FormatterTest.php deleted file mode 100644 index a46f2ec..0000000 --- a/tests/FormatterTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertSame($output, $edits[0]->newText); - } - - public function testFormatNoChange() - { - $expected = file_get_contents(__DIR__ . '/../fixtures/format_expected.php'); - - $edits = Formatter::format($expected, 'file:///whatever'); - $this->assertSame([], $edits); - } -} diff --git a/tests/Index/IndexTest.php b/tests/Index/IndexTest.php new file mode 100644 index 0000000..8236f8d --- /dev/null +++ b/tests/Index/IndexTest.php @@ -0,0 +1,30 @@ +setDefinition('SomeNamespace\SomeClass', new Definition); + $methodDefinition = new Definition; + $methodFqn = 'SomeNamespace\SomeClass->someMethod()'; + $index->setDefinition($methodFqn, $methodDefinition); + $index->setDefinition('SomeNamespace\SomeClass->someProperty', new Definition); + $this->assertSame($methodDefinition, $index->getDefinition($methodFqn)); + } + + public function testGetSetClassDefinition() + { + $index = new Index; + $definition = new Definition; + $fqn = 'SomeNamespace\SomeClass'; + $index->setDefinition($fqn, $definition); + $this->assertSame($definition, $index->getDefinition($fqn)); + } +} diff --git a/tests/LanguageServerTest.php b/tests/LanguageServerTest.php index 9b16902..3354b68 100644 --- a/tests/LanguageServerTest.php +++ b/tests/LanguageServerTest.php @@ -15,7 +15,8 @@ use LanguageServer\Protocol\{ TextDocumentIdentifier, InitializeResult, ServerCapabilities, - CompletionOptions + CompletionOptions, + SignatureHelpOptions }; use AdvancedJsonRpc; use Webmozart\Glob\Glob; @@ -35,13 +36,14 @@ class LanguageServerTest extends TestCase $serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL; $serverCapabilities->documentSymbolProvider = true; $serverCapabilities->workspaceSymbolProvider = true; - $serverCapabilities->documentFormattingProvider = true; $serverCapabilities->definitionProvider = true; $serverCapabilities->referencesProvider = true; $serverCapabilities->hoverProvider = true; $serverCapabilities->completionProvider = new CompletionOptions; $serverCapabilities->completionProvider->resolveProvider = false; $serverCapabilities->completionProvider->triggerCharacters = ['$', '>']; + $serverCapabilities->signatureHelpProvider = new SignatureHelpOptions; + $serverCapabilities->signatureHelpProvider->triggerCharacters = ['(', ',']; $serverCapabilities->xworkspaceReferencesProvider = true; $serverCapabilities->xdefinitionProvider = true; $serverCapabilities->xdependenciesProvider = true; @@ -58,7 +60,7 @@ class LanguageServerTest extends TestCase if ($msg->body->method === 'window/logMessage' && $promise->state === Promise::PENDING) { if ($msg->body->params->type === MessageType::ERROR) { $promise->reject(new Exception($msg->body->params->message)); - } else if (strpos($msg->body->params->message, 'All 26 PHP files parsed') !== false) { + } else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) { $promise->fulfill(); } } @@ -104,7 +106,7 @@ class LanguageServerTest extends TestCase if ($promise->state === Promise::PENDING) { $promise->reject(new Exception($msg->body->params->message)); } - } else if (strpos($msg->body->params->message, 'All 26 PHP files parsed') !== false) { + } else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) { $promise->fulfill(); } } diff --git a/tests/MockPhpDocument.php b/tests/MockPhpDocument.php new file mode 100644 index 0000000..48d4b70 --- /dev/null +++ b/tests/MockPhpDocument.php @@ -0,0 +1,20 @@ +parse($content); - - $traverser = new NodeTraverser; - $traverser->addVisitor(new NameResolver); - $traverser->addVisitor(new ReferencesAdder($document)); - $definitionCollector = new DefinitionCollector($definitionResolver); - $traverser->addVisitor($definitionCollector); - $traverser->traverse($stmts); - - $defNodes = $definitionCollector->nodes; + $defNodes = $this->collectDefinitions($path); $this->assertEquals([ 'TestNamespace', @@ -50,45 +32,53 @@ class DefinitionCollectorTest extends TestCase 'TestNamespace\\TestTrait', 'TestNamespace\\TestInterface', 'TestNamespace\\test_function()', - 'TestNamespace\\ChildClass' + 'TestNamespace\\ChildClass', + 'TestNamespace\\Example', + 'TestNamespace\\Example->__construct()', + 'TestNamespace\\Example->__destruct()' ], array_keys($defNodes)); - $this->assertInstanceOf(Node\Const_::class, $defNodes['TestNamespace\\TEST_CONST']); - $this->assertInstanceOf(Node\Stmt\Class_::class, $defNodes['TestNamespace\\TestClass']); - $this->assertInstanceOf(Node\Const_::class, $defNodes['TestNamespace\\TestClass::TEST_CLASS_CONST']); - $this->assertInstanceOf(Node\Stmt\PropertyProperty::class, $defNodes['TestNamespace\\TestClass::$staticTestProperty']); - $this->assertInstanceOf(Node\Stmt\PropertyProperty::class, $defNodes['TestNamespace\\TestClass->testProperty']); - $this->assertInstanceOf(Node\Stmt\ClassMethod::class, $defNodes['TestNamespace\\TestClass::staticTestMethod()']); - $this->assertInstanceOf(Node\Stmt\ClassMethod::class, $defNodes['TestNamespace\\TestClass->testMethod()']); - $this->assertInstanceOf(Node\Stmt\Trait_::class, $defNodes['TestNamespace\\TestTrait']); - $this->assertInstanceOf(Node\Stmt\Interface_::class, $defNodes['TestNamespace\\TestInterface']); - $this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\test_function()']); - $this->assertInstanceOf(Node\Stmt\Class_::class, $defNodes['TestNamespace\\ChildClass']); + + $this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TEST_CONST']); + $this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\TestClass']); + $this->assertInstanceOf(Node\ConstElement::class, $defNodes['TestNamespace\\TestClass::TEST_CLASS_CONST']); + // TODO - should we parse properties more strictly? + $this->assertInstanceOf(Node\Expression\Variable::class, $defNodes['TestNamespace\\TestClass::$staticTestProperty']); + $this->assertInstanceOf(Node\Expression\Variable::class, $defNodes['TestNamespace\\TestClass->testProperty']); + $this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\TestClass::staticTestMethod()']); + $this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\TestClass->testMethod()']); + $this->assertInstanceOf(Node\Statement\TraitDeclaration::class, $defNodes['TestNamespace\\TestTrait']); + $this->assertInstanceOf(Node\Statement\InterfaceDeclaration::class, $defNodes['TestNamespace\\TestInterface']); + $this->assertInstanceOf(Node\Statement\FunctionDeclaration::class, $defNodes['TestNamespace\\test_function()']); + $this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\ChildClass']); + $this->assertInstanceOf(Node\Statement\ClassDeclaration::class, $defNodes['TestNamespace\\Example']); + $this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\Example->__construct()']); + $this->assertInstanceOf(Node\MethodDeclaration::class, $defNodes['TestNamespace\\Example->__destruct()']); } public function testDoesNotCollectReferences() { $path = realpath(__DIR__ . '/../../fixtures/references.php'); + $defNodes = $this->collectDefinitions($path); + + $this->assertEquals(['TestNamespace', 'TestNamespace\\whatever()'], array_keys($defNodes)); + $this->assertInstanceOf(Node\Statement\NamespaceDefinition::class, $defNodes['TestNamespace']); + $this->assertInstanceOf(Node\Statement\FunctionDeclaration::class, $defNodes['TestNamespace\\whatever()']); + } + + /** + * @param $path + */ + private function collectDefinitions(string $path): array + { $uri = pathToUri($path); - $parser = new Parser; + $parser = new PhpParser\Parser(); + $docBlockFactory = DocBlockFactory::createInstance(); $index = new Index; $definitionResolver = new DefinitionResolver($index); $content = file_get_contents($path); - $document = new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver); - $stmts = $parser->parse($content); - $traverser = new NodeTraverser; - $traverser->addVisitor(new NameResolver); - $traverser->addVisitor(new ReferencesAdder($document)); - $definitionCollector = new DefinitionCollector($definitionResolver); - $traverser->addVisitor($definitionCollector); - $traverser->traverse($stmts); - - $defNodes = $definitionCollector->nodes; - - $this->assertEquals(['TestNamespace', 'TestNamespace\\whatever()'], array_keys($defNodes)); - $this->assertInstanceOf(Node\Name::class, $defNodes['TestNamespace']); - $this->assertInstanceOf(Node\Stmt\Namespace_::class, $defNodes['TestNamespace']->getAttribute('parentNode')); - $this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\whatever()']); + $treeAnalyzer = new TreeAnalyzer($parser, $content, $docBlockFactory, $definitionResolver, $uri); + return $treeAnalyzer->getDefinitionNodes(); } } diff --git a/tests/PhpDocumentLoaderTest.php b/tests/PhpDocumentLoaderTest.php index 7be062d..348f23f 100644 --- a/tests/PhpDocumentLoaderTest.php +++ b/tests/PhpDocumentLoaderTest.php @@ -3,20 +3,14 @@ declare(strict_types = 1); namespace LanguageServer\Tests\Server; -use PHPUnit\Framework\TestCase; -use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument, PhpDocumentLoader, DefinitionResolver}; -use LanguageServer\ContentRetriever\FileSystemContentRetriever; -use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; -use LanguageServer\Protocol\{ - TextDocumentItem, - TextDocumentIdentifier, - SymbolKind, - DiagnosticSeverity, - FormattingOptions, - ClientCapabilities +use LanguageServer\{ + PhpDocument, PhpDocumentLoader, Project, DefinitionResolver }; -use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody}; +use LanguageServer\ContentRetriever\FileSystemContentRetriever; +use LanguageServer\Index\{ + DependenciesIndex, Index, ProjectIndex +}; +use PHPUnit\Framework\TestCase; use function LanguageServer\pathToUri; class PhpDocumentLoaderTest extends TestCase diff --git a/tests/PhpDocumentTest.php b/tests/PhpDocumentTest.php index 011a231..ae1b5cd 100644 --- a/tests/PhpDocumentTest.php +++ b/tests/PhpDocumentTest.php @@ -3,22 +3,26 @@ declare(strict_types = 1); namespace LanguageServer\Tests\Server; -use PHPUnit\Framework\TestCase; +use LanguageServer\{ + PhpDocument, DefinitionResolver +}; +use LanguageServer\Index\{ + Index +}; +use LanguageServer\Protocol\{ + Position +}; +use Microsoft\PhpParser; +use Microsoft\PhpParser\Node; use phpDocumentor\Reflection\DocBlockFactory; -use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{LanguageClient, PhpDocument, DefinitionResolver, Parser}; -use LanguageServer\NodeVisitor\NodeAtPositionFinder; -use LanguageServer\ContentRetriever\FileSystemContentRetriever; -use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities}; -use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; -use PhpParser\Node; +use PHPUnit\Framework\TestCase; use function LanguageServer\isVendored; class PhpDocumentTest extends TestCase { public function createDocument(string $uri, string $content) { - $parser = new Parser; + $parser = new PhpParser\Parser(); $docBlockFactory = DocBlockFactory::createInstance(); $index = new Index; $definitionResolver = new DefinitionResolver($index); @@ -36,10 +40,15 @@ class PhpDocumentTest extends TestCase { $document = $this->createDocument('whatever', "getNodeAtPosition(new Position(1, 13)); - $this->assertInstanceOf(Node\Name\FullyQualified::class, $node); + $this->assertQualifiedName($node); $this->assertEquals('SomeClass', (string)$node); } + private function assertQualifiedName($node) + { + $this->assertInstanceOf(Node\QualifiedName::class, $node); + } + public function testIsVendored() { $document = $this->createDocument('file:///dir/vendor/x.php', "setComplete(); - $rootPath = realpath(__DIR__ . '/../../fixtures/'); - $options = new Options; - $filesFinder = new FileSystemFilesFinder; - $cache = new FileSystemCache; - $definitionResolver = new DefinitionResolver($projectIndex); $client = new LanguageClient(new MockProtocolStream, new MockProtocolStream); $this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver); $this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex); - $indexer = new Indexer($filesFinder, $rootPath, $client, $cache, $dependenciesIndex, $sourceIndex, $this->documentLoader, null, null, $options); - $this->workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $this->documentLoader, null, $indexer, $options); + $this->workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $this->documentLoader); $globalSymbolsUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_symbols.php')); $globalReferencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_references.php')); @@ -80,34 +73,41 @@ abstract class ServerTestCase extends TestCase $this->definitionLocations = [ // Global - 'TEST_CONST' => new Location($globalSymbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))), - 'TestClass' => new Location($globalSymbolsUri, new Range(new Position(20, 0), new Position(61, 1))), - 'ChildClass' => new Location($globalSymbolsUri, new Range(new Position(99, 0), new Position(99, 37))), - 'TestTrait' => new Location($globalSymbolsUri, new Range(new Position(63, 0), new Position(66, 1))), - 'TestInterface' => new Location($globalSymbolsUri, new Range(new Position(68, 0), new Position(71, 1))), - 'TestClass::TEST_CLASS_CONST' => new Location($globalSymbolsUri, new Range(new Position(27, 10), new Position(27, 32))), - 'TestClass::testProperty' => new Location($globalSymbolsUri, new Range(new Position(41, 11), new Position(41, 24))), - 'TestClass::staticTestProperty' => new Location($globalSymbolsUri, new Range(new Position(34, 18), new Position(34, 37))), - 'TestClass::staticTestMethod()' => new Location($globalSymbolsUri, new Range(new Position(46, 4), new Position(49, 5))), - 'TestClass::testMethod()' => new Location($globalSymbolsUri, new Range(new Position(57, 4), new Position(60, 5))), - 'test_function()' => new Location($globalSymbolsUri, new Range(new Position(78, 0), new Position(81, 1))), - 'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))), + 'TEST_DEFINE_CONSTANT' => new Location($globalSymbolsUri, new Range(new Position(104, 0), new Position(104, 37))), + 'TEST_CONST' => new Location($globalSymbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))), + 'TestClass' => new Location($globalSymbolsUri, new Range(new Position(20, 0), new Position(61, 1))), + 'ChildClass' => new Location($globalSymbolsUri, new Range(new Position(99, 0), new Position(99, 37))), + 'TestTrait' => new Location($globalSymbolsUri, new Range(new Position(63, 0), new Position(66, 1))), + 'TestInterface' => new Location($globalSymbolsUri, new Range(new Position(68, 0), new Position(71, 1))), + 'TestClass::TEST_CLASS_CONST' => new Location($globalSymbolsUri, new Range(new Position(27, 10), new Position(27, 32))), + 'TestClass::testProperty' => new Location($globalSymbolsUri, new Range(new Position(41, 11), new Position(41, 24))), + 'TestClass::staticTestProperty' => new Location($globalSymbolsUri, new Range(new Position(34, 18), new Position(34, 37))), + 'TestClass::staticTestMethod()' => new Location($globalSymbolsUri, new Range(new Position(46, 4), new Position(49, 5))), + 'TestClass::testMethod()' => new Location($globalSymbolsUri, new Range(new Position(57, 4), new Position(60, 5))), + 'test_function()' => new Location($globalSymbolsUri, new Range(new Position(78, 0), new Position(81, 1))), + 'UnusedClass' => new Location($globalSymbolsUri, new Range(new Position(111, 0), new Position(118, 1))), + 'UnusedClass::unusedProperty' => new Location($globalSymbolsUri, new Range(new Position(113,11), new Position(113, 26))), + 'UnusedClass::unusedMethod' => new Location($globalSymbolsUri, new Range(new Position(115, 4), new Position(117, 5))), + 'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))), // Namespaced - 'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 10), new Position( 2, 23))), - 'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 10), new Position( 2, 29))), + 'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 0), new Position( 2, 24))), + 'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 0), new Position( 2, 30))), 'TestNamespace\\TEST_CONST' => new Location($symbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))), 'TestNamespace\\TestClass' => new Location($symbolsUri, new Range(new Position(20, 0), new Position(61, 1))), 'TestNamespace\\ChildClass' => new Location($symbolsUri, new Range(new Position(99, 0), new Position(99, 37))), 'TestNamespace\\TestTrait' => new Location($symbolsUri, new Range(new Position(63, 0), new Position(66, 1))), 'TestNamespace\\TestInterface' => new Location($symbolsUri, new Range(new Position(68, 0), new Position(71, 1))), 'TestNamespace\\TestClass::TEST_CLASS_CONST' => new Location($symbolsUri, new Range(new Position(27, 10), new Position(27, 32))), - 'TestNamespace\\TestClass::testProperty' => new Location($symbolsUri, new Range(new Position(41, 11), new Position(41, 24))), - 'TestNamespace\\TestClass::staticTestProperty' => new Location($symbolsUri, new Range(new Position(34, 18), new Position(34, 37))), - 'TestNamespace\\TestClass::staticTestMethod()' => new Location($symbolsUri, new Range(new Position(46, 4), new Position(49, 5))), - 'TestNamespace\\TestClass::testMethod()' => new Location($symbolsUri, new Range(new Position(57, 4), new Position(60, 5))), - 'TestNamespace\\test_function()' => new Location($symbolsUri, new Range(new Position(78, 0), new Position(81, 1))), - 'TestNamespace\\whatever()' => new Location($referencesUri, new Range(new Position(21, 0), new Position(23, 1))) + 'TestNamespace\\TestClass::testProperty' => new Location($symbolsUri, new Range(new Position(41, 11), new Position(41, 24))), + 'TestNamespace\\TestClass::staticTestProperty' => new Location($symbolsUri, new Range(new Position(34, 18), new Position(34, 37))), + 'TestNamespace\\TestClass::staticTestMethod()' => new Location($symbolsUri, new Range(new Position(46, 4), new Position(49, 5))), + 'TestNamespace\\TestClass::testMethod()' => new Location($symbolsUri, new Range(new Position(57, 4), new Position(60, 5))), + 'TestNamespace\\test_function()' => new Location($symbolsUri, new Range(new Position(78, 0), new Position(81, 1))), + 'TestNamespace\\whatever()' => new Location($referencesUri, new Range(new Position(21, 0), new Position(23, 1))), + 'TestNamespace\\Example' => new Location($symbolsUri, new Range(new Position(101, 0), new Position(104, 1))), + 'TestNamespace\\Example::__construct' => new Location($symbolsUri, new Range(new Position(102, 4), new Position(102, 36))), + 'TestNamespace\\Example::__destruct' => new Location($symbolsUri, new Range(new Position(103, 4), new Position(103, 35))) ]; $this->referenceLocations = [ @@ -116,21 +116,22 @@ abstract class ServerTestCase extends TestCase 'TestNamespace' => [ 0 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40))), // use function TestNamespace\test_function; 1 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass; - 2 => new Location($useUri, new Range(new Position( 5, 4), new Position( 5, 17))) // use TestNamespace\{TestTrait, TestInterface}; + 2 => new Location($useUri, new Range(new Position( 5, 4), new Position( 5, 18))) // use TestNamespace\{TestTrait, TestInterface}; ], 'TestNamespace\\TEST_CONST' => [ 0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15))) ], 'TestNamespace\\TestClass' => [ - 0 => new Location($symbolsUri , new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {} - 1 => new Location($referencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass(); - 2 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod(); - 3 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty; - 4 => new Location($referencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST; - 5 => new Location($referencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param) - 6 => new Location($referencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass - 7 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty; - 8 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass; + 0 => new Location($symbolsUri, new Range(new Position(48, 13), new Position(48, 17))), // echo self::TEST_CLASS_CONST; + 1 => new Location($symbolsUri , new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {} + 2 => new Location($referencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass(); + 3 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod(); + 4 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty; + 5 => new Location($referencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST; + 6 => new Location($referencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param) + 7 => new Location($referencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass + 8 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty; + 9 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass; ], 'TestNamespace\\TestChild' => [ 0 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 25))), // echo $child->testProperty; @@ -151,16 +152,16 @@ abstract class ServerTestCase extends TestCase 3 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty; ], 'TestNamespace\\TestClass::staticTestProperty' => [ - 0 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 35))), // echo TestClass::$staticTestProperty; - 1 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty; + 0 => new Location($referencesUri, new Range(new Position( 8, 16), new Position( 8, 35))), // echo TestClass::$staticTestProperty; + 1 => new Location($referencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty; ], 'TestNamespace\\TestClass::staticTestMethod()' => [ - 0 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 29))) + 0 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 27))) ], 'TestNamespace\\TestClass::testMethod()' => [ - 0 => new Location($referencesUri, new Range(new Position( 5, 0), new Position( 5, 18))), // $obj->testMethod(); - 1 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 32))), // $obj->testProperty->testMethod(); - 2 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 25))) // $child->testMethod(); + 0 => new Location($referencesUri, new Range(new Position( 5, 0), new Position( 5, 16))), // $obj->testMethod(); + 1 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 30))), // $obj->testProperty->testMethod(); + 2 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 23))) // $child->testMethod(); ], 'TestNamespace\\test_function()' => [ 0 => new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))), @@ -168,26 +169,30 @@ abstract class ServerTestCase extends TestCase ], // Global + 'TEST_DEFINE_CONSTANT' => [ + 0 => new Location($globalSymbolsUri, new Range(new Position(106, 6), new Position(106, 26))) + ], 'TEST_CONST' => [ 0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15))), 1 => new Location($globalReferencesUri, new Range(new Position(29, 5), new Position(29, 15))) ], 'TestClass' => [ - 0 => new Location($globalSymbolsUri, new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {} - 1 => new Location($globalReferencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass(); - 2 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod(); - 3 => new Location($globalReferencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty; - 4 => new Location($globalReferencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST; - 5 => new Location($globalReferencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param) - 6 => new Location($globalReferencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass - 7 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty; + 0 => new Location($globalSymbolsUri, new Range(new Position(48, 13), new Position(48, 17))), // echo self::TEST_CLASS_CONST; + 1 => new Location($globalSymbolsUri, new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {} + 2 => new Location($globalReferencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass(); + 3 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod(); + 4 => new Location($globalReferencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty; + 5 => new Location($globalReferencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST; + 6 => new Location($globalReferencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param) + 7 => new Location($globalReferencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass + 8 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty; ], 'TestChild' => [ 0 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 25))), // echo $child->testProperty; ], 'TestInterface' => [ 0 => new Location($globalSymbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface - 1 => new Location($globalSymbolsUri, new Range(new Position(57, 48), new Position(57, 61))), // public function testMethod($testParameter): TestInterface + 1 => new Location($globalSymbolsUri, new Range(new Position(57, 49), new Position(57, 61))), // public function testMethod($testParameter) : TestInterface 2 => new Location($globalReferencesUri, new Range(new Position(33, 20), new Position(33, 33))) // if ($abc instanceof TestInterface) ], 'TestClass::TEST_CLASS_CONST' => [ @@ -201,16 +206,16 @@ abstract class ServerTestCase extends TestCase 3 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty; ], 'TestClass::staticTestProperty' => [ - 0 => new Location($globalReferencesUri, new Range(new Position( 8, 5), new Position( 8, 35))), // echo TestClass::$staticTestProperty; - 1 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty; + 0 => new Location($globalReferencesUri, new Range(new Position( 8, 16), new Position( 8, 35))), // echo TestClass::$staticTestProperty; + 1 => new Location($globalReferencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty; ], 'TestClass::staticTestMethod()' => [ - 0 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 29))) + 0 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 27))) ], 'TestClass::testMethod()' => [ - 0 => new Location($globalReferencesUri, new Range(new Position( 5, 0), new Position( 5, 18))), // $obj->testMethod(); - 1 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 32))), // $obj->testProperty->testMethod(); - 2 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 25))) // $child->testMethod(); + 0 => new Location($globalReferencesUri, new Range(new Position( 5, 0), new Position( 5, 16))), // $obj->testMethod(); + 1 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 30))), // $obj->testProperty->testMethod(); + 2 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 23))) // $child->testMethod(); ], 'test_function()' => [ 0 => new Location($globalReferencesUri, new Range(new Position(10, 0), new Position(10, 13))), diff --git a/tests/Server/TextDocument/CompletionTest.php b/tests/Server/TextDocument/CompletionTest.php index dd9e680..2b4e9ff 100644 --- a/tests/Server/TextDocument/CompletionTest.php +++ b/tests/Server/TextDocument/CompletionTest.php @@ -5,18 +5,21 @@ namespace LanguageServer\Tests\Server\TextDocument; use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, CompletionProvider, DefinitionResolver}; -use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex, GlobalIndex, StubsIndex}; +use LanguageServer\{ + Server, LanguageClient, PhpDocumentLoader, DefinitionResolver +}; +use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; use LanguageServer\ContentRetriever\FileSystemContentRetriever; use LanguageServer\Protocol\{ TextDocumentIdentifier, TextEdit, Range, Position, - ClientCapabilities, CompletionList, CompletionItem, - CompletionItemKind + CompletionItemKind, + CompletionContext, + CompletionTriggerKind }; use function LanguageServer\pathToUri; @@ -52,7 +55,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(3, 7) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'testProperty', CompletionItemKind::PROPERTY, @@ -68,6 +71,27 @@ class CompletionTest extends TestCase ], true), $items); } + public function testGlobalFunctionInsideNamespaceAndClass() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/inside_namespace_and_method.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(8, 11) + )->wait(); + $this->assertCompletionsListSubset(new CompletionList([ + new CompletionItem( + 'test_function', + CompletionItemKind::FUNCTION, + 'void', // Return type + 'Officia aliquip adipisicing et nulla et laboris dolore labore.', + null, + null, + '\test_function' + ) + ], true), $items); + } + public function testPropertyAndMethodWithoutPrefix() { $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/property.php'); @@ -76,7 +100,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(3, 6) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'testProperty', CompletionItemKind::PROPERTY, @@ -100,7 +124,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(8, 5) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( '$var', CompletionItemKind::VARIABLE, @@ -132,7 +156,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(8, 6) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( '$param', CompletionItemKind::VARIABLE, @@ -154,13 +178,18 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(6, 10) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ // Global TestClass definition (inserted as \TestClass) new CompletionItem( 'TestClass', CompletionItemKind::CLASS_, null, - 'Pariatur ut laborum tempor voluptate consequat ea deserunt.', + 'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" . + 'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" . + 'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" . + 'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" . + 'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" . + 'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.', null, null, '\TestClass' @@ -179,7 +208,12 @@ class CompletionTest extends TestCase 'TestClass', CompletionItemKind::CLASS_, 'TestNamespace', - 'Pariatur ut laborum tempor voluptate consequat ea deserunt.', + 'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" . + 'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" . + 'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" . + 'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" . + 'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" . + 'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.', null, null, 'TestClass' @@ -193,6 +227,15 @@ class CompletionTest extends TestCase null, '\TestNamespace\ChildClass' ), + new CompletionItem( + 'Example', + CompletionItemKind::CLASS_, + 'TestNamespace', + null, + null, + null, + '\TestNamespace\Example' + ) ], true), $items); } @@ -204,12 +247,17 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(6, 5) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'TestClass', CompletionItemKind::CLASS_, 'TestNamespace', - 'Pariatur ut laborum tempor voluptate consequat ea deserunt.' + 'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" . + 'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" . + 'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" . + 'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" . + 'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" . + 'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.' ) ], true), $items); } @@ -222,7 +270,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(2, 14) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'staticTestProperty', CompletionItemKind::PROPERTY, @@ -243,7 +291,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(2, 11) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'TEST_CLASS_CONST', CompletionItemKind::VARIABLE, @@ -276,7 +324,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(2, 13) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'TEST_CLASS_CONST', CompletionItemKind::VARIABLE, @@ -309,7 +357,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(2, 13) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'TEST_CLASS_CONST', CompletionItemKind::VARIABLE, @@ -342,12 +390,17 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(6, 6) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'TestClass', CompletionItemKind::CLASS_, null, - 'Pariatur ut laborum tempor voluptate consequat ea deserunt.', + 'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" . + 'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" . + 'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" . + 'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" . + 'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" . + 'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.', null, null, 'TestClass' @@ -363,9 +416,9 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(2, 1) )->wait(); - $this->assertEquals(new CompletionList([ - new CompletionItem('class', CompletionItemKind::KEYWORD, null, null, null, null, 'class '), - new CompletionItem('clone', CompletionItemKind::KEYWORD, null, null, null, null, 'clone ') + $this->assertCompletionsListSubset(new CompletionList([ + new CompletionItem('class', CompletionItemKind::KEYWORD, null, null, null, null, 'class'), + new CompletionItem('clone', CompletionItemKind::KEYWORD, null, null, null, null, 'clone') ], true), $items); } @@ -377,7 +430,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(0, 0) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'loader->open($completionUri, file_get_contents($completionUri)); @@ -399,6 +452,55 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(0, 1) )->wait(); + + $this->assertEquals(new CompletionList([], true), $items); + } + + public function testHtmlWontBeProposedWithPrefixWithCompletionContext() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_with_prefix.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(0, 1), + new CompletionContext(CompletionTriggerKind::TRIGGER_CHARACTER, '<') + )->wait(); + + $this->assertEquals(new CompletionList([ + new CompletionItem( + 'loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(0, 1), + new CompletionContext(CompletionTriggerKind::TRIGGER_CHARACTER, '>') + )->wait(); + $this->assertEquals(new CompletionList([], true), $items); + } + + public function testHtmlPrefixShouldTriggerCompletionIfManuallyInvoked() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_no_completion.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(0, 1), + new CompletionContext(CompletionTriggerKind::INVOKED) + )->wait(); $this->assertEquals(new CompletionList([ new CompletionItem( 'wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( 'SomeNamespace', CompletionItemKind::MODULE, @@ -442,7 +544,7 @@ class CompletionTest extends TestCase new TextDocumentIdentifier($completionUri), new Position(4, 8) )->wait(); - $this->assertEquals(new CompletionList([ + $this->assertCompletionsListSubset(new CompletionList([ new CompletionItem( '$abc2', CompletionItemKind::VARIABLE, @@ -465,4 +567,323 @@ class CompletionTest extends TestCase ) ], true), $items); } + + /** + * @dataProvider foreachProvider + */ + public function testForeach(Position $position, array $expectedItems) + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/foreach.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + $position + )->wait(); + $this->assertCompletionsListSubset(new CompletionList($expectedItems, true), $items); + } + + public function foreachProvider(): array + { + return [ + 'foreach value' => [ + new Position(18, 6), + [ + new CompletionItem( + '$value', + CompletionItemKind::VARIABLE, + '\\Foo\\Bar', + null, + null, + null, + null, + new TextEdit(new Range(new Position(18, 6), new Position(18, 6)), 'alue') + ), + ] + ], + 'foreach value resolved' => [ + new Position(19, 12), + [ + new CompletionItem( + 'foo', + CompletionItemKind::PROPERTY, + 'mixed' + ), + new CompletionItem( + 'test', + CompletionItemKind::METHOD, + '\\Foo\\Bar[]' + ), + ] + ], + 'array creation with multiple objects' => [ + new Position(23, 5), + [ + new CompletionItem( + '$value', + CompletionItemKind::VARIABLE, + '\\Foo\\Bar|\\stdClass', + null, + null, + null, + null, + new TextEdit(new Range(new Position(23, 5), new Position(23, 5)), 'value') + ), + new CompletionItem( + '$key', + CompletionItemKind::VARIABLE, + 'int', + null, + null, + null, + null, + new TextEdit(new Range(new Position(23, 5), new Position(23, 5)), 'key') + ), + ] + ], + 'array creation with string/int keys and object values' => [ + new Position(27, 5), + [ + new CompletionItem( + '$value', + CompletionItemKind::VARIABLE, + '\\Foo\\Bar', + null, + null, + null, + null, + new TextEdit(new Range(new Position(27, 5), new Position(27, 5)), 'value') + ), + new CompletionItem( + '$key', + CompletionItemKind::VARIABLE, + 'string|int', + null, + null, + null, + null, + new TextEdit(new Range(new Position(27, 5), new Position(27, 5)), 'key') + ), + ] + ], + 'array creation with only string keys' => [ + new Position(31, 5), + [ + new CompletionItem( + '$value', + CompletionItemKind::VARIABLE, + '\\Foo\\Bar', + null, + null, + null, + null, + new TextEdit(new Range(new Position(31, 5), new Position(31, 5)), 'value') + ), + new CompletionItem( + '$key', + CompletionItemKind::VARIABLE, + 'string', + null, + null, + null, + null, + new TextEdit(new Range(new Position(31, 5), new Position(31, 5)), 'key') + ), + ] + ], + 'foreach function call' => [ + new Position(35, 5), + [ + new CompletionItem( + '$value', + CompletionItemKind::VARIABLE, + '\\Foo\\Bar', + null, + null, + null, + null, + new TextEdit(new Range(new Position(35, 5), new Position(35, 5)), 'value') + ), + ] + ], + 'foreach unknown type' => [ + new Position(39, 10), + [ + new CompletionItem( + '$unknown', + CompletionItemKind::VARIABLE, + 'mixed', + null, + null, + null, + null, + new TextEdit(new Range(new Position(39, 10), new Position(39, 10)), 'wn') + ), + ] + ], + ]; + } + + public function testMethodReturnType() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/method_return_type.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(10, 6) + )->wait(); + $this->assertCompletionsListSubset(new CompletionList([ + new CompletionItem( + 'foo', + CompletionItemKind::METHOD, + '\FooClass', + null, + null, + null, + null, + null + ) + ], true), $items); + } + + public function testStaticMethodReturnType() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/static_method_return_type.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(11, 6) + )->wait(); + $this->assertCompletionsListSubset(new CompletionList([ + new CompletionItem( + 'bar', + CompletionItemKind::METHOD, + 'mixed', + null, + null, + null, + null, + null + ) + ], true), $items); + } + + private function assertCompletionsListSubset(CompletionList $subsetList, CompletionList $list) + { + foreach ($subsetList->items as $expectedItem) { + $this->assertContains($expectedItem, $list->items, null, null, false); + } + + $this->assertEquals($subsetList->isIncomplete, $list->isIncomplete); + } + + public function testThisWithoutPrefix() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(12, 15) + )->wait(); + $this->assertEquals(new CompletionList([ + new CompletionItem( + 'foo', + CompletionItemKind::PROPERTY, + 'mixed', // Type of the property + null + ), + new CompletionItem( + 'bar', + CompletionItemKind::PROPERTY, + 'mixed', // Type of the property + null + ), + new CompletionItem( + 'method', + CompletionItemKind::METHOD, + 'mixed', // Return type of the method + null + ), + new CompletionItem( + 'test', + CompletionItemKind::METHOD, + 'mixed', // Return type of the method + null + ) + ], true), $items); + } + + public function testThisWithPrefix() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this_with_prefix.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(12, 16) + )->wait(); + $this->assertEquals(new CompletionList([ + new CompletionItem( + 'testProperty', + CompletionItemKind::PROPERTY, + '\TestClass', // Type of the property + 'Reprehenderit magna velit mollit ipsum do.' + ), + new CompletionItem( + 'testMethod', + CompletionItemKind::METHOD, + '\TestClass', // Return type of the method + 'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.' + ), + new CompletionItem( + 'foo', + CompletionItemKind::PROPERTY, + 'mixed', // Type of the property + null + ), + new CompletionItem( + 'bar', + CompletionItemKind::PROPERTY, + 'mixed', // Type of the property + null + ), + new CompletionItem( + 'method', + CompletionItemKind::METHOD, + 'mixed', // Return type of the method + null + ), + new CompletionItem( + 'test', + CompletionItemKind::METHOD, + 'mixed', // Return type of the method + null + ) + ], true), $items); + } + + public function testThisReturnValue() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this_return_value.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(17, 23) + )->wait(); + $this->assertEquals(new CompletionList([ + new CompletionItem( + 'foo', + CompletionItemKind::METHOD, + '$this' // Return type of the method + ), + new CompletionItem( + 'bar', + CompletionItemKind::METHOD, + 'mixed' // Return type of the method + ), + new CompletionItem( + 'qux', + CompletionItemKind::METHOD, + 'mixed' // Return type of the method + ) + ], true), $items); + } } diff --git a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php index f3c0771..4e45f9e 100644 --- a/tests/Server/TextDocument/Definition/GlobalFallbackTest.php +++ b/tests/Server/TextDocument/Definition/GlobalFallbackTest.php @@ -5,11 +5,12 @@ namespace LanguageServer\Tests\Server\TextDocument\Definition; use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\Server\ServerTestCase; -use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, DefinitionResolver}; +use LanguageServer\{ + Server, LanguageClient, PhpDocumentLoader, DefinitionResolver +}; use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; use LanguageServer\ContentRetriever\FileSystemContentRetriever; -use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location, ClientCapabilities}; -use Sabre\Event\Promise; +use LanguageServer\Protocol\{TextDocumentIdentifier, Position, Range, Location}; class GlobalFallbackTest extends ServerTestCase { diff --git a/tests/Server/TextDocument/Definition/GlobalTest.php b/tests/Server/TextDocument/Definition/GlobalTest.php index b5d7425..2a80873 100644 --- a/tests/Server/TextDocument/Definition/GlobalTest.php +++ b/tests/Server/TextDocument/Definition/GlobalTest.php @@ -24,16 +24,28 @@ class GlobalTest extends ServerTestCase // namespace keyword $result = $this->textDocument->definition( new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), - new Position(2, 4) + new Position(1, 0) )->wait(); $this->assertEquals([], $result); } + public function testDefinitionForSelfKeyword() + { + // echo self::TEST_CLASS_CONST; + // Get definition for self + $reference = $this->getReferenceLocations('TestClass')[0]; + $result = $this->textDocument->definition( + new TextDocumentIdentifier($reference->uri), + $reference->range->start + )->wait(); + $this->assertEquals($this->getDefinitionLocation('TestClass'), $result); + } + public function testDefinitionForClassLike() { // $obj = new TestClass(); // Get definition for TestClass - $reference = $this->getReferenceLocations('TestClass')[0]; + $reference = $this->getReferenceLocations('TestClass')[1]; $result = $this->textDocument->definition( new TextDocumentIdentifier($reference->uri), $reference->range->start @@ -45,7 +57,7 @@ class GlobalTest extends ServerTestCase { // TestClass::staticTestMethod(); // Get definition for TestClass - $reference = $this->getReferenceLocations('TestClass')[1]; + $reference = $this->getReferenceLocations('TestClass')[2]; $result = $this->textDocument->definition( new TextDocumentIdentifier($reference->uri), $reference->range->start @@ -57,7 +69,7 @@ class GlobalTest extends ServerTestCase { // echo TestClass::$staticTestProperty; // Get definition for TestClass - $reference = $this->getReferenceLocations('TestClass')[2]; + $reference = $this->getReferenceLocations('TestClass')[3]; $result = $this->textDocument->definition( new TextDocumentIdentifier($reference->uri), $reference->range->start @@ -69,7 +81,7 @@ class GlobalTest extends ServerTestCase { // TestClass::TEST_CLASS_CONST; // Get definition for TestClass - $reference = $this->getReferenceLocations('TestClass')[3]; + $reference = $this->getReferenceLocations('TestClass')[4]; $result = $this->textDocument->definition( new TextDocumentIdentifier($reference->uri), $reference->range->start @@ -213,7 +225,7 @@ class GlobalTest extends ServerTestCase { // function whatever(TestClass $param) { // Get definition for TestClass - $reference = $this->getReferenceLocations('TestClass')[4]; + $reference = $this->getReferenceLocations('TestClass')[5]; $result = $this->textDocument->definition( new TextDocumentIdentifier($reference->uri), $reference->range->start @@ -225,7 +237,7 @@ class GlobalTest extends ServerTestCase { // function whatever(TestClass $param): TestClass { // Get definition for TestClass - $reference = $this->getReferenceLocations('TestClass')[5]; + $reference = $this->getReferenceLocations('TestClass')[6]; $result = $this->textDocument->definition( new TextDocumentIdentifier($reference->uri), $reference->range->start diff --git a/tests/Server/TextDocument/Definition/NamespacedTest.php b/tests/Server/TextDocument/Definition/NamespacedTest.php index ef66624..395881b 100644 --- a/tests/Server/TextDocument/Definition/NamespacedTest.php +++ b/tests/Server/TextDocument/Definition/NamespacedTest.php @@ -34,7 +34,7 @@ class NamespacedTest extends GlobalTest { // use TestNamespace\TestClass; // Get definition for TestClass - $reference = $this->getReferenceLocations('TestClass')[6]; + $reference = $this->getReferenceLocations('TestClass')[7]; $result = $this->textDocument->definition( new TextDocumentIdentifier($reference->uri), $reference->range->start @@ -46,7 +46,7 @@ class NamespacedTest extends GlobalTest { // use TestNamespace\{TestTrait, TestInterface}; // Get definition for TestInterface - $reference = $this->getReferenceLocations('TestClass')[0]; + $reference = $this->getReferenceLocations('TestClass')[1]; $result = $this->textDocument->definition( new TextDocumentIdentifier($reference->uri), $reference->range->start diff --git a/tests/Server/TextDocument/DidChangeTest.php b/tests/Server/TextDocument/DidChangeTest.php index bdd3b22..4d26ed8 100644 --- a/tests/Server/TextDocument/DidChangeTest.php +++ b/tests/Server/TextDocument/DidChangeTest.php @@ -5,17 +5,16 @@ namespace LanguageServer\Tests\Server\TextDocument; use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{Server, Client, LanguageClient, PhpDocumentLoader, DefinitionResolver}; +use LanguageServer\{ + Server, LanguageClient, PhpDocumentLoader, DefinitionResolver +}; use LanguageServer\ContentRetriever\FileSystemContentRetriever; use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; use LanguageServer\Protocol\{ - TextDocumentIdentifier, - TextDocumentItem, VersionedTextDocumentIdentifier, TextDocumentContentChangeEvent, Range, - Position, - ClientCapabilities + Position }; class DidChangeTest extends TestCase diff --git a/tests/Server/TextDocument/DidCloseTest.php b/tests/Server/TextDocument/DidCloseTest.php index b31a58d..42daec5 100644 --- a/tests/Server/TextDocument/DidCloseTest.php +++ b/tests/Server/TextDocument/DidCloseTest.php @@ -5,11 +5,12 @@ namespace LanguageServer\Tests\Server\TextDocument; use PHPUnit\Framework\TestCase; use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{Server, Client, LanguageClient, PhpDocumentLoader, DefinitionResolver}; +use LanguageServer\{ + Server, LanguageClient, PhpDocumentLoader, DefinitionResolver +}; use LanguageServer\ContentRetriever\FileSystemContentRetriever; use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; -use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, ClientCapabilities}; -use Exception; +use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier}; class DidCloseTest extends TestCase { diff --git a/tests/Server/TextDocument/DocumentSymbolTest.php b/tests/Server/TextDocument/DocumentSymbolTest.php index 89d24ee..155e4a2 100644 --- a/tests/Server/TextDocument/DocumentSymbolTest.php +++ b/tests/Server/TextDocument/DocumentSymbolTest.php @@ -18,18 +18,21 @@ class DocumentSymbolTest extends ServerTestCase $result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri))->wait(); // @codingStandardsIgnoreStart $this->assertEquals([ - new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace'), ''), - new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), - new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'), - new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'), - new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'), - new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'), - new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'), - new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'), - new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'), - new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'), - new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'), - new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'), + new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace'), ''), + new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), + new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'), + new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'), + new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'), + new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'), + new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'), + new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'), + new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'), + new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'), + new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'), + new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'), + new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), 'TestNamespace'), + new SymbolInformation('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'), + new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example') ], $result); // @codingStandardsIgnoreEnd } diff --git a/tests/Server/TextDocument/FormattingTest.php b/tests/Server/TextDocument/FormattingTest.php deleted file mode 100644 index abb3d6d..0000000 --- a/tests/Server/TextDocument/FormattingTest.php +++ /dev/null @@ -1,49 +0,0 @@ -uri = $uri; - $textDocumentItem->languageId = 'php'; - $textDocumentItem->version = 1; - $textDocumentItem->text = file_get_contents($path); - $textDocument->didOpen($textDocumentItem); - - // how code should look after formatting - $expected = file_get_contents(__DIR__ . '/../../../fixtures/format_expected.php'); - // Request formatting - $result = $textDocument->formatting(new TextDocumentIdentifier($uri), new FormattingOptions())->wait(); - $this->assertEquals([new TextEdit(new Range(new Position(0, 0), new Position(20, 0)), $expected)], $result); - } -} diff --git a/tests/Server/TextDocument/HoverTest.php b/tests/Server/TextDocument/HoverTest.php index 7d61354..2ba49e1 100644 --- a/tests/Server/TextDocument/HoverTest.php +++ b/tests/Server/TextDocument/HoverTest.php @@ -15,14 +15,19 @@ class HoverTest extends ServerTestCase { // $obj = new TestClass(); // Get hover for TestClass - $reference = $this->getReferenceLocations('TestClass')[0]; + $reference = $this->getReferenceLocations('TestClass')[1]; $result = $this->textDocument->hover( new TextDocumentIdentifier($reference->uri), $reference->range->start )->wait(); $this->assertEquals(new Hover([ - new MarkedString('php', "range), $result); } @@ -36,8 +41,13 @@ class HoverTest extends ServerTestCase $definition->range->start )->wait(); $this->assertEquals(new Hover([ - new MarkedString('php', "range), $result); } @@ -51,7 +61,7 @@ class HoverTest extends ServerTestCase $reference->range->end )->wait(); $this->assertEquals(new Hover([ - new MarkedString('php', "range), $result); } @@ -146,6 +156,22 @@ class HoverTest extends ServerTestCase ], $reference->range), $result); } + public function testHoverForGlobalConstant() + { + // print TEST_DEFINE_CONSTANT ? 'true' : 'false'; + // Get hover for TEST_DEFINE_CONSTANT + $reference = $this->getReferenceLocations('TEST_DEFINE_CONSTANT')[0]; + $result = $this->textDocument->hover( + new TextDocumentIdentifier($reference->uri), + $reference->range->end + )->wait(); + // TODO - should pretty print with fqns, like \define, \false. Not yet supported by tolerant-php-parser + $this->assertEquals(new Hover([ + new MarkedString('php', "range), $result); + } + public function testHoverForVariable() { // echo $var; @@ -153,7 +179,7 @@ class HoverTest extends ServerTestCase $uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php')); $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7))->wait(); $this->assertEquals(new Hover( - [new MarkedString('php', "textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11))->wait(); $this->assertEquals(new Hover( [ - new MarkedString('php', "textDocument->hover(new TextDocumentIdentifier($uri), new Position(59, 11))->wait(); $this->assertEquals(new Hover([ - new MarkedString('php', " [ 'start' => [ 'line' => 2, - 'character' => 10 + 'character' => 9 + ], + 'end' => [ + 'line' => 2, + 'character' => 9 + ] + ], + 'severity' => DiagnosticSeverity::ERROR, + 'code' => null, + 'source' => 'php', + 'message' => "'Name' expected." + ], + [ + 'range' => [ + 'start' => [ + 'line' => 2, + 'character' => 9 + ], + 'end' => [ + 'line' => 2, + 'character' => 9 + ] + ], + 'severity' => DiagnosticSeverity::ERROR, + 'code' => null, + 'source' => 'php', + 'message' => "'{' expected." + ], + [ + 'range' => [ + 'start' => [ + 'line' => 2, + 'character' => 9 + ], + 'end' => [ + 'line' => 2, + 'character' => 9 + ] + ], + 'severity' => DiagnosticSeverity::ERROR, + 'code' => null, + 'source' => 'php', + 'message' => "'}' expected." + ], + [ + 'range' => [ + 'start' => [ + 'line' => 2, + 'character' => 15 ], 'end' => [ 'line' => 2, @@ -72,13 +122,14 @@ class ParseErrorsTest extends TestCase 'severity' => DiagnosticSeverity::ERROR, 'code' => null, 'source' => 'php', - 'message' => "Syntax error, unexpected T_CLASS, expecting T_STRING" + 'message' => "'Name' expected." ]] ], json_decode(json_encode($this->args), true)); } public function testParseErrorsWithOnlyStartLine() { + $this->markTestIncomplete('This diagnostic not yet implemented in tolerant-php-parser'); $this->openFile(__DIR__ . '/../../../fixtures/namespace_not_first.php'); $this->assertEquals([ 'whatever', diff --git a/tests/Server/TextDocument/References/GlobalFallbackTest.php b/tests/Server/TextDocument/References/GlobalFallbackTest.php index ac7b355..abfefce 100644 --- a/tests/Server/TextDocument/References/GlobalFallbackTest.php +++ b/tests/Server/TextDocument/References/GlobalFallbackTest.php @@ -3,12 +3,17 @@ declare(strict_types = 1); namespace LanguageServer\Tests\Server\TextDocument\References; -use PHPUnit\Framework\TestCase; -use LanguageServer\Tests\MockProtocolStream; -use LanguageServer\{Server, LanguageClient, PhpDocumentLoader, DefinitionResolver}; -use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex}; +use LanguageServer\{ + LanguageClient, PhpDocumentLoader, Server, DefinitionResolver +}; use LanguageServer\ContentRetriever\FileSystemContentRetriever; -use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range, ClientCapabilities}; +use LanguageServer\Index\{ + DependenciesIndex, Index, ProjectIndex +}; +use LanguageServer\Protocol\{ + Location, Position, Range, ReferenceContext, TextDocumentIdentifier +}; +use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\Server\ServerTestCase; class GlobalFallbackTest extends ServerTestCase diff --git a/tests/Server/TextDocument/References/GlobalTest.php b/tests/Server/TextDocument/References/GlobalTest.php index 4febaf3..105dfef 100644 --- a/tests/Server/TextDocument/References/GlobalTest.php +++ b/tests/Server/TextDocument/References/GlobalTest.php @@ -151,7 +151,7 @@ class GlobalTest extends ServerTestCase { // $obj = new TestClass(); // Get references for TestClass - $reference = $this->getReferenceLocations('TestClass')[0]; + $reference = $this->getReferenceLocations('TestClass')[1]; $result = $this->textDocument->references( new ReferenceContext, new TextDocumentIdentifier($reference->uri), @@ -159,4 +159,43 @@ class GlobalTest extends ServerTestCase )->wait(); $this->assertEquals($this->getReferenceLocations('TestClass'), $result); } + + public function testReferencesForUnusedClass() + { + // class UnusedClass + // Get references for UnusedClass + $symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php')); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($symbolsUri), + new Position(111, 10) + )->wait(); + $this->assertEquals([], $result); + } + + public function testReferencesForUnusedProperty() + { + // public $unusedProperty + // Get references for unusedProperty + $symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php')); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($symbolsUri), + new Position(113, 18) + )->wait(); + $this->assertEquals([], $result); + } + + public function testReferencesForUnusedMethod() + { + // public function unusedMethod() + // Get references for unusedMethod + $symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php')); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($symbolsUri), + new Position(115, 26) + )->wait(); + $this->assertEquals([], $result); + } } diff --git a/tests/Server/TextDocument/SignatureHelpTest.php b/tests/Server/TextDocument/SignatureHelpTest.php new file mode 100644 index 0000000..2004aae --- /dev/null +++ b/tests/Server/TextDocument/SignatureHelpTest.php @@ -0,0 +1,199 @@ +loader = new PhpDocumentLoader($contentRetriever, $projectIndex, $definitionResolver); + $this->textDocument = new Server\TextDocument($this->loader, $definitionResolver, $client, $projectIndex); + $index->setComplete(); + } + + /** + * @dataProvider signatureHelpProvider + */ + public function testSignatureHelp(Position $position, SignatureHelp $expectedSignature) + { + $callsUri = pathToUri(__DIR__ . '/../../../fixtures/signature_help/calls.php'); + $this->loader->open($callsUri, file_get_contents($callsUri)); + $signatureHelp = $this->textDocument->signatureHelp( + new TextDocumentIdentifier($callsUri), + $position + )->wait(); + $this->assertEquals($expectedSignature, $signatureHelp); + } + + public function signatureHelpProvider(): array + { + return [ + 'member call' => [ + new Position(50, 9), + new SignatureHelp( + [ + new SignatureInformation( + '(\\Foo\\SomethingElse $a, int|null $b = null)', + [ + new ParameterInformation('\\Foo\\SomethingElse $a', 'A param with a different doc type'), + new ParameterInformation('int|null $b = null', 'Param with default value'), + ], + 'Function doc' + ) + ], + 0, + 0 + ), + ], + 'member call 2nd param active' => [ + new Position(51, 12), + new SignatureHelp( + [ + new SignatureInformation( + '(\\Foo\\SomethingElse $a, int|null $b = null)', + [ + new ParameterInformation('\\Foo\\SomethingElse $a', 'A param with a different doc type'), + new ParameterInformation('int|null $b = null', 'Param with default value'), + ], + 'Function doc' + ) + ], + 0, + 1 + ), + ], + 'member call 2nd param active and closing )' => [ + new Position(52, 11), + new SignatureHelp( + [ + new SignatureInformation( + '(\\Foo\\SomethingElse $a, int|null $b = null)', + [ + new ParameterInformation('\\Foo\\SomethingElse $a', 'A param with a different doc type'), + new ParameterInformation('int|null $b = null', 'Param with default value'), + ], + 'Function doc' + ) + ], + 0, + 1 + ), + ], + 'method with no params' => [ + new Position(53, 9), + new SignatureHelp([new SignatureInformation('()', [], 'Method with no params', 0, 0)]), + ], + 'constructor' => [ + new Position(48, 14), + new SignatureHelp( + [ + new SignatureInformation( + '(string $first, int $second, \Foo\Test $third)', + [ + new ParameterInformation('string $first', 'First param'), + new ParameterInformation('int $second', 'Second param'), + new ParameterInformation('\Foo\Test $third', 'Third param with a longer description'), + ], + 'Constructor comment goes here' + ) + ], + 0, + 0 + ), + ], + 'constructor argument expression list' => [ + new Position(49, 16), + new SignatureHelp( + [ + new SignatureInformation( + '(string $first, int $second, \Foo\Test $third)', + [ + new ParameterInformation('string $first', 'First param'), + new ParameterInformation('int $second', 'Second param'), + new ParameterInformation('\Foo\Test $third', 'Third param with a longer description'), + ], + 'Constructor comment goes here' + ) + ], + 0, + 1 + ), + ], + 'global function' => [ + new Position(57, 15), + new SignatureHelp( + [ + new SignatureInformation( + '(int $i, bool $b = false, \Foo\Test|null ...$things = null)', + [ + new ParameterInformation('int $i', 'Global function param one'), + new ParameterInformation('bool $b = false', 'Default false param'), + new ParameterInformation('\Foo\Test|null ...$things = null', 'Test things'), + ] + ), + ], + 0, + 2 + ) + ], + 'static method' => [ + new Position(60, 10), + new SignatureHelp( + [new SignatureInformation('(mixed $a)', [new ParameterInformation('mixed $a')])], + 0, + 0 + ), + ], + 'no signature help' => [ + new Position(0, 0), + new SignatureHelp([]), + ], + 'construct from non fqn (not supported)' => [ + new Position(62, 9), + new SignatureHelp([]), + ], + 'construct from non fqn (not supported) argument expression' => [ + new Position(63, 11), + new SignatureHelp([]), + ], + 'invalid var' => [ + new Position(65, 13), + new SignatureHelp([]), + ], + ]; + } +} diff --git a/tests/Server/Workspace/SymbolTest.php b/tests/Server/Workspace/SymbolTest.php index d3d13b6..765841b 100644 --- a/tests/Server/Workspace/SymbolTest.php +++ b/tests/Server/Workspace/SymbolTest.php @@ -27,37 +27,45 @@ class SymbolTest extends ServerTestCase // Request symbols $result = $this->workspace->symbol('')->wait(); $referencesUri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php')); + // @codingStandardsIgnoreStart $this->assertEquals([ - new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 10), new Position(2, 23))), ''), + new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''), // Namespaced - new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), - new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'), - new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'), - new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'), - new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'), - new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'), - new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'), - new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'), - new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'), - new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'), - new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'), - new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'), + new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), + new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'), + new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'), + new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'), + new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'), + new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'), + new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'), + new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'), + new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'), + new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'), + new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'), + new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), 'TestNamespace'), + new SymbolInformation('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'), + new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'), + new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'), // Global - new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''), - new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestClass'), ''), - new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), 'TestClass'), - new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::staticTestProperty'), 'TestClass'), - new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::testProperty'), 'TestClass'), - new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'), - new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass'), - new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestTrait'), ''), - new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestInterface'), ''), - new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''), - new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('ChildClass'), ''), - new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), ''), + new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''), + new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestClass'), ''), + new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), 'TestClass'), + new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::staticTestProperty'), 'TestClass'), + new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::testProperty'), 'TestClass'), + new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'), + new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass'), + new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestTrait'), ''), + new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestInterface'), ''), + new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''), + new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('ChildClass'), ''), + new SymbolInformation('TEST_DEFINE_CONSTANT', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_DEFINE_CONSTANT'), ''), + new SymbolInformation('UnusedClass', SymbolKind::CLASS_, $this->getDefinitionLocation('UnusedClass'), ''), + new SymbolInformation('unusedProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('UnusedClass::unusedProperty'), 'UnusedClass'), + new SymbolInformation('unusedMethod', SymbolKind::METHOD, $this->getDefinitionLocation('UnusedClass::unusedMethod'), 'UnusedClass'), + new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), ''), - new SymbolInformation('SecondTestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('SecondTestNamespace'), '') + new SymbolInformation('SecondTestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('SecondTestNamespace'), ''), ], $result); // @codingStandardsIgnoreEnd } diff --git a/tests/Validation/ValidationTest.php b/tests/Validation/ValidationTest.php new file mode 100644 index 0000000..9057c9d --- /dev/null +++ b/tests/Validation/ValidationTest.php @@ -0,0 +1,152 @@ +getSize() < 100000) { + $testProviderArray[] = [$file->getPathname()]; + } + } + } + + return $testProviderArray; + } + + /** + * This test loads the test cases specified in .php files under cases/ and looks at the whole set of + * Definitions and References produced. It reads the expected results from associated .json files + * and compares to the actual result. If they don't match, the test fails and it writes the new baseline + * to the .json file. + * @group validation + * @dataProvider validationTestProvider + * @param $testCaseFile + */ + public function testDefinitionsAndReferences($testCaseFile) + { + $fileContents = file_get_contents($testCaseFile); + $actualValues = $this->getActualTestValues($testCaseFile, $fileContents); + + $outputFile = getExpectedValuesFile($testCaseFile); + if (!file_exists($outputFile)) { + file_put_contents($outputFile, json_encode($actualValues, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + } + + $expectedValues = (array)json_decode(file_get_contents($outputFile)); + + try { + $this->assertEquals($expectedValues['definitions'], $actualValues['definitions']); + $this->assertEquals((array)$expectedValues['references'], (array)$actualValues['references'], 'references don\'t match.'); + } catch (\Throwable $e) { + $outputFile = getExpectedValuesFile($testCaseFile); + file_put_contents($outputFile, json_encode($actualValues, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + + throw $e; + } + } + + private function getActualTestValues($filename, $fileContents): array + { + $index = new Index(); + $parser = new PhpParser\Parser(); + $docBlockFactory = DocBlockFactory::createInstance(); + $definitionResolver = new DefinitionResolver($index); + + $document = new PhpDocument($filename, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver); + + $actualRefs = $index->getReferences(); + $actualDefs = $this->getTestValuesFromDefs($document->getDefinitions()); + + // There's probably a more PHP-typical way to do this. Need to compare the objects parsed from json files + // to the real objects. + $refsAndDefs = array( + 'references' => json_decode(json_encode($actualRefs)), + 'definitions' => json_decode(json_encode($actualDefs)) + ); + + // Turn references into relative paths + $testCasesDir = realpath(__DIR__ . '/cases'); + foreach ($refsAndDefs['references'] as $key => $list) { + $fixedPathRefs = array_map(function ($ref) use ($testCasesDir) { + $ref = str_replace($testCasesDir, '.', $ref); + $ref = str_replace(DIRECTORY_SEPARATOR, '/', $ref); + return $ref; + }, $list); + + $refsAndDefs['references']->$key = $fixedPathRefs; + } + + // Turn def locations into relative paths + foreach ($refsAndDefs['definitions'] as $key => $def) { + if ($def !== null && $def->symbolInformation !== null && + $def->symbolInformation->location !== null && $def->symbolInformation->location->uri !== null) { + $def->symbolInformation->location->uri = str_replace($testCasesDir, '.', $def->symbolInformation->location->uri); + $def->symbolInformation->location->uri = str_replace(DIRECTORY_SEPARATOR, '/', $def->symbolInformation->location->uri); + } + } + + return $refsAndDefs; + } + + /** + * @param $definitions Definition[] + * @return array|\array[] + */ + private function getTestValuesFromDefs($definitions): array + { + $propertyNames = get_class_vars(Definition::class); + + $defsForAssert = []; + foreach ($definitions as $definition) { + $fqn = $definition->fqn; + + foreach ($propertyNames as $propertyName => $value) { + if ($propertyName === 'symbolInformation') { + // Range is very often different - don't check it, for now + unset($definition->$propertyName->location->range); + } elseif ($propertyName === 'extends') { + $definition->$propertyName = $definition->$propertyName ?? []; + } elseif ($propertyName === 'type' && $definition->type !== null) { + $defsForAssert[$fqn]['type__tostring'] = (string)$definition->type; + } + + $defsForAssert[$fqn][$propertyName] = $definition->$propertyName; + } + } + + return $defsForAssert; + } +} + +function getExpectedValuesFile($testCaseFile): string +{ + return $testCaseFile . '.expected.json'; +} diff --git a/tests/Validation/cases/WithReturnTypehints.php b/tests/Validation/cases/WithReturnTypehints.php new file mode 100644 index 0000000..045051f --- /dev/null +++ b/tests/Validation/cases/WithReturnTypehints.php @@ -0,0 +1,18 @@ +getSelf()": { + "fqn": "Fixtures\\Prophecy\\WithReturnTypehints->getSelf()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "getSelf", + "kind": 6, + "location": { + "uri": "./WithReturnTypehints.php" + }, + "containerName": "Fixtures\\Prophecy\\WithReturnTypehints" + }, + "type__tostring": "\\Fixtures\\Prophecy\\WithReturnTypehints", + "type": {}, + "declarationLine": "public function getSelf(): self {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "Fixtures\\Prophecy\\WithReturnTypehints->getName()": { + "fqn": "Fixtures\\Prophecy\\WithReturnTypehints->getName()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "getName", + "kind": 6, + "location": { + "uri": "./WithReturnTypehints.php" + }, + "containerName": "Fixtures\\Prophecy\\WithReturnTypehints" + }, + "type__tostring": "string", + "type": {}, + "declarationLine": "public function getName(): string {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "Fixtures\\Prophecy\\WithReturnTypehints->getParent()": { + "fqn": "Fixtures\\Prophecy\\WithReturnTypehints->getParent()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "getParent", + "kind": 6, + "location": { + "uri": "./WithReturnTypehints.php" + }, + "containerName": "Fixtures\\Prophecy\\WithReturnTypehints" + }, + "type__tostring": "\\parent", + "type": {}, + "declarationLine": "public function getParent(): parent {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/anonymousClassMembersShouldNotBeSymbols.php b/tests/Validation/cases/anonymousClassMembersShouldNotBeSymbols.php new file mode 100644 index 0000000..260a8cc --- /dev/null +++ b/tests/Validation/cases/anonymousClassMembersShouldNotBeSymbols.php @@ -0,0 +1,11 @@ + TRUE]; +} \ No newline at end of file diff --git a/tests/Validation/cases/arrayValueShouldBeBoolean.php.expected.json b/tests/Validation/cases/arrayValueShouldBeBoolean.php.expected.json new file mode 100644 index 0000000..f0cdb24 --- /dev/null +++ b/tests/Validation/cases/arrayValueShouldBeBoolean.php.expected.json @@ -0,0 +1,46 @@ +{ + "references": [], + "definitions": { + "A": { + "fqn": "A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./arrayValueShouldBeBoolean.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "A->foo": { + "fqn": "A->foo", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "foo", + "kind": 7, + "location": { + "uri": "./arrayValueShouldBeBoolean.php" + }, + "containerName": "A" + }, + "type__tostring": "bool[]", + "type": {}, + "declarationLine": "protected $foo;", + "documentation": null, + "signatureInformation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/caseStatement1.php b/tests/Validation/cases/caseStatement1.php new file mode 100644 index 0000000..69851aa --- /dev/null +++ b/tests/Validation/cases/caseStatement1.php @@ -0,0 +1,7 @@ +a; + +class A { + public $a = 3; +} \ No newline at end of file diff --git a/tests/Validation/cases/classDefinition1.php.expected.json b/tests/Validation/cases/classDefinition1.php.expected.json new file mode 100644 index 0000000..211a12f --- /dev/null +++ b/tests/Validation/cases/classDefinition1.php.expected.json @@ -0,0 +1,73 @@ +{ + "references": { + "TestNamespace\\A": [ + "./classDefinition1.php" + ], + "TestNamespace\\A->a": [ + "./classDefinition1.php" + ] + }, + "definitions": { + "TestNamespace": { + "fqn": "TestNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "TestNamespace", + "kind": 3, + "location": { + "uri": "./classDefinition1.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace TestNamespace;", + "documentation": null, + "signatureInformation": null + }, + "TestNamespace\\A": { + "fqn": "TestNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./classDefinition1.php" + }, + "containerName": "TestNamespace" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "TestNamespace\\A->a": { + "fqn": "TestNamespace\\A->a", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 7, + "location": { + "uri": "./classDefinition1.php" + }, + "containerName": "TestNamespace\\A" + }, + "type__tostring": "int", + "type": {}, + "declarationLine": "public $a;", + "documentation": null, + "signatureInformation": null + } + } +} \ No newline at end of file diff --git a/fixtures/format_expected.php b/tests/Validation/cases/classProperty1.php similarity index 96% rename from fixtures/format_expected.php rename to tests/Validation/cases/classProperty1.php index 87fa432..000e788 100644 --- a/fixtures/format_expected.php +++ b/tests/Validation/cases/classProperty1.php @@ -11,7 +11,7 @@ class TestClass public function testMethod($testParameter) { $testVariable = 123; - + if (empty($testParameter)) { echo 'Empty'; } diff --git a/tests/Validation/cases/classProperty1.php.expected.json b/tests/Validation/cases/classProperty1.php.expected.json new file mode 100644 index 0000000..f5ae22a --- /dev/null +++ b/tests/Validation/cases/classProperty1.php.expected.json @@ -0,0 +1,103 @@ +{ + "references": { + "SomeNamespace\\Goo": [ + "./classProperty1.php" + ], + "SomeNamespace": [ + "./classProperty1.php" + ] + }, + "definitions": { + "TestNamespace": { + "fqn": "TestNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "TestNamespace", + "kind": 3, + "location": { + "uri": "./classProperty1.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace TestNamespace;", + "documentation": null, + "signatureInformation": null + }, + "TestNamespace\\TestClass": { + "fqn": "TestNamespace\\TestClass", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "TestClass", + "kind": 5, + "location": { + "uri": "./classProperty1.php" + }, + "containerName": "TestNamespace" + }, + "type": null, + "declarationLine": "class TestClass", + "documentation": null, + "signatureInformation": null + }, + "TestNamespace\\TestClass->testProperty": { + "fqn": "TestNamespace\\TestClass->testProperty", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "testProperty", + "kind": 7, + "location": { + "uri": "./classProperty1.php" + }, + "containerName": "TestNamespace\\TestClass" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public $testProperty;", + "documentation": null, + "signatureInformation": null + }, + "TestNamespace\\TestClass->testMethod()": { + "fqn": "TestNamespace\\TestClass->testMethod()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "testMethod", + "kind": 6, + "location": { + "uri": "./classProperty1.php" + }, + "containerName": "TestNamespace\\TestClass" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public function testMethod($testParameter)", + "documentation": null, + "signatureInformation": { + "label": "(mixed $testParameter)", + "documentation": null, + "parameters": [ + { + "label": "mixed $testParameter", + "documentation": null + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/constants.php b/tests/Validation/cases/constants.php new file mode 100644 index 0000000..23423cf --- /dev/null +++ b/tests/Validation/cases/constants.php @@ -0,0 +1,13 @@ + BYE + ]; + } +} \ No newline at end of file diff --git a/tests/Validation/cases/constants.php.expected.json b/tests/Validation/cases/constants.php.expected.json new file mode 100644 index 0000000..3fcc367 --- /dev/null +++ b/tests/Validation/cases/constants.php.expected.json @@ -0,0 +1,77 @@ +{ + "references": { + "MyNamespace\\BYE": [ + "./constants.php" + ], + "BYE": [ + "./constants.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./constants.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./constants.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A::suite()": { + "fqn": "MyNamespace\\A::suite()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "suite", + "kind": 6, + "location": { + "uri": "./constants.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public static function suite()", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/constants2.php b/tests/Validation/cases/constants2.php new file mode 100644 index 0000000..fda345e --- /dev/null +++ b/tests/Validation/cases/constants2.php @@ -0,0 +1,13 @@ + "hi" + ]; + } +} \ No newline at end of file diff --git a/tests/Validation/cases/constants2.php.expected.json b/tests/Validation/cases/constants2.php.expected.json new file mode 100644 index 0000000..0837b67 --- /dev/null +++ b/tests/Validation/cases/constants2.php.expected.json @@ -0,0 +1,77 @@ +{ + "references": { + "MyNamespace\\BYE": [ + "./constants2.php" + ], + "BYE": [ + "./constants2.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./constants2.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./constants2.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A::suite()": { + "fqn": "MyNamespace\\A::suite()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "suite", + "kind": 6, + "location": { + "uri": "./constants2.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public static function suite()", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/constants3.php b/tests/Validation/cases/constants3.php new file mode 100644 index 0000000..7ee1dde --- /dev/null +++ b/tests/Validation/cases/constants3.php @@ -0,0 +1,11 @@ +suite()": { + "fqn": "MyNamespace\\A->suite()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "suite", + "kind": 6, + "location": { + "uri": "./constants4.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public function suite()", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/constants5.php b/tests/Validation/cases/constants5.php new file mode 100644 index 0000000..82f833f --- /dev/null +++ b/tests/Validation/cases/constants5.php @@ -0,0 +1,8 @@ +b()": { + "fqn": "A->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./constantsInFunctionParamDefault.php" + }, + "containerName": "A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b ($a = MY_CONSTANT);", + "documentation": null, + "signatureInformation": { + "label": "(\\MY_CONSTANT $a = MY_CONSTANT)", + "documentation": null, + "parameters": [ + { + "label": "\\MY_CONSTANT $a = MY_CONSTANT", + "documentation": null + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/docBlocksOnNamespaceDefinition.php b/tests/Validation/cases/docBlocksOnNamespaceDefinition.php new file mode 100644 index 0000000..4871946 --- /dev/null +++ b/tests/Validation/cases/docBlocksOnNamespaceDefinition.php @@ -0,0 +1,6 @@ +foo()) { + } + } + + public function foo() { + return $this; + } +} diff --git a/tests/Validation/cases/forLoopReference1.php.expected.json b/tests/Validation/cases/forLoopReference1.php.expected.json new file mode 100644 index 0000000..e861248 --- /dev/null +++ b/tests/Validation/cases/forLoopReference1.php.expected.json @@ -0,0 +1,60 @@ +{ + "references": { + "ForLoopReference1->foo()": [ + "./_cases/forLoopReference1.php" + ] + }, + "definitions": { + "ForLoopReference1": { + "extends": [], + "isGlobal": true, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "ForLoopReference1", + "kind": 5, + "location": { + "uri": "./_cases/forLoopReference1.php" + }, + "containerName": "" + }, + "type__class": "LanguageServer\\Tests\\ValidationTest", + "type": null, + "documentation": null + }, + "ForLoopReference1->getThat()": { + "extends": [], + "isGlobal": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "getThat", + "kind": 6, + "location": { + "uri": "./_cases/forLoopReference1.php" + }, + "containerName": "ForLoopReference1" + }, + "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", + "type": {}, + "documentation": null + }, + "ForLoopReference1->foo()": { + "extends": [], + "isGlobal": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "foo", + "kind": 6, + "location": { + "uri": "./_cases/forLoopReference1.php" + }, + "containerName": "ForLoopReference1" + }, + "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", + "type": {}, + "documentation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/functionUse.php b/tests/Validation/cases/functionUse.php new file mode 100644 index 0000000..ff3fe9e --- /dev/null +++ b/tests/Validation/cases/functionUse.php @@ -0,0 +1,7 @@ +b(); +}; \ No newline at end of file diff --git a/tests/Validation/cases/functionUse.php.expected.json b/tests/Validation/cases/functionUse.php.expected.json new file mode 100644 index 0000000..5c9af19 --- /dev/null +++ b/tests/Validation/cases/functionUse.php.expected.json @@ -0,0 +1,11 @@ +{ + "references": { + "A": [ + "./functionUse.php" + ], + "A->b()": [ + "./functionUse.php" + ] + }, + "definitions": [] +} \ No newline at end of file diff --git a/tests/Validation/cases/functionUse2.php b/tests/Validation/cases/functionUse2.php new file mode 100644 index 0000000..593e6ac --- /dev/null +++ b/tests/Validation/cases/functionUse2.php @@ -0,0 +1,4 @@ + true + ); +} \ No newline at end of file diff --git a/tests/Validation/cases/magicConsts.php.expected.json b/tests/Validation/cases/magicConsts.php.expected.json new file mode 100644 index 0000000..27608e5 --- /dev/null +++ b/tests/Validation/cases/magicConsts.php.expected.json @@ -0,0 +1,50 @@ +{ + "references": { + "__CLASS__": [ + "./magicConsts.php" + ] + }, + "definitions": { + "A": { + "fqn": "A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./magicConsts.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "A::$deprecationsTriggered": { + "fqn": "A::$deprecationsTriggered", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "deprecationsTriggered", + "kind": 7, + "location": { + "uri": "./magicConsts.php" + }, + "containerName": "A" + }, + "type__tostring": "bool[]", + "type": {}, + "declarationLine": "private static $deprecationsTriggered;", + "documentation": null, + "signatureInformation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/memberAccess1.php b/tests/Validation/cases/memberAccess1.php new file mode 100644 index 0000000..e1bb231 --- /dev/null +++ b/tests/Validation/cases/memberAccess1.php @@ -0,0 +1,10 @@ +a(); + } +} diff --git a/tests/Validation/cases/memberAccess1.php.expected.json b/tests/Validation/cases/memberAccess1.php.expected.json new file mode 100644 index 0000000..f0286c3 --- /dev/null +++ b/tests/Validation/cases/memberAccess1.php.expected.json @@ -0,0 +1,77 @@ +{ + "references": { + "MyNamespace\\a": [ + "./memberAccess1.php" + ], + "MyNamespace\\a->a()": [ + "./memberAccess1.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./memberAccess1.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./memberAccess1.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A::a()": { + "fqn": "MyNamespace\\A::a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./memberAccess1.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "static function a() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/memberAccess2.php b/tests/Validation/cases/memberAccess2.php new file mode 100644 index 0000000..e1bb231 --- /dev/null +++ b/tests/Validation/cases/memberAccess2.php @@ -0,0 +1,10 @@ +a(); + } +} diff --git a/tests/Validation/cases/memberAccess2.php.expected.json b/tests/Validation/cases/memberAccess2.php.expected.json new file mode 100644 index 0000000..6060e8e --- /dev/null +++ b/tests/Validation/cases/memberAccess2.php.expected.json @@ -0,0 +1,77 @@ +{ + "references": { + "MyNamespace\\a": [ + "./memberAccess2.php" + ], + "MyNamespace\\a->a()": [ + "./memberAccess2.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./memberAccess2.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./memberAccess2.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A::a()": { + "fqn": "MyNamespace\\A::a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./memberAccess2.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "static function a() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/memberAccess3.php b/tests/Validation/cases/memberAccess3.php new file mode 100644 index 0000000..b2078d7 --- /dev/null +++ b/tests/Validation/cases/memberAccess3.php @@ -0,0 +1,13 @@ +prefixesPsr0 = ComposerStaticInitIncludePath::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} diff --git a/tests/Validation/cases/memberAccess3.php.expected.json b/tests/Validation/cases/memberAccess3.php.expected.json new file mode 100644 index 0000000..b0af5aa --- /dev/null +++ b/tests/Validation/cases/memberAccess3.php.expected.json @@ -0,0 +1,97 @@ +{ + "references": { + "MyNamespace\\ClassLoader": [ + "./memberAccess3.php" + ], + "Closure::bind()": [ + "./memberAccess3.php" + ], + "Closure": [ + "./memberAccess3.php" + ], + "MyNamespace\\ClassLoader->prefixesPsr0": [ + "./memberAccess3.php" + ], + "MyNamespace\\ComposerStaticInitIncludePath": [ + "./memberAccess3.php" + ], + "MyNamespace\\ComposerStaticInitIncludePath::$prefixesPsr0": [ + "./memberAccess3.php" + ], + "MyNamespace\\ClassLoader::class": [ + "./memberAccess3.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./memberAccess3.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./memberAccess3.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A::getInitializer()": { + "fqn": "MyNamespace\\A::getInitializer()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "getInitializer", + "kind": 6, + "location": { + "uri": "./memberAccess3.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public static function getInitializer(ClassLoader $loader)", + "documentation": null, + "signatureInformation": { + "label": "(\\MyNamespace\\ClassLoader $loader)", + "documentation": null, + "parameters": [ + { + "label": "\\MyNamespace\\ClassLoader $loader", + "documentation": null + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/memberAccess4.php b/tests/Validation/cases/memberAccess4.php new file mode 100644 index 0000000..dd1b2fb --- /dev/null +++ b/tests/Validation/cases/memberAccess4.php @@ -0,0 +1,10 @@ +toString()); + } +} diff --git a/tests/Validation/cases/memberAccess4.php.expected.json b/tests/Validation/cases/memberAccess4.php.expected.json new file mode 100644 index 0000000..951cacd --- /dev/null +++ b/tests/Validation/cases/memberAccess4.php.expected.json @@ -0,0 +1,83 @@ +{ + "references": { + "MyNamespace\\Request::create()": [ + "./memberAccess4.php" + ], + "MyNamespace\\Request": [ + "./memberAccess4.php" + ], + "MyNamespace\\Url->toString()": [ + "./memberAccess4.php" + ], + "MyNamespace\\Url": [ + "./memberAccess4.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./memberAccess4.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./memberAccess4.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->testRequest()": { + "fqn": "MyNamespace\\A->testRequest()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "testRequest", + "kind": 6, + "location": { + "uri": "./memberAccess4.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public function testRequest()", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/memberAccess5.php b/tests/Validation/cases/memberAccess5.php new file mode 100644 index 0000000..7061acb --- /dev/null +++ b/tests/Validation/cases/memberAccess5.php @@ -0,0 +1,11 @@ +args) { }; + } +} \ No newline at end of file diff --git a/tests/Validation/cases/memberAccess5.php.expected.json b/tests/Validation/cases/memberAccess5.php.expected.json new file mode 100644 index 0000000..d4e9104 --- /dev/null +++ b/tests/Validation/cases/memberAccess5.php.expected.json @@ -0,0 +1,74 @@ +{ + "references": { + "MyNamespace\\ParseErrorsTest->args": [ + "./memberAccess5.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./memberAccess5.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\ParseErrorsTest": { + "fqn": "MyNamespace\\ParseErrorsTest", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "ParseErrorsTest", + "kind": 5, + "location": { + "uri": "./memberAccess5.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class ParseErrorsTest {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\ParseErrorsTest->setUp()": { + "fqn": "MyNamespace\\ParseErrorsTest->setUp()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "setUp", + "kind": 6, + "location": { + "uri": "./memberAccess5.php" + }, + "containerName": "MyNamespace\\ParseErrorsTest" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public function setUp()", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/memberCall1.php b/tests/Validation/cases/memberCall1.php new file mode 100644 index 0000000..4f7ebed --- /dev/null +++ b/tests/Validation/cases/memberCall1.php @@ -0,0 +1,16 @@ +getAccount(); + } + + } +} \ No newline at end of file diff --git a/tests/Validation/cases/memberCall1.php.expected.json b/tests/Validation/cases/memberCall1.php.expected.json new file mode 100644 index 0000000..50e91d1 --- /dev/null +++ b/tests/Validation/cases/memberCall1.php.expected.json @@ -0,0 +1,85 @@ +{ + "references": { + "MyNamespace\\AccountInterface": [ + "./memberCall1.php" + ], + "MyNamespace\\A": [ + "./memberCall1.php" + ], + "MyNamespace\\AccountInterface->getAccount()": [ + "./memberCall1.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./memberCall1.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\ParseErrorsTest": { + "fqn": "MyNamespace\\ParseErrorsTest", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "ParseErrorsTest", + "kind": 5, + "location": { + "uri": "./memberCall1.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class ParseErrorsTest", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\ParseErrorsTest->setAccount()": { + "fqn": "MyNamespace\\ParseErrorsTest->setAccount()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "setAccount", + "kind": 6, + "location": { + "uri": "./memberCall1.php" + }, + "containerName": "MyNamespace\\ParseErrorsTest" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public function setAccount(AccountInterface $account)", + "documentation": null, + "signatureInformation": { + "label": "(\\MyNamespace\\AccountInterface $account)", + "documentation": null, + "parameters": [ + { + "label": "\\MyNamespace\\AccountInterface $account", + "documentation": null + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/methodReturnType.php b/tests/Validation/cases/methodReturnType.php new file mode 100644 index 0000000..824314a --- /dev/null +++ b/tests/Validation/cases/methodReturnType.php @@ -0,0 +1,10 @@ +foo()": { + "fqn": "FooClass->foo()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "foo", + "kind": 6, + "location": { + "uri": "./methodReturnType.php" + }, + "containerName": "FooClass" + }, + "type__tostring": "\\FooClass", + "type": {}, + "declarationLine": "public function foo(): FooClass {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "FooClass->bar()": { + "fqn": "FooClass->bar()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "bar", + "kind": 6, + "location": { + "uri": "./methodReturnType.php" + }, + "containerName": "FooClass" + }, + "type__tostring": "\\FooClass", + "type": {}, + "declarationLine": "public function bar() { }", + "documentation": "", + "signatureInformation": { + "label": "()", + "documentation": "", + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/multipleNamespaces.php b/tests/Validation/cases/multipleNamespaces.php new file mode 100644 index 0000000..0743635 --- /dev/null +++ b/tests/Validation/cases/multipleNamespaces.php @@ -0,0 +1,17 @@ +b(); + } +} \ No newline at end of file diff --git a/tests/Validation/cases/multipleNamespaces.php.expected.json b/tests/Validation/cases/multipleNamespaces.php.expected.json new file mode 100644 index 0000000..c308cf9 --- /dev/null +++ b/tests/Validation/cases/multipleNamespaces.php.expected.json @@ -0,0 +1,150 @@ +{ + "references": { + "MyNamespace2\\MyNamespace1\\B": [ + "./multipleNamespaces.php" + ], + "MyNamespace2\\MyNamespace1": [ + "./multipleNamespaces.php" + ], + "MyNamespace2": [ + "./multipleNamespaces.php" + ], + "MyNamespace2\\A->b()": [ + "./multipleNamespaces.php" + ] + }, + "definitions": { + "MyNamespace1": { + "fqn": "MyNamespace1", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace1", + "kind": 3, + "location": { + "uri": "./multipleNamespaces.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace1;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace1\\B": { + "fqn": "MyNamespace1\\B", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "B", + "kind": 5, + "location": { + "uri": "./multipleNamespaces.php" + }, + "containerName": "MyNamespace1" + }, + "type": null, + "declarationLine": "class B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace1\\B->b()": { + "fqn": "MyNamespace1\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./multipleNamespaces.php" + }, + "containerName": "MyNamespace1\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace2": { + "fqn": "MyNamespace2", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace2", + "kind": 3, + "location": { + "uri": "./multipleNamespaces.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace2;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace2\\A": { + "fqn": "MyNamespace2\\A", + "extends": [ + "MyNamespace2\\MyNamespace1\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./multipleNamespaces.php" + }, + "containerName": "MyNamespace2" + }, + "type": null, + "declarationLine": "class A extends MyNamespace1\\B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace2\\A->a()": { + "fqn": "MyNamespace2\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./multipleNamespaces.php" + }, + "containerName": "MyNamespace2\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/multiplePreceedingComments.php b/tests/Validation/cases/multiplePreceedingComments.php new file mode 100644 index 0000000..1490385 --- /dev/null +++ b/tests/Validation/cases/multiplePreceedingComments.php @@ -0,0 +1,15 @@ +fn()": { + "fqn": "Foo->fn()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "fn", + "kind": 6, + "location": { + "uri": "./multiplePreceedingComments.php" + }, + "containerName": "Foo" + }, + "type__tostring": "\\Iterator", + "type": {}, + "declarationLine": "public function fn()", + "documentation": "Foo", + "signatureInformation": { + "label": "()", + "documentation": "Foo", + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/nameToken.php b/tests/Validation/cases/nameToken.php new file mode 100644 index 0000000..770aef2 --- /dev/null +++ b/tests/Validation/cases/nameToken.php @@ -0,0 +1,7 @@ +b()": { + "fqn": "A->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./nameToken.php" + }, + "containerName": "A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/namespaces2.php b/tests/Validation/cases/namespaces2.php new file mode 100644 index 0000000..a49478d --- /dev/null +++ b/tests/Validation/cases/namespaces2.php @@ -0,0 +1,5 @@ +foo(); + } + + private function foo() { + } +} \ No newline at end of file diff --git a/tests/Validation/cases/newStatic.php.expected.json b/tests/Validation/cases/newStatic.php.expected.json new file mode 100644 index 0000000..cafe114 --- /dev/null +++ b/tests/Validation/cases/newStatic.php.expected.json @@ -0,0 +1,63 @@ +{ + "references": { + "static": [ + "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" + ], + "NewStatic->foo()": [ + "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" + ] + }, + "definitions": { + "NewStatic": { + "extends": [], + "isGlobal": true, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "NewStatic", + "kind": 5, + "location": { + "uri": "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" + }, + "containerName": "" + }, + "type__class": "LanguageServer\\Tests\\ValidationTest", + "type": null, + "documentation": null + }, + "NewStatic::main()": { + "extends": [], + "isGlobal": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "main", + "kind": 6, + "location": { + "uri": "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" + }, + "containerName": "NewStatic" + }, + "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", + "type": {}, + "documentation": null + }, + "NewStatic->foo()": { + "extends": [], + "isGlobal": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "foo", + "kind": 6, + "location": { + "uri": "\/Users\/roblou\/code\/php-language-server\/tests\/Validation\/..\/..\/validation\/frameworks\/_cases\/newStatic.php" + }, + "containerName": "NewStatic" + }, + "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", + "type": {}, + "documentation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/objectCreation.php b/tests/Validation/cases/objectCreation.php new file mode 100644 index 0000000..01437d3 --- /dev/null +++ b/tests/Validation/cases/objectCreation.php @@ -0,0 +1,9 @@ +inline_diff_renderer; + } +} diff --git a/tests/Validation/cases/objectCreation.php.expected.json b/tests/Validation/cases/objectCreation.php.expected.json new file mode 100644 index 0000000..90ad0f5 --- /dev/null +++ b/tests/Validation/cases/objectCreation.php.expected.json @@ -0,0 +1,74 @@ +{ + "references": { + "MyNamespace\\A->inline_diff_renderer": [ + "./objectCreation.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./objectCreation.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./objectCreation.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./objectCreation.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/objectCreation2.php b/tests/Validation/cases/objectCreation2.php new file mode 100644 index 0000000..494cd7f --- /dev/null +++ b/tests/Validation/cases/objectCreation2.php @@ -0,0 +1,12 @@ +hi(); + } +} diff --git a/tests/Validation/cases/objectCreation2.php.expected.json b/tests/Validation/cases/objectCreation2.php.expected.json new file mode 100644 index 0000000..b6f69a3 --- /dev/null +++ b/tests/Validation/cases/objectCreation2.php.expected.json @@ -0,0 +1,97 @@ +{ + "references": { + "MyNamespace\\B->hi()": [ + "./objectCreation2.php" + ], + "MyNamespace\\B": [ + "./objectCreation2.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./objectCreation2.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\B": { + "fqn": "MyNamespace\\B", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "B", + "kind": 5, + "location": { + "uri": "./objectCreation2.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./objectCreation2.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./objectCreation2.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/objectCreation3.php b/tests/Validation/cases/objectCreation3.php new file mode 100644 index 0000000..dc91dcd --- /dev/null +++ b/tests/Validation/cases/objectCreation3.php @@ -0,0 +1,9 @@ +textDocument = new class($this->args) + { + }; + } +} diff --git a/tests/Validation/cases/objectCreation3.php.expected.json b/tests/Validation/cases/objectCreation3.php.expected.json new file mode 100644 index 0000000..a5f389b --- /dev/null +++ b/tests/Validation/cases/objectCreation3.php.expected.json @@ -0,0 +1,54 @@ +{ + "references": { + "A->args": [ + "./objectCreation3.php" + ] + }, + "definitions": { + "A": { + "fqn": "A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./objectCreation3.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "class A {", + "documentation": null, + "signatureInformation": null + }, + "A->a()": { + "fqn": "A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./objectCreation3.php" + }, + "containerName": "A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/param1.php b/tests/Validation/cases/param1.php new file mode 100644 index 0000000..a7128fe --- /dev/null +++ b/tests/Validation/cases/param1.php @@ -0,0 +1,6 @@ +getAccount(); + } + } +} diff --git a/tests/Validation/cases/parameterTypeResolution1.php.expected.json b/tests/Validation/cases/parameterTypeResolution1.php.expected.json new file mode 100644 index 0000000..2910eaf --- /dev/null +++ b/tests/Validation/cases/parameterTypeResolution1.php.expected.json @@ -0,0 +1,66 @@ +{ + "references": { + "ParamType": [ + "./_cases/parameterTypeResolution1.php" + ], + "static": [ + "./_cases/parameterTypeResolution1.php" + ], + "ParamType->getAccount()": [ + "./_cases/parameterTypeResolution1.php" + ] + }, + "definitions": { + "ParamType": { + "extends": [], + "isGlobal": true, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "ParamType", + "kind": 5, + "location": { + "uri": "./_cases/parameterTypeResolution1.php" + }, + "containerName": "" + }, + "type__class": "LanguageServer\\Tests\\ValidationTest", + "type": null, + "documentation": null + }, + "ParamType->setAccount()": { + "extends": [], + "isGlobal": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "setAccount", + "kind": 6, + "location": { + "uri": "./_cases/parameterTypeResolution1.php" + }, + "containerName": "ParamType" + }, + "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", + "type": {}, + "documentation": null + }, + "ParamType->getAccount()": { + "extends": [], + "isGlobal": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "getAccount", + "kind": 6, + "location": { + "uri": "./_cases/parameterTypeResolution1.php" + }, + "containerName": "ParamType" + }, + "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", + "type": {}, + "documentation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/parent1.php b/tests/Validation/cases/parent1.php new file mode 100644 index 0000000..6708e7b --- /dev/null +++ b/tests/Validation/cases/parent1.php @@ -0,0 +1,15 @@ +b()": { + "fqn": "MyNamespace\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./parent1.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [ + "MyNamespace\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./parent1.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A extends B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./parent1.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/parent2.php b/tests/Validation/cases/parent2.php new file mode 100644 index 0000000..bb2955e --- /dev/null +++ b/tests/Validation/cases/parent2.php @@ -0,0 +1,15 @@ +b()": { + "extends": [], + "isGlobal": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./_cases/parent2.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", + "type": {}, + "documentation": null + }, + "MyNamespace\\A": { + "extends": [ + "MyNamespace\\B" + ], + "isGlobal": true, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./_cases/parent2.php" + }, + "containerName": "MyNamespace" + }, + "type__class": "LanguageServer\\Tests\\ValidationTest", + "type": null, + "documentation": null + }, + "MyNamespace\\A->a()": { + "extends": [], + "isGlobal": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./_cases/parent2.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__class": "phpDocumentor\\Reflection\\Types\\Mixed", + "type": {}, + "documentation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/parent3.php b/tests/Validation/cases/parent3.php new file mode 100644 index 0000000..813194d --- /dev/null +++ b/tests/Validation/cases/parent3.php @@ -0,0 +1,15 @@ +b(); + } +} diff --git a/tests/Validation/cases/parent3.php.expected.json b/tests/Validation/cases/parent3.php.expected.json new file mode 100644 index 0000000..9ad39f8 --- /dev/null +++ b/tests/Validation/cases/parent3.php.expected.json @@ -0,0 +1,124 @@ +{ + "references": { + "MyNamespace\\B": [ + "./parent3.php" + ], + "MyNamespace\\B->b()": [ + "./parent3.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./parent3.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\B": { + "fqn": "MyNamespace\\B", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "B", + "kind": 5, + "location": { + "uri": "./parent3.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\B->b()": { + "fqn": "MyNamespace\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./parent3.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [ + "MyNamespace\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./parent3.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A extends B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./parent3.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/propertyName1.php b/tests/Validation/cases/propertyName1.php new file mode 100644 index 0000000..c53cf54 --- /dev/null +++ b/tests/Validation/cases/propertyName1.php @@ -0,0 +1,12 @@ +mainPropertyName": { + "fqn": "MyClass->mainPropertyName", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "mainPropertyName", + "kind": 7, + "location": { + "uri": "./propertyName1.php" + }, + "containerName": "MyClass" + }, + "type__tostring": "string", + "type": {}, + "declarationLine": "protected $mainPropertyName;", + "documentation": "The name of the main property, or NULL if there is none.", + "signatureInformation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/propertyName2.php b/tests/Validation/cases/propertyName2.php new file mode 100644 index 0000000..d79f758 --- /dev/null +++ b/tests/Validation/cases/propertyName2.php @@ -0,0 +1,10 @@ +mainPropertyName": { + "fqn": "MyClass->mainPropertyName", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "mainPropertyName", + "kind": 7, + "location": { + "uri": "./propertyName2.php" + }, + "containerName": "MyClass" + }, + "type__tostring": "string", + "type": {}, + "declarationLine": "protected $mainPropertyName;", + "documentation": "The name of the main property, or NULL if there is none.", + "signatureInformation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/returnType.php b/tests/Validation/cases/returnType.php new file mode 100644 index 0000000..5aad940 --- /dev/null +++ b/tests/Validation/cases/returnType.php @@ -0,0 +1,13 @@ +testProperty; \ No newline at end of file diff --git a/tests/Validation/cases/scopedPropertyAccess5.php.expected.json b/tests/Validation/cases/scopedPropertyAccess5.php.expected.json new file mode 100644 index 0000000..ee944ed --- /dev/null +++ b/tests/Validation/cases/scopedPropertyAccess5.php.expected.json @@ -0,0 +1,59 @@ +{ + "references": { + "TestInterface": [ + "./scopedPropertyAccess5.php" + ], + "TestClass": [ + "./scopedPropertyAccess5.php" + ], + "TestClass::$testProperty": [ + "./scopedPropertyAccess5.php" + ], + "TestClass::$staticTestProperty": [ + "./scopedPropertyAccess5.php" + ] + }, + "definitions": { + "TestClass": { + "fqn": "TestClass", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "TestClass", + "kind": 5, + "location": { + "uri": "./scopedPropertyAccess5.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "class TestClass implements TestInterface {", + "documentation": null, + "signatureInformation": null + }, + "TestClass::$testProperty": { + "fqn": "TestClass::$testProperty", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "testProperty", + "kind": 7, + "location": { + "uri": "./scopedPropertyAccess5.php" + }, + "containerName": "TestClass" + }, + "type__tostring": "\\TestClass[]", + "type": {}, + "declarationLine": "public static $testProperty;", + "documentation": "Lorem excepteur officia sit anim velit veniam enim.", + "signatureInformation": null + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/self1.php b/tests/Validation/cases/self1.php new file mode 100644 index 0000000..e286381 --- /dev/null +++ b/tests/Validation/cases/self1.php @@ -0,0 +1,16 @@ +b()": { + "fqn": "MyNamespace\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./self1.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [ + "MyNamespace\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./self1.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A extends B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./self1.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/self2.php b/tests/Validation/cases/self2.php new file mode 100644 index 0000000..50a2c6e --- /dev/null +++ b/tests/Validation/cases/self2.php @@ -0,0 +1,15 @@ +b()": { + "fqn": "MyNamespace\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./self2.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [ + "MyNamespace\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./self2.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A extends B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./self2.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/self3.php b/tests/Validation/cases/self3.php new file mode 100644 index 0000000..5034502 --- /dev/null +++ b/tests/Validation/cases/self3.php @@ -0,0 +1,15 @@ +b(); + } +} diff --git a/tests/Validation/cases/self3.php.expected.json b/tests/Validation/cases/self3.php.expected.json new file mode 100644 index 0000000..0a43146 --- /dev/null +++ b/tests/Validation/cases/self3.php.expected.json @@ -0,0 +1,127 @@ +{ + "references": { + "MyNamespace\\B": [ + "./self3.php" + ], + "MyNamespace\\A->b()": [ + "./self3.php" + ], + "MyNamespace\\A": [ + "./self3.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./self3.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\B": { + "fqn": "MyNamespace\\B", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "B", + "kind": 5, + "location": { + "uri": "./self3.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\B->b()": { + "fqn": "MyNamespace\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./self3.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [ + "MyNamespace\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./self3.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A extends B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./self3.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/self4.php b/tests/Validation/cases/self4.php new file mode 100644 index 0000000..4f0ffe1 --- /dev/null +++ b/tests/Validation/cases/self4.php @@ -0,0 +1,13 @@ +addTestFile(__DIR__ . DS . 'Database' . DS . 'ConnectionTest.php'); + + } +} \ No newline at end of file diff --git a/tests/Validation/cases/self4.php.expected.json b/tests/Validation/cases/self4.php.expected.json new file mode 100644 index 0000000..b925fdd --- /dev/null +++ b/tests/Validation/cases/self4.php.expected.json @@ -0,0 +1,89 @@ +{ + "references": { + "MyNamespace\\A": [ + "./self4.php" + ], + "MyNamespace\\A->addTestFile()": [ + "./self4.php" + ], + "MyNamespace\\__DIR__": [ + "./self4.php" + ], + "__DIR__": [ + "./self4.php" + ], + "MyNamespace\\DS": [ + "./self4.php" + ], + "DS": [ + "./self4.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./self4.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./self4.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A::suite()": { + "fqn": "MyNamespace\\A::suite()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "suite", + "kind": 6, + "location": { + "uri": "./self4.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public static function suite()", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/self5.php b/tests/Validation/cases/self5.php new file mode 100644 index 0000000..0fcd08f --- /dev/null +++ b/tests/Validation/cases/self5.php @@ -0,0 +1,12 @@ +assertTrue("HI"); + } +} \ No newline at end of file diff --git a/tests/Validation/cases/self5.php.expected.json b/tests/Validation/cases/self5.php.expected.json new file mode 100644 index 0000000..10ba7fb --- /dev/null +++ b/tests/Validation/cases/self5.php.expected.json @@ -0,0 +1,74 @@ +{ + "references": { + "MyNamespace\\A->assertTrue()": [ + "./self5.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./self5.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./self5.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->typesProvider()": { + "fqn": "MyNamespace\\A->typesProvider()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "typesProvider", + "kind": 6, + "location": { + "uri": "./self5.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public function typesProvider()", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/static1.php b/tests/Validation/cases/static1.php new file mode 100644 index 0000000..f512e00 --- /dev/null +++ b/tests/Validation/cases/static1.php @@ -0,0 +1,15 @@ +b()": { + "fqn": "MyNamespace\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./static1.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [ + "MyNamespace\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./static1.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A extends B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./static1.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/static2.php b/tests/Validation/cases/static2.php new file mode 100644 index 0000000..088682a --- /dev/null +++ b/tests/Validation/cases/static2.php @@ -0,0 +1,15 @@ +b()": { + "fqn": "MyNamespace\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./static2.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [ + "MyNamespace\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./static2.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A extends B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./static2.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/static3.php b/tests/Validation/cases/static3.php new file mode 100644 index 0000000..62a3025 --- /dev/null +++ b/tests/Validation/cases/static3.php @@ -0,0 +1,15 @@ +b(); + } +} diff --git a/tests/Validation/cases/static3.php.expected.json b/tests/Validation/cases/static3.php.expected.json new file mode 100644 index 0000000..a88f554 --- /dev/null +++ b/tests/Validation/cases/static3.php.expected.json @@ -0,0 +1,127 @@ +{ + "references": { + "MyNamespace\\B": [ + "./static3.php" + ], + "static->b()": [ + "./static3.php" + ], + "MyNamespace\\A": [ + "./static3.php" + ] + }, + "definitions": { + "MyNamespace": { + "fqn": "MyNamespace", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "MyNamespace", + "kind": 3, + "location": { + "uri": "./static3.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "namespace MyNamespace;", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\B": { + "fqn": "MyNamespace\\B", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "B", + "kind": 5, + "location": { + "uri": "./static3.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\B->b()": { + "fqn": "MyNamespace\\B->b()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "b", + "kind": 6, + "location": { + "uri": "./static3.php" + }, + "containerName": "MyNamespace\\B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function b() {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, + "MyNamespace\\A": { + "fqn": "MyNamespace\\A", + "extends": [ + "MyNamespace\\B" + ], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "A", + "kind": 5, + "location": { + "uri": "./static3.php" + }, + "containerName": "MyNamespace" + }, + "type": null, + "declarationLine": "class A extends B {", + "documentation": null, + "signatureInformation": null + }, + "MyNamespace\\A->a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./static3.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/static4.php b/tests/Validation/cases/static4.php new file mode 100644 index 0000000..bada62f --- /dev/null +++ b/tests/Validation/cases/static4.php @@ -0,0 +1,9 @@ +a()": { + "fqn": "MyNamespace\\A->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./static4.php" + }, + "containerName": "MyNamespace\\A" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/staticInArray.php b/tests/Validation/cases/staticInArray.php new file mode 100644 index 0000000..fbfaba9 --- /dev/null +++ b/tests/Validation/cases/staticInArray.php @@ -0,0 +1,5 @@ +bar()": { + "fqn": "FooClass->bar()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "bar", + "kind": 6, + "location": { + "uri": "./staticMethodReturnType.php" + }, + "containerName": "FooClass" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public function bar() { }", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/stringVariable.php b/tests/Validation/cases/stringVariable.php new file mode 100644 index 0000000..a3685c9 --- /dev/null +++ b/tests/Validation/cases/stringVariable.php @@ -0,0 +1,9 @@ +hi"; + } +} \ No newline at end of file diff --git a/tests/Validation/cases/stringVariable.php.expected.json b/tests/Validation/cases/stringVariable.php.expected.json new file mode 100644 index 0000000..6372c89 --- /dev/null +++ b/tests/Validation/cases/stringVariable.php.expected.json @@ -0,0 +1,75 @@ +{ + "references": { + "B->hi": [ + "./stringVariable.php" + ] + }, + "definitions": { + "B": { + "fqn": "B", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "B", + "kind": 5, + "location": { + "uri": "./stringVariable.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "class B", + "documentation": null, + "signatureInformation": null + }, + "B->hi": { + "fqn": "B->hi", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "hi", + "kind": 7, + "location": { + "uri": "./stringVariable.php" + }, + "containerName": "B" + }, + "type__tostring": "int", + "type": {}, + "declarationLine": "public $hi;", + "documentation": null, + "signatureInformation": null + }, + "B->a()": { + "fqn": "B->a()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "a", + "kind": 6, + "location": { + "uri": "./stringVariable.php" + }, + "containerName": "B" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "function a () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/cases/testQualifiedNameOutsideOfNamespace.php b/tests/Validation/cases/testQualifiedNameOutsideOfNamespace.php new file mode 100644 index 0000000..61fe57e --- /dev/null +++ b/tests/Validation/cases/testQualifiedNameOutsideOfNamespace.php @@ -0,0 +1,5 @@ +bar = 'hello'; + } +} diff --git a/tests/Validation/cases/verifyFqsenOnClassProperty.php.expected.json b/tests/Validation/cases/verifyFqsenOnClassProperty.php.expected.json new file mode 100644 index 0000000..a434cf2 --- /dev/null +++ b/tests/Validation/cases/verifyFqsenOnClassProperty.php.expected.json @@ -0,0 +1,78 @@ +{ + "references": { + "CURLAUTH_BASIC": [ + "./verifyFqsenOnClassProperty.php" + ], + "Foo->bar": [ + "./verifyFqsenOnClassProperty.php" + ] + }, + "definitions": { + "Foo": { + "fqn": "Foo", + "extends": [], + "isMember": false, + "roamed": false, + "isStatic": false, + "canBeInstantiated": true, + "symbolInformation": { + "name": "Foo", + "kind": 5, + "location": { + "uri": "./verifyFqsenOnClassProperty.php" + }, + "containerName": "" + }, + "type": null, + "declarationLine": "class Foo {", + "documentation": null, + "signatureInformation": null + }, + "Foo->bar": { + "fqn": "Foo->bar", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "bar", + "kind": 7, + "location": { + "uri": "./verifyFqsenOnClassProperty.php" + }, + "containerName": "Foo" + }, + "type__tostring": "\\", + "type": {}, + "declarationLine": "protected $bar;", + "documentation": null, + "signatureInformation": null + }, + "Foo->foo()": { + "fqn": "Foo->foo()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": false, + "canBeInstantiated": false, + "symbolInformation": { + "name": "foo", + "kind": 6, + "location": { + "uri": "./verifyFqsenOnClassProperty.php" + }, + "containerName": "Foo" + }, + "type__tostring": "mixed", + "type": {}, + "declarationLine": "public function foo () {", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + } + } +} \ No newline at end of file diff --git a/tests/Validation/disabled.json b/tests/Validation/disabled.json new file mode 100644 index 0000000..5e223ef --- /dev/null +++ b/tests/Validation/disabled.json @@ -0,0 +1,7 @@ +[ + "forLoopReference1.php", + "namespaces3.php", + "parameterTypeResolution1.php", + "parent2.php", + "newStatic.php" +] \ No newline at end of file diff --git a/validation/frameworks/cakephp b/validation/frameworks/cakephp new file mode 160000 index 0000000..0450ecc --- /dev/null +++ b/validation/frameworks/cakephp @@ -0,0 +1 @@ +Subproject commit 0450ecc030ae37ca0a3f8c0e4e56ce9ceec8402d diff --git a/validation/frameworks/codeigniter b/validation/frameworks/codeigniter new file mode 160000 index 0000000..c06bc67 --- /dev/null +++ b/validation/frameworks/codeigniter @@ -0,0 +1 @@ +Subproject commit c06bc67d6c4059b3d1050221d5b1624ac4e2f1f8 diff --git a/validation/frameworks/drupal b/validation/frameworks/drupal new file mode 160000 index 0000000..ac7313d --- /dev/null +++ b/validation/frameworks/drupal @@ -0,0 +1 @@ +Subproject commit ac7313dda0644f35031428ff954b6d9e6f90e857 diff --git a/validation/frameworks/math-php b/validation/frameworks/math-php new file mode 160000 index 0000000..601baa6 --- /dev/null +++ b/validation/frameworks/math-php @@ -0,0 +1 @@ +Subproject commit 601baa6267cc4a357c8032d9407a0206975aa26e diff --git a/validation/frameworks/php-language-server b/validation/frameworks/php-language-server new file mode 160000 index 0000000..546660f --- /dev/null +++ b/validation/frameworks/php-language-server @@ -0,0 +1 @@ +Subproject commit 546660f957623b2cdc179fe107b28581d60ba190 diff --git a/validation/frameworks/phpunit b/validation/frameworks/phpunit new file mode 160000 index 0000000..bb74d4e --- /dev/null +++ b/validation/frameworks/phpunit @@ -0,0 +1 @@ +Subproject commit bb74d4eac541cf63f3454ca5fa31c4a20391032b diff --git a/validation/frameworks/symfony b/validation/frameworks/symfony new file mode 160000 index 0000000..9d9f628 --- /dev/null +++ b/validation/frameworks/symfony @@ -0,0 +1 @@ +Subproject commit 9d9f628d926aaf34e020ddccb4454ff7c4ce5ceb diff --git a/validation/frameworks/tolerant-php-parser b/validation/frameworks/tolerant-php-parser new file mode 160000 index 0000000..6ce1e1f --- /dev/null +++ b/validation/frameworks/tolerant-php-parser @@ -0,0 +1 @@ +Subproject commit 6ce1e1f978f1c17d555b83660b97899f0d9dbeec diff --git a/validation/frameworks/wordpress b/validation/frameworks/wordpress new file mode 160000 index 0000000..afdef17 --- /dev/null +++ b/validation/frameworks/wordpress @@ -0,0 +1 @@ +Subproject commit afdef17903c52c15642be01169495e08a08b04a7