upgraded dependencies

This commit is contained in:
RafficMohammed
2023-01-08 01:59:16 +05:30
parent 51056e3aad
commit f9ae387337
6895 changed files with 133617 additions and 178680 deletions

View File

@@ -1,15 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

View File

@@ -1,9 +0,0 @@
## Code style
Please make your code look like the other code in the project.
PsySH follows [PSR-1](http://php-fig.org/psr/psr-1/) and [PSR-2](http://php-fig.org/psr/psr-2/). The easiest way to do make sure you're following the coding standard is to [install `php-cs-fixer`](https://github.com/friendsofphp/php-cs-fixer) and run `php-cs-fixer fix` before committing.
## Branching model
Please branch off and send pull requests to the `develop` branch.

View File

@@ -1,9 +0,0 @@
/build/
/dist/
/composer.lock
/manual/
/psysh
/__pycache__
/.php_cs.cache
/vendor/
/vendor-bin/*/vendor/

View File

@@ -1,46 +0,0 @@
<?php
/**
* This configuration will be read and overlaid on top of the
* default configuration. Command line arguments will be applied
* after this file is read.
*/
return [
// A list of directories that should be parsed for class and
// method information. After excluding the directories
// defined in exclude_analysis_directory_list, the remaining
// files will be statically analyzed for errors.
//
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
'src/',
'vendor/dnoegel/php-xdg-base-dir/src/',
'vendor/doctrine/instantiator/src/',
'vendor/hoa/console/',
'vendor/jakub-onderka/php-console-color/src/',
'vendor/jakub-onderka/php-console-highlighter/src/',
'vendor/nikic/php-parser/lib/',
'vendor/phpdocumentor/reflection-docblock/',
'vendor/symfony/console/',
'vendor/symfony/filesystem/',
'vendor/symfony/finder/',
'vendor/symfony/var-dumper/',
],
// A directory list that defines files that will be excluded
// from static analysis, but whose class and method
// information should be included.
//
// Generally, you'll want to include the directories for
// third-party code (such as "vendor/") in this list.
//
// n.b.: If you'd like to parse but not analyze 3rd
// party code, directories containing that code
// should be added to both the `directory_list`
// and `exclude_analysis_directory_list` arrays.
"exclude_analysis_directory_list" => [
'vendor/'
],
];

View File

@@ -1,32 +0,0 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in(__DIR__)
->name('.php_cs')
->name('build-manual')
->name('build-phar')
->exclude('build-vendor');
$header = <<<EOF
This file is part of Psy Shell.
(c) 2012-2018 Justin Hileman
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF;
return PhpCsFixer\Config::create()
->setRules(array(
'@Symfony' => true,
'array_syntax' => array('syntax' => 'short'),
'binary_operator_spaces' => false,
'concat_space' => array('spacing' => 'one'),
'header_comment' => array('header' => $header),
'increment_style' => array('style' => 'post'),
'method_argument_space' => array('keep_multiple_spaces_after_comma' => true),
'ordered_imports' => true,
'pre_increment' => false,
'yoda_style' => false,
))
->setFinder($finder);

View File

@@ -1,29 +0,0 @@
preset: symfony
enabled:
- align_double_arrow
- concat_with_spaces
- short_array_syntax
- ordered_use
- strict
disabled:
- blank_line_before_break
- blank_line_before_continue
- blank_line_before_throw
- blank_line_before_try
- concat_without_spaces
- method_argument_space
- pre_increment
- unalign_double_arrow
- unalign_equals
- yoda_style
- property_separation
- const_separation
finder:
name:
- "*.php"
- ".php_cs"
- "build-manual"
- "build-phar"

View File

@@ -1,47 +0,0 @@
language: php
sudo: false
matrix:
include:
- php: 5.4
dist: trusty
- php: 5.4
env: 'COMPOSER_FLAGS="--prefer-lowest --prefer-stable"'
dist: trusty
- php: 5.5
dist: trusty
- php: 5.6
- php: 7.0
- php: 7.1
- php: 7.2
- php: hhvm
dist: trusty
allow_failures:
- php: 5.4
env: 'COMPOSER_FLAGS="--prefer-lowest --prefer-stable"'
- php: hhvm
fast_finish: true
install: travis_retry composer update --no-interaction $COMPOSER_FLAGS
script:
- vendor/bin/phpunit --verbose --coverage-clover=coverage.xml
- '[[ $TRAVIS_PHP_VERSION = 7.2* ]] && make build -j 4 || true'
after_success:
- bash <(curl -s https://codecov.io/bash)
before_deploy: make dist -j 4
deploy:
provider: releases
api_key:
secure: LL8koDM1xDqzF9t0URHvmMPyWjojyd4PeZ7IW7XYgyvD6n1H6GYrVAeKCh5wfUKFbwHoa9s5AAn6pLzra00bODVkPTmUH+FSMWz9JKLw9ODAn8HvN7C+IooxmeClGHFZc0TfHfya8/D1E9C1iXtGGEoE/GqtaYq/z0C1DLpO0OU=
file_glob: true
file: dist/psysh-*.tar.gz
skip_cleanup: true
on:
tags: true
repo: bobthecow/psysh
condition: $TRAVIS_PHP_VERSION = 7.2*

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2012-2018 Justin Hileman
Copyright (c) 2012-2022 Justin Hileman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,95 +0,0 @@
PSYSH_SRC = bin src box.json.dist composer.json build/stub
PSYSH_SRC_FILES = $(shell find src -type f -name "*.php")
VERSION = $(shell git describe --tag --always --dirty=-dev)
COMPOSER_OPTS = --no-interaction --no-progress --verbose
COMPOSER_REQUIRE_OPTS = $(COMPOSER_OPTS) --no-update
COMPOSER_UPDATE_OPTS = $(COMPOSER_OPTS) --prefer-stable --no-dev --classmap-authoritative --prefer-dist
# Commands
.PHONY: help clean build dist
.DEFAULT_GOAL := help
help:
@echo "\033[33mUsage:\033[0m\n make TARGET\n\n\033[33mTargets:\033[0m"
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[32m%-7s\033[0m %s\n", $$1, $$2}'
clean: ## Clean all created artifacts
rm -rf build/*
rm -rf dist/*
rm -rf vendor-bin/*/vendor/
build: ## Compile PHARs
build: build/psysh/psysh build/psysh-compat/psysh build/psysh-php54/psysh build/psysh-php54-compat/psysh
dist: ## Build tarballs for distribution
dist: dist/psysh-$(VERSION).tar.gz dist/psysh-$(VERSION)-compat.tar.gz dist/psysh-$(VERSION)-php54.tar.gz dist/psysh-$(VERSION)-php54-compat.tar.gz
# All the composer stuffs
composer.lock: composer.json
composer install
touch $@
vendor/autoload.php: composer.lock
composer install
touch $@
vendor/bin/box: vendor/autoload.php
composer bin box install
touch $@
# Lots of PHARs
build/stub: bin/build-stub bin/psysh LICENSE
bin/build-stub
build/psysh: $(PSYSH_SRC) $(PSYSH_SRC_FILES)
rm -rf $@ || true
mkdir $@
cp -R $(PSYSH_SRC) $@/
composer config --working-dir $@ platform.php 7.0
composer require --working-dir $@ $(COMPOSER_REQUIRE_OPTS) php:'>=7.0.0'
composer update --working-dir $@ $(COMPOSER_UPDATE_OPTS)
build/psysh-compat: $(PSYSH_SRC) $(PSYSH_SRC_FILES)
rm -rf $@ || true
mkdir $@
cp -R $(PSYSH_SRC) $@/
composer config --working-dir $@ platform.php 7.0
composer require --working-dir $@ $(COMPOSER_REQUIRE_OPTS) php:'>=7.0.0'
composer require --working-dir $@ $(COMPOSER_REQUIRE_OPTS) symfony/polyfill-iconv symfony/polyfill-mbstring hoa/console
composer update --working-dir $@ $(COMPOSER_UPDATE_OPTS)
build/psysh-php54: $(PSYSH_SRC) $(PSYSH_SRC_FILES)
rm -rf $@ || true
mkdir $@
cp -R $(PSYSH_SRC) $@/
composer config --working-dir $@ platform.php 5.4
composer update --working-dir $@ $(COMPOSER_UPDATE_OPTS)
build/psysh-php54-compat: $(PSYSH_SRC) $(PSYSH_SRC_FILES)
rm -rf $@ || true
mkdir $@
cp -R $(PSYSH_SRC) $@/
composer config --working-dir $@ platform.php 5.4
composer require --working-dir $@ $(COMPOSER_REQUIRE_OPTS) symfony/polyfill-iconv symfony/polyfill-mbstring hoa/console:^2.15
composer update --working-dir $@ $(COMPOSER_UPDATE_OPTS)
build/%/psysh: vendor/bin/box build/%
vendor/bin/box compile --working-dir $(dir $@)
# Dist packages
dist/psysh-$(VERSION).tar.gz: build/psysh/psysh
@mkdir -p $(@D)
tar -C $(dir $<) -czf $@ $(notdir $<)
dist/psysh-$(VERSION)-%.tar.gz: build/psysh-%/psysh
@mkdir -p $(@D)
tar -C $(dir $<) -czf $@ $(notdir $<)

View File

@@ -7,7 +7,7 @@ PsySH is a runtime developer console, interactive debugger and [REPL](https://en
[![Monthly downloads](http://img.shields.io/packagist/dm/psy/psysh.svg?style=flat-square)](https://packagist.org/packages/psy/psysh)
[![Made out of awesome](https://img.shields.io/badge/made_out_of_awesome-✓-brightgreen.svg?style=flat-square)](http://psysh.org)
[![Build status](https://img.shields.io/travis/bobthecow/psysh/master.svg?style=flat-square)](http://travis-ci.org/bobthecow/psysh)
[![Build status](https://img.shields.io/github/actions/workflow/status/bobthecow/psysh/tests.yml?branch=main&style=flat-square)](https://github.com/bobthecow/psysh/actions?query=branch:main)
[![StyleCI](https://styleci.io/repos/4549925/shield)](https://styleci.io/repos/4549925)
@@ -24,11 +24,13 @@ PsySH is a runtime developer console, interactive debugger and [REPL](https://en
* [⏳ Managing history](https://github.com/bobthecow/psysh/wiki/History)
* [💲 System shell integration](https://github.com/bobthecow/psysh/wiki/Shell-integration)
* [🎥 Tutorials & guides](https://github.com/bobthecow/psysh/wiki/Tutorials)
* [🐛 Troubleshooting](https://github.com/bobthecow/psysh/wiki/Troubleshooting)
### [📢 Commands](https://github.com/bobthecow/psysh/wiki/Commands)
### [🛠 Configuration](https://github.com/bobthecow/psysh/wiki/Configuration)
* [🎛 Config options](https://github.com/bobthecow/psysh/wiki/Config-options)
* [🎨 Themes](https://github.com/bobthecow/psysh/wiki/Themes)
* [📄 Sample config file](https://github.com/bobthecow/psysh/wiki/Sample-config)
### [🔌 Integrations](https://github.com/bobthecow/psysh/wiki/Integrations)

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env php
<?php
$license = file_get_contents(dirname(__DIR__) . '/LICENSE');
$license = str_replace('The MIT License (MIT)', '', $license);
$license = str_replace("\n", "\n * ", trim($license));
$autoload = <<<'EOS'
Phar::mapPhar('psysh.phar');
require 'phar://psysh.phar/.box/check_requirements.php';
require 'phar://psysh.phar/vendor/autoload.php';
EOS;
$content = file_get_contents(dirname(__DIR__) . '/bin/psysh');
$content = preg_replace('{/\* <<<.*?>>> \*/}sm', $autoload, $content);
$content = preg_replace('/\\(c\\) .*?with this source code./sm', $license, $content);
$content .= '__HALT_COMPILER();';
@mkdir(dirname(__DIR__) . '/build');
file_put_contents(dirname(__DIR__) . '/build/stub', $content);

View File

@@ -4,7 +4,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2017 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -20,7 +20,7 @@ call_user_func(function () {
foreach ($argv as $i => $arg) {
if ($arg === '--cwd') {
if ($i >= count($argv) - 1) {
echo 'Missing --cwd argument.' . PHP_EOL;
fwrite(STDERR, 'Missing --cwd argument.' . PHP_EOL);
exit(1);
}
$cwd = $argv[$i + 1];
@@ -43,14 +43,21 @@ call_user_func(function () {
$chunks = explode('/', $cwd);
while (!empty($chunks)) {
$path = implode('/', $chunks);
$prettyPath = $path;
if (isset($_SERVER['HOME']) && $_SERVER['HOME']) {
$prettyPath = preg_replace('/^' . preg_quote($_SERVER['HOME'], '/') . '/', '~', $path);
}
// Find composer.json
if (is_file($path . '/composer.json')) {
if ($cfg = json_decode(file_get_contents($path . '/composer.json'), true)) {
if (isset($cfg['name']) && $cfg['name'] === 'psy/psysh') {
// We're inside the psysh project. Let's use the local
// Composer autoload.
// We're inside the psysh project. Let's use the local Composer autoload.
if (is_file($path . '/vendor/autoload.php')) {
if (realpath($path) !== realpath(__DIR__ . '/..')) {
fwrite(STDERR, 'Using local PsySH version at ' . $prettyPath . PHP_EOL);
}
require $path . '/vendor/autoload.php';
}
@@ -64,9 +71,12 @@ call_user_func(function () {
if ($cfg = json_decode(file_get_contents($path . '/composer.lock'), true)) {
foreach (array_merge($cfg['packages'], $cfg['packages-dev']) as $pkg) {
if (isset($pkg['name']) && $pkg['name'] === 'psy/psysh') {
// We're inside a project which requires psysh. We'll
// use the local Composer autoload.
// We're inside a project which requires psysh. We'll use the local Composer autoload.
if (is_file($path . '/vendor/autoload.php')) {
if (realpath($path . '/vendor') !== realpath(__DIR__ . '/../../..')) {
fwrite(STDERR, 'Using local PsySH version at ' . $prettyPath . PHP_EOL);
}
require $path . '/vendor/autoload.php';
}
@@ -89,8 +99,8 @@ if (!class_exists('Psy\Shell')) {
} elseif (is_file(__DIR__ . '/../../../autoload.php')) {
require __DIR__ . '/../../../autoload.php';
} else {
echo 'PsySH dependencies not found, be sure to run `composer install`.' . PHP_EOL;
echo 'See https://getcomposer.org to get Composer.' . PHP_EOL;
fwrite(STDERR, 'PsySH dependencies not found, be sure to run `composer install`.' . PHP_EOL);
fwrite(STDERR, 'See https://getcomposer.org to get Composer.' . PHP_EOL);
exit(1);
}
/* >>> */
@@ -99,14 +109,14 @@ if (!class_exists('Psy\Shell')) {
// If the psysh binary was included directly, assume they just wanted an
// autoloader and bail early.
//
// Keep this PHP 5.3 code around for a while in case someone is using a globally
// installed psysh as a bin launcher for older local versions.
// Keep this PHP 5.3 and 5.4 code around for a while in case someone is using a
// globally installed psysh as a bin launcher for older local versions.
if (version_compare(PHP_VERSION, '5.3.6', '<')) {
$trace = debug_backtrace();
} elseif (version_compare(PHP_VERSION, '5.4.0', '<')) {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
} else {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
}
if (Psy\Shell::isIncluded($trace)) {
@@ -120,17 +130,17 @@ unset($trace);
// If the local version is too old, we can't do this
if (!function_exists('Psy\bin')) {
$argv = $_SERVER['argv'];
$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array();
$first = array_shift($argv);
if (preg_match('/php(\.exe)?$/', $first)) {
array_shift($argv);
}
array_unshift($argv, 'vendor/bin/psysh');
echo 'A local PsySH dependency was found, but it cannot be loaded. Please update to' . PHP_EOL;
echo 'the latest version, or run the local copy directly, e.g.:' . PHP_EOL;
echo PHP_EOL;
echo ' ' . implode(' ', $argv) . PHP_EOL;
fwrite(STDERR, 'A local PsySH dependency was found, but it cannot be loaded. Please update to' . PHP_EOL);
fwrite(STDERR, 'the latest version, or run the local copy directly, e.g.:' . PHP_EOL);
fwrite(STDERR, PHP_EOL);
fwrite(STDERR, ' ' . implode(' ', $argv) . PHP_EOL);
exit(1);
}

View File

@@ -1,12 +0,0 @@
{
"stub": "stub",
"output": "psysh",
"compactors": [
"KevinGH\\Box\\Compactor\\Php"
],
"blacklist": [
"grammar",
"test_old",
"Documentation"
]
}

View File

@@ -13,26 +13,21 @@
}
],
"require": {
"php": ">=5.4.0",
"php": "^8.0 || ^7.0.8",
"ext-json": "*",
"ext-tokenizer": "*",
"symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0|~5.0",
"symfony/var-dumper": "~2.7|~3.0|~4.0|~5.0",
"nikic/php-parser": "~1.3|~2.0|~3.0|~4.0",
"dnoegel/php-xdg-base-dir": "0.1.*",
"jakub-onderka/php-console-highlighter": "0.3.*|0.4.*"
"symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4",
"symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4",
"nikic/php-parser": "^4.0 || ^3.1"
},
"require-dev": {
"phpunit/phpunit": "~4.8.35|~5.0|~6.0|~7.0",
"hoa/console": "~2.15|~3.16",
"bamarni/composer-bin-plugin": "^1.2"
},
"suggest": {
"ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
"ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.",
"ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.",
"ext-pdo-sqlite": "The doc command requires SQLite to work.",
"hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit."
"ext-pdo-sqlite": "The doc command requires SQLite to work."
},
"autoload": {
"files": ["src/functions.php"],
@@ -46,9 +41,17 @@
}
},
"bin": ["bin/psysh"],
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"branch-alias": {
"dev-develop": "0.9.x-dev"
"dev-main": "0.11.x-dev"
}
},
"conflict": {
"symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4"
}
}

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" colors="true" bootstrap="vendor/autoload.php">
<testsuite name="PsySH">
<directory suffix="Test.php">./test</directory>
</testsuite>
<filter>
<whitelist>
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -18,24 +18,26 @@ use Psy\CodeCleaner\AbstractClassPass;
use Psy\CodeCleaner\AssignThisVariablePass;
use Psy\CodeCleaner\CalledClassPass;
use Psy\CodeCleaner\CallTimePassByReferencePass;
use Psy\CodeCleaner\EmptyArrayDimFetchPass;
use Psy\CodeCleaner\ExitPass;
use Psy\CodeCleaner\FinalClassPass;
use Psy\CodeCleaner\FunctionContextPass;
use Psy\CodeCleaner\FunctionReturnInWriteContextPass;
use Psy\CodeCleaner\ImplicitReturnPass;
use Psy\CodeCleaner\InstanceOfPass;
use Psy\CodeCleaner\IssetPass;
use Psy\CodeCleaner\LabelContextPass;
use Psy\CodeCleaner\LeavePsyshAlonePass;
use Psy\CodeCleaner\LegacyEmptyPass;
use Psy\CodeCleaner\ListPass;
use Psy\CodeCleaner\LoopContextPass;
use Psy\CodeCleaner\MagicConstantsPass;
use Psy\CodeCleaner\NamespacePass;
use Psy\CodeCleaner\PassableByReferencePass;
use Psy\CodeCleaner\RequirePass;
use Psy\CodeCleaner\ReturnTypePass;
use Psy\CodeCleaner\StrictTypesPass;
use Psy\CodeCleaner\UseStatementPass;
use Psy\CodeCleaner\ValidClassNamePass;
use Psy\CodeCleaner\ValidConstantPass;
use Psy\CodeCleaner\ValidConstructorPass;
use Psy\CodeCleaner\ValidFunctionNamePass;
use Psy\Exception\ParseErrorException;
@@ -46,6 +48,7 @@ use Psy\Exception\ParseErrorException;
*/
class CodeCleaner
{
private $yolo = false;
private $parser;
private $printer;
private $traverser;
@@ -54,19 +57,22 @@ class CodeCleaner
/**
* CodeCleaner constructor.
*
* @param Parser $parser A PhpParser Parser instance. One will be created if not explicitly supplied
* @param Printer $printer A PhpParser Printer instance. One will be created if not explicitly supplied
* @param NodeTraverser $traverser A PhpParser NodeTraverser instance. One will be created if not explicitly supplied
* @param Parser|null $parser A PhpParser Parser instance. One will be created if not explicitly supplied
* @param Printer|null $printer A PhpParser Printer instance. One will be created if not explicitly supplied
* @param NodeTraverser|null $traverser A PhpParser NodeTraverser instance. One will be created if not explicitly supplied
* @param bool $yolo run without input validation
*/
public function __construct(Parser $parser = null, Printer $printer = null, NodeTraverser $traverser = null)
public function __construct(Parser $parser = null, Printer $printer = null, NodeTraverser $traverser = null, bool $yolo = false)
{
$this->yolo = $yolo;
if ($parser === null) {
$parserFactory = new ParserFactory();
$parser = $parserFactory->createParser();
$parser = $parserFactory->createParser();
}
$this->parser = $parser;
$this->printer = $printer ?: new Printer();
$this->parser = $parser;
$this->printer = $printer ?: new Printer();
$this->traverser = $traverser ?: new NodeTraverser();
foreach ($this->getDefaultPasses() as $pass) {
@@ -74,15 +80,29 @@ class CodeCleaner
}
}
/**
* Check whether this CodeCleaner is in YOLO mode.
*
* @return bool
*/
public function yolo(): bool
{
return $this->yolo;
}
/**
* Get default CodeCleaner passes.
*
* @return array
*/
private function getDefaultPasses()
private function getDefaultPasses(): array
{
if ($this->yolo) {
return $this->getYoloPasses();
}
$useStatementPass = new UseStatementPass();
$namespacePass = new NamespacePass($this);
$namespacePass = new NamespacePass($this);
// Try to add implicit `use` statements and an implicit namespace,
// based on the file in which the `debug` call was made.
@@ -98,11 +118,14 @@ class CodeCleaner
new FunctionContextPass(),
new FunctionReturnInWriteContextPass(),
new InstanceOfPass(),
new IssetPass(),
new LabelContextPass(),
new LeavePsyshAlonePass(),
new LegacyEmptyPass(),
new ListPass(),
new LoopContextPass(),
new PassableByReferencePass(),
new ReturnTypePass(),
new EmptyArrayDimFetchPass(),
new ValidConstructorPass(),
// Rewriting shenanigans
@@ -116,11 +139,40 @@ class CodeCleaner
// Namespace-aware validation (which depends on aforementioned shenanigans)
new ValidClassNamePass(),
new ValidConstantPass(),
new ValidFunctionNamePass(),
];
}
/**
* A set of code cleaner passes that don't try to do any validation, and
* only do minimal rewriting to make things work inside the REPL.
*
* This list should stay in sync with the "rewriting shenanigans" in
* getDefaultPasses above.
*
* @return array
*/
private function getYoloPasses(): array
{
$useStatementPass = new UseStatementPass();
$namespacePass = new NamespacePass($this);
// Try to add implicit `use` statements and an implicit namespace,
// based on the file in which the `debug` call was made.
$this->addImplicitDebugContext([$useStatementPass, $namespacePass]);
return [
new LeavePsyshAlonePass(),
$useStatementPass, // must run before the namespace pass
new ExitPass(),
new ImplicitReturnPass(),
new MagicConstantsPass(),
$namespacePass, // must run after the implicit return pass
new RequirePass(),
new StrictTypesPass(),
];
}
/**
* "Warm up" code cleaner passes when we're coming from a debug call.
*
@@ -157,8 +209,6 @@ class CodeCleaner
$traverser->traverse($stmts);
} catch (\Throwable $e) {
// Don't care.
} catch (\Exception $e) {
// Still don't care.
}
}
@@ -169,7 +219,7 @@ class CodeCleaner
*/
private static function getDebugFile()
{
$trace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
foreach (\array_reverse($trace) as $stackFrame) {
if (!self::isDebugCall($stackFrame)) {
@@ -193,13 +243,13 @@ class CodeCleaner
*
* @return bool
*/
private static function isDebugCall(array $stackFrame)
private static function isDebugCall(array $stackFrame): bool
{
$class = isset($stackFrame['class']) ? $stackFrame['class'] : null;
$class = isset($stackFrame['class']) ? $stackFrame['class'] : null;
$function = isset($stackFrame['function']) ? $stackFrame['function'] : null;
return ($class === null && $function === 'Psy\debug') ||
($class === 'Psy\Shell' && $function === 'debug');
return ($class === null && $function === 'Psy\\debug') ||
($class === Shell::class && $function === 'debug');
}
/**
@@ -212,9 +262,9 @@ class CodeCleaner
*
* @return string|false Cleaned PHP code, False if the input is incomplete
*/
public function clean(array $codeLines, $requireSemicolons = false)
public function clean(array $codeLines, bool $requireSemicolons = false)
{
$stmts = $this->parse('<?php ' . \implode(PHP_EOL, $codeLines) . PHP_EOL, $requireSemicolons);
$stmts = $this->parse('<?php '.\implode(\PHP_EOL, $codeLines).\PHP_EOL, $requireSemicolons);
if ($stmts === false) {
return false;
}
@@ -223,13 +273,13 @@ class CodeCleaner
$stmts = $this->traverser->traverse($stmts);
// Work around https://github.com/nikic/PHP-Parser/issues/399
$oldLocale = \setlocale(LC_NUMERIC, 0);
\setlocale(LC_NUMERIC, 'C');
$oldLocale = \setlocale(\LC_NUMERIC, 0);
\setlocale(\LC_NUMERIC, 'C');
$code = $this->printer->prettyPrint($stmts);
// Now put the locale back
\setlocale(LC_NUMERIC, $oldLocale);
\setlocale(\LC_NUMERIC, $oldLocale);
return $code;
}
@@ -237,9 +287,9 @@ class CodeCleaner
/**
* Set the current local namespace.
*
* @param null|array $namespace (default: null)
* @param array|null $namespace (default: null)
*
* @return null|array
* @return array|null
*/
public function setNamespace(array $namespace = null)
{
@@ -249,7 +299,7 @@ class CodeCleaner
/**
* Get the current local namespace.
*
* @return null|array
* @return array|null
*/
public function getNamespace()
{
@@ -269,7 +319,7 @@ class CodeCleaner
*
* @return array|false A set of statements, or false if incomplete
*/
protected function parse($code, $requireSemicolons = false)
protected function parse(string $code, bool $requireSemicolons = false)
{
try {
return $this->parser->parse($code);
@@ -296,14 +346,14 @@ class CodeCleaner
try {
// Unexpected EOF, try again with an implicit semicolon
return $this->parser->parse($code . ';');
return $this->parser->parse($code.';');
} catch (\PhpParser\Error $e) {
return false;
}
}
}
private function parseErrorIsEOF(\PhpParser\Error $e)
private function parseErrorIsEOF(\PhpParser\Error $e): bool
{
$msg = $e->getRawMessage();
@@ -322,27 +372,27 @@ class CodeCleaner
*
* @return bool
*/
private function parseErrorIsUnclosedString(\PhpParser\Error $e, $code)
private function parseErrorIsUnclosedString(\PhpParser\Error $e, string $code): bool
{
if ($e->getRawMessage() !== 'Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE') {
return false;
}
try {
$this->parser->parse($code . "';");
} catch (\Exception $e) {
$this->parser->parse($code."';");
} catch (\Throwable $e) {
return false;
}
return true;
}
private function parseErrorIsUnterminatedComment(\PhpParser\Error $e, $code)
private function parseErrorIsUnterminatedComment(\PhpParser\Error $e, $code): bool
{
return $e->getRawMessage() === 'Unterminated comment';
}
private function parseErrorIsTrailingComma(\PhpParser\Error $e, $code)
private function parseErrorIsTrailingComma(\PhpParser\Error $e, $code): bool
{
return ($e->getRawMessage() === 'A trailing comma is not allowed here') && (\substr(\rtrim($code), -1) === ',');
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -25,9 +25,11 @@ class AbstractClassPass extends CodeCleanerPass
private $abstractMethods;
/**
* @throws RuntimeException if the node is an abstract function with a body
* @throws FatalErrorException if the node is an abstract function with a body
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -41,16 +43,18 @@ class AbstractClassPass extends CodeCleanerPass
if ($node->stmts !== null) {
$msg = \sprintf('Abstract function %s cannot contain body', $name);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
}
}
/**
* @throws RuntimeException if the node is a non-abstract class with abstract methods
* @throws FatalErrorException if the node is a non-abstract class with abstract methods
*
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
@@ -64,7 +68,7 @@ class AbstractClassPass extends CodeCleanerPass
($count === 1) ? '' : 's',
\implode(', ', $this->abstractMethods)
);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -26,14 +26,16 @@ class AssignThisVariablePass extends CodeCleanerPass
/**
* Validate that the user input does not assign the `$this` variable.
*
* @throws RuntimeException if the user assign the `$this` variable
* @throws FatalErrorException if the user assign the `$this` variable
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof Assign && $node->var instanceof Variable && $node->var->name === 'this') {
throw new FatalErrorException('Cannot re-assign $this', 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException('Cannot re-assign $this', 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,6 +15,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\VariadicPlaceholder;
use Psy\Exception\FatalErrorException;
/**
@@ -31,9 +32,11 @@ class CallTimePassByReferencePass extends CodeCleanerPass
/**
* Validate of use call-time pass-by-reference.
*
* @throws RuntimeException if the user used call-time pass-by-reference
* @throws FatalErrorException if the user used call-time pass-by-reference
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -42,8 +45,12 @@ class CallTimePassByReferencePass extends CodeCleanerPass
}
foreach ($node->args as $arg) {
if ($arg instanceof VariadicPlaceholder) {
continue;
}
if ($arg->byRef) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -17,6 +17,7 @@ use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\VariadicPlaceholder;
use Psy\Exception\ErrorException;
/**
@@ -29,6 +30,8 @@ class CalledClassPass extends CodeCleanerPass
/**
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -39,6 +42,8 @@ class CalledClassPass extends CodeCleanerPass
* @throws ErrorException if get_class or get_called_class is called without an object from outside a class
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -61,13 +66,15 @@ class CalledClassPass extends CodeCleanerPass
$name = \strtolower($node->name);
if (\in_array($name, ['get_class', 'get_called_class'])) {
$msg = \sprintf('%s() called without object from outside a class', $name);
throw new ErrorException($msg, 0, E_USER_WARNING, null, $node->getLine());
throw new ErrorException($msg, 0, \E_USER_WARNING, null, $node->getLine());
}
}
}
/**
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
@@ -76,8 +83,12 @@ class CalledClassPass extends CodeCleanerPass
}
}
private function isNull(Node $node)
private function isNull(Node $node): bool
{
if ($node instanceof VariadicPlaceholder) {
return false;
}
return $node->value instanceof ConstFetch && \strtolower($node->value->name) === 'null';
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use Psy\Exception\FatalErrorException;
/**
* Validate empty brackets are only used for assignment.
*/
class EmptyArrayDimFetchPass extends CodeCleanerPass
{
const EXCEPTION_MESSAGE = 'Cannot use [] for reading';
private $theseOnesAreFine = [];
/**
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->theseOnesAreFine = [];
}
/**
* @throws FatalErrorException if the user used empty empty array dim fetch outside of assignment
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof Assign && $node->var instanceof ArrayDimFetch) {
$this->theseOnesAreFine[] = $node->var;
}
if ($node instanceof ArrayDimFetch && $node->dim === null) {
if (!\in_array($node, $this->theseOnesAreFine)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, $node->getLine());
}
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,6 +15,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr\Exit_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use Psy\Exception\BreakException;
class ExitPass extends CodeCleanerPass
{
@@ -22,11 +23,13 @@ class ExitPass extends CodeCleanerPass
* Converts exit calls to BreakExceptions.
*
* @param \PhpParser\Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if ($node instanceof Exit_) {
return new StaticCall(new FullyQualifiedName('Psy\Exception\BreakException'), 'exitShell');
return new StaticCall(new FullyQualifiedName(BreakException::class), 'exitShell');
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -24,6 +24,8 @@ class FinalClassPass extends CodeCleanerPass
/**
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -31,9 +33,11 @@ class FinalClassPass extends CodeCleanerPass
}
/**
* @throws RuntimeException if the node is a class that extends a final class
* @throws FatalErrorException if the node is a class that extends a final class
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -42,7 +46,7 @@ class FinalClassPass extends CodeCleanerPass
$extends = (string) $node->extends;
if ($this->isFinalClass($extends)) {
$msg = \sprintf('Class %s may not inherit from final class (%s)', $node->name, $extends);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
@@ -57,7 +61,7 @@ class FinalClassPass extends CodeCleanerPass
*
* @return bool
*/
private function isFinalClass($name)
private function isFinalClass(string $name): bool
{
if (!\class_exists($name)) {
return isset($this->finalClasses[\strtolower($name)]);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -23,12 +23,17 @@ class FunctionContextPass extends CodeCleanerPass
/**
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->functionDepth = 0;
}
/**
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof FunctionLike) {
@@ -45,12 +50,14 @@ class FunctionContextPass extends CodeCleanerPass
// It causes fatal error.
if ($node instanceof Yield_) {
$msg = 'The "yield" expression can only be used inside a function';
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
/**
* @param \PhpParser\Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,12 +14,12 @@ namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Unset_;
use PhpParser\Node\VariadicPlaceholder;
use Psy\Exception\FatalErrorException;
/**
@@ -29,33 +29,31 @@ use Psy\Exception\FatalErrorException;
*/
class FunctionReturnInWriteContextPass extends CodeCleanerPass
{
const PHP55_MESSAGE = 'Cannot use isset() on the result of a function call (you can use "null !== func()" instead)';
const ISSET_MESSAGE = 'Cannot use isset() on the result of an expression (you can use "null !== expression" instead)';
const EXCEPTION_MESSAGE = "Can't use function return value in write context";
private $atLeastPhp55;
public function __construct()
{
$this->atLeastPhp55 = \version_compare(PHP_VERSION, '5.5', '>=');
}
/**
* Validate that the functions are used correctly.
*
* @throws FatalErrorException if a function is passed as an argument reference
* @throws FatalErrorException if a function is used as an argument in the isset
* @throws FatalErrorException if a function is used as an argument in the empty, only for PHP < 5.5
* @throws FatalErrorException if a value is assigned to a function
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof Array_ || $this->isCallNode($node)) {
$items = $node instanceof Array_ ? $node->items : $node->args;
foreach ($items as $item) {
if ($item instanceof VariadicPlaceholder) {
continue;
}
if ($item && $item->byRef && $this->isCallNode($item->value)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
} elseif ($node instanceof Isset_ || $node instanceof Unset_) {
@@ -64,17 +62,15 @@ class FunctionReturnInWriteContextPass extends CodeCleanerPass
continue;
}
$msg = ($node instanceof Isset_ && $this->atLeastPhp55) ? self::PHP55_MESSAGE : self::EXCEPTION_MESSAGE;
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
$msg = $node instanceof Isset_ ? self::ISSET_MESSAGE : self::EXCEPTION_MESSAGE;
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
} elseif ($node instanceof Empty_ && !$this->atLeastPhp55 && $this->isCallNode($node->expr)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine()); // @codeCoverageIgnore
} elseif ($node instanceof Assign && $this->isCallNode($node->var)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
private function isCallNode(Node $node)
private function isCallNode(Node $node): bool
{
return $node instanceof FuncCall || $node instanceof MethodCall || $node instanceof StaticCall;
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -32,7 +32,7 @@ class ImplicitReturnPass extends CodeCleanerPass
*
* @return array
*/
public function beforeTraverse(array $nodes)
public function beforeTraverse(array $nodes): array
{
return $this->addImplicitReturn($nodes);
}
@@ -42,7 +42,7 @@ class ImplicitReturnPass extends CodeCleanerPass
*
* @return array
*/
private function addImplicitReturn(array $nodes)
private function addImplicitReturn(array $nodes): array
{
// If nodes is empty, it can't have a return value.
if (empty($nodes)) {
@@ -118,7 +118,7 @@ class ImplicitReturnPass extends CodeCleanerPass
*
* @return bool
*/
private static function isNonExpressionStmt(Node $node)
private static function isNonExpressionStmt(Node $node): bool
{
return $node instanceof Stmt &&
!$node instanceof Expression &&

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -12,6 +12,9 @@
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Scalar;
@@ -27,21 +30,40 @@ class InstanceOfPass extends CodeCleanerPass
{
const EXCEPTION_MSG = 'instanceof expects an object instance, constant given';
private $atLeastPhp73;
public function __construct()
{
$this->atLeastPhp73 = \version_compare(\PHP_VERSION, '7.3', '>=');
}
/**
* Validate that the instanceof statement does not receive a scalar value or a non-class constant.
*
* @throws FatalErrorException if a scalar or a non-class constant is given
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
// Basically everything is allowed in PHP 7.3 :)
if ($this->atLeastPhp73) {
return;
}
if (!$node instanceof Instanceof_) {
return;
}
if (($node->expr instanceof Scalar && !$node->expr instanceof Encapsed) || $node->expr instanceof ConstFetch) {
throw new FatalErrorException(self::EXCEPTION_MSG, 0, E_ERROR, null, $node->getLine());
if (($node->expr instanceof Scalar && !$node->expr instanceof Encapsed) ||
$node->expr instanceof BinaryOp ||
$node->expr instanceof Array_ ||
$node->expr instanceof ConstFetch ||
$node->expr instanceof ClassConstFetch
) {
throw new FatalErrorException(self::EXCEPTION_MSG, 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\NullsafePropertyFetch;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\FatalErrorException;
/**
* Code cleaner pass to ensure we only allow variables, array fetch and property
* fetch expressions in isset() calls.
*/
class IssetPass extends CodeCleanerPass
{
const EXCEPTION_MSG = 'Cannot use isset() on the result of an expression (you can use "null !== expression" instead)';
/**
* @throws FatalErrorException
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if (!$node instanceof Isset_) {
return;
}
foreach ($node->vars as $var) {
if (!$var instanceof Variable && !$var instanceof ArrayDimFetch && !$var instanceof PropertyFetch && !$var instanceof NullsafePropertyFetch) {
throw new FatalErrorException(self::EXCEPTION_MSG, 0, \E_ERROR, null, $node->getLine());
}
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Goto_;
use PhpParser\Node\Stmt\Label;
use Psy\Exception\FatalErrorException;
/**
* CodeCleanerPass for label context.
*
* This class partially emulates the PHP label specification.
* PsySH can not declare labels by sequentially executing lines with eval,
* but since it is not a syntax error, no error is raised.
* This class warns before invalid goto causes a fatal error.
* Since this is a simple checker, it does not block real fatal error
* with complex syntax. (ex. it does not parse inside function.)
*
* @see http://php.net/goto
*/
class LabelContextPass extends CodeCleanerPass
{
/** @var int */
private $functionDepth;
/** @var array */
private $labelDeclarations;
/** @var array */
private $labelGotos;
/**
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->functionDepth = 0;
$this->labelDeclarations = [];
$this->labelGotos = [];
}
/**
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof FunctionLike) {
$this->functionDepth++;
return;
}
// node is inside function context
if ($this->functionDepth !== 0) {
return;
}
if ($node instanceof Goto_) {
$this->labelGotos[\strtolower($node->name)] = $node->getLine();
} elseif ($node instanceof Label) {
$this->labelDeclarations[\strtolower($node->name)] = $node->getLine();
}
}
/**
* @param \PhpParser\Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if ($node instanceof FunctionLike) {
$this->functionDepth--;
}
}
/**
* @return Node[]|null Array of nodes
*/
public function afterTraverse(array $nodes)
{
foreach ($this->labelGotos as $name => $line) {
if (!isset($this->labelDeclarations[$name])) {
$msg = "'goto' to undefined label '{$name}'";
throw new FatalErrorException($msg, 0, \E_ERROR, null, $line);
}
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -26,6 +26,8 @@ class LeavePsyshAlonePass extends CodeCleanerPass
* @throws RuntimeException if the user is messing with $__psysh__
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{

View File

@@ -1,73 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\ParseErrorException;
/**
* Validate that the user did not call the language construct `empty()` on a
* statement in PHP < 5.5.
*
* @codeCoverageIgnore
*/
class LegacyEmptyPass extends CodeCleanerPass
{
private $atLeastPhp55;
public function __construct()
{
$this->atLeastPhp55 = \version_compare(PHP_VERSION, '5.5', '>=');
}
/**
* Validate use of empty in PHP < 5.5.
*
* @throws ParseErrorException if the user used empty with anything but a variable
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($this->atLeastPhp55) {
return;
}
if (!$node instanceof Empty_) {
return;
}
if (!$node->expr instanceof Variable) {
$msg = \sprintf('syntax error, unexpected %s', $this->getUnexpectedThing($node->expr));
throw new ParseErrorException($msg, $node->expr->getLine());
}
}
private function getUnexpectedThing(Node $node)
{
switch ($node->getType()) {
case 'Scalar_String':
case 'Scalar_LNumber':
case 'Scalar_DNumber':
return \json_encode($node->value);
case 'Expr_ConstFetch':
return (string) $node->name;
default:
return $node->getType();
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -33,7 +33,7 @@ class ListPass extends CodeCleanerPass
public function __construct()
{
$this->atLeastPhp71 = \version_compare(PHP_VERSION, '7.1', '>=');
$this->atLeastPhp71 = \version_compare(\PHP_VERSION, '7.1', '>=');
}
/**
@@ -42,6 +42,8 @@ class ListPass extends CodeCleanerPass
* @throws ParseErrorException if the user used empty with anything but a variable
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -97,7 +99,7 @@ class ListPass extends CodeCleanerPass
*
* @return bool
*/
private static function isValidArrayItem(Expr $item)
private static function isValidArrayItem(Expr $item): bool
{
$value = ($item instanceof ArrayItem) ? $item->value : $item;

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -32,6 +32,8 @@ class LoopContextPass extends CodeCleanerPass
/**
* {@inheritdoc}
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -45,6 +47,8 @@ class LoopContextPass extends CodeCleanerPass
* @throws FatalErrorException if the node is a break or continue and has an argument less than 1
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -63,23 +67,23 @@ class LoopContextPass extends CodeCleanerPass
if ($this->loopDepth === 0) {
$msg = \sprintf("'%s' not in the 'loop' or 'switch' context", $operator);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
if ($node->num instanceof LNumber || $node->num instanceof DNumber) {
$num = $node->num->value;
if ($node->num instanceof DNumber || $num < 1) {
$msg = \sprintf("'%s' operator accepts only positive numbers", $operator);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
if ($num > $this->loopDepth) {
$msg = \sprintf("Cannot '%s' %d levels", $operator, $num);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
} elseif ($node->num) {
$msg = \sprintf("'%s' operator with non-constant operand is no longer supported", $operator);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
break;
}
@@ -87,6 +91,8 @@ class LoopContextPass extends CodeCleanerPass
/**
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -29,7 +29,7 @@ class MagicConstantsPass extends CodeCleanerPass
*
* @param Node $node
*
* @return null|FuncCall|String_
* @return FuncCall|String_|null
*/
public function enterNode(Node $node)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -29,10 +29,12 @@ abstract class NamespaceAwarePass extends CodeCleanerPass
* use afterTraverse or call parent::beforeTraverse() when overloading.
*
* Reset the namespace and the current scope before beginning analysis
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->namespace = [];
$this->namespace = [];
$this->currentScope = [];
}
@@ -41,6 +43,8 @@ abstract class NamespaceAwarePass extends CodeCleanerPass
* leaveNode or call parent::enterNode() when overloading
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -56,7 +60,7 @@ abstract class NamespaceAwarePass extends CodeCleanerPass
*
* @return string
*/
protected function getFullyQualifiedName($name)
protected function getFullyQualifiedName($name): string
{
if ($name instanceof FullyQualifiedName) {
return \implode('\\', $name->parts);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Namespace_;
use Psy\CodeCleaner;
@@ -46,6 +47,8 @@ class NamespacePass extends CodeCleanerPass
* is encountered.
*
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -78,7 +81,7 @@ class NamespacePass extends CodeCleanerPass
* Remember the namespace and (re)set the namespace on the CodeCleaner as
* well.
*
* @param null|Name $namespace
* @param Name|null $namespace
*/
private function setNamespace($namespace)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -26,10 +26,10 @@ class NoReturnValue
/**
* Get PhpParser AST expression for creating a new NoReturnValue.
*
* @return PhpParser\Node\Expr\New_
* @return New_
*/
public static function create()
public static function create(): New_
{
return new New_(new FullyQualifiedName('Psy\CodeCleaner\NoReturnValue'));
return new New_(new FullyQualifiedName(self::class));
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,6 +13,8 @@ namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
@@ -32,6 +34,8 @@ class PassableByReferencePass extends CodeCleanerPass
* @throws FatalErrorException if non-variables are passed by reference
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -59,15 +63,20 @@ class PassableByReferencePass extends CodeCleanerPass
if (\array_key_exists($key, $node->args)) {
$arg = $node->args[$key];
if ($param->isPassedByReference() && !$this->isPassableByReference($arg)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
}
}
}
private function isPassableByReference(Node $arg)
private function isPassableByReference(Node $arg): bool
{
// Unpacked arrays can be passed by reference
if ($arg->value instanceof Array_) {
return $arg->unpack;
}
// FuncCall, MethodCall and StaticCall are all PHP _warnings_ not fatal errors, so we'll let
// PHP handle those ones :)
return $arg->value instanceof ClassConstFetch ||
@@ -75,7 +84,8 @@ class PassableByReferencePass extends CodeCleanerPass
$arg->value instanceof Variable ||
$arg->value instanceof FuncCall ||
$arg->value instanceof MethodCall ||
$arg->value instanceof StaticCall;
$arg->value instanceof StaticCall ||
$arg->value instanceof ArrayDimFetch;
}
/**
@@ -102,7 +112,7 @@ class PassableByReferencePass extends CodeCleanerPass
} elseif (++$nonPassable > 2) {
// There can be *at most* two non-passable-by-reference args in a row. This is about
// as close as we can get to validating the arguments for this function :-/
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -19,7 +19,6 @@ use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Scalar\LNumber;
use Psy\Exception\ErrorException;
use Psy\Exception\FatalErrorException;
use Psy\Shell;
/**
* Add runtime validation for `require` and `require_once` calls.
@@ -30,6 +29,8 @@ class RequirePass extends CodeCleanerPass
/**
* {@inheritdoc}
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $origNode)
{
@@ -49,7 +50,7 @@ class RequirePass extends CodeCleanerPass
* $foo = require \Psy\CodeCleaner\RequirePass::resolve($bar)
*/
$node->expr = new StaticCall(
new FullyQualifiedName('Psy\CodeCleaner\RequirePass'),
new FullyQualifiedName(self::class),
'resolve',
[new Arg($origNode->expr), new Arg(new LNumber($origNode->getLine()))],
$origNode->getAttributes()
@@ -63,15 +64,18 @@ class RequirePass extends CodeCleanerPass
*
* If $file can be resolved, return $file. Otherwise throw a fatal error exception.
*
* If $file collides with a path in the currently running PsySH phar, it will be resolved
* relative to the include path, to prevent PHP from grabbing the phar version of the file.
*
* @throws FatalErrorException when unable to resolve include path for $file
* @throws ErrorException if $file is empty and E_WARNING is included in error_reporting level
*
* @param string $file
* @param int $lineNumber Line number of the original require expression
*
* @return string Exactly the same as $file
* @return string Exactly the same as $file, unless $file collides with a path in the currently running phar
*/
public static function resolve($file, $lineNumber = null)
public static function resolve($file, $lineNumber = null): string
{
$file = (string) $file;
@@ -79,23 +83,51 @@ class RequirePass extends CodeCleanerPass
// @todo Shell::handleError would be better here, because we could
// fake the file and line number, but we can't call it statically.
// So we're duplicating some of the logics here.
if (E_WARNING & \error_reporting()) {
ErrorException::throwException(E_WARNING, 'Filename cannot be empty', null, $lineNumber);
if (\E_WARNING & \error_reporting()) {
ErrorException::throwException(\E_WARNING, 'Filename cannot be empty', null, $lineNumber);
}
// @todo trigger an error as fallback? this is pretty ugly…
// trigger_error('Filename cannot be empty', E_USER_WARNING);
}
if ($file === '' || !\stream_resolve_include_path($file)) {
$resolvedPath = \stream_resolve_include_path($file);
if ($file === '' || !$resolvedPath) {
$msg = \sprintf("Failed opening required '%s'", $file);
throw new FatalErrorException($msg, 0, E_ERROR, null, $lineNumber);
throw new FatalErrorException($msg, 0, \E_ERROR, null, $lineNumber);
}
// Special case: if the path is not already relative or absolute, and it would resolve to
// something inside the currently running phar (e.g. `vendor/autoload.php`), we'll resolve
// it relative to the include path so PHP won't grab the phar version.
//
// Note that this only works if the phar has `psysh` in the path. We might want to lift this
// restriction and special case paths that would collide with any running phar?
if ($resolvedPath !== $file && $file[0] !== '.') {
$runningPhar = \Phar::running();
if (\strpos($runningPhar, 'psysh') !== false && \is_file($runningPhar.\DIRECTORY_SEPARATOR.$file)) {
foreach (self::getIncludePath() as $prefix) {
$resolvedPath = $prefix.\DIRECTORY_SEPARATOR.$file;
if (\is_file($resolvedPath)) {
return $resolvedPath;
}
}
}
}
return $file;
}
private function isRequireNode(Node $node)
private function isRequireNode(Node $node): bool
{
return $node instanceof Include_ && \in_array($node->type, self::$requireTypes);
}
private static function getIncludePath(): array
{
if (\PATH_SEPARATOR === ':') {
return \preg_split('#:(?!//)#', \get_include_path());
}
return \explode(\PATH_SEPARATOR, \get_include_path());
}
}

View File

@@ -0,0 +1,127 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Identifier;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\UnionType;
use Psy\Exception\FatalErrorException;
/**
* Add runtime validation for return types.
*/
class ReturnTypePass extends CodeCleanerPass
{
const MESSAGE = 'A function with return type must return a value';
const NULLABLE_MESSAGE = 'A function with return type must return a value (did you mean "return null;" instead of "return;"?)';
const VOID_MESSAGE = 'A void function must not return a value';
const VOID_NULL_MESSAGE = 'A void function must not return a value (did you mean "return;" instead of "return null;"?)';
const NULLABLE_VOID_MESSAGE = 'Void type cannot be nullable';
private $atLeastPhp71;
private $returnTypeStack = [];
public function __construct()
{
$this->atLeastPhp71 = \version_compare(\PHP_VERSION, '7.1', '>=');
}
/**
* {@inheritdoc}
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if (!$this->atLeastPhp71) {
return; // @codeCoverageIgnore
}
if ($this->isFunctionNode($node)) {
$this->returnTypeStack[] = $node->returnType;
return;
}
if (!empty($this->returnTypeStack) && $node instanceof Return_) {
$expectedType = \end($this->returnTypeStack);
if ($expectedType === null) {
return;
}
$msg = null;
if ($this->typeName($expectedType) === 'void') {
// Void functions
if ($expectedType instanceof NullableType) {
$msg = self::NULLABLE_VOID_MESSAGE;
} elseif ($node->expr instanceof ConstFetch && \strtolower($node->expr->name) === 'null') {
$msg = self::VOID_NULL_MESSAGE;
} elseif ($node->expr !== null) {
$msg = self::VOID_MESSAGE;
}
} else {
// Everything else
if ($node->expr === null) {
$msg = $expectedType instanceof NullableType ? self::NULLABLE_MESSAGE : self::MESSAGE;
}
}
if ($msg !== null) {
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
}
/**
* {@inheritdoc}
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if (!$this->atLeastPhp71) {
return; // @codeCoverageIgnore
}
if (!empty($this->returnTypeStack) && $this->isFunctionNode($node)) {
\array_pop($this->returnTypeStack);
}
}
private function isFunctionNode(Node $node): bool
{
return $node instanceof Function_ || $node instanceof Closure;
}
private function typeName(Node $node): string
{
if ($node instanceof UnionType) {
return \implode('|', \array_map([$this, 'typeName'], $node->types));
}
if ($node instanceof NullableType) {
return \strtolower($node->type->name);
}
if ($node instanceof Identifier) {
return \strtolower($node->name);
}
throw new \InvalidArgumentException('Unable to find type name');
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Declare_;
@@ -32,12 +33,6 @@ class StrictTypesPass extends CodeCleanerPass
const EXCEPTION_MESSAGE = 'strict_types declaration must have 0 or 1 as its value';
private $strictTypes = false;
private $atLeastPhp7;
public function __construct()
{
$this->atLeastPhp7 = \version_compare(PHP_VERSION, '7.0', '>=');
}
/**
* If this is a standalone strict types declaration, remember it for later.
@@ -48,16 +43,14 @@ class StrictTypesPass extends CodeCleanerPass
* @throws FatalErrorException if an invalid `strict_types` declaration is found
*
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
if (!$this->atLeastPhp7) {
return; // @codeCoverageIgnore
}
$prependStrictTypes = $this->strictTypes;
foreach ($nodes as $key => $node) {
foreach ($nodes as $node) {
if ($node instanceof Declare_) {
foreach ($node->declares as $declare) {
// For PHP Parser 4.x
@@ -65,7 +58,7 @@ class StrictTypesPass extends CodeCleanerPass
if ($declareKey === 'strict_types') {
$value = $declare->value;
if (!$value instanceof LNumber || ($value->value !== 0 && $value->value !== 1)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
$this->strictTypes = $value->value === 1;

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -17,6 +17,7 @@ use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\NodeTraverser;
/**
@@ -31,8 +32,8 @@ use PhpParser\NodeTraverser;
*/
class UseStatementPass extends CodeCleanerPass
{
private $aliases = [];
private $lastAliases = [];
private $aliases = [];
private $lastAliases = [];
private $lastNamespace = null;
/**
@@ -43,13 +44,15 @@ class UseStatementPass extends CodeCleanerPass
* work like you'd expect.
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof Namespace_) {
// If this is the same namespace as last namespace, let's do ourselves
// a favor and reload all the aliases...
if (\strtolower($node->name) === \strtolower($this->lastNamespace)) {
if (\strtolower($node->name ?: '') === \strtolower($this->lastNamespace ?: '')) {
$this->aliases = $this->lastAliases;
}
}
@@ -62,21 +65,23 @@ class UseStatementPass extends CodeCleanerPass
* remembered aliases to the code.
*
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
// Store a reference to every "use" statement, because we'll need them in a bit.
if ($node instanceof Use_) {
// Store a reference to every "use" statement, because we'll need
// them in a bit.
foreach ($node->uses as $use) {
$alias = $use->alias ?: \end($use->name->parts);
$this->aliases[\strtolower($alias)] = $use->name;
}
return NodeTraverser::REMOVE_NODE;
} elseif ($node instanceof GroupUse) {
// Expand every "use" statement in the group into a full, standalone
// "use" and store 'em with the others.
}
// Expand every "use" statement in the group into a full, standalone "use" and store 'em with the others.
if ($node instanceof GroupUse) {
foreach ($node->uses as $use) {
$alias = $use->alias ?: \end($use->name->parts);
$this->aliases[\strtolower($alias)] = Name::concat($node->prefix, $use->name, [
@@ -86,23 +91,32 @@ class UseStatementPass extends CodeCleanerPass
}
return NodeTraverser::REMOVE_NODE;
} elseif ($node instanceof Namespace_) {
// Start fresh, since we're done with this namespace.
}
// Start fresh, since we're done with this namespace.
if ($node instanceof Namespace_) {
$this->lastNamespace = $node->name;
$this->lastAliases = $this->aliases;
$this->aliases = [];
} else {
foreach ($node as $name => $subNode) {
if ($subNode instanceof Name) {
// Implicitly thunk all aliases.
if ($replacement = $this->findAlias($subNode)) {
$node->$name = $replacement;
}
$this->lastAliases = $this->aliases;
$this->aliases = [];
return;
}
// Do nothing with UseUse; this an entry in the list of uses in the use statement.
if ($node instanceof UseUse) {
return;
}
// For everything else, we'll implicitly thunk all aliases into fully-qualified names.
foreach ($node as $name => $subNode) {
if ($subNode instanceof Name) {
if ($replacement = $this->findAlias($subNode)) {
$node->$name = $replacement;
}
}
return $node;
}
return $node;
}
/**
@@ -118,8 +132,8 @@ class UseStatementPass extends CodeCleanerPass
foreach ($this->aliases as $alias => $prefix) {
if ($that === $alias) {
return new FullyQualifiedName($prefix->toString());
} elseif (\substr($that, 0, \strlen($alias) + 1) === $alias . '\\') {
return new FullyQualifiedName($prefix->toString() . \substr($name, \strlen($alias)));
} elseif (\substr($that, 0, \strlen($alias) + 1) === $alias.'\\') {
return new FullyQualifiedName($prefix->toString().\substr($name, \strlen($alias)));
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,9 +13,7 @@ namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Do_;
@@ -34,17 +32,11 @@ use Psy\Exception\FatalErrorException;
*/
class ValidClassNamePass extends NamespaceAwarePass
{
const CLASS_TYPE = 'class';
const CLASS_TYPE = 'class';
const INTERFACE_TYPE = 'interface';
const TRAIT_TYPE = 'trait';
const TRAIT_TYPE = 'trait';
private $conditionalScopes = 0;
private $atLeastPhp55;
public function __construct()
{
$this->atLeastPhp55 = \version_compare(PHP_VERSION, '5.5', '>=');
}
/**
* Validate class, interface and trait definitions.
@@ -54,6 +46,8 @@ class ValidClassNamePass extends NamespaceAwarePass
* trait methods.
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -61,51 +55,42 @@ class ValidClassNamePass extends NamespaceAwarePass
if (self::isConditional($node)) {
$this->conditionalScopes++;
} else {
// @todo add an "else" here which adds a runtime check for instances where we can't tell
// whether a class is being redefined by static analysis alone.
if ($this->conditionalScopes === 0) {
if ($node instanceof Class_) {
$this->validateClassStatement($node);
} elseif ($node instanceof Interface_) {
$this->validateInterfaceStatement($node);
} elseif ($node instanceof Trait_) {
$this->validateTraitStatement($node);
}
return;
}
if ($this->conditionalScopes === 0) {
if ($node instanceof Class_) {
$this->validateClassStatement($node);
} elseif ($node instanceof Interface_) {
$this->validateInterfaceStatement($node);
} elseif ($node instanceof Trait_) {
$this->validateTraitStatement($node);
}
}
}
/**
* Validate `new` expressions, class constant fetches, and static calls.
*
* @throws FatalErrorException if a class, interface or trait is referenced which does not exist
* @throws FatalErrorException if a class extends something that is not a class
* @throws FatalErrorException if a class implements something that is not an interface
* @throws FatalErrorException if an interface extends something that is not an interface
* @throws FatalErrorException if a class, interface or trait redefines an existing class, interface or trait name
*
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if (self::isConditional($node)) {
$this->conditionalScopes--;
} elseif ($node instanceof New_) {
$this->validateNewExpression($node);
} elseif ($node instanceof ClassConstFetch) {
$this->validateClassConstFetchExpression($node);
} elseif ($node instanceof StaticCall) {
$this->validateStaticCallExpression($node);
return;
}
}
private static function isConditional(Node $node)
private static function isConditional(Node $node): bool
{
return $node instanceof If_ ||
$node instanceof While_ ||
$node instanceof Do_ ||
$node instanceof Switch_;
$node instanceof Switch_ ||
$node instanceof Ternary;
}
/**
@@ -143,50 +128,6 @@ class ValidClassNamePass extends NamespaceAwarePass
$this->ensureCanDefine($stmt, self::TRAIT_TYPE);
}
/**
* Validate a `new` expression.
*
* @param New_ $stmt
*/
protected function validateNewExpression(New_ $stmt)
{
// if class name is an expression or an anonymous class, give it a pass for now
if (!$stmt->class instanceof Expr && !$stmt->class instanceof Class_) {
$this->ensureClassExists($this->getFullyQualifiedName($stmt->class), $stmt);
}
}
/**
* Validate a class constant fetch expression's class.
*
* @param ClassConstFetch $stmt
*/
protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
{
// there is no need to check exists for ::class const for php 5.5 or newer
if (\strtolower($stmt->name) === 'class' && $this->atLeastPhp55) {
return;
}
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$this->ensureClassOrInterfaceExists($this->getFullyQualifiedName($stmt->class), $stmt);
}
}
/**
* Validate a class constant fetch expression's class.
*
* @param StaticCall $stmt
*/
protected function validateStaticCallExpression(StaticCall $stmt)
{
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$this->ensureMethodExists($this->getFullyQualifiedName($stmt->class), $stmt->name, $stmt);
}
}
/**
* Ensure that no class, interface or trait name collides with a new definition.
*
@@ -195,8 +136,13 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param Stmt $stmt
* @param string $scopeType
*/
protected function ensureCanDefine(Stmt $stmt, $scopeType = self::CLASS_TYPE)
protected function ensureCanDefine(Stmt $stmt, string $scopeType = self::CLASS_TYPE)
{
// Anonymous classes don't have a name, and uniqueness shouldn't be enforced.
if ($stmt->name === null) {
return;
}
$name = $this->getFullyQualifiedName($stmt->name);
// check for name collisions
@@ -226,7 +172,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param string $name
* @param Stmt $stmt
*/
protected function ensureClassExists($name, $stmt)
protected function ensureClassExists(string $name, Stmt $stmt)
{
if (!$this->classExists($name)) {
throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
@@ -241,7 +187,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param string $name
* @param Stmt $stmt
*/
protected function ensureClassOrInterfaceExists($name, $stmt)
protected function ensureClassOrInterfaceExists(string $name, Stmt $stmt)
{
if (!$this->classExists($name) && !$this->interfaceExists($name)) {
throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
@@ -256,7 +202,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param string $name
* @param Stmt $stmt
*/
protected function ensureClassOrTraitExists($name, $stmt)
protected function ensureClassOrTraitExists(string $name, Stmt $stmt)
{
if (!$this->classExists($name) && !$this->traitExists($name)) {
throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
@@ -272,7 +218,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param string $name
* @param Stmt $stmt
*/
protected function ensureMethodExists($class, $name, $stmt)
protected function ensureMethodExists(string $class, string $name, Stmt $stmt)
{
$this->ensureClassOrTraitExists($class, $stmt);
@@ -304,7 +250,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param Interface_[] $interfaces
* @param Stmt $stmt
*/
protected function ensureInterfacesExist($interfaces, $stmt)
protected function ensureInterfacesExist(array $interfaces, Stmt $stmt)
{
foreach ($interfaces as $interface) {
/** @var string $name */
@@ -321,11 +267,13 @@ class ValidClassNamePass extends NamespaceAwarePass
* @deprecated No longer used. Scope type should be passed into ensureCanDefine directly.
* @codeCoverageIgnore
*
* @throws FatalErrorException
*
* @param Stmt $stmt
*
* @return string
*/
protected function getScopeType(Stmt $stmt)
protected function getScopeType(Stmt $stmt): string
{
if ($stmt instanceof Class_) {
return self::CLASS_TYPE;
@@ -334,6 +282,8 @@ class ValidClassNamePass extends NamespaceAwarePass
} elseif ($stmt instanceof Trait_) {
return self::TRAIT_TYPE;
}
throw $this->createError('Unsupported statement type', $stmt);
}
/**
@@ -345,7 +295,7 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return bool
*/
protected function classExists($name)
protected function classExists(string $name): bool
{
// Give `self`, `static` and `parent` a pass. This will actually let
// some errors through, since we're not checking whether the keyword is
@@ -364,7 +314,7 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return bool
*/
protected function interfaceExists($name)
protected function interfaceExists(string $name): bool
{
return \interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE;
}
@@ -376,7 +326,7 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return bool
*/
protected function traitExists($name)
protected function traitExists(string $name): bool
{
return \trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE;
}
@@ -388,7 +338,7 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return string|null
*/
protected function findInScope($name)
protected function findInScope(string $name)
{
$name = \strtolower($name);
if (isset($this->currentScope[$name])) {
@@ -404,8 +354,8 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return FatalErrorException
*/
protected function createError($msg, $stmt)
protected function createError(string $msg, Stmt $stmt): FatalErrorException
{
return new FatalErrorException($msg, 0, E_ERROR, null, $stmt->getLine());
return new FatalErrorException($msg, 0, \E_ERROR, null, $stmt->getLine());
}
}

View File

@@ -1,90 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Identifier;
use Psy\Exception\FatalErrorException;
/**
* Validate that namespaced constant references will succeed.
*
* This pass throws a FatalErrorException rather than letting PHP run
* headfirst into a real fatal error and die.
*
* @todo Detect constants defined in the current code snippet?
* ... Might not be worth it, since it would need to both be defining and
* referencing a namespaced constant, which doesn't seem like that big of
* a target for failure
*/
class ValidConstantPass extends NamespaceAwarePass
{
/**
* Validate that namespaced constant references will succeed.
*
* Note that this does not (yet) detect constants defined in the current code
* snippet. It won't happen very often, so we'll punt for now.
*
* @throws FatalErrorException if a constant reference is not defined
*
* @param Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof ConstFetch && \count($node->name->parts) > 1) {
$name = $this->getFullyQualifiedName($node->name);
if (!\defined($name)) {
$msg = \sprintf('Undefined constant %s', $name);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
}
} elseif ($node instanceof ClassConstFetch) {
$this->validateClassConstFetchExpression($node);
}
}
/**
* Validate a class constant fetch expression.
*
* @throws FatalErrorException if a class constant is not defined
*
* @param ClassConstFetch $stmt
*/
protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
{
// For PHP Parser 4.x
$constName = $stmt->name instanceof Identifier ? $stmt->name->toString() : $stmt->name;
// give the `class` pseudo-constant a pass
if ($constName === 'class') {
return;
}
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$className = $this->getFullyQualifiedName($stmt->class);
// if the class doesn't exist, don't throw an exception… it might be
// defined in the same line it's used or something stupid like that.
if (\class_exists($className) || \interface_exists($className)) {
$refl = new \ReflectionClass($className);
if (!$refl->hasConstant($constName)) {
$constType = \class_exists($className) ? 'Class' : 'Interface';
$msg = \sprintf('%s constant \'%s::%s\' not found', $constType, $className, $constName);
throw new FatalErrorException($msg, 0, E_ERROR, null, $stmt->getLine());
}
}
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -35,6 +35,9 @@ class ValidConstructorPass extends CodeCleanerPass
{
private $namespace;
/**
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->namespace = [];
@@ -47,6 +50,8 @@ class ValidConstructorPass extends CodeCleanerPass
* @throws FatalErrorException the constructor function has a return type
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -94,7 +99,7 @@ class ValidConstructorPass extends CodeCleanerPass
\implode('\\', \array_merge($this->namespace, (array) $className)),
$constructor->name
);
throw new FatalErrorException($msg, 0, E_ERROR, null, $classNode->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $classNode->getLine());
}
if (\method_exists($constructor, 'getReturnType') && $constructor->getReturnType()) {
@@ -106,7 +111,7 @@ class ValidConstructorPass extends CodeCleanerPass
\implode('\\', \array_merge($this->namespace, (array) $className)),
$constructor->name
);
throw new FatalErrorException($msg, 0, E_ERROR, null, $classNode->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $classNode->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -12,9 +12,6 @@
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\If_;
@@ -35,7 +32,11 @@ class ValidFunctionNamePass extends NamespaceAwarePass
/**
* Store newly defined function names on the way in, to allow recursion.
*
* @throws FatalErrorException if a function is redefined in a non-conditional scope
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -52,7 +53,7 @@ class ValidFunctionNamePass extends NamespaceAwarePass
if (\function_exists($name) ||
isset($this->currentScope[\strtolower($name)])) {
$msg = \sprintf('Cannot redeclare %s()', $name);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
@@ -61,29 +62,14 @@ class ValidFunctionNamePass extends NamespaceAwarePass
}
/**
* Validate that function calls will succeed.
*
* @throws FatalErrorException if a function is redefined
* @throws FatalErrorException if the function name is a string (not an expression) and is not defined
*
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if (self::isConditional($node)) {
$this->conditionalScopes--;
} elseif ($node instanceof FuncCall) {
// if function name is an expression or a variable, give it a pass for now.
$name = $node->name;
if (!$name instanceof Expr && !$name instanceof Variable) {
$shortName = \implode('\\', $name->parts);
$fullName = $this->getFullyQualifiedName($name);
$inScope = isset($this->currentScope[\strtolower($fullName)]);
if (!$inScope && !\function_exists($shortName) && !\function_exists($fullName)) {
$message = \sprintf('Call to undefined function %s()', $name);
throw new FatalErrorException($message, 0, E_ERROR, null, $node->getLine());
}
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\Command;
use Psy\Exception\RuntimeException;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -46,12 +47,19 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$buf = $this->getApplication()->getCodeBuffer();
$app = $this->getApplication();
if (!$app instanceof \Psy\Shell) {
throw new RuntimeException('Buffer command requires a \Psy\Shell application');
}
$buf = $app->getCodeBuffer();
if ($input->getOption('clear')) {
$this->getApplication()->resetCodeBuffer();
$app->resetCodeBuffer();
$output->writeln($this->formatLines($buf, 'urgent'), ShellOutput::NUMBER_LINES);
} else {
$output->writeln($this->formatLines($buf), ShellOutput::NUMBER_LINES);
@@ -68,7 +76,7 @@ HELP
*
* @return array Formatted strings
*/
protected function formatLines(array $lines, $type = 'return')
protected function formatLines(array $lines, string $type = 'return'): array
{
$template = \sprintf('<%s>%%s</%s>', $type, $type);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -41,6 +41,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -27,7 +27,7 @@ abstract class Command extends BaseCommand
/**
* Sets the application instance for this command.
*
* @param Application $application An Application instance
* @param Application|null $application An Application instance
*
* @api
*/
@@ -43,11 +43,11 @@ abstract class Command extends BaseCommand
/**
* {@inheritdoc}
*/
public function asText()
public function asText(): string
{
$messages = [
'<comment>Usage:</comment>',
' ' . $this->getSynopsis(),
' '.$this->getSynopsis(),
'',
];
@@ -65,7 +65,7 @@ abstract class Command extends BaseCommand
if ($help = $this->getProcessedHelp()) {
$messages[] = '<comment>Help:</comment>';
$messages[] = ' ' . \str_replace("\n", "\n ", $help) . "\n";
$messages[] = ' '.\str_replace("\n", "\n ", $help)."\n";
}
return \implode("\n", $messages);
@@ -74,7 +74,7 @@ abstract class Command extends BaseCommand
/**
* {@inheritdoc}
*/
private function getArguments()
private function getArguments(): array
{
$hidden = $this->getHiddenArguments();
@@ -88,7 +88,7 @@ abstract class Command extends BaseCommand
*
* @return array
*/
protected function getHiddenArguments()
protected function getHiddenArguments(): array
{
return ['command'];
}
@@ -96,7 +96,7 @@ abstract class Command extends BaseCommand
/**
* {@inheritdoc}
*/
private function getOptions()
private function getOptions(): array
{
$hidden = $this->getHiddenOptions();
@@ -110,7 +110,7 @@ abstract class Command extends BaseCommand
*
* @return array
*/
protected function getHiddenOptions()
protected function getHiddenOptions(): array
{
return ['verbose'];
}
@@ -120,9 +120,9 @@ abstract class Command extends BaseCommand
*
* @return string
*/
private function aliasesAsText()
private function aliasesAsText(): string
{
return '<comment>Aliases:</comment> <info>' . \implode(', ', $this->getAliases()) . '</info>' . PHP_EOL;
return '<comment>Aliases:</comment> <info>'.\implode(', ', $this->getAliases()).'</info>'.\PHP_EOL;
}
/**
@@ -130,7 +130,7 @@ abstract class Command extends BaseCommand
*
* @return string
*/
private function argumentsAsText()
private function argumentsAsText(): string
{
$max = $this->getMaxWidth();
$messages = [];
@@ -145,15 +145,15 @@ abstract class Command extends BaseCommand
$default = '';
}
$description = \str_replace("\n", "\n" . \str_pad('', $max + 2, ' '), $argument->getDescription());
$description = \str_replace("\n", "\n".\str_pad('', $max + 2, ' '), $argument->getDescription());
$messages[] = \sprintf(" <info>%-${max}s</info> %s%s", $argument->getName(), $description, $default);
$messages[] = \sprintf(" <info>%-{$max}s</info> %s%s", $argument->getName(), $description, $default);
}
$messages[] = '';
}
return \implode(PHP_EOL, $messages);
return \implode(\PHP_EOL, $messages);
}
/**
@@ -161,7 +161,7 @@ abstract class Command extends BaseCommand
*
* @return string
*/
private function optionsAsText()
private function optionsAsText(): string
{
$max = $this->getMaxWidth();
$messages = [];
@@ -178,12 +178,12 @@ abstract class Command extends BaseCommand
}
$multiple = $option->isArray() ? '<comment> (multiple values allowed)</comment>' : '';
$description = \str_replace("\n", "\n" . \str_pad('', $max + 2, ' '), $option->getDescription());
$description = \str_replace("\n", "\n".\str_pad('', $max + 2, ' '), $option->getDescription());
$optionMax = $max - \strlen($option->getName()) - 2;
$messages[] = \sprintf(
" <info>%s</info> %-${optionMax}s%s%s%s",
'--' . $option->getName(),
" <info>%s</info> %-{$optionMax}s%s%s%s",
'--'.$option->getName(),
$option->getShortcut() ? \sprintf('(-%s) ', $option->getShortcut()) : '',
$description,
$default,
@@ -194,7 +194,7 @@ abstract class Command extends BaseCommand
$messages[] = '';
}
return \implode(PHP_EOL, $messages);
return \implode(\PHP_EOL, $messages);
}
/**
@@ -202,7 +202,7 @@ abstract class Command extends BaseCommand
*
* @return int
*/
private function getMaxWidth()
private function getMaxWidth(): int
{
$max = 0;
@@ -229,10 +229,10 @@ abstract class Command extends BaseCommand
*
* @return string
*/
private function formatDefaultValue($default)
private function formatDefaultValue($default): string
{
if (\is_array($default) && $default === \array_values($default)) {
return \sprintf("array('%s')", \implode("', '", $default));
return \sprintf("['%s']", \implode("', '", $default));
}
return \str_replace("\n", '', \var_export($default, true));
@@ -247,15 +247,22 @@ abstract class Command extends BaseCommand
*/
protected function getTable(OutputInterface $output)
{
if (!\class_exists('Symfony\Component\Console\Helper\Table')) {
if (!\class_exists(Table::class)) {
return $this->getTableHelper();
}
$style = new TableStyle();
$style
->setVerticalBorderChar(' ')
->setHorizontalBorderChar('')
->setCrossingChar('');
// Symfony 4.1 deprecated single-argument style setters.
if (\method_exists($style, 'setVerticalBorderChars')) {
$style->setVerticalBorderChars(' ');
$style->setHorizontalBorderChars('');
$style->setCrossingChars('', '', '', '', '', '', '', '', '');
} else {
$style->setVerticalBorderChar(' ');
$style->setHorizontalBorderChar('');
$style->setCrossingChar('');
}
$table = new Table($output);
@@ -269,7 +276,7 @@ abstract class Command extends BaseCommand
*
* @return TableHelper
*/
protected function getTableHelper()
protected function getTableHelper(): TableHelper
{
$table = $this->getApplication()->getHelperSet()->get('table');

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,8 +14,12 @@ namespace Psy\Command;
use Psy\Formatter\DocblockFormatter;
use Psy\Formatter\SignatureFormatter;
use Psy\Input\CodeArgument;
use Psy\Output\ShellOutput;
use Psy\Reflection\ReflectionClassConstant;
use Psy\Reflection\ReflectionConstant_;
use Psy\Reflection\ReflectionLanguageConstruct;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
@@ -23,6 +27,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class DocCommand extends ReflectingCommand
{
const INHERIT_DOC_TAG = '{@inheritdoc}';
/**
* {@inheritdoc}
*/
@@ -32,6 +38,7 @@ class DocCommand extends ReflectingCommand
->setName('doc')
->setAliases(['rtfm', 'man'])
->setDefinition([
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show documentation for superclasses as well as the current class.'),
new CodeArgument('target', CodeArgument::REQUIRED, 'Function, class, instance, constant, method or property to document.'),
])
->setDescription('Read the documentation for an object, class, constant, method or property.')
@@ -53,6 +60,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -67,18 +76,51 @@ HELP
$db = $this->getApplication()->getManualDb();
$output->page(function ($output) use ($reflector, $doc, $db) {
$output->writeln(SignatureFormatter::format($reflector));
$output->writeln('');
if ($output instanceof ShellOutput) {
$output->startPaging();
}
if (empty($doc) && !$db) {
$output->writeln('<warning>PHP manual not found</warning>');
$output->writeln(' To document core PHP functionality, download the PHP reference manual:');
$output->writeln(' https://github.com/bobthecow/psysh/wiki/PHP-manual');
} else {
$output->writeln($doc);
// Maybe include the declaring class
if ($reflector instanceof \ReflectionMethod || $reflector instanceof \ReflectionProperty) {
$output->writeln(SignatureFormatter::format($reflector->getDeclaringClass()));
}
$output->writeln(SignatureFormatter::format($reflector));
$output->writeln('');
if (empty($doc) && !$db) {
$output->writeln('<warning>PHP manual not found</warning>');
$output->writeln(' To document core PHP functionality, download the PHP reference manual:');
$output->writeln(' https://github.com/bobthecow/psysh/wiki/PHP-manual');
} else {
$output->writeln($doc);
}
// Implicit --all if the original docblock has an {@inheritdoc} tag.
if ($input->getOption('all') || \stripos($doc, self::INHERIT_DOC_TAG) !== false) {
$parent = $reflector;
foreach ($this->getParentReflectors($reflector) as $parent) {
$output->writeln('');
$output->writeln('---');
$output->writeln('');
// Maybe include the declaring class
if ($parent instanceof \ReflectionMethod || $parent instanceof \ReflectionProperty) {
$output->writeln(SignatureFormatter::format($parent->getDeclaringClass()));
}
$output->writeln(SignatureFormatter::format($parent));
$output->writeln('');
if ($doc = $this->getManualDoc($parent) ?: DocblockFormatter::format($parent)) {
$output->writeln($doc);
}
}
});
}
if ($output instanceof ShellOutput) {
$output->stopPaging();
}
// Set some magic local variables
$this->setCommandScopeVariables($reflector);
@@ -89,29 +131,29 @@ HELP
private function getManualDoc($reflector)
{
switch (\get_class($reflector)) {
case 'ReflectionClass':
case 'ReflectionObject':
case 'ReflectionFunction':
case \ReflectionClass::class:
case \ReflectionObject::class:
case \ReflectionFunction::class:
$id = $reflector->name;
break;
case 'ReflectionMethod':
$id = $reflector->class . '::' . $reflector->name;
case \ReflectionMethod::class:
$id = $reflector->class.'::'.$reflector->name;
break;
case 'ReflectionProperty':
$id = $reflector->class . '::$' . $reflector->name;
case \ReflectionProperty::class:
$id = $reflector->class.'::$'.$reflector->name;
break;
case 'ReflectionClassConstant':
case 'Psy\Reflection\ReflectionClassConstant':
case \ReflectionClassConstant::class:
case ReflectionClassConstant::class:
// @todo this is going to collide with ReflectionMethod ids
// someday... start running the query by id + type if the DB
// supports it.
$id = $reflector->class . '::' . $reflector->name;
$id = $reflector->class.'::'.$reflector->name;
break;
case 'Psy\Reflection\ReflectionConstant_':
case ReflectionConstant_::class:
$id = $reflector->name;
break;
@@ -122,12 +164,91 @@ HELP
return $this->getManualDocById($id);
}
/**
* Get all all parent Reflectors for a given Reflector.
*
* For example, passing a Class, Object or TraitReflector will yield all
* traits and parent classes. Passing a Method or PropertyReflector will
* yield Reflectors for the same-named method or property on all traits and
* parent classes.
*
* @return \Generator a whole bunch of \Reflector instances
*/
private function getParentReflectors($reflector): \Generator
{
$seenClasses = [];
switch (\get_class($reflector)) {
case \ReflectionClass::class:
case \ReflectionObject::class:
foreach ($reflector->getTraits() as $trait) {
if (!\in_array($trait->getName(), $seenClasses)) {
$seenClasses[] = $trait->getName();
yield $trait;
}
}
foreach ($reflector->getInterfaces() as $interface) {
if (!\in_array($interface->getName(), $seenClasses)) {
$seenClasses[] = $interface->getName();
yield $interface;
}
}
while ($reflector = $reflector->getParentClass()) {
yield $reflector;
foreach ($reflector->getTraits() as $trait) {
if (!\in_array($trait->getName(), $seenClasses)) {
$seenClasses[] = $trait->getName();
yield $trait;
}
}
foreach ($reflector->getInterfaces() as $interface) {
if (!\in_array($interface->getName(), $seenClasses)) {
$seenClasses[] = $interface->getName();
yield $interface;
}
}
}
return;
case \ReflectionMethod::class:
foreach ($this->getParentReflectors($reflector->getDeclaringClass()) as $parent) {
if ($parent->hasMethod($reflector->getName())) {
$parentMethod = $parent->getMethod($reflector->getName());
if (!\in_array($parentMethod->getDeclaringClass()->getName(), $seenClasses)) {
$seenClasses[] = $parentMethod->getDeclaringClass()->getName();
yield $parentMethod;
}
}
}
return;
case \ReflectionProperty::class:
foreach ($this->getParentReflectors($reflector->getDeclaringClass()) as $parent) {
if ($parent->hasProperty($reflector->getName())) {
$parentProperty = $parent->getProperty($reflector->getName());
if (!\in_array($parentProperty->getDeclaringClass()->getName(), $seenClasses)) {
$seenClasses[] = $parentProperty->getDeclaringClass()->getName();
yield $parentProperty;
}
}
}
break;
}
}
private function getManualDocById($id)
{
if ($db = $this->getApplication()->getManualDb()) {
return $db
->query(\sprintf('SELECT doc FROM php_manual WHERE id = %s', $db->quote($id)))
->fetchColumn(0);
$result = $db->query(\sprintf('SELECT doc FROM php_manual WHERE id = %s', $db->quote($id)));
if ($result !== false) {
return $result->fetchColumn(0);
}
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -66,10 +66,12 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$depth = $input->getOption('depth');
$depth = $input->getOption('depth');
$target = $this->resolveCode($input->getArgument('target'));
$output->page($this->presenter->present($target, $depth, $input->getOption('all') ? Presenter::VERBOSE : 0));
@@ -87,9 +89,9 @@ HELP
*
* @return mixed
*/
protected function resolveTarget($name)
protected function resolveTarget(string $name)
{
@\trigger_error('`resolveTarget` is deprecated; use `resolveCode` instead.', E_USER_DEPRECATED);
@\trigger_error('`resolveTarget` is deprecated; use `resolveCode` instead.', \E_USER_DEPRECATED);
return $this->resolveCode($name);
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -74,6 +74,8 @@ class EditCommand extends Command implements ContextAware
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int 0 if everything went fine, or an exit code
*
* @throws \InvalidArgumentException when both exec and no-exec flags are given or if a given variable is not found in the current context
* @throws \UnexpectedValueException if file_get_contents on the edited file returns false instead of a string
*/
@@ -115,7 +117,7 @@ class EditCommand extends Command implements ContextAware
*
* @return bool
*/
private function shouldExecuteFile($execOption, $noExecOption, $filePath)
private function shouldExecuteFile(bool $execOption, bool $noExecOption, string $filePath = null): bool
{
if ($execOption) {
return true;
@@ -136,11 +138,11 @@ class EditCommand extends Command implements ContextAware
*
* @throws \InvalidArgumentException If the variable is not found in the current context
*/
private function extractFilePath($fileArgument)
private function extractFilePath(string $fileArgument = null)
{
// If the file argument was a variable, get it from the context
if ($fileArgument !== null &&
\strlen($fileArgument) > 0 &&
$fileArgument !== '' &&
$fileArgument[0] === '$') {
$fileArgument = $this->context->get(\preg_replace('/^\$/', '', $fileArgument));
}
@@ -150,18 +152,19 @@ class EditCommand extends Command implements ContextAware
/**
* @param string $filePath
* @param string $shouldRemoveFile
* @param bool $shouldRemoveFile
*
* @return string
*
* @throws \UnexpectedValueException if file_get_contents on $filePath returns false instead of a string
*/
private function editFile($filePath, $shouldRemoveFile)
private function editFile(string $filePath, bool $shouldRemoveFile): string
{
$escapedFilePath = \escapeshellarg($filePath);
$editor = (isset($_SERVER['EDITOR']) && $_SERVER['EDITOR']) ? $_SERVER['EDITOR'] : 'nano';
$pipes = [];
$proc = \proc_open((\getenv('EDITOR') ?: 'nano') . " {$escapedFilePath}", [STDIN, STDOUT, STDERR], $pipes);
$proc = \proc_open("{$editor} {$escapedFilePath}", [\STDIN, \STDOUT, \STDERR], $pipes);
\proc_close($proc);
$editedContent = @\file_get_contents($filePath);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -44,6 +44,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\Command;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -45,13 +46,15 @@ class HelpCommand extends Command
*
* @param Command $command
*/
public function setCommand($command)
public function setCommand(Command $command)
{
$this->command = $command;
}
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -86,13 +89,19 @@ class HelpCommand extends Command
]);
}
$output->startPaging();
if ($output instanceof ShellOutput) {
$output->startPaging();
}
if ($table instanceof TableHelper) {
$table->render($output);
} else {
$table->render();
}
$output->stopPaging();
if ($output instanceof ShellOutput) {
$output->stopPaging();
}
}
return 0;

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -60,19 +60,19 @@ class HistoryCommand extends Command
->setName('history')
->setAliases(['hist'])
->setDefinition([
new InputOption('show', 's', InputOption::VALUE_REQUIRED, 'Show the given range of lines.'),
new InputOption('head', 'H', InputOption::VALUE_REQUIRED, 'Display the first N items.'),
new InputOption('tail', 'T', InputOption::VALUE_REQUIRED, 'Display the last N items.'),
new InputOption('show', 's', InputOption::VALUE_REQUIRED, 'Show the given range of lines.'),
new InputOption('head', 'H', InputOption::VALUE_REQUIRED, 'Display the first N items.'),
new InputOption('tail', 'T', InputOption::VALUE_REQUIRED, 'Display the last N items.'),
$grep,
$insensitive,
$invert,
new InputOption('no-numbers', 'N', InputOption::VALUE_NONE, 'Omit line numbers.'),
new InputOption('no-numbers', 'N', InputOption::VALUE_NONE, 'Omit line numbers.'),
new InputOption('save', '', InputOption::VALUE_REQUIRED, 'Save history to a file.'),
new InputOption('replay', '', InputOption::VALUE_NONE, 'Replay.'),
new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the history.'),
new InputOption('save', '', InputOption::VALUE_REQUIRED, 'Save history to a file.'),
new InputOption('replay', '', InputOption::VALUE_NONE, 'Replay.'),
new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the history.'),
])
->setDescription('Show the Psy Shell history.')
->setHelp(
@@ -90,6 +90,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -105,14 +107,14 @@ HELP
$this->filter->bind($input);
if ($this->filter->hasFilter()) {
$matches = [];
$matches = [];
$highlighted = [];
foreach ($history as $i => $line) {
if ($this->filter->match($line, $matches)) {
if (isset($matches[0])) {
$chunks = \explode($matches[0], $history[$i]);
$chunks = \array_map([__CLASS__, 'escape'], $chunks);
$glue = \sprintf('<urgent>%s</urgent>', self::escape($matches[0]));
$glue = \sprintf('<urgent>%s</urgent>', self::escape($matches[0]));
$highlighted[$i] = \implode($glue, $chunks);
}
@@ -124,7 +126,7 @@ HELP
if ($save = $input->getOption('save')) {
$output->writeln(\sprintf('Saving history in %s...', $save));
\file_put_contents($save, \implode(PHP_EOL, $history) . PHP_EOL);
\file_put_contents($save, \implode(\PHP_EOL, $history).\PHP_EOL);
$output->writeln('<info>History saved.</info>');
} elseif ($input->getOption('replay')) {
if (!($input->getOption('show') || $input->getOption('head') || $input->getOption('tail'))) {
@@ -156,7 +158,7 @@ HELP
*
* @return array [ start, end ]
*/
private function extractRange($range)
private function extractRange(string $range): array
{
if (\preg_match('/^\d+$/', $range)) {
return [$range, $range + 1];
@@ -164,25 +166,25 @@ HELP
$matches = [];
if ($range !== '..' && \preg_match('/^(\d*)\.\.(\d*)$/', $range, $matches)) {
$start = $matches[1] ? \intval($matches[1]) : 0;
$end = $matches[2] ? \intval($matches[2]) + 1 : PHP_INT_MAX;
$start = $matches[1] ? (int) $matches[1] : 0;
$end = $matches[2] ? (int) $matches[2] + 1 : \PHP_INT_MAX;
return [$start, $end];
}
throw new \InvalidArgumentException('Unexpected range: ' . $range);
throw new \InvalidArgumentException('Unexpected range: '.$range);
}
/**
* Retrieve a slice of the readline history.
*
* @param string $show
* @param string $head
* @param string $tail
* @param string|null $show
* @param string|null $head
* @param string|null $tail
*
* @return array A slilce of history
* @return array A slice of history
*/
private function getHistorySlice($show, $head, $tail)
private function getHistorySlice($show, $head, $tail): array
{
$history = $this->readline->listHistory();
@@ -197,15 +199,15 @@ HELP
throw new \InvalidArgumentException('Please specify an integer argument for --head');
}
$start = 0;
$length = \intval($head);
$start = 0;
$length = (int) $head;
} elseif ($tail) {
if (!\preg_match('/^\d+$/', $tail)) {
throw new \InvalidArgumentException('Please specify an integer argument for --tail');
}
$start = \count($history) - $tail;
$length = \intval($tail) + 1;
$start = \count($history) - $tail;
$length = (int) $tail + 1;
} else {
return $history;
}
@@ -229,7 +231,7 @@ HELP
}
if ($count > 1) {
throw new \InvalidArgumentException('Please specify only one of --' . \implode(', --', $options));
throw new \InvalidArgumentException('Please specify only one of --'.\implode(', --', $options));
}
}
@@ -241,7 +243,7 @@ HELP
$this->readline->clearHistory();
}
public static function escape($string)
public static function escape(string $string): string
{
return OutputFormatter::escape($string);
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -22,6 +22,7 @@ use Psy\Command\ListCommand\VariableEnumerator;
use Psy\Exception\RuntimeException;
use Psy\Input\CodeArgument;
use Psy\Input\FilterOptions;
use Psy\Output\ShellOutput;
use Psy\VarDumper\Presenter;
use Psy\VarDumper\PresenterAware;
use Symfony\Component\Console\Formatter\OutputFormatter;
@@ -57,33 +58,33 @@ class ListCommand extends ReflectingCommand implements PresenterAware
$this
->setName('ls')
->setAliases(['list', 'dir'])
->setAliases(['dir'])
->setDefinition([
new CodeArgument('target', CodeArgument::OPTIONAL, 'A target class or object to list.'),
new InputOption('vars', '', InputOption::VALUE_NONE, 'Display variables.'),
new InputOption('constants', 'c', InputOption::VALUE_NONE, 'Display defined constants.'),
new InputOption('functions', 'f', InputOption::VALUE_NONE, 'Display defined functions.'),
new InputOption('classes', 'k', InputOption::VALUE_NONE, 'Display declared classes.'),
new InputOption('interfaces', 'I', InputOption::VALUE_NONE, 'Display declared interfaces.'),
new InputOption('traits', 't', InputOption::VALUE_NONE, 'Display declared traits.'),
new InputOption('vars', '', InputOption::VALUE_NONE, 'Display variables.'),
new InputOption('constants', 'c', InputOption::VALUE_NONE, 'Display defined constants.'),
new InputOption('functions', 'f', InputOption::VALUE_NONE, 'Display defined functions.'),
new InputOption('classes', 'k', InputOption::VALUE_NONE, 'Display declared classes.'),
new InputOption('interfaces', 'I', InputOption::VALUE_NONE, 'Display declared interfaces.'),
new InputOption('traits', 't', InputOption::VALUE_NONE, 'Display declared traits.'),
new InputOption('no-inherit', '', InputOption::VALUE_NONE, 'Exclude inherited methods, properties and constants.'),
new InputOption('no-inherit', '', InputOption::VALUE_NONE, 'Exclude inherited methods, properties and constants.'),
new InputOption('properties', 'p', InputOption::VALUE_NONE, 'Display class or object properties (public properties by default).'),
new InputOption('methods', 'm', InputOption::VALUE_NONE, 'Display class or object methods (public methods by default).'),
new InputOption('properties', 'p', InputOption::VALUE_NONE, 'Display class or object properties (public properties by default).'),
new InputOption('methods', 'm', InputOption::VALUE_NONE, 'Display class or object methods (public methods by default).'),
$grep,
$insensitive,
$invert,
new InputOption('globals', 'g', InputOption::VALUE_NONE, 'Include global variables.'),
new InputOption('internal', 'n', InputOption::VALUE_NONE, 'Limit to internal functions and classes.'),
new InputOption('user', 'u', InputOption::VALUE_NONE, 'Limit to user-defined constants, functions and classes.'),
new InputOption('category', 'C', InputOption::VALUE_REQUIRED, 'Limit to constants in a specific category (e.g. "date").'),
new InputOption('globals', 'g', InputOption::VALUE_NONE, 'Include global variables.'),
new InputOption('internal', 'n', InputOption::VALUE_NONE, 'Limit to internal functions and classes.'),
new InputOption('user', 'u', InputOption::VALUE_NONE, 'Limit to user-defined constants, functions and classes.'),
new InputOption('category', 'C', InputOption::VALUE_REQUIRED, 'Limit to constants in a specific category (e.g. "date").'),
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'),
new InputOption('long', 'l', InputOption::VALUE_NONE, 'List in long format: includes class names and method signatures.'),
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'),
new InputOption('long', 'l', InputOption::VALUE_NONE, 'List in long format: includes class names and method signatures.'),
])
->setDescription('List local, instance or class variables, methods and constants.')
->setHelp(
@@ -111,6 +112,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -126,7 +129,7 @@ HELP
}
// @todo something cleaner than this :-/
if ($input->getOption('long')) {
if ($output instanceof ShellOutput && $input->getOption('long')) {
$output->startPaging();
}
@@ -134,7 +137,7 @@ HELP
$this->$method($output, $enumerator->enumerate($input, $reflector, $target));
}
if ($input->getOption('long')) {
if ($output instanceof ShellOutput && $input->getOption('long')) {
$output->stopPaging();
}
@@ -171,11 +174,11 @@ HELP
* Write the list items to $output.
*
* @param OutputInterface $output
* @param null|array $result List of enumerated items
* @param array $result List of enumerated items
*/
protected function write(OutputInterface $output, array $result = null)
protected function write(OutputInterface $output, array $result)
{
if ($result === null) {
if (\count($result) === 0) {
return;
}
@@ -191,11 +194,11 @@ HELP
* Items are listed one per line, and include the item signature.
*
* @param OutputInterface $output
* @param null|array $result List of enumerated items
* @param array $result List of enumerated items
*/
protected function writeLong(OutputInterface $output, array $result = null)
protected function writeLong(OutputInterface $output, array $result)
{
if ($result === null) {
if (\count($result) === 0) {
return;
}
@@ -225,7 +228,7 @@ HELP
*
* @return string
*/
private function formatItemName($item)
private function formatItemName(array $item): string
{
return \sprintf('<%s>%s</%s>', $item['style'], OutputFormatter::escape($item['name']), $item['style']);
}
@@ -243,7 +246,7 @@ HELP
// if no target is passed, there can be no properties or methods
foreach (['properties', 'methods', 'no-inherit'] as $option) {
if ($input->getOption($option)) {
throw new RuntimeException('--' . $option . ' does not make sense without a specified target');
throw new RuntimeException('--'.$option.' does not make sense without a specified target');
}
}
@@ -257,22 +260,23 @@ HELP
$input->setOption('vars', true);
} else {
// if a target is passed, classes, functions, etc don't make sense
foreach (['vars', 'globals', 'functions', 'classes', 'interfaces', 'traits'] as $option) {
foreach (['vars', 'globals'] as $option) {
if ($input->getOption($option)) {
throw new RuntimeException('--' . $option . ' does not make sense with a specified target');
throw new RuntimeException('--'.$option.' does not make sense with a specified target');
}
}
foreach (['constants', 'properties', 'methods'] as $option) {
// @todo ensure that 'functions', 'classes', 'interfaces', 'traits' only accept namespace target?
foreach (['constants', 'properties', 'methods', 'functions', 'classes', 'interfaces', 'traits'] as $option) {
if ($input->getOption($option)) {
return;
}
}
// default to --constants --properties --methods if no other options are passed
$input->setOption('constants', true);
$input->setOption('constants', true);
$input->setOption('properties', true);
$input->setOption('methods', true);
$input->setOption('methods', true);
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -22,30 +22,29 @@ class ClassConstantEnumerator extends Enumerator
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
// only list constants when a Reflector is present.
if ($reflector === null) {
return;
return [];
}
// We can only list constants on actual class (or object) reflectors.
if (!$reflector instanceof \ReflectionClass) {
// @todo handle ReflectionExtension as well
return;
return [];
}
// only list constants if we are specifically asked
if (!$input->getOption('constants')) {
return;
return [];
}
$noInherit = $input->getOption('no-inherit');
$constants = $this->prepareConstants($this->getConstants($reflector, $noInherit));
if (empty($constants)) {
return;
return [];
}
$ret = [];
@@ -62,7 +61,7 @@ class ClassConstantEnumerator extends Enumerator
*
* @return array
*/
protected function getConstants(\Reflector $reflector, $noInherit = false)
protected function getConstants(\Reflector $reflector, bool $noInherit = false): array
{
$className = $reflector->getName();
@@ -77,7 +76,7 @@ class ClassConstantEnumerator extends Enumerator
$constants[$name] = $constReflector;
}
\ksort($constants, SORT_NATURAL | SORT_FLAG_CASE);
\ksort($constants, \SORT_NATURAL | \SORT_FLAG_CASE);
return $constants;
}
@@ -89,7 +88,7 @@ class ClassConstantEnumerator extends Enumerator
*
* @return array
*/
protected function prepareConstants(array $constants)
protected function prepareConstants(array $constants): array
{
// My kingdom for a generator.
$ret = [];
@@ -114,12 +113,10 @@ class ClassConstantEnumerator extends Enumerator
*
* @return string
*/
protected function getKindLabel(\ReflectionClass $reflector)
protected function getKindLabel(\ReflectionClass $reflector): string
{
if ($reflector->isInterface()) {
return 'Interface Constants';
} elseif (\method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
return 'Trait Constants';
} else {
return 'Class Constants';
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\Command\ListCommand;
use Psy\Reflection\ReflectionNamespace;
use Symfony\Component\Console\Input\InputInterface;
/**
@@ -21,37 +22,31 @@ class ClassEnumerator extends Enumerator
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
// only list classes when no Reflector is present.
//
// @todo make a NamespaceReflector and pass that in for commands like:
//
// ls --classes Foo
//
// ... for listing classes in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
// if we have a reflector, ensure that it's a namespace reflector
if (($target !== null || $reflector !== null) && !$reflector instanceof ReflectionNamespace) {
return [];
}
$user = $input->getOption('user');
$internal = $input->getOption('internal');
$user = $input->getOption('user');
$prefix = $reflector === null ? null : \strtolower($reflector->getName()).'\\';
$ret = [];
// only list classes, interfaces and traits if we are specifically asked
if ($input->getOption('classes')) {
$ret = \array_merge($ret, $this->filterClasses('Classes', \get_declared_classes(), $internal, $user));
$ret = \array_merge($ret, $this->filterClasses('Classes', \get_declared_classes(), $internal, $user, $prefix));
}
if ($input->getOption('interfaces')) {
$ret = \array_merge($ret, $this->filterClasses('Interfaces', \get_declared_interfaces(), $internal, $user));
$ret = \array_merge($ret, $this->filterClasses('Interfaces', \get_declared_interfaces(), $internal, $user, $prefix));
}
if ($input->getOption('traits')) {
$ret = \array_merge($ret, $this->filterClasses('Traits', \get_declared_traits(), $internal, $user));
$ret = \array_merge($ret, $this->filterClasses('Traits', \get_declared_traits(), $internal, $user, $prefix));
}
return \array_map([$this, 'prepareClasses'], \array_filter($ret));
@@ -67,15 +62,20 @@ class ClassEnumerator extends Enumerator
* @param array $classes
* @param bool $internal
* @param bool $user
* @param string $prefix
*
* @return array
*/
protected function filterClasses($key, $classes, $internal, $user)
protected function filterClasses(string $key, array $classes, bool $internal, bool $user, string $prefix = null): array
{
$ret = [];
if ($internal) {
$ret['Internal ' . $key] = \array_filter($classes, function ($class) {
$ret['Internal '.$key] = \array_filter($classes, function ($class) use ($prefix) {
if ($prefix !== null && \strpos(\strtolower($class), $prefix) !== 0) {
return false;
}
$refl = new \ReflectionClass($class);
return $refl->isInternal();
@@ -83,7 +83,11 @@ class ClassEnumerator extends Enumerator
}
if ($user) {
$ret['User ' . $key] = \array_filter($classes, function ($class) {
$ret['User '.$key] = \array_filter($classes, function ($class) use ($prefix) {
if ($prefix !== null && \strpos(\strtolower($class), $prefix) !== 0) {
return false;
}
$refl = new \ReflectionClass($class);
return !$refl->isInternal();
@@ -91,7 +95,9 @@ class ClassEnumerator extends Enumerator
}
if (!$user && !$internal) {
$ret[$key] = $classes;
$ret[$key] = \array_filter($classes, function ($class) use ($prefix) {
return $prefix === null || \strpos(\strtolower($class), $prefix) === 0;
});
}
return $ret;
@@ -104,7 +110,7 @@ class ClassEnumerator extends Enumerator
*
* @return array
*/
protected function prepareClasses(array $classes)
protected function prepareClasses(array $classes): array
{
\natcasesort($classes);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\Command\ListCommand;
use Psy\Reflection\ReflectionNamespace;
use Symfony\Component\Console\Input\InputInterface;
/**
@@ -18,31 +19,64 @@ use Symfony\Component\Console\Input\InputInterface;
*/
class ConstantEnumerator extends Enumerator
{
// Because `Json` is ugly.
private static $categoryLabels = [
'libxml' => 'libxml',
'openssl' => 'OpenSSL',
'pcre' => 'PCRE',
'sqlite3' => 'SQLite3',
'curl' => 'cURL',
'dom' => 'DOM',
'ftp' => 'FTP',
'gd' => 'GD',
'gmp' => 'GMP',
'iconv' => 'iconv',
'json' => 'JSON',
'ldap' => 'LDAP',
'mbstring' => 'mbstring',
'odbc' => 'ODBC',
'pcntl' => 'PCNTL',
'pgsql' => 'pgsql',
'posix' => 'POSIX',
'mysqli' => 'mysqli',
'soap' => 'SOAP',
'exif' => 'EXIF',
'sysvmsg' => 'sysvmsg',
'xml' => 'XML',
'xsl' => 'XSL',
];
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
// only list constants when no Reflector is present.
//
// @todo make a NamespaceReflector and pass that in for commands like:
//
// ls --constants Foo
//
// ... for listing constants in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
// if we have a reflector, ensure that it's a namespace reflector
if (($target !== null || $reflector !== null) && !$reflector instanceof ReflectionNamespace) {
return [];
}
// only list constants if we are specifically asked
if (!$input->getOption('constants')) {
return;
return [];
}
$user = $input->getOption('user');
$user = $input->getOption('user');
$internal = $input->getOption('internal');
$category = $input->getOption('category');
if ($category) {
$category = \strtolower($category);
if ($category === 'internal') {
$internal = true;
$category = null;
} elseif ($category === 'user') {
$user = true;
$category = null;
}
}
$ret = [];
if ($user) {
@@ -50,11 +84,12 @@ class ConstantEnumerator extends Enumerator
}
if ($internal) {
$ret['Interal Constants'] = $this->getConstants('internal');
$ret['Internal Constants'] = $this->getConstants('internal');
}
if ($category) {
$label = \ucfirst($category) . ' Constants';
$caseCategory = \array_key_exists($category, self::$categoryLabels) ? self::$categoryLabels[$category] : \ucfirst($category);
$label = $caseCategory.' Constants';
$ret[$label] = $this->getConstants($category);
}
@@ -62,6 +97,18 @@ class ConstantEnumerator extends Enumerator
$ret['Constants'] = $this->getConstants();
}
if ($reflector !== null) {
$prefix = \strtolower($reflector->getName()).'\\';
foreach ($ret as $key => $names) {
foreach (\array_keys($names) as $name) {
if (\strpos(\strtolower($name), $prefix) !== 0) {
unset($ret[$key][$name]);
}
}
}
}
return \array_map([$this, 'prepareConstants'], \array_filter($ret));
}
@@ -75,7 +122,7 @@ class ConstantEnumerator extends Enumerator
*
* @return array
*/
protected function getConstants($category = null)
protected function getConstants(string $category = null): array
{
if (!$category) {
return \get_defined_constants();
@@ -86,10 +133,16 @@ class ConstantEnumerator extends Enumerator
if ($category === 'internal') {
unset($consts['user']);
return \call_user_func_array('array_merge', $consts);
return \array_merge(...\array_values($consts));
}
return isset($consts[$category]) ? $consts[$category] : [];
foreach ($consts as $key => $value) {
if (\strtolower($key) === $category) {
return $value;
}
}
return [];
}
/**
@@ -99,7 +152,7 @@ class ConstantEnumerator extends Enumerator
*
* @return array
*/
protected function prepareConstants(array $constants)
protected function prepareConstants(array $constants): array
{
// My kingdom for a generator.
$ret = [];

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -23,13 +23,13 @@ use Symfony\Component\Console\Input\InputInterface;
abstract class Enumerator
{
// Output styles
const IS_PUBLIC = 'public';
const IS_PUBLIC = 'public';
const IS_PROTECTED = 'protected';
const IS_PRIVATE = 'private';
const IS_GLOBAL = 'global';
const IS_CONSTANT = 'const';
const IS_CLASS = 'class';
const IS_FUNCTION = 'function';
const IS_PRIVATE = 'private';
const IS_GLOBAL = 'global';
const IS_CONSTANT = 'const';
const IS_CLASS = 'class';
const IS_FUNCTION = 'function';
private $filter;
private $presenter;
@@ -48,13 +48,13 @@ abstract class Enumerator
/**
* Return a list of categorized things with the given input options and target.
*
* @param InputInterface $input
* @param \Reflector $reflector
* @param mixed $target
* @param InputInterface $input
* @param \Reflector|null $reflector
* @param mixed $target
*
* @return array
*/
public function enumerate(InputInterface $input, \Reflector $reflector = null, $target = null)
public function enumerate(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
$this->filter->bind($input);
@@ -76,13 +76,13 @@ abstract class Enumerator
* ],
* ]
*
* @param InputInterface $input
* @param \Reflector $reflector
* @param mixed $target
* @param InputInterface $input
* @param \Reflector|null $reflector
* @param mixed $target
*
* @return array
*/
abstract protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null);
abstract protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array;
protected function showItem($name)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\Command\ListCommand;
use Psy\Reflection\ReflectionNamespace;
use Symfony\Component\Console\Input\InputInterface;
/**
@@ -21,40 +22,34 @@ class FunctionEnumerator extends Enumerator
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
// only list functions when no Reflector is present.
//
// @todo make a NamespaceReflector and pass that in for commands like:
//
// ls --functions Foo
//
// ... for listing functions in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
// if we have a reflector, ensure that it's a namespace reflector
if (($target !== null || $reflector !== null) && !$reflector instanceof ReflectionNamespace) {
return [];
}
// only list functions if we are specifically asked
if (!$input->getOption('functions')) {
return;
return [];
}
if ($input->getOption('user')) {
$label = 'User Functions';
$label = 'User Functions';
$functions = $this->getFunctions('user');
} elseif ($input->getOption('internal')) {
$label = 'Internal Functions';
$label = 'Internal Functions';
$functions = $this->getFunctions('internal');
} else {
$label = 'Functions';
$label = 'Functions';
$functions = $this->getFunctions();
}
$functions = $this->prepareFunctions($functions);
$prefix = $reflector === null ? null : \strtolower($reflector->getName()).'\\';
$functions = $this->prepareFunctions($functions, $prefix);
if (empty($functions)) {
return;
return [];
}
$ret = [];
@@ -68,11 +63,11 @@ class FunctionEnumerator extends Enumerator
*
* Optionally limit functions to "user" or "internal" functions.
*
* @param null|string $type "user" or "internal" (default: both)
* @param string|null $type "user" or "internal" (default: both)
*
* @return array
*/
protected function getFunctions($type = null)
protected function getFunctions(string $type = null): array
{
$funcs = \get_defined_functions();
@@ -86,11 +81,12 @@ class FunctionEnumerator extends Enumerator
/**
* Prepare formatted function array.
*
* @param array $functions
* @param array $functions
* @param string $prefix
*
* @return array
*/
protected function prepareFunctions(array $functions)
protected function prepareFunctions(array $functions, string $prefix = null): array
{
\natcasesort($functions);
@@ -98,12 +94,20 @@ class FunctionEnumerator extends Enumerator
$ret = [];
foreach ($functions as $name) {
if ($prefix !== null && \strpos(\strtolower($name), $prefix) !== 0) {
continue;
}
if ($this->showItem($name)) {
$ret[$name] = [
'name' => $name,
'style' => self::IS_FUNCTION,
'value' => $this->presentSignature($name),
];
try {
$ret[$name] = [
'name' => $name,
'style' => self::IS_FUNCTION,
'value' => $this->presentSignature($name),
];
} catch (\Throwable $e) {
// Ignore failures.
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,22 +21,22 @@ class GlobalVariableEnumerator extends Enumerator
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
// only list globals when no Reflector is present.
if ($reflector !== null || $target !== null) {
return;
return [];
}
// only list globals if we are specifically asked
if (!$input->getOption('globals')) {
return;
return [];
}
$globals = $this->prepareGlobals($this->getGlobals());
if (empty($globals)) {
return;
return [];
}
return [
@@ -49,7 +49,7 @@ class GlobalVariableEnumerator extends Enumerator
*
* @return array
*/
protected function getGlobals()
protected function getGlobals(): array
{
global $GLOBALS;
@@ -71,14 +71,14 @@ class GlobalVariableEnumerator extends Enumerator
*
* @return array
*/
protected function prepareGlobals($globals)
protected function prepareGlobals(array $globals): array
{
// My kingdom for a generator.
$ret = [];
foreach ($globals as $name => $value) {
if ($this->showItem($name)) {
$fname = '$' . $name;
$fname = '$'.$name;
$ret[$fname] = [
'name' => $fname,
'style' => self::IS_GLOBAL,

View File

@@ -1,89 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Psy\VarDumper\Presenter;
use Symfony\Component\Console\Input\InputInterface;
/**
* Interface Enumerator class.
*
* @deprecated Nothing should use this anymore
*/
class InterfaceEnumerator extends Enumerator
{
public function __construct(Presenter $presenter)
{
@\trigger_error('InterfaceEnumerator is no longer used', E_USER_DEPRECATED);
parent::__construct($presenter);
}
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list interfaces when no Reflector is present.
//
// @todo make a NamespaceReflector and pass that in for commands like:
//
// ls --interfaces Foo
//
// ... for listing interfaces in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
}
// only list interfaces if we are specifically asked
if (!$input->getOption('interfaces')) {
return;
}
$interfaces = $this->prepareInterfaces(\get_declared_interfaces());
if (empty($interfaces)) {
return;
}
return [
'Interfaces' => $interfaces,
];
}
/**
* Prepare formatted interface array.
*
* @param array $interfaces
*
* @return array
*/
protected function prepareInterfaces(array $interfaces)
{
\natcasesort($interfaces);
// My kingdom for a generator.
$ret = [];
foreach ($interfaces as $name) {
if ($this->showItem($name)) {
$ret[$name] = [
'name' => $name,
'style' => self::IS_CLASS,
'value' => $this->presentSignature($name),
];
}
}
return $ret;
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,30 +21,29 @@ class MethodEnumerator extends Enumerator
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
// only list methods when a Reflector is present.
if ($reflector === null) {
return;
return [];
}
// We can only list methods on actual class (or object) reflectors.
if (!$reflector instanceof \ReflectionClass) {
return;
return [];
}
// only list methods if we are specifically asked
if (!$input->getOption('methods')) {
return;
return [];
}
$showAll = $input->getOption('all');
$showAll = $input->getOption('all');
$noInherit = $input->getOption('no-inherit');
$methods = $this->prepareMethods($this->getMethods($showAll, $reflector, $noInherit));
$methods = $this->prepareMethods($this->getMethods($showAll, $reflector, $noInherit));
if (empty($methods)) {
return;
return [];
}
$ret = [];
@@ -62,13 +61,15 @@ class MethodEnumerator extends Enumerator
*
* @return array
*/
protected function getMethods($showAll, \Reflector $reflector, $noInherit = false)
protected function getMethods(bool $showAll, \Reflector $reflector, bool $noInherit = false): array
{
$className = $reflector->getName();
$methods = [];
foreach ($reflector->getMethods() as $name => $method) {
if ($noInherit && $method->getDeclaringClass()->getName() !== $className) {
// For some reason PHP reflection shows private methods from the parent class, even
// though they're effectively worthless. Let's suppress them here, like --no-inherit
if (($noInherit || $method->isPrivate()) && $method->getDeclaringClass()->getName() !== $className) {
continue;
}
@@ -77,7 +78,7 @@ class MethodEnumerator extends Enumerator
}
}
\ksort($methods, SORT_NATURAL | SORT_FLAG_CASE);
\ksort($methods, \SORT_NATURAL | \SORT_FLAG_CASE);
return $methods;
}
@@ -89,7 +90,7 @@ class MethodEnumerator extends Enumerator
*
* @return array
*/
protected function prepareMethods(array $methods)
protected function prepareMethods(array $methods): array
{
// My kingdom for a generator.
$ret = [];
@@ -114,7 +115,7 @@ class MethodEnumerator extends Enumerator
*
* @return string
*/
protected function getKindLabel(\ReflectionClass $reflector)
protected function getKindLabel(\ReflectionClass $reflector): string
{
if ($reflector->isInterface()) {
return 'Interface Methods';
@@ -132,7 +133,7 @@ class MethodEnumerator extends Enumerator
*
* @return string
*/
private function getVisibilityStyle(\ReflectionMethod $method)
private function getVisibilityStyle(\ReflectionMethod $method): string
{
if ($method->isPublic()) {
return self::IS_PUBLIC;

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,30 +21,30 @@ class PropertyEnumerator extends Enumerator
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
// only list properties when a Reflector is present.
if ($reflector === null) {
return;
return [];
}
// We can only list properties on actual class (or object) reflectors.
if (!$reflector instanceof \ReflectionClass) {
return;
return [];
}
// only list properties if we are specifically asked
if (!$input->getOption('properties')) {
return;
return [];
}
$showAll = $input->getOption('all');
$noInherit = $input->getOption('no-inherit');
$showAll = $input->getOption('all');
$noInherit = $input->getOption('no-inherit');
$properties = $this->prepareProperties($this->getProperties($showAll, $reflector, $noInherit), $target);
if (empty($properties)) {
return;
return [];
}
$ret = [];
@@ -62,7 +62,7 @@ class PropertyEnumerator extends Enumerator
*
* @return array
*/
protected function getProperties($showAll, \Reflector $reflector, $noInherit = false)
protected function getProperties(bool $showAll, \Reflector $reflector, bool $noInherit = false): array
{
$className = $reflector->getName();
@@ -77,7 +77,7 @@ class PropertyEnumerator extends Enumerator
}
}
\ksort($properties, SORT_NATURAL | SORT_FLAG_CASE);
\ksort($properties, \SORT_NATURAL | \SORT_FLAG_CASE);
return $properties;
}
@@ -89,14 +89,14 @@ class PropertyEnumerator extends Enumerator
*
* @return array
*/
protected function prepareProperties(array $properties, $target = null)
protected function prepareProperties(array $properties, $target = null): array
{
// My kingdom for a generator.
$ret = [];
foreach ($properties as $name => $property) {
if ($this->showItem($name)) {
$fname = '$' . $name;
$fname = '$'.$name;
$ret[$fname] = [
'name' => $fname,
'style' => $this->getVisibilityStyle($property),
@@ -115,11 +115,9 @@ class PropertyEnumerator extends Enumerator
*
* @return string
*/
protected function getKindLabel(\ReflectionClass $reflector)
protected function getKindLabel(\ReflectionClass $reflector): string
{
if ($reflector->isInterface()) {
return 'Interface Properties';
} elseif (\method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
if (\method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
return 'Trait Properties';
} else {
return 'Class Properties';
@@ -133,7 +131,7 @@ class PropertyEnumerator extends Enumerator
*
* @return string
*/
private function getVisibilityStyle(\ReflectionProperty $property)
private function getVisibilityStyle(\ReflectionProperty $property): string
{
if ($property->isPublic()) {
return self::IS_PUBLIC;
@@ -152,9 +150,13 @@ class PropertyEnumerator extends Enumerator
*
* @return string
*/
protected function presentValue(\ReflectionProperty $property, $target)
protected function presentValue(\ReflectionProperty $property, $target): string
{
// If $target is a class, trait or interface (try to) get the default
if (!$target) {
return '';
}
// If $target is a class or trait (try to) get the default
// value for the property.
if (!\is_object($target)) {
try {
@@ -163,9 +165,9 @@ class PropertyEnumerator extends Enumerator
if (\array_key_exists($property->name, $props)) {
$suffix = $property->isStatic() ? '' : ' <aside>(default)</aside>';
return $this->presentRef($props[$property->name]) . $suffix;
return $this->presentRef($props[$property->name]).$suffix;
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
// Well, we gave it a shot.
}

View File

@@ -1,89 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Psy\VarDumper\Presenter;
use Symfony\Component\Console\Input\InputInterface;
/**
* Trait Enumerator class.
*
* @deprecated Nothing should use this anymore
*/
class TraitEnumerator extends Enumerator
{
public function __construct(Presenter $presenter)
{
@\trigger_error('TraitEnumerator is no longer used', E_USER_DEPRECATED);
parent::__construct($presenter);
}
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list traits when no Reflector is present.
//
// @todo make a NamespaceReflector and pass that in for commands like:
//
// ls --traits Foo
//
// ... for listing traits in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
}
// only list traits if we are specifically asked
if (!$input->getOption('traits')) {
return;
}
$traits = $this->prepareTraits(\get_declared_traits());
if (empty($traits)) {
return;
}
return [
'Traits' => $traits,
];
}
/**
* Prepare formatted trait array.
*
* @param array $traits
*
* @return array
*/
protected function prepareTraits(array $traits)
{
\natcasesort($traits);
// My kingdom for a generator.
$ret = [];
foreach ($traits as $name) {
if ($this->showItem($name)) {
$ret[$name] = [
'name' => $name,
'style' => self::IS_CLASS,
'value' => $this->presentSignature($name),
];
}
}
return $ret;
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -45,23 +45,23 @@ class VariableEnumerator extends Enumerator
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array
{
// only list variables when no Reflector is present.
if ($reflector !== null || $target !== null) {
return;
return [];
}
// only list variables if we are specifically asked
if (!$input->getOption('vars')) {
return;
return [];
}
$showAll = $input->getOption('all');
$showAll = $input->getOption('all');
$variables = $this->prepareVariables($this->getVariables($showAll));
if (empty($variables)) {
return;
return [];
}
return [
@@ -76,7 +76,7 @@ class VariableEnumerator extends Enumerator
*
* @return array
*/
protected function getVariables($showAll)
protected function getVariables(bool $showAll): array
{
$scopeVars = $this->context->getAll();
\uksort($scopeVars, function ($a, $b) {
@@ -117,13 +117,13 @@ class VariableEnumerator extends Enumerator
*
* @return array
*/
protected function prepareVariables(array $variables)
protected function prepareVariables(array $variables): array
{
// My kingdom for a generator.
$ret = [];
foreach ($variables as $name => $val) {
if ($this->showItem($name)) {
$fname = '$' . $name;
$fname = '$'.$name;
$ret[$fname] = [
'name' => $fname,
'style' => \in_array($name, self::$specialNames) ? self::IS_PRIVATE : self::IS_PUBLIC,

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -46,7 +46,7 @@ class ParseCommand extends Command implements ContextAware, PresenterAware
public function __construct($name = null)
{
$this->parserFactory = new ParserFactory();
$this->parsers = [];
$this->parsers = [];
parent::__construct($name);
}
@@ -70,14 +70,14 @@ class ParseCommand extends Command implements ContextAware, PresenterAware
{
$this->presenter = clone $presenter;
$this->presenter->addCasters([
'PhpParser\Node' => function (Node $node, array $a) {
Node::class => function (Node $node, array $a) {
$a = [
Caster::PREFIX_VIRTUAL . 'type' => $node->getType(),
Caster::PREFIX_VIRTUAL . 'attributes' => $node->getAttributes(),
Caster::PREFIX_VIRTUAL.'type' => $node->getType(),
Caster::PREFIX_VIRTUAL.'attributes' => $node->getAttributes(),
];
foreach ($node->getSubNodeNames() as $name) {
$a[Caster::PREFIX_VIRTUAL . $name] = $node->$name;
$a[Caster::PREFIX_VIRTUAL.$name] = $node->$name;
}
return $a;
@@ -90,23 +90,17 @@ class ParseCommand extends Command implements ContextAware, PresenterAware
*/
protected function configure()
{
$definition = [
new CodeArgument('code', CodeArgument::REQUIRED, 'PHP code to parse.'),
new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse.', 10),
];
if ($this->parserFactory->hasKindsSupport()) {
$msg = 'One of PhpParser\\ParserFactory constants: '
. \implode(', ', ParserFactory::getPossibleKinds())
. " (default is based on current interpreter's version).";
$defaultKind = $this->parserFactory->getDefaultKind();
$definition[] = new InputOption('kind', '', InputOption::VALUE_REQUIRED, $msg, $defaultKind);
}
$kindMsg = 'One of PhpParser\\ParserFactory constants: '
.\implode(', ', ParserFactory::getPossibleKinds())
." (default is based on current interpreter's version).";
$this
->setName('parse')
->setDefinition($definition)
->setDefinition([
new CodeArgument('code', CodeArgument::REQUIRED, 'PHP code to parse.'),
new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse.', 10),
new InputOption('kind', '', InputOption::VALUE_REQUIRED, $kindMsg, $this->parserFactory->getDefaultKind()),
])
->setDescription('Parse PHP code and show the abstract syntax tree.')
->setHelp(
<<<'HELP'
@@ -128,13 +122,13 @@ HELP
protected function execute(InputInterface $input, OutputInterface $output)
{
$code = $input->getArgument('code');
if (\strpos('<?', $code) === false) {
$code = '<?php ' . $code;
if (\strpos($code, '<?') === false) {
$code = '<?php '.$code;
}
$parserKind = $this->parserFactory->hasKindsSupport() ? $input->getOption('kind') : null;
$depth = $input->getOption('depth');
$nodes = $this->parse($this->getParser($parserKind), $code);
$parserKind = $input->getOption('kind');
$depth = $input->getOption('depth');
$nodes = $this->parse($this->getParser($parserKind), $code);
$output->page($this->presenter->present($nodes, $depth));
$this->context->setReturnValue($nodes);
@@ -150,7 +144,7 @@ HELP
*
* @return array Statements
*/
private function parse(Parser $parser, $code)
private function parse(Parser $parser, string $code): array
{
try {
return $parser->parse($code);
@@ -160,7 +154,7 @@ HELP
}
// If we got an unexpected EOF, let's try it again with a semicolon.
return $parser->parse($code . ';');
return $parser->parse($code.';');
}
}
@@ -171,7 +165,7 @@ HELP
*
* @return Parser
*/
private function getParser($kind = null)
private function getParser(string $kind = null): Parser
{
if (!\array_key_exists($kind, $this->parsers)) {
$this->parsers[$kind] = $this->parserFactory->createParser($kind);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -16,6 +16,9 @@ use Psy\Context;
use Psy\ContextAware;
use Psy\Exception\ErrorException;
use Psy\Exception\RuntimeException;
use Psy\Exception\UnexpectedTargetException;
use Psy\Reflection\ReflectionClassConstant;
use Psy\Reflection\ReflectionConstant_;
use Psy\Util\Mirror;
/**
@@ -23,9 +26,9 @@ use Psy\Util\Mirror;
*/
abstract class ReflectingCommand extends Command implements ContextAware
{
const CLASS_OR_FUNC = '/^[\\\\\w]+$/';
const CLASS_MEMBER = '/^([\\\\\w]+)::(\w+)$/';
const CLASS_STATIC = '/^([\\\\\w]+)::\$(\w+)$/';
const CLASS_OR_FUNC = '/^[\\\\\w]+$/';
const CLASS_MEMBER = '/^([\\\\\w]+)::(\w+)$/';
const CLASS_STATIC = '/^([\\\\\w]+)::\$(\w+)$/';
const INSTANCE_MEMBER = '/^(\$\w+)(::|->)(\w+)$/';
/**
@@ -54,10 +57,10 @@ abstract class ReflectingCommand extends Command implements ContextAware
*
* @return array (class or instance name, member name, kind)
*/
protected function getTarget($valueName)
protected function getTarget(string $valueName): array
{
$valueName = \trim($valueName);
$matches = [];
$matches = [];
switch (true) {
case \preg_match(self::CLASS_OR_FUNC, $valueName, $matches):
return [$this->resolveName($matches[0], true), null, 0];
@@ -92,7 +95,7 @@ abstract class ReflectingCommand extends Command implements ContextAware
*
* @return string
*/
protected function resolveName($name, $includeFunctions = false)
protected function resolveName(string $name, bool $includeFunctions = false): string
{
$shell = $this->getApplication();
@@ -107,15 +110,24 @@ abstract class ReflectingCommand extends Command implements ContextAware
}
$msg = \sprintf('Cannot use "%s" when no class scope is active', \strtolower($name));
throw new ErrorException($msg, 0, E_USER_ERROR, "eval()'d code", 1);
throw new ErrorException($msg, 0, \E_USER_ERROR, "eval()'d code", 1);
}
if (\substr($name, 0, 1) === '\\') {
return $name;
}
// Check $name against the current namespace and use statements.
if (self::couldBeClassName($name)) {
try {
$name = $this->resolveCode($name.'::class');
} catch (RuntimeException $e) {
// /shrug
}
}
if ($namespace = $shell->getNamespace()) {
$fullName = $namespace . '\\' . $name;
$fullName = $namespace.'\\'.$name;
if (\class_exists($fullName) || \interface_exists($fullName) || ($includeFunctions && \function_exists($fullName))) {
return $fullName;
@@ -125,6 +137,15 @@ abstract class ReflectingCommand extends Command implements ContextAware
return $name;
}
/**
* Check whether a given name could be a class name.
*/
protected function couldBeClassName(string $name): bool
{
// Regex based on https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class
return \preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/', $name) === 1;
}
/**
* Get a Reflector and documentation for a function, class or instance, constant, method or property.
*
@@ -132,7 +153,7 @@ abstract class ReflectingCommand extends Command implements ContextAware
*
* @return array (value, Reflector)
*/
protected function getTargetAndReflector($valueName)
protected function getTargetAndReflector(string $valueName): array
{
list($value, $member, $kind) = $this->getTarget($valueName);
@@ -148,16 +169,16 @@ abstract class ReflectingCommand extends Command implements ContextAware
*
* @return mixed Variable value
*/
protected function resolveCode($code)
protected function resolveCode(string $code)
{
try {
$value = $this->getApplication()->execute($code, true);
} catch (\Exception $e) {
} catch (\Throwable $e) {
// Swallow all exceptions?
}
if (!isset($value) || $value instanceof NoReturnValue) {
throw new RuntimeException('Unknown target: ' . $code);
throw new RuntimeException('Unknown target: '.$code);
}
return $value;
@@ -166,18 +187,18 @@ abstract class ReflectingCommand extends Command implements ContextAware
/**
* Resolve code to an object in the current scope.
*
* @throws RuntimeException when the code resolves to a non-object value
* @throws UnexpectedTargetException when the code resolves to a non-object value
*
* @param string $code
*
* @return object Variable instance
*/
private function resolveObject($code)
private function resolveObject(string $code)
{
$value = $this->resolveCode($code);
if (!\is_object($value)) {
throw new RuntimeException('Unable to inspect a non-object');
throw new UnexpectedTargetException($value, 'Unable to inspect a non-object');
}
return $value;
@@ -190,9 +211,9 @@ abstract class ReflectingCommand extends Command implements ContextAware
*
* @return mixed Variable instance
*/
protected function resolveInstance($name)
protected function resolveInstance(string $name)
{
@\trigger_error('`resolveInstance` is deprecated; use `resolveCode` instead.', E_USER_DEPRECATED);
@\trigger_error('`resolveInstance` is deprecated; use `resolveCode` instead.', \E_USER_DEPRECATED);
return $this->resolveCode($name);
}
@@ -204,7 +225,7 @@ abstract class ReflectingCommand extends Command implements ContextAware
*
* @return mixed
*/
protected function getScopeVariable($name)
protected function getScopeVariable(string $name)
{
return $this->context->get($name);
}
@@ -214,7 +235,7 @@ abstract class ReflectingCommand extends Command implements ContextAware
*
* @return array
*/
protected function getScopeVariables()
protected function getScopeVariables(): array
{
return $this->context->getAll();
}
@@ -231,15 +252,15 @@ abstract class ReflectingCommand extends Command implements ContextAware
$vars = [];
switch (\get_class($reflector)) {
case 'ReflectionClass':
case 'ReflectionObject':
case \ReflectionClass::class:
case \ReflectionObject::class:
$vars['__class'] = $reflector->name;
if ($reflector->inNamespace()) {
$vars['__namespace'] = $reflector->getNamespaceName();
}
break;
case 'ReflectionMethod':
case \ReflectionMethod::class:
$vars['__method'] = \sprintf('%s::%s', $reflector->class, $reflector->name);
$vars['__class'] = $reflector->class;
$classReflector = $reflector->getDeclaringClass();
@@ -248,14 +269,14 @@ abstract class ReflectingCommand extends Command implements ContextAware
}
break;
case 'ReflectionFunction':
case \ReflectionFunction::class:
$vars['__function'] = $reflector->name;
if ($reflector->inNamespace()) {
$vars['__namespace'] = $reflector->getNamespaceName();
}
break;
case 'ReflectionGenerator':
case \ReflectionGenerator::class:
$funcReflector = $reflector->getFunction();
$vars['__function'] = $funcReflector->name;
if ($funcReflector->inNamespace()) {
@@ -264,13 +285,13 @@ abstract class ReflectingCommand extends Command implements ContextAware
if ($fileName = $reflector->getExecutingFile()) {
$vars['__file'] = $fileName;
$vars['__line'] = $reflector->getExecutingLine();
$vars['__dir'] = \dirname($fileName);
$vars['__dir'] = \dirname($fileName);
}
break;
case 'ReflectionProperty':
case 'ReflectionClassConstant':
case 'Psy\Reflection\ReflectionClassConstant':
case \ReflectionProperty::class:
case \ReflectionClassConstant::class:
case ReflectionClassConstant::class:
$classReflector = $reflector->getDeclaringClass();
$vars['__class'] = $classReflector->name;
if ($classReflector->inNamespace()) {
@@ -279,11 +300,11 @@ abstract class ReflectingCommand extends Command implements ContextAware
// no line for these, but this'll do
if ($fileName = $reflector->getDeclaringClass()->getFileName()) {
$vars['__file'] = $fileName;
$vars['__dir'] = \dirname($fileName);
$vars['__dir'] = \dirname($fileName);
}
break;
case 'Psy\Reflection\ReflectionConstant_':
case ReflectionConstant_::class:
if ($reflector->inNamespace()) {
$vars['__namespace'] = $reflector->getNamespaceName();
}
@@ -294,7 +315,7 @@ abstract class ReflectingCommand extends Command implements ContextAware
if ($fileName = $reflector->getFileName()) {
$vars['__file'] = $fileName;
$vars['__line'] = $reflector->getStartLine();
$vars['__dir'] = \dirname($fileName);
$vars['__dir'] = \dirname($fileName);
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,10 +11,8 @@
namespace Psy\Command;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
use Psy\Configuration;
use Psy\ConsoleColorFactory;
use Psy\Exception\RuntimeException;
use Psy\Exception\UnexpectedTargetException;
use Psy\Formatter\CodeFormatter;
use Psy\Formatter\SignatureFormatter;
use Psy\Input\CodeArgument;
@@ -28,18 +26,14 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class ShowCommand extends ReflectingCommand
{
private $colorMode;
private $highlighter;
private $lastException;
private $lastExceptionIndex;
/**
* @param null|string $colorMode (default: null)
* @param string|null $colorMode (deprecated and ignored)
*/
public function __construct($colorMode = null)
{
$this->colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;
parent::__construct();
}
@@ -52,7 +46,7 @@ class ShowCommand extends ReflectingCommand
->setName('show')
->setDefinition([
new CodeArgument('target', CodeArgument::OPTIONAL, 'Function, class, instance, constant, method or property to show.'),
new InputOption('ex', null, InputOption::VALUE_OPTIONAL, 'Show last exception context. Optionally specify a stack index.', 1),
new InputOption('ex', null, InputOption::VALUE_OPTIONAL, 'Show last exception context. Optionally specify a stack index.', 1),
])
->setDescription('Show the code for an object, class, constant, method or property.')
->setHelp(
@@ -75,6 +69,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -114,13 +110,33 @@ HELP
private function writeCodeContext(InputInterface $input, OutputInterface $output)
{
list($target, $reflector) = $this->getTargetAndReflector($input->getArgument('target'));
try {
list($target, $reflector) = $this->getTargetAndReflector($input->getArgument('target'));
} catch (UnexpectedTargetException $e) {
// If we didn't get a target and Reflector, maybe we got a filename?
$target = $e->getTarget();
if (\is_string($target) && \is_file($target) && $code = @\file_get_contents($target)) {
$file = \realpath($target);
if ($file !== $this->context->get('__file')) {
$this->context->setCommandScopeVariables([
'__file' => $file,
'__dir' => \dirname($file),
]);
}
$output->page(CodeFormatter::formatCode($code));
return;
} else {
throw $e;
}
}
// Set some magic local variables
$this->setCommandScopeVariables($reflector);
try {
$output->page(CodeFormatter::format($reflector, $this->colorMode), OutputInterface::OUTPUT_RAW);
$output->page(CodeFormatter::format($reflector));
} catch (RuntimeException $e) {
$output->writeln(SignatureFormatter::format($reflector));
throw $e;
@@ -143,7 +159,7 @@ HELP
$index = 0;
}
} else {
$index = \max(0, \intval($input->getOption('ex')) - 1);
$index = \max(0, (int) $input->getOption('ex') - 1);
}
$trace = $exception->getTrace();
@@ -173,7 +189,7 @@ HELP
$line = isset($trace[$index]['line']) ? $trace[$index]['line'] : 'n/a';
$output->writeln(\sprintf(
'From <info>%s:%d</info> at <strong>level %d</strong> of backtrace (of %d).',
'From <info>%s:%d</info> at <strong>level %d</strong> of backtrace (of %d):',
OutputFormatter::escape($file),
OutputFormatter::escape($line),
$index + 1,
@@ -181,16 +197,16 @@ HELP
));
}
private function replaceCwd($file)
private function replaceCwd(string $file): string
{
if ($cwd = \getcwd()) {
$cwd = \rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$cwd = \rtrim($cwd, \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
}
if ($cwd === false) {
return $file;
} else {
return \preg_replace('/^' . \preg_quote($cwd, '/') . '/', '', $file);
return \preg_replace('/^'.\preg_quote($cwd, '/').'/', '', $file);
}
}
@@ -219,17 +235,10 @@ HELP
return;
}
$output->write($this->getHighlighter()->getCodeSnippet($code, $line, 5, 5), false, OutputInterface::OUTPUT_RAW);
}
$startLine = \max($line - 5, 0);
$endLine = $line + 5;
private function getHighlighter()
{
if (!$this->highlighter) {
$factory = new ConsoleColorFactory($this->colorMode);
$this->highlighter = new Highlighter($factory->getConsoleColor());
}
return $this->highlighter;
$output->write(CodeFormatter::formatCode($code, $startLine, $endLine, $line), false);
}
private function setCommandScopeVariablesFromContext(array $context)
@@ -247,7 +256,7 @@ HELP
if ($namespace = $refl->getNamespaceName()) {
$vars['__namespace'] = $namespace;
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
// oh well
}
} elseif (isset($context['function'])) {
@@ -258,7 +267,7 @@ HELP
if ($namespace = $refl->getNamespaceName()) {
$vars['__namespace'] = $namespace;
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
// oh well
}
}
@@ -283,7 +292,7 @@ HELP
$this->context->setCommandScopeVariables($vars);
}
private function extractEvalFileAndLine($file)
private function extractEvalFileAndLine(string $file)
{
if (\preg_match('/(.*)\\((\\d+)\\) : eval\\(\\)\'d code$/', $file, $matches)) {
return [$matches[1], $matches[2]];

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -95,6 +95,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -109,8 +111,8 @@ HELP
$code = $history[\count($history) - 2];
}
if (\strpos('<?', $code) === false) {
$code = '<?php ' . $code;
if (\strpos($code, '<?') === false) {
$code = '<?php '.$code;
}
$nodes = $this->traverser->traverse($this->parse($code));
@@ -129,7 +131,7 @@ HELP
*
* @return array Statements
*/
private function parse($code)
private function parse(string $code): array
{
try {
return $this->parser->parse($code);
@@ -139,7 +141,7 @@ HELP
}
// If we got an unexpected EOF, let's try it again with a semicolon.
return $this->parser->parse($code . ';');
return $this->parser->parse($code.';');
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,7 +13,6 @@ namespace Psy\Command;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Scalar\String_;
@@ -21,6 +20,7 @@ use PhpParser\Node\Stmt\Throw_;
use PhpParser\PrettyPrinter\Standard as Printer;
use Psy\Context;
use Psy\ContextAware;
use Psy\Exception\ThrowUpException;
use Psy\Input\CodeArgument;
use Psy\ParserFactory;
use Symfony\Component\Console\Input\InputInterface;
@@ -31,18 +31,9 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class ThrowUpCommand extends Command implements ContextAware
{
const THROW_CLASS = 'Psy\Exception\ThrowUpException';
private $parser;
private $printer;
/**
* Context instance (for ContextAware interface).
*
* @var Context
*/
protected $context;
/**
* {@inheritdoc}
*/
@@ -50,20 +41,20 @@ class ThrowUpCommand extends Command implements ContextAware
{
$parserFactory = new ParserFactory();
$this->parser = $parserFactory->createParser();
$this->parser = $parserFactory->createParser();
$this->printer = new Printer();
parent::__construct($name);
}
/**
* ContextAware interface.
* @deprecated throwUp no longer needs to be ContextAware
*
* @param Context $context
*/
public function setContext(Context $context)
{
$this->context = $context;
// Do nothing
}
/**
@@ -95,12 +86,14 @@ HELP
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException if there is no exception to throw
* @return int 0 if everything went fine, or an exit code
*
* @throws \InvalidArgumentException if there is no exception to throw
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$args = $this->prepareArgs($input->getArgument('exception'));
$throwStmt = new Throw_(new StaticCall(new FullyQualifiedName(self::THROW_CLASS), 'fromThrowable', $args));
$throwStmt = new Throw_(new New_(new FullyQualifiedName(ThrowUpException::class), $args));
$throwCode = $this->printer->prettyPrint([$throwStmt]);
$shell = $this->getApplication();
@@ -114,21 +107,21 @@ HELP
*
* If no argument was given, this falls back to `$_e`
*
* @throws InvalidArgumentException if there is no exception to throw
* @throws \InvalidArgumentException if there is no exception to throw
*
* @param string $code
*
* @return Arg[]
*/
private function prepareArgs($code = null)
private function prepareArgs(string $code = null): array
{
if (!$code) {
// Default to last exception if nothing else was supplied
return [new Arg(new Variable('_e'))];
}
if (\strpos('<?', $code) === false) {
$code = '<?php ' . $code;
if (\strpos($code, '<?') === false) {
$code = '<?php '.$code;
}
$nodes = $this->parse($code);
@@ -145,7 +138,7 @@ HELP
// Allow throwing via a string, e.g. `throw-up "SUP"`
if ($expr instanceof String_) {
return [new New_(new FullyQualifiedName('Exception'), $args)];
return [new New_(new FullyQualifiedName(\Exception::class), $args)];
}
return $args;
@@ -158,7 +151,7 @@ HELP
*
* @return array Statements
*/
private function parse($code)
private function parse(string $code): array
{
try {
return $this->parser->parse($code);
@@ -168,7 +161,7 @@ HELP
}
// If we got an unexpected EOF, let's try it again with a semicolon.
return $this->parser->parse($code . ';');
return $this->parser->parse($code.';');
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -25,7 +25,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class TimeitCommand extends Command
{
const RESULT_MSG = '<info>Command took %.6f seconds to complete.</info>';
const RESULT_MSG = '<info>Command took %.6f seconds to complete.</info>';
const AVG_RESULT_MSG = '<info>Command took %.6f seconds on average (%.6f median; %.6f total) to complete.</info>';
private static $start = null;
@@ -76,6 +76,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -167,7 +169,7 @@ HELP
*
* @return string
*/
private function instrumentCode($code)
private function instrumentCode(string $code): string
{
return $this->printer->prettyPrint($this->traverser->traverse($this->parse($code)));
}
@@ -179,9 +181,9 @@ HELP
*
* @return array Statements
*/
private function parse($code)
private function parse(string $code): array
{
$code = '<?php ' . $code;
$code = '<?php '.$code;
try {
return $this->parser->parse($code);
@@ -191,7 +193,7 @@ HELP
}
// If we got an unexpected EOF, let's try it again with a semicolon.
return $this->parser->parse($code . ';');
return $this->parser->parse($code.';');
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,6 +21,7 @@ use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeVisitorAbstract;
use Psy\CodeCleaner\NoReturnValue;
use Psy\Command\TimeitCommand;
/**
* A node visitor for instrumenting code to be executed by the `timeit` command.
@@ -34,6 +35,8 @@ class TimeitVisitor extends NodeVisitorAbstract
/**
* {@inheritdoc}
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -42,6 +45,8 @@ class TimeitVisitor extends NodeVisitorAbstract
/**
* {@inheritdoc}
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -61,6 +66,8 @@ class TimeitVisitor extends NodeVisitorAbstract
/**
* {@inheritdoc}
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
@@ -71,6 +78,8 @@ class TimeitVisitor extends NodeVisitorAbstract
/**
* {@inheritdoc}
*
* @return Node[]|null Array of nodes
*/
public function afterTraverse(array $nodes)
{
@@ -97,11 +106,11 @@ class TimeitVisitor extends NodeVisitorAbstract
/**
* Get PhpParser AST nodes for a `markStart` call.
*
* @return PhpParser\Node\Expr\StaticCall
* @return \PhpParser\Node\Expr\StaticCall
*/
private function getStartCall()
private function getStartCall(): StaticCall
{
return new StaticCall(new FullyQualifiedName('Psy\Command\TimeitCommand'), 'markStart');
return new StaticCall(new FullyQualifiedName(TimeitCommand::class), 'markStart');
}
/**
@@ -111,15 +120,15 @@ class TimeitVisitor extends NodeVisitorAbstract
*
* @param Expr|null $arg
*
* @return PhpParser\Node\Expr\StaticCall
* @return \PhpParser\Node\Expr\StaticCall
*/
private function getEndCall(Expr $arg = null)
private function getEndCall(Expr $arg = null): StaticCall
{
if ($arg === null) {
$arg = NoReturnValue::create();
}
return new StaticCall(new FullyQualifiedName('Psy\Command\TimeitCommand'), 'markEnd', [new Arg($arg)]);
return new StaticCall(new FullyQualifiedName(TimeitCommand::class), 'markEnd', [new Arg($arg)]);
}
/**
@@ -127,13 +136,13 @@ class TimeitVisitor extends NodeVisitorAbstract
*
* Wrap $expr in a PhpParser\Node\Stmt\Expression if the class exists.
*
* @param PhpParser\Node $expr
* @param array $attrs
* @param \PhpParser\Node $expr
* @param array $attrs
*
* @return PhpParser\Node\Expr|PhpParser\Node\Stmt\Expression
* @return \PhpParser\Node\Expr|\PhpParser\Node\Stmt\Expression
*/
private function maybeExpression($expr, $attrs = [])
private function maybeExpression(Node $expr, array $attrs = [])
{
return \class_exists('PhpParser\Node\Stmt\Expression') ? new Expression($expr, $attrs) : $expr;
return \class_exists(Expression::class) ? new Expression($expr, $attrs) : $expr;
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,9 +11,9 @@
namespace Psy\Command;
use Psy\Formatter\TraceFormatter;
use Psy\Input\FilterOptions;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -45,8 +45,8 @@ class TraceCommand extends Command
$this
->setName('trace')
->setDefinition([
new InputOption('include-psy', 'p', InputOption::VALUE_NONE, 'Include Psy in the call stack.'),
new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Only include NUM lines.'),
new InputOption('include-psy', 'p', InputOption::VALUE_NONE, 'Include Psy in the call stack.'),
new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Only include NUM lines.'),
$grep,
$insensitive,
@@ -68,6 +68,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -79,91 +81,19 @@ HELP
}
/**
* Get a backtrace for an exception.
* Get a backtrace for an exception or error.
*
* Optionally limit the number of rows to include with $count, and exclude
* Psy from the trace.
*
* @param \Exception $e The exception with a backtrace
* @param \Throwable $e The exception or error with a backtrace
* @param int $count (default: PHP_INT_MAX)
* @param bool $includePsy (default: true)
*
* @return array Formatted stacktrace lines
*/
protected function getBacktrace(\Exception $e, $count = null, $includePsy = true)
protected function getBacktrace(\Throwable $e, int $count = null, bool $includePsy = true): array
{
if ($cwd = \getcwd()) {
$cwd = \rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
if ($count === null) {
$count = PHP_INT_MAX;
}
$lines = [];
$trace = $e->getTrace();
\array_unshift($trace, [
'function' => '',
'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
'args' => [],
]);
if (!$includePsy) {
for ($i = \count($trace) - 1; $i >= 0; $i--) {
$thing = isset($trace[$i]['class']) ? $trace[$i]['class'] : $trace[$i]['function'];
if (\preg_match('/\\\\?Psy\\\\/', $thing)) {
$trace = \array_slice($trace, $i + 1);
break;
}
}
}
for ($i = 0, $count = \min($count, \count($trace)); $i < $count; $i++) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
$function = $trace[$i]['function'];
$file = isset($trace[$i]['file']) ? $this->replaceCwd($cwd, $trace[$i]['file']) : 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
// Leave execution loop out of the `eval()'d code` lines
if (\preg_match("#/src/Execution(?:Loop)?Closure.php\(\d+\) : eval\(\)'d code$#", \str_replace('\\', '/', $file))) {
$file = "eval()'d code";
}
// Skip any lines that don't match our filter options
if (!$this->filter->match(\sprintf('%s%s%s() at %s:%s', $class, $type, $function, $file, $line))) {
continue;
}
$lines[] = \sprintf(
' <class>%s</class>%s%s() at <info>%s:%s</info>',
OutputFormatter::escape($class),
OutputFormatter::escape($type),
OutputFormatter::escape($function),
OutputFormatter::escape($file),
OutputFormatter::escape($line)
);
}
return $lines;
}
/**
* Replace the given directory from the start of a filepath.
*
* @param string $cwd
* @param string $file
*
* @return string
*/
private function replaceCwd($cwd, $file)
{
if ($cwd === false) {
return $file;
} else {
return \preg_replace('/^' . \preg_quote($cwd, '/') . '/', '', $file);
}
return TraceFormatter::formatTrace($e, $this->filter, $count, $includePsy);
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,9 +11,9 @@
namespace Psy\Command;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
use Psy\Configuration;
use Psy\ConsoleColorFactory;
use Psy\Formatter\CodeFormatter;
use Psy\Output\ShellOutput;
use Psy\Shell;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -23,16 +23,14 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class WhereamiCommand extends Command
{
private $colorMode;
private $backtrace;
/**
* @param null|string $colorMode (default: null)
* @param string|null $colorMode (deprecated and ignored)
*/
public function __construct($colorMode = null)
{
$this->colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;
$this->backtrace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$this->backtrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
parent::__construct();
}
@@ -46,17 +44,20 @@ class WhereamiCommand extends Command
->setName('whereami')
->setDefinition([
new InputOption('num', 'n', InputOption::VALUE_OPTIONAL, 'Number of lines before and after.', '5'),
new InputOption('file', 'f|a', InputOption::VALUE_NONE, 'Show the full source for the current file.'),
])
->setDescription('Show where you are in the code.')
->setHelp(
<<<'HELP'
Show where you are in the code.
Optionally, include how many lines before and after you want to display.
Optionally, include the number of lines before and after you want to display,
or --file for the whole file.
e.g.
<return>> whereami </return>
<return>> whereami -n10</return>
<return>> whereami --file</return>
HELP
);
}
@@ -66,7 +67,7 @@ HELP
*
* @return array
*/
protected function trace()
protected function trace(): array
{
foreach (\array_reverse($this->backtrace) as $stackFrame) {
if ($this->isDebugCall($stackFrame)) {
@@ -77,13 +78,13 @@ HELP
return \end($this->backtrace);
}
private static function isDebugCall(array $stackFrame)
private static function isDebugCall(array $stackFrame): bool
{
$class = isset($stackFrame['class']) ? $stackFrame['class'] : null;
$class = isset($stackFrame['class']) ? $stackFrame['class'] : null;
$function = isset($stackFrame['function']) ? $stackFrame['function'] : null;
return ($class === null && $function === 'Psy\debug') ||
($class === 'Psy\Shell' && \in_array($function, ['__construct', 'debug']));
return ($class === null && $function === 'Psy\\debug') ||
($class === Shell::class && \in_array($function, ['__construct', 'debug']));
}
/**
@@ -91,7 +92,7 @@ HELP
*
* @return array
*/
protected function fileInfo()
protected function fileInfo(): array
{
$stackFrame = $this->trace();
if (\preg_match('/eval\(/', $stackFrame['file'])) {
@@ -108,22 +109,33 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$info = $this->fileInfo();
$num = $input->getOption('num');
$factory = new ConsoleColorFactory($this->colorMode);
$colors = $factory->getConsoleColor();
$highlighter = new Highlighter($colors);
$contents = \file_get_contents($info['file']);
$info = $this->fileInfo();
$num = $input->getOption('num');
$lineNum = $info['line'];
$startLine = \max($lineNum - $num, 1);
$endLine = $lineNum + $num;
$code = \file_get_contents($info['file']);
$output->startPaging();
$output->writeln('');
$output->writeln(\sprintf('From <info>%s:%s</info>:', $this->replaceCwd($info['file']), $info['line']));
$output->writeln('');
$output->write($highlighter->getCodeSnippet($contents, $info['line'], $num, $num), false, OutputInterface::OUTPUT_RAW);
$output->stopPaging();
if ($input->getOption('file')) {
$startLine = 1;
$endLine = null;
}
if ($output instanceof ShellOutput) {
$output->startPaging();
}
$output->writeln(\sprintf('From <info>%s:%s</info>:', $this->replaceCwd($info['file']), $lineNum));
$output->write(CodeFormatter::formatCode($code, $startLine, $endLine, $lineNum), false);
if ($output instanceof ShellOutput) {
$output->stopPaging();
}
return 0;
}
@@ -135,15 +147,15 @@ HELP
*
* @return string
*/
private function replaceCwd($file)
private function replaceCwd(string $file): string
{
$cwd = \getcwd();
if ($cwd === false) {
return $file;
}
$cwd = \rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$cwd = \rtrim($cwd, \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
return \preg_replace('/^' . \preg_quote($cwd, '/') . '/', '', $file);
return \preg_replace('/^'.\preg_quote($cwd, '/').'/', '', $file);
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -54,7 +54,7 @@ class WtfCommand extends TraceCommand implements ContextAware
->setAliases(['last-exception', 'wtf?'])
->setDefinition([
new InputArgument('incredulity', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Number of lines to show.'),
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show entire backtrace.'),
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show entire backtrace.'),
$grep,
$insensitive,
@@ -81,6 +81,8 @@ HELP
/**
* {@inheritdoc}
*
* @return int 0 if everything went fine, or an exit code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -92,19 +94,23 @@ HELP
}
$exception = $this->context->getLastException();
$count = $input->getOption('all') ? PHP_INT_MAX : \max(3, \pow(2, \strlen($incredulity) + 1));
$count = $input->getOption('all') ? \PHP_INT_MAX : \max(3, \pow(2, \strlen($incredulity) + 1));
$shell = $this->getApplication();
$output->startPaging();
if ($output instanceof ShellOutput) {
$output->startPaging();
}
do {
$traceCount = \count($exception->getTrace());
$showLines = $count;
$showLines = $count;
// Show the whole trace if we'd only be hiding a few lines
if ($traceCount < \max($count * 1.2, $count + 2)) {
$showLines = PHP_INT_MAX;
$showLines = \PHP_INT_MAX;
}
$trace = $this->getBacktrace($exception, $showLines);
$trace = $this->getBacktrace($exception, $showLines);
$moreLines = $traceCount - \count($trace);
$output->writeln($shell->formatException($exception));
@@ -120,7 +126,10 @@ HELP
$output->writeln('');
}
} while ($exception = $exception->getPrevious());
$output->stopPaging();
if ($output instanceof ShellOutput) {
$output->stopPaging();
}
return 0;
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,13 +11,93 @@
namespace Psy;
use XdgBaseDir\Xdg;
/**
* A Psy Shell configuration path helper.
*/
class ConfigPaths
{
private $configDir;
private $dataDir;
private $runtimeDir;
private $env;
/**
* ConfigPaths constructor.
*
* Optionally provide `configDir`, `dataDir` and `runtimeDir` overrides.
*
* @see self::overrideDirs
*
* @param string[] $overrides Directory overrides
* @param EnvInterface $env
*/
public function __construct(array $overrides = [], EnvInterface $env = null)
{
$this->overrideDirs($overrides);
$this->env = $env ?: new SuperglobalsEnv();
}
/**
* Provide `configDir`, `dataDir` and `runtimeDir` overrides.
*
* If a key is set but empty, the override will be removed. If it is not set
* at all, any existing override will persist.
*
* @param string[] $overrides Directory overrides
*/
public function overrideDirs(array $overrides)
{
if (\array_key_exists('configDir', $overrides)) {
$this->configDir = $overrides['configDir'] ?: null;
}
if (\array_key_exists('dataDir', $overrides)) {
$this->dataDir = $overrides['dataDir'] ?: null;
}
if (\array_key_exists('runtimeDir', $overrides)) {
$this->runtimeDir = $overrides['runtimeDir'] ?: null;
}
}
/**
* Get the current home directory.
*
* @return string|null
*/
public function homeDir()
{
if ($homeDir = $this->getEnv('HOME') ?: $this->windowsHomeDir()) {
return \strtr($homeDir, '\\', '/');
}
return null;
}
private function windowsHomeDir()
{
if (\defined('PHP_WINDOWS_VERSION_MAJOR')) {
$homeDrive = $this->getEnv('HOMEDRIVE');
$homePath = $this->getEnv('HOMEPATH');
if ($homeDrive && $homePath) {
return $homeDrive.'/'.$homePath;
}
}
return null;
}
private function homeConfigDir()
{
if ($homeConfigDir = $this->getEnv('XDG_CONFIG_HOME')) {
return $homeConfigDir;
}
$homeDir = $this->homeDir();
return $homeDir === '/' ? $homeDir.'.config' : $homeDir.'/.config';
}
/**
* Get potential config directory paths.
*
@@ -28,11 +108,23 @@ class ConfigPaths
*
* @return string[]
*/
public static function getConfigDirs()
public function configDirs(): array
{
$xdg = new Xdg();
if ($this->configDir !== null) {
return [$this->configDir];
}
return self::getDirNames($xdg->getConfigDirs());
$configDirs = $this->getEnvArray('XDG_CONFIG_DIRS') ?: ['/etc/xdg'];
return $this->allDirNames(\array_merge([$this->homeConfigDir()], $configDirs));
}
/**
* @deprecated
*/
public static function getConfigDirs(): array
{
return (new self())->configDirs();
}
/**
@@ -43,13 +135,14 @@ class ConfigPaths
*
* http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
*
* @deprecated
*
* @return string[]
*/
public static function getHomeConfigDirs()
public static function getHomeConfigDirs(): array
{
$xdg = new Xdg();
return self::getDirNames([$xdg->getHomeConfigDir()]);
// Not quite the same, but this is deprecated anyway /shrug
return self::getConfigDirs();
}
/**
@@ -60,13 +153,18 @@ class ConfigPaths
* config directory (`%APPDATA%/PsySH` on Windows, `~/.config/psysh`
* everywhere else).
*
* @see self::getHomeConfigDirs
* @see self::homeConfigDir
*
* @return string
*/
public static function getCurrentConfigDir()
public function currentConfigDir(): string
{
$configDirs = self::getHomeConfigDirs();
if ($this->configDir !== null) {
return $this->configDir;
}
$configDirs = $this->allDirNames([$this->homeConfigDir()]);
foreach ($configDirs as $configDir) {
if (@\is_dir($configDir)) {
return $configDir;
@@ -76,19 +174,32 @@ class ConfigPaths
return $configDirs[0];
}
/**
* @deprecated
*/
public static function getCurrentConfigDir(): string
{
return (new self())->currentConfigDir();
}
/**
* Find real config files in config directories.
*
* @param string[] $names Config file names
* @param string $configDir Optionally use a specific config directory
* @param string[] $names Config file names
*
* @return string[]
*/
public static function getConfigFiles(array $names, $configDir = null)
public function configFiles(array $names): array
{
$dirs = ($configDir === null) ? self::getConfigDirs() : [$configDir];
return $this->allRealFiles($this->configDirs(), $names);
}
return self::getRealFiles($dirs, $names);
/**
* @deprecated
*/
public static function getConfigFiles(array $names, $configDir = null): array
{
return (new self(['configDir' => $configDir]))->configFiles($names);
}
/**
@@ -103,90 +214,159 @@ class ConfigPaths
*
* @return string[]
*/
public static function getDataDirs()
public function dataDirs(): array
{
$xdg = new Xdg();
if ($this->dataDir !== null) {
return [$this->dataDir];
}
return self::getDirNames($xdg->getDataDirs());
$homeDataDir = $this->getEnv('XDG_DATA_HOME') ?: $this->homeDir().'/.local/share';
$dataDirs = $this->getEnvArray('XDG_DATA_DIRS') ?: ['/usr/local/share', '/usr/share'];
return $this->allDirNames(\array_merge([$homeDataDir], $dataDirs));
}
/**
* @deprecated
*/
public static function getDataDirs(): array
{
return (new self())->dataDirs();
}
/**
* Find real data files in config directories.
*
* @param string[] $names Config file names
* @param string $dataDir Optionally use a specific config directory
* @param string[] $names Config file names
*
* @return string[]
*/
public static function getDataFiles(array $names, $dataDir = null)
public function dataFiles(array $names): array
{
$dirs = ($dataDir === null) ? self::getDataDirs() : [$dataDir];
return $this->allRealFiles($this->dataDirs(), $names);
}
return self::getRealFiles($dirs, $names);
/**
* @deprecated
*/
public static function getDataFiles(array $names, $dataDir = null): array
{
return (new self(['dataDir' => $dataDir]))->dataFiles($names);
}
/**
* Get a runtime directory.
*
* Defaults to `/psysh` inside the system's temp dir.
* Defaults to `/psysh` inside the system's temp dir.
*
* @return string
*/
public static function getRuntimeDir()
public function runtimeDir(): string
{
$xdg = new Xdg();
\set_error_handler(['Psy\Exception\ErrorException', 'throwException']);
try {
// XDG doesn't really work on Windows, sometimes complains about
// permissions, sometimes tries to remove non-empty directories.
// It's a bit flaky. So we'll give this a shot first...
$runtimeDir = $xdg->getRuntimeDir(false);
} catch (\Exception $e) {
// Well. That didn't work. Fall back to a boring old folder in the
// system temp dir.
$runtimeDir = \sys_get_temp_dir();
if ($this->runtimeDir !== null) {
return $this->runtimeDir;
}
\restore_error_handler();
// Fallback to a boring old folder in the system temp dir.
$runtimeDir = $this->getEnv('XDG_RUNTIME_DIR') ?: \sys_get_temp_dir();
return \strtr($runtimeDir, '\\', '/') . '/psysh';
return \strtr($runtimeDir, '\\', '/').'/psysh';
}
private static function getDirNames(array $baseDirs)
/**
* @deprecated
*/
public static function getRuntimeDir(): string
{
return (new self())->runtimeDir();
}
/**
* Get a list of directories in PATH.
*
* If $PATH is unset/empty it defaults to '/usr/sbin:/usr/bin:/sbin:/bin'.
*
* @return string[]
*/
public function pathDirs(): array
{
return $this->getEnvArray('PATH') ?: ['/usr/sbin', '/usr/bin', '/sbin', '/bin'];
}
/**
* Locate a command (an executable) in $PATH.
*
* Behaves like 'command -v COMMAND' or 'which COMMAND'.
* If $PATH is unset/empty it defaults to '/usr/sbin:/usr/bin:/sbin:/bin'.
*
* @param string $command the executable to locate
*
* @return string
*/
public function which($command)
{
foreach ($this->pathDirs() as $path) {
$fullpath = $path.\DIRECTORY_SEPARATOR.$command;
if (@\is_file($fullpath) && @\is_executable($fullpath)) {
return $fullpath;
}
}
return null;
}
/**
* Get all PsySH directory name candidates given a list of base directories.
*
* This expects that XDG-compatible directory paths will be passed in.
* `psysh` will be added to each of $baseDirs, and we'll throw in `~/.psysh`
* and a couple of Windows-friendly paths as well.
*
* @param string[] $baseDirs base directory paths
*
* @return string[]
*/
private function allDirNames(array $baseDirs): array
{
$dirs = \array_map(function ($dir) {
return \strtr($dir, '\\', '/') . '/psysh';
return \strtr($dir, '\\', '/').'/psysh';
}, $baseDirs);
// Add ~/.psysh
if ($home = \getenv('HOME')) {
$dirs[] = \strtr($home, '\\', '/') . '/.psysh';
if ($home = $this->getEnv('HOME')) {
$dirs[] = \strtr($home, '\\', '/').'/.psysh';
}
// Add some Windows specific ones :)
if (\defined('PHP_WINDOWS_VERSION_MAJOR')) {
if ($appData = \getenv('APPDATA')) {
if ($appData = $this->getEnv('APPDATA')) {
// AppData gets preference
\array_unshift($dirs, \strtr($appData, '\\', '/') . '/PsySH');
\array_unshift($dirs, \strtr($appData, '\\', '/').'/PsySH');
}
$dir = \strtr(\getenv('HOMEDRIVE') . '/' . \getenv('HOMEPATH'), '\\', '/') . '/.psysh';
if (!\in_array($dir, $dirs)) {
$dirs[] = $dir;
if ($windowsHomeDir = $this->windowsHomeDir()) {
$dir = \strtr($windowsHomeDir, '\\', '/').'/.psysh';
if (!\in_array($dir, $dirs)) {
$dirs[] = $dir;
}
}
}
return $dirs;
}
private static function getRealFiles(array $dirNames, array $fileNames)
/**
* Given a list of directories, and a list of filenames, find the ones that
* are real files.
*
* @return string[]
*/
private function allRealFiles(array $dirNames, array $fileNames): array
{
$files = [];
foreach ($dirNames as $dir) {
foreach ($fileNames as $name) {
$file = $dir . '/' . $name;
$file = $dir.'/'.$name;
if (@\is_file($file)) {
$files[] = $file;
}
@@ -196,6 +376,31 @@ class ConfigPaths
return $files;
}
/**
* Ensure that $dir exists and is writable.
*
* Generates E_USER_NOTICE error if the directory is not writable or creatable.
*
* @param string $dir
*
* @return bool False if directory exists but is not writeable, or cannot be created
*/
public static function ensureDir(string $dir): bool
{
if (!\is_dir($dir)) {
// Just try making it and see if it works
@\mkdir($dir, 0700, true);
}
if (!\is_dir($dir) || !\is_writable($dir)) {
\trigger_error(\sprintf('Writing to directory %s is not allowed.', $dir), \E_USER_NOTICE);
return false;
}
return true;
}
/**
* Ensure that $file exists and is writable, make the parent directory if necessary.
*
@@ -205,28 +410,19 @@ class ConfigPaths
*
* @return string|false Full path to $file, or false if file is not writable
*/
public static function touchFileWithMkdir($file)
public static function touchFileWithMkdir(string $file)
{
if (\file_exists($file)) {
if (\is_writable($file)) {
return $file;
}
\trigger_error(\sprintf('Writing to %s is not allowed.', $file), E_USER_NOTICE);
\trigger_error(\sprintf('Writing to %s is not allowed.', $file), \E_USER_NOTICE);
return false;
}
$dir = \dirname($file);
if (!\is_dir($dir)) {
// Just try making it and see if it works
@\mkdir($dir, 0700, true);
}
if (!\is_dir($dir) || !\is_writable($dir)) {
\trigger_error(\sprintf('Writing to %s is not allowed.', $dir), E_USER_NOTICE);
if (!self::ensureDir(\dirname($file))) {
return false;
}
@@ -234,4 +430,18 @@ class ConfigPaths
return $file;
}
private function getEnv($key)
{
return $this->env->get($key);
}
private function getEnvArray($key)
{
if ($value = $this->getEnv($key)) {
return \explode(\PATH_SEPARATOR, $value);
}
return null;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,82 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
use JakubOnderka\PhpConsoleColor\ConsoleColor;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
/**
* Builds `ConsoleColor` instances configured according to the given color mode.
*/
class ConsoleColorFactory
{
private $colorMode;
/**
* @param string $colorMode
*/
public function __construct($colorMode)
{
$this->colorMode = $colorMode;
}
/**
* Get a `ConsoleColor` instance configured according to the given color
* mode.
*
* @return ConsoleColor
*/
public function getConsoleColor()
{
if ($this->colorMode === Configuration::COLOR_MODE_AUTO) {
return $this->getDefaultConsoleColor();
} elseif ($this->colorMode === Configuration::COLOR_MODE_FORCED) {
return $this->getForcedConsoleColor();
} elseif ($this->colorMode === Configuration::COLOR_MODE_DISABLED) {
return $this->getDisabledConsoleColor();
}
}
private function getDefaultConsoleColor()
{
$color = new ConsoleColor();
$color->addTheme(Highlighter::LINE_NUMBER, ['blue']);
$color->addTheme(Highlighter::TOKEN_KEYWORD, ['yellow']);
$color->addTheme(Highlighter::TOKEN_STRING, ['green']);
$color->addTheme(Highlighter::TOKEN_COMMENT, ['dark_gray']);
return $color;
}
private function getForcedConsoleColor()
{
$color = $this->getDefaultConsoleColor();
$color->setForceStyle(true);
return $color;
}
private function getDisabledConsoleColor()
{
$color = new ConsoleColor();
$color->addTheme(Highlighter::TOKEN_STRING, ['none']);
$color->addTheme(Highlighter::TOKEN_COMMENT, ['none']);
$color->addTheme(Highlighter::TOKEN_KEYWORD, ['none']);
$color->addTheme(Highlighter::TOKEN_DEFAULT, ['none']);
$color->addTheme(Highlighter::TOKEN_HTML, ['none']);
$color->addTheme(Highlighter::ACTUAL_LINE_MARK, ['none']);
$color->addTheme(Highlighter::LINE_NUMBER, ['none']);
return $color;
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,7 +21,7 @@ class Context
{
private static $specialNames = ['_', '_e', '__out', '__psysh__', 'this'];
// Whitelist a very limited number of command-scope magic variable names.
// Include a very limited number of command-scope magic variable names.
// This might be a bad idea, but future me can sort it out.
private static $commandScopeNames = [
'__function', '__method', '__class', '__namespace', '__file', '__line', '__dir',
@@ -38,13 +38,13 @@ class Context
/**
* Get a context variable.
*
* @throws InvalidArgumentException If the variable is not found in the current context
* @throws \InvalidArgumentException If the variable is not found in the current context
*
* @param string $name
*
* @return mixed
*/
public function get($name)
public function get(string $name)
{
switch ($name) {
case '_':
@@ -87,7 +87,7 @@ class Context
break;
}
throw new \InvalidArgumentException('Unknown variable: $' . $name);
throw new \InvalidArgumentException('Unknown variable: $'.$name);
}
/**
@@ -95,7 +95,7 @@ class Context
*
* @return array
*/
public function getAll()
public function getAll(): array
{
return \array_merge($this->scopeVariables, $this->getSpecialVariables());
}
@@ -105,7 +105,7 @@ class Context
*
* @return array
*/
public function getSpecialVariables()
public function getSpecialVariables(): array
{
$vars = [
'_' => $this->returnValue,
@@ -168,21 +168,21 @@ class Context
}
/**
* Set the most recent Exception.
* Set the most recent Exception or Error.
*
* @param \Exception $e
* @param \Throwable $e
*/
public function setLastException(\Exception $e)
public function setLastException(\Throwable $e)
{
$this->lastException = $e;
}
/**
* Get the most recent Exception.
* Get the most recent Exception or Error.
*
* @throws \InvalidArgumentException If no Exception has been caught
*
* @return null|\Exception
* @return \Throwable|null
*/
public function getLastException()
{
@@ -198,7 +198,7 @@ class Context
*
* @param string $lastStdout
*/
public function setLastStdout($lastStdout)
public function setLastStdout(string $lastStdout)
{
$this->lastStdout = $lastStdout;
}
@@ -208,7 +208,7 @@ class Context
*
* @throws \InvalidArgumentException If no output has happened yet
*
* @return null|string
* @return string|null
*/
public function getLastStdout()
{
@@ -288,7 +288,7 @@ class Context
*
* @return array
*/
public function getCommandScopeVariables()
public function getCommandScopeVariables(): array
{
return $this->commandScopeVariables;
}
@@ -301,7 +301,7 @@ class Context
*
* @return array Array of unused variable names
*/
public function getUnusedCommandScopeVariableNames()
public function getUnusedCommandScopeVariableNames(): array
{
return \array_diff(self::$commandScopeNames, \array_keys($this->commandScopeVariables));
}
@@ -313,7 +313,7 @@ class Context
*
* @return bool
*/
public static function isSpecialVariableName($name)
public static function isSpecialVariableName(string $name): bool
{
return \in_array($name, self::$specialNames) || \in_array($name, self::$commandScopeNames);
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

25
vendor/psy/psysh/src/EnvInterface.php vendored Normal file
View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
/**
* Abstraction around environment variables.
*/
interface EnvInterface
{
/**
* Get an environment variable by name.
*
* @return string|null
*/
public function get(string $key);
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,7 +21,7 @@ class BreakException extends \Exception implements Exception
/**
* {@inheritdoc}
*/
public function __construct($message = '', $code = 0, \Exception $previous = null)
public function __construct($message = '', $code = 0, \Throwable $previous = null)
{
$this->rawMessage = $message;
parent::__construct(\sprintf('Exit: %s', $message), $code, $previous);
@@ -32,7 +32,7 @@ class BreakException extends \Exception implements Exception
*
* @return string
*/
public function getRawMessage()
public function getRawMessage(): string
{
return $this->rawMessage;
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,14 +21,14 @@ class ErrorException extends \ErrorException implements Exception
/**
* Construct a Psy ErrorException.
*
* @param string $message (default: "")
* @param int $code (default: 0)
* @param int $severity (default: 1)
* @param string $filename (default: null)
* @param int $lineno (default: null)
* @param Exception $previous (default: null)
* @param string $message (default: "")
* @param int $code (default: 0)
* @param int $severity (default: 1)
* @param string|null $filename (default: null)
* @param int|null $lineno (default: null)
* @param \Throwable|null $previous (default: null)
*/
public function __construct($message = '', $code = 0, $severity = 1, $filename = null, $lineno = null, $previous = null)
public function __construct($message = '', $code = 0, $severity = 1, $filename = null, $lineno = null, \Throwable $previous = null)
{
$this->rawMessage = $message;
@@ -37,28 +37,28 @@ class ErrorException extends \ErrorException implements Exception
}
switch ($severity) {
case E_STRICT:
case \E_STRICT:
$type = 'Strict error';
break;
case E_NOTICE:
case E_USER_NOTICE:
case \E_NOTICE:
case \E_USER_NOTICE:
$type = 'Notice';
break;
case E_WARNING:
case E_CORE_WARNING:
case E_COMPILE_WARNING:
case E_USER_WARNING:
case \E_WARNING:
case \E_CORE_WARNING:
case \E_COMPILE_WARNING:
case \E_USER_WARNING:
$type = 'Warning';
break;
case E_DEPRECATED:
case E_USER_DEPRECATED:
case \E_DEPRECATED:
case \E_USER_DEPRECATED:
$type = 'Deprecated';
break;
case E_RECOVERABLE_ERROR:
case \E_RECOVERABLE_ERROR:
$type = 'Recoverable fatal error';
break;
@@ -67,7 +67,7 @@ class ErrorException extends \ErrorException implements Exception
break;
}
$message = \sprintf('PHP %s: %s%s on line %d', $type, $message, $filename ? ' in ' . $filename : '', $lineno);
$message = \sprintf('PHP %s: %s%s on line %d', $type, $message, $filename ? ' in '.$filename : '', $lineno);
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
}
@@ -76,7 +76,7 @@ class ErrorException extends \ErrorException implements Exception
*
* @return string
*/
public function getRawMessage()
public function getRawMessage(): string
{
return $this->rawMessage;
}
@@ -86,9 +86,9 @@ class ErrorException extends \ErrorException implements Exception
*
* This allows us to:
*
* set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
* set_error_handler([ErrorException::class, 'throwException']);
*
* @throws ErrorException
* @throws self
*
* @param int $errno Error type
* @param string $errstr Message
@@ -103,11 +103,13 @@ class ErrorException extends \ErrorException implements Exception
/**
* Create an ErrorException from an Error.
*
* @deprecated psySH no longer wraps Errors
*
* @param \Error $e
*
* @return ErrorException
* @return self
*/
public static function fromError(\Error $e)
public static function fromError(\Error $e): self
{
return new self($e->getMessage(), $e->getCode(), 1, $e->getFile(), $e->getLine(), $e);
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,14 +21,14 @@ class FatalErrorException extends \ErrorException implements Exception
/**
* Create a fatal error.
*
* @param string $message (default: "")
* @param int $code (default: 0)
* @param int $severity (default: 1)
* @param string $filename (default: null)
* @param int $lineno (default: null)
* @param \Exception $previous (default: null)
* @param string $message (default: "")
* @param int $code (default: 0)
* @param int $severity (default: 1)
* @param string|null $filename (default: null)
* @param int|null $lineno (default: null)
* @param \Throwable|null $previous (default: null)
*/
public function __construct($message = '', $code = 0, $severity = 1, $filename = null, $lineno = null, $previous = null)
public function __construct($message = '', $code = 0, $severity = 1, $filename = null, $lineno = null, \Throwable $previous = null)
{
// Since these are basically always PHP Parser Node line numbers, treat -1 as null.
if ($lineno === -1) {
@@ -45,7 +45,7 @@ class FatalErrorException extends \ErrorException implements Exception
*
* @return string
*/
public function getRawMessage()
public function getRawMessage(): string
{
return $this->rawMessage;
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -22,7 +22,7 @@ class ParseErrorException extends \PhpParser\Error implements Exception
* @param string $message (default: "")
* @param int $line (default: -1)
*/
public function __construct($message = '', $line = -1)
public function __construct(string $message = '', int $line = -1)
{
$message = \sprintf('PHP Parse error: %s', $message);
parent::__construct($message, $line);
@@ -33,9 +33,9 @@ class ParseErrorException extends \PhpParser\Error implements Exception
*
* @param \PhpParser\Error $e
*
* @return ParseErrorException
* @return self
*/
public static function fromParseError(\PhpParser\Error $e)
public static function fromParseError(\PhpParser\Error $e): self
{
return new self($e->getRawMessage(), $e->getStartLine());
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,11 +21,11 @@ class RuntimeException extends \RuntimeException implements Exception
/**
* Make this bad boy.
*
* @param string $message (default: "")
* @param int $code (default: 0)
* @param \Exception $previous (default: null)
* @param string $message (default: "")
* @param int $code (default: 0)
* @param \Throwable|null $previous (default: null)
*/
public function __construct($message = '', $code = 0, \Exception $previous = null)
public function __construct(string $message = '', int $code = 0, \Throwable $previous = null)
{
$this->rawMessage = $message;
parent::__construct($message, $code, $previous);
@@ -36,7 +36,7 @@ class RuntimeException extends \RuntimeException implements Exception
*
* @return string
*/
public function getRawMessage()
public function getRawMessage(): string
{
return $this->rawMessage;
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -19,10 +19,10 @@ class ThrowUpException extends \Exception implements Exception
/**
* {@inheritdoc}
*/
public function __construct(\Exception $exception)
public function __construct(\Throwable $throwable)
{
$message = \sprintf("Throwing %s with message '%s'", \get_class($exception), $exception->getMessage());
parent::__construct($message, $exception->getCode(), $exception);
$message = \sprintf("Throwing %s with message '%s'", \get_class($throwable), $throwable->getMessage());
parent::__construct($message, $throwable->getCode(), $throwable);
}
/**
@@ -30,7 +30,7 @@ class ThrowUpException extends \Exception implements Exception
*
* @return string
*/
public function getRawMessage()
public function getRawMessage(): string
{
return $this->getPrevious()->getMessage();
}
@@ -38,11 +38,13 @@ class ThrowUpException extends \Exception implements Exception
/**
* Create a ThrowUpException from a Throwable.
*
* @deprecated psySH no longer wraps Throwables
*
* @param \Throwable $throwable
*
* @return ThrowUpException
* @return self
*/
public static function fromThrowable($throwable)
public static function fromThrowable($throwable): self
{
if ($throwable instanceof \Error) {
$throwable = ErrorException::fromError($throwable);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -21,14 +21,17 @@ class TypeErrorException extends \Exception implements Exception
/**
* Constructor!
*
* @param string $message (default: "")
* @param int $code (default: 0)
* @deprecated psySH no longer wraps TypeErrors
*
* @param string $message (default: "")
* @param int $code (default: 0)
* @param \Throwable|null $previous (default: null)
*/
public function __construct($message = '', $code = 0)
public function __construct(string $message = '', int $code = 0, \Throwable $previous = null)
{
$this->rawMessage = $message;
$message = \preg_replace('/, called in .*?: eval\\(\\)\'d code/', '', $message);
parent::__construct(\sprintf('TypeError: %s', $message), $code);
parent::__construct(\sprintf('TypeError: %s', $message), $code, $previous);
}
/**
@@ -36,7 +39,7 @@ class TypeErrorException extends \Exception implements Exception
*
* @return string
*/
public function getRawMessage()
public function getRawMessage(): string
{
return $this->rawMessage;
}
@@ -44,12 +47,14 @@ class TypeErrorException extends \Exception implements Exception
/**
* Create a TypeErrorException from a TypeError.
*
* @deprecated psySH no longer wraps TypeErrors
*
* @param \TypeError $e
*
* @return TypeErrorException
* @return self
*/
public static function fromTypeError(\TypeError $e)
public static function fromTypeError(\TypeError $e): self
{
return new self($e->getMessage(), $e->getCode());
return new self($e->getMessage(), $e->getCode(), $e);
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
class UnexpectedTargetException extends RuntimeException
{
private $target;
/**
* @param mixed $target
* @param string $message (default: "")
* @param int $code (default: 0)
* @param \Throwable|null $previous (default: null)
*/
public function __construct($target, string $message = '', int $code = 0, \Throwable $previous = null)
{
$this->target = $target;
parent::__construct($message, $code, $previous);
}
/**
* @return mixed
*/
public function getTarget()
{
return $this->target;
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -37,28 +37,19 @@ class ExecutionClosure
\set_error_handler([$__psysh__, 'handleError']);
// Evaluate the current code buffer
$_ = eval($__psysh__->onExecute($__psysh__->flushCode() ?: ExecutionClosure::NOOP_INPUT));
$_ = eval($__psysh__->onExecute($__psysh__->flushCode() ?: self::NOOP_INPUT));
} catch (\Throwable $_e) {
// Clean up on our way out.
\restore_error_handler();
if (\ob_get_level() > 0) {
\ob_end_clean();
}
throw $_e;
} catch (\Exception $_e) {
// Clean up on our way out.
} finally {
// Won't be needing this anymore
\restore_error_handler();
if (\ob_get_level() > 0) {
\ob_end_clean();
}
throw $_e;
}
// Won't be needing this anymore
\restore_error_handler();
// Flush stdout (write to shell output, plus save to magic variable)
\ob_end_flush();
@@ -72,21 +63,18 @@ class ExecutionClosure
/**
* Set the closure instance.
*
* @param Shell $psysh
* @param Shell $shell
* @param \Closure $closure
*/
protected function setClosure(Shell $shell, \Closure $closure)
{
if (self::shouldBindClosure()) {
$that = $shell->getBoundObject();
if (\is_object($that)) {
$closure = $closure->bindTo($that, \get_class($that));
} else {
$closure = $closure->bindTo(null, $shell->getBoundClass());
}
}
$that = $shell->getBoundObject();
$this->closure = $closure;
if (\is_object($that)) {
$this->closure = $closure->bindTo($that, \get_class($that));
} else {
$this->closure = $closure->bindTo(null, $shell->getBoundClass());
}
}
/**
@@ -100,20 +88,4 @@ class ExecutionClosure
return $closure();
}
/**
* Decide whether to bind the execution closure.
*
* @return bool
*/
protected static function shouldBindClosure()
{
// skip binding on HHVM < 3.5.0
// see https://github.com/facebook/hhvm/issues/1203
if (\defined('HHVM_VERSION')) {
return \version_compare(HHVM_VERSION, '3.5.0', '>=');
}
return true;
}
}

View File

@@ -1,67 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
use Psy\Exception\ErrorException;
/**
* The Psy Shell execution loop.
*/
class ExecutionLoop
{
/**
* Run the execution loop.
*
* @throws ThrowUpException if thrown by the `throw-up` command
*
* @param Shell $shell
*/
public function run(Shell $shell)
{
$this->loadIncludes($shell);
$closure = new ExecutionLoopClosure($shell);
$closure->execute();
}
/**
* Load user-defined includes.
*
* @param Shell $shell
*/
protected function loadIncludes(Shell $shell)
{
// Load user-defined includes
$load = function (Shell $__psysh__) {
\set_error_handler([$__psysh__, 'handleError']);
foreach ($__psysh__->getIncludes() as $__psysh_include__) {
try {
include $__psysh_include__;
} catch (\Error $_e) {
$__psysh__->writeException(ErrorException::fromError($_e));
} catch (\Exception $_e) {
$__psysh__->writeException($_e);
}
}
\restore_error_handler();
unset($__psysh_include__);
// Override any new local variables with pre-defined scope variables
\extract($__psysh__->getScopeVariables(false));
// ... then add the whole mess of variables back.
$__psysh__->setScopeVariables(\get_defined_vars());
};
$load($shell);
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -35,14 +35,14 @@ abstract class AbstractListener implements Listener
/**
* {@inheritdoc}
*/
public function onInput(Shell $shell, $input)
public function onInput(Shell $shell, string $input)
{
}
/**
* {@inheritdoc}
*/
public function onExecute(Shell $shell, $code)
public function onExecute(Shell $shell, string $code)
{
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -23,7 +23,7 @@ interface Listener
*
* @return bool
*/
public static function isSupported();
public static function isSupported(): bool;
/**
* Called once before the REPL session starts.
@@ -49,7 +49,7 @@ interface Listener
*
* @return string|null User input override
*/
public function onInput(Shell $shell, $input);
public function onInput(Shell $shell, string $input);
/**
* Called before executing user code.
@@ -65,7 +65,7 @@ interface Listener
*
* @return string|null User code override
*/
public function onExecute(Shell $shell, $code);
public function onExecute(Shell $shell, string $code);
/**
* Called at the end of each loop.

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -26,18 +26,80 @@ class ProcessForker extends AbstractListener
private $savegame;
private $up;
private static $pcntlFunctions = [
'pcntl_fork',
'pcntl_signal_dispatch',
'pcntl_signal',
'pcntl_waitpid',
'pcntl_wexitstatus',
];
private static $posixFunctions = [
'posix_getpid',
'posix_kill',
];
/**
* Process forker is supported if pcntl and posix extensions are available.
*
* @return bool
*/
public static function isSupported()
public static function isSupported(): bool
{
return \function_exists('pcntl_signal') && \function_exists('posix_getpid');
return self::isPcntlSupported() && !self::disabledPcntlFunctions() && self::isPosixSupported() && !self::disabledPosixFunctions();
}
/**
* Forks into a master and a loop process.
* Verify that all required pcntl functions are, in fact, available.
*/
public static function isPcntlSupported(): bool
{
foreach (self::$pcntlFunctions as $func) {
if (!\function_exists($func)) {
return false;
}
}
return true;
}
/**
* Check whether required pcntl functions are disabled.
*/
public static function disabledPcntlFunctions()
{
return self::checkDisabledFunctions(self::$pcntlFunctions);
}
/**
* Verify that all required posix functions are, in fact, available.
*/
public static function isPosixSupported(): bool
{
foreach (self::$posixFunctions as $func) {
if (!\function_exists($func)) {
return false;
}
}
return true;
}
/**
* Check whether required posix functions are disabled.
*/
public static function disabledPosixFunctions()
{
return self::checkDisabledFunctions(self::$posixFunctions);
}
private static function checkDisabledFunctions(array $functions): array
{
return \array_values(\array_intersect($functions, \array_map('strtolower', \array_map('trim', \explode(',', \ini_get('disable_functions'))))));
}
/**
* Forks into a main and a loop process.
*
* The loop process will handle the evaluation of all instructions, then
* return its state via a socket upon completion.
@@ -46,7 +108,7 @@ class ProcessForker extends AbstractListener
*/
public function beforeRun(Shell $shell)
{
list($up, $down) = \stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
list($up, $down) = \stream_socket_pair(\STREAM_PF_UNIX, \STREAM_SOCK_STREAM, \STREAM_IPPROTO_IP);
if (!$up) {
throw new \RuntimeException('Unable to create socket pair');
@@ -62,8 +124,8 @@ class ProcessForker extends AbstractListener
\fclose($up);
// Wait for a return value from the loop process.
$read = [$down];
$write = null;
$read = [$down];
$write = null;
$except = null;
do {
@@ -95,8 +157,11 @@ class ProcessForker extends AbstractListener
}
// This is the child process. It's going to do all the work.
if (\function_exists('setproctitle')) {
setproctitle('psysh (loop)');
if (!@\cli_set_process_title('psysh (loop)')) {
// Fall back to `setproctitle` if that wasn't succesful.
if (\function_exists('setproctitle')) {
@\setproctitle('psysh (loop)');
}
}
// We won't be needing this one.
@@ -125,7 +190,7 @@ class ProcessForker extends AbstractListener
{
// if there's an old savegame hanging around, let's kill it.
if (isset($this->savegame)) {
\posix_kill($this->savegame, SIGKILL);
\posix_kill($this->savegame, \SIGKILL);
\pcntl_signal_dispatch();
}
}
@@ -143,7 +208,7 @@ class ProcessForker extends AbstractListener
\fwrite($this->up, $this->serializeReturn($shell->getScopeVariables(false)));
\fclose($this->up);
\posix_kill(\posix_getpid(), SIGKILL);
\posix_kill(\posix_getpid(), \SIGKILL);
}
}
@@ -168,7 +233,7 @@ class ProcessForker extends AbstractListener
// worker exited cleanly, let's bail
if (!\pcntl_wexitstatus($status)) {
\posix_kill(\posix_getpid(), SIGKILL);
\posix_kill(\posix_getpid(), \SIGKILL);
}
// worker didn't exit cleanly, we'll need to have another go
@@ -188,7 +253,7 @@ class ProcessForker extends AbstractListener
*
* @return string
*/
private function serializeReturn(array $return)
private function serializeReturn(array $return): string
{
$serializable = [];
@@ -203,14 +268,19 @@ class ProcessForker extends AbstractListener
continue;
}
if (\version_compare(\PHP_VERSION, '8.1', '>=') && $value instanceof \UnitEnum) {
// Enums defined in the REPL session can't be unserialized.
$ref = new \ReflectionObject($value);
if (\strpos($ref->getFileName(), ": eval()'d code") !== false) {
continue;
}
}
try {
@\serialize($value);
$serializable[$key] = $value;
} catch (\Throwable $e) {
// we'll just ignore this one...
} catch (\Exception $e) {
// and this one too...
// @todo remove this once we don't support PHP 5.x anymore :)
}
}

Some files were not shown because too many files have changed in this diff Show More