diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 6c6b057d3..efb87446c 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -7,14 +7,3 @@ PsySH follows [PSR-1](http://php-fig.org/psr/psr-1/) and [PSR-2](http://php-fig.
## Branching model
Please branch off and send pull requests to the `develop` branch.
-
-## Building the manual
-
-```sh
-svn co https://svn.php.net/repository/phpdoc/en/trunk/reference/ php_manual
-bin/build_manual phpdoc_manual ~/.local/share/psysh/php_manual.sqlite
-```
-
-To build the manual for another language, switch out `en` above for `de`, `es`, or any of the other languages listed in the docs.
-
-[Partial or outdated documentation is available for other languages](http://www.php.net/manual/help-translate.php) but these translations are outdated, so their content may be completely wrong or insecure!
diff --git a/.travis.yml b/.travis.yml
index 30ab567ce..3a77512aa 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,12 +24,12 @@ 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 || true'
+ - '[[ $TRAVIS_PHP_VERSION = 7.2* ]] && make build -j 4 || true'
after_success:
- bash <(curl -s https://codecov.io/bash)
-before_deploy: make dist
+before_deploy: make dist -j 4
deploy:
provider: releases
diff --git a/Makefile b/Makefile
index d144ab487..a1fdb42ed 100644
--- a/Makefile
+++ b/Makefile
@@ -87,7 +87,9 @@ build/%/psysh: vendor/bin/box build/%
# Dist packages
dist/psysh-$(VERSION).tar.gz: build/psysh/psysh
- tar -czf $@ $<
+ @mkdir -p $(@D)
+ tar -C $(dir $<) -czf $@ $(notdir $<)
dist/psysh-$(VERSION)-%.tar.gz: build/psysh-%/psysh
- tar -czf $@ $<
+ @mkdir -p $(@D)
+ tar -C $(dir $<) -czf $@ $(notdir $<)
diff --git a/src/CodeCleaner/CallTimePassByReferencePass.php b/src/CodeCleaner/CallTimePassByReferencePass.php
index 87e685e79..cff2c519b 100644
--- a/src/CodeCleaner/CallTimePassByReferencePass.php
+++ b/src/CodeCleaner/CallTimePassByReferencePass.php
@@ -31,7 +31,7 @@ class CallTimePassByReferencePass extends CodeCleanerPass
/**
* Validate of use call-time pass-by-reference.
*
- * @throws RuntimeException if the user used call-time pass-by-reference in PHP >= 5.4.0
+ * @throws RuntimeException if the user used call-time pass-by-reference
*
* @param Node $node
*/
diff --git a/src/CodeCleaner/ValidClassNamePass.php b/src/CodeCleaner/ValidClassNamePass.php
index 5de75d5f9..682399bfa 100644
--- a/src/CodeCleaner/ValidClassNamePass.php
+++ b/src/CodeCleaner/ValidClassNamePass.php
@@ -38,13 +38,11 @@ class ValidClassNamePass extends NamespaceAwarePass
const INTERFACE_TYPE = 'interface';
const TRAIT_TYPE = 'trait';
- protected $checkTraits;
private $conditionalScopes = 0;
private $atLeastPhp55;
public function __construct()
{
- $this->checkTraits = function_exists('trait_exists');
$this->atLeastPhp55 = version_compare(PHP_VERSION, '5.5', '>=');
}
@@ -365,7 +363,7 @@ protected function interfaceExists($name)
*/
protected function traitExists($name)
{
- return $this->checkTraits && (trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE);
+ return trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE;
}
/**
diff --git a/src/Command/DocCommand.php b/src/Command/DocCommand.php
index a5f90f034..fe2028690 100644
--- a/src/Command/DocCommand.php
+++ b/src/Command/DocCommand.php
@@ -14,6 +14,7 @@
use Psy\Formatter\DocblockFormatter;
use Psy\Formatter\SignatureFormatter;
use Psy\Input\CodeArgument;
+use Psy\Reflection\ReflectionClassConstant;
use Psy\Reflection\ReflectionLanguageConstruct;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -101,6 +102,18 @@ private function getManualDoc($reflector)
$id = $reflector->class . '::$' . $reflector->name;
break;
+ case 'ReflectionClassConstant':
+ case 'Psy\Reflection\ReflectionClassConstant':
+ // @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;
+ break;
+
+ case 'Psy\Reflection\ReflectionConstant_':
+ $id = $reflector->name;
+ break;
+
default:
return false;
}
diff --git a/src/Command/ListCommand/ClassConstantEnumerator.php b/src/Command/ListCommand/ClassConstantEnumerator.php
index 66d75d0ef..0b0992709 100644
--- a/src/Command/ListCommand/ClassConstantEnumerator.php
+++ b/src/Command/ListCommand/ClassConstantEnumerator.php
@@ -11,7 +11,7 @@
namespace Psy\Command\ListCommand;
-use Psy\Reflection\ReflectionConstant;
+use Psy\Reflection\ReflectionClassConstant;
use Symfony\Component\Console\Input\InputInterface;
/**
@@ -68,7 +68,7 @@ protected function getConstants(\Reflector $reflector, $noInherit = false)
$constants = [];
foreach ($reflector->getConstants() as $name => $constant) {
- $constReflector = new ReflectionConstant($reflector, $name);
+ $constReflector = ReflectionClassConstant::create($reflector, $name);
if ($noInherit && $constReflector->getDeclaringClass()->getName() !== $className) {
continue;
diff --git a/src/Command/ListCommand/ClassEnumerator.php b/src/Command/ListCommand/ClassEnumerator.php
index 126a49c22..858f5a2e1 100644
--- a/src/Command/ListCommand/ClassEnumerator.php
+++ b/src/Command/ListCommand/ClassEnumerator.php
@@ -50,7 +50,7 @@ protected function listItems(InputInterface $input, \Reflector $reflector = null
$ret = array_merge($ret, $this->filterClasses('Interfaces', get_declared_interfaces(), $internal, $user));
}
- if (function_exists('get_declared_traits') && $input->getOption('traits')) {
+ if ($input->getOption('traits')) {
$ret = array_merge($ret, $this->filterClasses('Traits', get_declared_traits(), $internal, $user));
}
diff --git a/src/Command/ListCommand/TraitEnumerator.php b/src/Command/ListCommand/TraitEnumerator.php
index ddc36ac1d..c4c74da7e 100644
--- a/src/Command/ListCommand/TraitEnumerator.php
+++ b/src/Command/ListCommand/TraitEnumerator.php
@@ -32,11 +32,6 @@ public function __construct(Presenter $presenter)
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
- // bail early if current PHP doesn't know about traits.
- if (!function_exists('trait_exists')) {
- return;
- }
-
// only list traits when no Reflector is present.
//
// @todo make a NamespaceReflector and pass that in for commands like:
diff --git a/src/Command/ReflectingCommand.php b/src/Command/ReflectingCommand.php
index 8e1091d63..8bc56f6fd 100644
--- a/src/Command/ReflectingCommand.php
+++ b/src/Command/ReflectingCommand.php
@@ -269,7 +269,8 @@ protected function setCommandScopeVariables(\Reflector $reflector)
break;
case 'ReflectionProperty':
- case 'Psy\Reflection\ReflectionConstant':
+ case 'ReflectionClassConstant':
+ case 'Psy\Reflection\ReflectionClassConstant':
$classReflector = $reflector->getDeclaringClass();
$vars['__class'] = $classReflector->name;
if ($classReflector->inNamespace()) {
@@ -281,6 +282,12 @@ protected function setCommandScopeVariables(\Reflector $reflector)
$vars['__dir'] = dirname($fileName);
}
break;
+
+ case 'Psy\Reflection\ReflectionConstant_':
+ if ($reflector->inNamespace()) {
+ $vars['__namespace'] = $reflector->getNamespaceName();
+ }
+ break;
}
if ($reflector instanceof \ReflectionClass || $reflector instanceof \ReflectionFunctionAbstract) {
diff --git a/src/Configuration.php b/src/Configuration.php
index 82d20eee2..b37f2e57f 100644
--- a/src/Configuration.php
+++ b/src/Configuration.php
@@ -880,7 +880,7 @@ public function getPager()
{
if (!isset($this->pager) && $this->usePcntl()) {
if ($pager = ini_get('cli.pager')) {
- // use the default pager (5.4+)
+ // use the default pager
$this->pager = $pager;
} elseif ($less = exec('which less 2>/dev/null')) {
// check for the presence of less...
diff --git a/src/ExecutionLoop/ProcessForker.php b/src/ExecutionLoop/ProcessForker.php
index 1a5bb7a94..2e6450325 100644
--- a/src/ExecutionLoop/ProcessForker.php
+++ b/src/ExecutionLoop/ProcessForker.php
@@ -65,9 +65,24 @@ public function beforeRun(Shell $shell)
$read = [$down];
$write = null;
$except = null;
- if (stream_select($read, $write, $except, null) === false) {
- throw new \RuntimeException('Error waiting for execution loop');
- }
+
+ do {
+ $n = @stream_select($read, $write, $except, null);
+
+ if ($n === 0) {
+ throw new \RuntimeException('Process timed out waiting for execution loop');
+ }
+
+ if ($n === false) {
+ $err = error_get_last();
+ if (!isset($err['message']) || stripos($err['message'], 'interrupted system call') === false) {
+ $msg = $err['message'] ?
+ sprintf('Error waiting for execution loop: %s', $err['message']) :
+ 'Error waiting for execution loop';
+ throw new \RuntimeException($msg);
+ }
+ }
+ } while ($n < 1);
$content = stream_get_contents($down);
fclose($down);
diff --git a/src/Formatter/SignatureFormatter.php b/src/Formatter/SignatureFormatter.php
index 0ef3e8b32..2a83ad1cb 100644
--- a/src/Formatter/SignatureFormatter.php
+++ b/src/Formatter/SignatureFormatter.php
@@ -11,7 +11,8 @@
namespace Psy\Formatter;
-use Psy\Reflection\ReflectionConstant;
+use Psy\Reflection\ReflectionClassConstant;
+use Psy\Reflection\ReflectionConstant_;
use Psy\Reflection\ReflectionLanguageConstruct;
use Psy\Util\Json;
use Symfony\Component\Console\Formatter\OutputFormatter;
@@ -41,8 +42,9 @@ public static function format(\Reflector $reflector)
case $reflector instanceof \ReflectionClass:
return self::formatClass($reflector);
- case $reflector instanceof ReflectionConstant:
- return self::formatConstant($reflector);
+ case $reflector instanceof ReflectionClassConstant:
+ case $reflector instanceof \ReflectionClassConstant:
+ return self::formatClassConstant($reflector);
case $reflector instanceof \ReflectionMethod:
return self::formatMethod($reflector);
@@ -50,6 +52,9 @@ public static function format(\Reflector $reflector)
case $reflector instanceof \ReflectionProperty:
return self::formatProperty($reflector);
+ case $reflector instanceof ReflectionConstant_:
+ return self::formatConstant($reflector);
+
default:
throw new \InvalidArgumentException('Unexpected Reflector class: ' . get_class($reflector));
}
@@ -70,8 +75,6 @@ public static function formatName(\Reflector $reflector)
/**
* Print the method, property or class modifiers.
*
- * Technically this should be a trait. Can't wait for 5.4 :)
- *
* @param \Reflector $reflector
*
* @return string Formatted modifiers
@@ -135,11 +138,11 @@ private static function formatClass(\ReflectionClass $reflector)
/**
* Format a constant signature.
*
- * @param ReflectionConstant $reflector
+ * @param ReflectionClassConstant|\ReflectionClassConstant $reflector
*
* @return string Formatted signature
*/
- private static function formatConstant(ReflectionConstant $reflector)
+ private static function formatClassConstant($reflector)
{
$value = $reflector->getValue();
$style = self::getTypeStyle($value);
@@ -153,6 +156,27 @@ private static function formatConstant(ReflectionConstant $reflector)
);
}
+ /**
+ * Format a constant signature.
+ *
+ * @param ReflectionConstant_ $reflector
+ *
+ * @return string Formatted signature
+ */
+ private static function formatConstant($reflector)
+ {
+ $value = $reflector->getValue();
+ $style = self::getTypeStyle($value);
+
+ return sprintf(
+ 'define(%s, <%s>%s%s>)',
+ OutputFormatter::escape(Json::encode($reflector->getName())),
+ $style,
+ OutputFormatter::escape(Json::encode($value)),
+ $style
+ );
+ }
+
/**
* Helper for getting output style for a given value's type.
*
diff --git a/src/Input/FilterOptions.php b/src/Input/FilterOptions.php
index 113b8c944..a2ed49d05 100644
--- a/src/Input/FilterOptions.php
+++ b/src/Input/FilterOptions.php
@@ -137,6 +137,7 @@ private function validateRegex($pattern)
try {
preg_match($pattern, '');
} catch (ErrorException $e) {
+ restore_error_handler();
throw new RuntimeException(str_replace('preg_match(): ', 'Invalid regular expression: ', $e->getRawMessage()));
}
restore_error_handler();
diff --git a/src/Reflection/ReflectionClassConstant.php b/src/Reflection/ReflectionClassConstant.php
new file mode 100644
index 000000000..ab686bbef
--- /dev/null
+++ b/src/Reflection/ReflectionClassConstant.php
@@ -0,0 +1,228 @@
+class = $class;
+ $this->name = $name;
+
+ $constants = $class->getConstants();
+ if (!array_key_exists($name, $constants)) {
+ throw new \InvalidArgumentException('Unknown constant: ' . $name);
+ }
+
+ $this->value = $constants[$name];
+ }
+
+ /**
+ * Exports a reflection.
+ *
+ * @param string|object $class
+ * @param string $name
+ * @param bool $return pass true to return the export, as opposed to emitting it
+ *
+ * @return null|string
+ */
+ public static function export($class, $name, $return = false)
+ {
+ $refl = new self($class, $name);
+ $value = $refl->getValue();
+
+ $str = sprintf('Constant [ public %s %s ] { %s }', gettype($value), $refl->getName(), $value);
+
+ if ($return) {
+ return $str;
+ }
+
+ echo $str . "\n";
+ }
+
+ /**
+ * Gets the declaring class.
+ *
+ * @return \ReflectionClass
+ */
+ public function getDeclaringClass()
+ {
+ $parent = $this->class;
+
+ // Since we don't have real reflection constants, we can't see where
+ // it's actually defined. Let's check for a constant that is also
+ // available on the parent class which has exactly the same value.
+ //
+ // While this isn't _technically_ correct, it's prolly close enough.
+ do {
+ $class = $parent;
+ $parent = $class->getParentClass();
+ } while ($parent && $parent->hasConstant($this->name) && $parent->getConstant($this->name) === $this->value);
+
+ return $class;
+ }
+
+ /**
+ * Get the constant's docblock.
+ *
+ * @return false
+ */
+ public function getDocComment()
+ {
+ return false;
+ }
+
+ /**
+ * Gets the class constant modifiers.
+ *
+ * Since this is only used for PHP < 7.1, we can just return "public". All
+ * the fancier modifiers are only available on PHP versions which have their
+ * own ReflectionClassConstant class :)
+ *
+ * @return int
+ */
+ public function getModifiers()
+ {
+ return \ReflectionMethod::IS_PUBLIC;
+ }
+
+ /**
+ * Gets the constant name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Gets the value of the constant.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Checks if class constant is private.
+ *
+ * @return bool false
+ */
+ public function isPrivate()
+ {
+ return false;
+ }
+
+ /**
+ * Checks if class constant is protected.
+ *
+ * @return bool false
+ */
+ public function isProtected()
+ {
+ return false;
+ }
+
+ /**
+ * Checks if class constant is public.
+ *
+ * @return bool true
+ */
+ public function isPublic()
+ {
+ return true;
+ }
+
+ /**
+ * To string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getName();
+ }
+
+ /**
+ * Gets the constant's file name.
+ *
+ * Currently returns null, because if it returns a file name the signature
+ * formatter will barf.
+ */
+ public function getFileName()
+ {
+ return;
+ // return $this->class->getFileName();
+ }
+
+ /**
+ * Get the code start line.
+ *
+ * @throws \RuntimeException
+ */
+ public function getStartLine()
+ {
+ throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
+ }
+
+ /**
+ * Get the code end line.
+ *
+ * @throws \RuntimeException
+ */
+ public function getEndLine()
+ {
+ return $this->getStartLine();
+ }
+
+ /**
+ * Get a ReflectionClassConstant instance.
+ *
+ * In PHP >= 7.1, this will return a \ReflectionClassConstant from the
+ * standard reflection library. For older PHP, it will return this polyfill.
+ *
+ * @param string|object $class
+ * @param string $name
+ *
+ * @return ReflectionClassConstant|\ReflectionClassConstant
+ */
+ public static function create($class, $name)
+ {
+ if (class_exists('\\ReflectionClassConstant')) {
+ return new \ReflectionClassConstant($class, $name);
+ }
+
+ return new self($class, $name);
+ }
+}
diff --git a/src/Reflection/ReflectionConstant.php b/src/Reflection/ReflectionConstant.php
index b7f8da3ea..7fa9b8a6a 100644
--- a/src/Reflection/ReflectionConstant.php
+++ b/src/Reflection/ReflectionConstant.php
@@ -12,140 +12,19 @@
namespace Psy\Reflection;
/**
- * Somehow the standard reflection library doesn't include constants.
- *
- * ReflectionConstant corrects that omission.
+ * @deprecated ReflectionConstant is now ReflectionClassConstant. This class
+ * name will be reclaimed in the next stable release, to be used for
+ * ReflectionConstant_ :)
*/
-class ReflectionConstant implements \Reflector
+class ReflectionConstant extends ReflectionClassConstant
{
- private $class;
- private $name;
- private $value;
-
/**
- * Construct a ReflectionConstant object.
- *
- * @param mixed $class
- * @param string $name
+ * {inheritDoc}.
*/
public function __construct($class, $name)
{
- if (!$class instanceof \ReflectionClass) {
- $class = new \ReflectionClass($class);
- }
-
- $this->class = $class;
- $this->name = $name;
-
- $constants = $class->getConstants();
- if (!array_key_exists($name, $constants)) {
- throw new \InvalidArgumentException('Unknown constant: ' . $name);
- }
-
- $this->value = $constants[$name];
- }
-
- /**
- * Gets the declaring class.
- *
- * @return string
- */
- public function getDeclaringClass()
- {
- $parent = $this->class;
-
- // Since we don't have real reflection constants, we can't see where
- // it's actually defined. Let's check for a constant that is also
- // available on the parent class which has exactly the same value.
- //
- // While this isn't _technically_ correct, it's prolly close enough.
- do {
- $class = $parent;
- $parent = $class->getParentClass();
- } while ($parent && $parent->hasConstant($this->name) && $parent->getConstant($this->name) === $this->value);
-
- return $class;
- }
-
- /**
- * Gets the constant name.
- *
- * @return string
- */
- public function getName()
- {
- return $this->name;
- }
+ @trigger_error('ReflectionConstant is now ReflectionClassConstant', E_USER_DEPRECATED);
- /**
- * Gets the value of the constant.
- *
- * @return mixed
- */
- public function getValue()
- {
- return $this->value;
- }
-
- /**
- * Gets the constant's file name.
- *
- * Currently returns null, because if it returns a file name the signature
- * formatter will barf.
- */
- public function getFileName()
- {
- return;
- // return $this->class->getFileName();
- }
-
- /**
- * Get the code start line.
- *
- * @throws \RuntimeException
- */
- public function getStartLine()
- {
- throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
- }
-
- /**
- * Get the code end line.
- *
- * @throws \RuntimeException
- */
- public function getEndLine()
- {
- return $this->getStartLine();
- }
-
- /**
- * Get the constant's docblock.
- *
- * @return false
- */
- public function getDocComment()
- {
- return false;
- }
-
- /**
- * Export the constant? I don't think this is possible.
- *
- * @throws \RuntimeException
- */
- public static function export()
- {
- throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
- }
-
- /**
- * To string.
- *
- * @return string
- */
- public function __toString()
- {
- return $this->getName();
+ parent::__construct($class, $name);
}
}
diff --git a/src/Reflection/ReflectionConstant_.php b/src/Reflection/ReflectionConstant_.php
new file mode 100644
index 000000000..91f96b6f6
--- /dev/null
+++ b/src/Reflection/ReflectionConstant_.php
@@ -0,0 +1,182 @@
+name = $name;
+
+ if (!defined($name) && !self::isMagicConstant($name)) {
+ throw new \InvalidArgumentException('Unknown constant: ' . $name);
+ }
+
+ if (!self::isMagicConstant($name)) {
+ $this->value = @constant($name);
+ }
+ }
+
+ /**
+ * Exports a reflection.
+ *
+ * @param string $name
+ * @param bool $return pass true to return the export, as opposed to emitting it
+ *
+ * @return null|string
+ */
+ public static function export($name, $return = false)
+ {
+ $refl = new self($name);
+ $value = $refl->getValue();
+
+ $str = sprintf('Constant [ %s %s ] { %s }', gettype($value), $refl->getName(), $value);
+
+ if ($return) {
+ return $str;
+ }
+
+ echo $str . "\n";
+ }
+
+ public static function isMagicConstant($name)
+ {
+ return in_array($name, self::$magicConstants);
+ }
+
+ /**
+ * Get the constant's docblock.
+ *
+ * @return false
+ */
+ public function getDocComment()
+ {
+ return false;
+ }
+
+ /**
+ * Gets the constant name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Gets the namespace name.
+ *
+ * Returns '' when the constant is not namespaced.
+ *
+ * @return string
+ */
+ public function getNamespaceName()
+ {
+ if (!$this->inNamespace()) {
+ return '';
+ }
+
+ return preg_replace('/\\\\[^\\\\]+$/', '', $this->name);
+ }
+
+ /**
+ * Gets the value of the constant.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Checks if this constant is defined in a namespace.
+ *
+ * @return bool
+ */
+ public function inNamespace()
+ {
+ return strpos($this->name, '\\') !== false;
+ }
+
+ /**
+ * To string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getName();
+ }
+
+ /**
+ * Gets the constant's file name.
+ *
+ * Currently returns null, because if it returns a file name the signature
+ * formatter will barf.
+ */
+ public function getFileName()
+ {
+ return;
+ // return $this->class->getFileName();
+ }
+
+ /**
+ * Get the code start line.
+ *
+ * @throws \RuntimeException
+ */
+ public function getStartLine()
+ {
+ throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
+ }
+
+ /**
+ * Get the code end line.
+ *
+ * @throws \RuntimeException
+ */
+ public function getEndLine()
+ {
+ return $this->getStartLine();
+ }
+}
diff --git a/src/Shell.php b/src/Shell.php
index 2a82facdd..0c333d780 100644
--- a/src/Shell.php
+++ b/src/Shell.php
@@ -47,7 +47,7 @@
*/
class Shell extends Application
{
- const VERSION = 'v0.9.4';
+ const VERSION = 'v0.9.5';
const PROMPT = '>>> ';
const BUFF_PROMPT = '... ';
diff --git a/src/TabCompletion/AutoCompleter.php b/src/TabCompletion/AutoCompleter.php
index bf538106a..8b3164450 100644
--- a/src/TabCompletion/AutoCompleter.php
+++ b/src/TabCompletion/AutoCompleter.php
@@ -103,8 +103,6 @@ public function __destruct()
{
// PHP didn't implement the whole readline API when they first switched
// to libedit. And they still haven't.
- //
- // So this is a thing to make PsySH work on 5.3.x:
if (function_exists('readline_callback_handler_remove')) {
readline_callback_handler_remove();
}
diff --git a/src/Util/Mirror.php b/src/Util/Mirror.php
index 97eb68057..862d0bed0 100644
--- a/src/Util/Mirror.php
+++ b/src/Util/Mirror.php
@@ -12,7 +12,8 @@
namespace Psy\Util;
use Psy\Exception\RuntimeException;
-use Psy\Reflection\ReflectionConstant;
+use Psy\Reflection\ReflectionClassConstant;
+use Psy\Reflection\ReflectionConstant_;
/**
* A utility class for getting Reflectors.
@@ -43,8 +44,12 @@ class Mirror
*/
public static function get($value, $member = null, $filter = 15)
{
- if ($member === null && is_string($value) && function_exists($value)) {
- return new \ReflectionFunction($value);
+ if ($member === null && is_string($value)) {
+ if (function_exists($value)) {
+ return new \ReflectionFunction($value);
+ } elseif (defined($value) || ReflectionConstant_::isMagicConstant($value)) {
+ return new ReflectionConstant_($value);
+ }
}
$class = self::getClass($value);
@@ -52,7 +57,7 @@ public static function get($value, $member = null, $filter = 15)
if ($member === null) {
return $class;
} elseif ($filter & self::CONSTANT && $class->hasConstant($member)) {
- return new ReflectionConstant($class, $member);
+ return ReflectionClassConstant::create($value, $member);
} elseif ($filter & self::METHOD && $class->hasMethod($member)) {
return $class->getMethod($member);
} elseif ($filter & self::PROPERTY && $class->hasProperty($member)) {
@@ -85,7 +90,7 @@ private static function getClass($value)
if (!is_string($value)) {
throw new \InvalidArgumentException('Mirror expects an object or class');
- } elseif (!class_exists($value) && !interface_exists($value) && !(function_exists('trait_exists') && trait_exists($value))) {
+ } elseif (!class_exists($value) && !interface_exists($value) && !trait_exists($value)) {
throw new \InvalidArgumentException('Unknown class or function: ' . $value);
}
diff --git a/test/CodeCleaner/LoopContextPassTest.php b/test/CodeCleaner/LoopContextPassTest.php
index 057d0c950..3b3630683 100644
--- a/test/CodeCleaner/LoopContextPassTest.php
+++ b/test/CodeCleaner/LoopContextPassTest.php
@@ -53,29 +53,6 @@ public function invalidStatements()
['while (true) { break 2; }'],
['while (true) { continue 2; }'],
- // invalid in 5.4+ because they're floats
- // ... in 5.3 because the number is too big
- ['while (true) { break 2.0; }'],
- ['while (true) { continue 2.0; }'],
-
- // and once with nested loops, just for good measure
- ['while (true) { while (true) { break 3; } }'],
- ['while (true) { while (true) { continue 3; } }'],
- ];
- }
-
- /**
- * @dataProvider invalidPHP54Statements
- * @expectedException \Psy\Exception\FatalErrorException
- */
- public function testPHP54ProcessStatementFails($code)
- {
- $this->parseAndTraverse($code);
- }
-
- public function invalidPHP54Statements()
- {
- return [
// In PHP 5.4+, only positive literal integers are allowed
['while (true) { break $n; }'],
['while (true) { continue $n; }'],
@@ -87,6 +64,12 @@ public function invalidPHP54Statements()
['while (true) { continue -1; }'],
['while (true) { break 1.0; }'],
['while (true) { continue 1.0; }'],
+ ['while (true) { break 2.0; }'],
+ ['while (true) { continue 2.0; }'],
+
+ // and once with nested loops, just for good measure
+ ['while (true) { while (true) { break 3; } }'],
+ ['while (true) { while (true) { continue 3; } }'],
];
}
diff --git a/test/Exception/FatalErrorExceptionTest.php b/test/Exception/FatalErrorExceptionTest.php
index f84268d00..36c7dd8c1 100644
--- a/test/Exception/FatalErrorExceptionTest.php
+++ b/test/Exception/FatalErrorExceptionTest.php
@@ -42,4 +42,10 @@ public function testMessageWithNoFilename()
$this->assertContains('{msg}', $e->getMessage());
$this->assertContains('eval()\'d code', $e->getMessage());
}
+
+ public function testNegativeOneLineNumberIgnored()
+ {
+ $e = new FatalErrorException('{msg}', 0, 1, null, -1);
+ $this->assertEquals(0, $e->getLine());
+ }
}
diff --git a/test/Formatter/SignatureFormatterTest.php b/test/Formatter/SignatureFormatterTest.php
index 423617920..28d7ba328 100644
--- a/test/Formatter/SignatureFormatterTest.php
+++ b/test/Formatter/SignatureFormatterTest.php
@@ -12,7 +12,7 @@
namespace Psy\Test\Formatter;
use Psy\Formatter\SignatureFormatter;
-use Psy\Reflection\ReflectionConstant;
+use Psy\Reflection\ReflectionClassConstant;
class SignatureFormatterTest extends \PHPUnit\Framework\TestCase
{
@@ -39,7 +39,7 @@ public function signatureReflectors()
defined('HHVM_VERSION') ? 'function implode($arg1, $arg2 = null)' : 'function implode($glue, $pieces)',
],
[
- new ReflectionConstant($this, 'FOO'),
+ ReflectionClassConstant::create($this, 'FOO'),
'const FOO = "foo value"',
],
[
diff --git a/test/Input/FilterOptionsTest.php b/test/Input/FilterOptionsTest.php
new file mode 100644
index 000000000..7ed8919bd
--- /dev/null
+++ b/test/Input/FilterOptionsTest.php
@@ -0,0 +1,105 @@
+assertCount(3, $opts);
+ }
+
+ /**
+ * @dataProvider validInputs
+ */
+ public function testBindValidInput($input, $hasFilter = true)
+ {
+ $input = $this->getInput($input);
+ $filterOptions = new FilterOptions();
+ $filterOptions->bind($input);
+
+ $this->assertEquals($hasFilter, $filterOptions->hasFilter());
+ }
+
+ public function validInputs()
+ {
+ return [
+ ['--grep="bar"'],
+ ['--grep="bar" --invert'],
+ ['--grep="bar" --insensitive'],
+ ['--grep="bar" --invert --insensitive'],
+ ['', false],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidInputs
+ * @expectedException \Psy\Exception\RuntimeException
+ */
+ public function testBindInvalidInput($input)
+ {
+ $input = $this->getInput($input);
+ $filterOptions = new FilterOptions();
+ $filterOptions->bind($input);
+ }
+
+ public function invalidInputs()
+ {
+ return [
+ ['--invert'],
+ ['--insensitive'],
+ ['--invert --insensitive'],
+
+ // invalid because regex
+ ['--grep /*/'],
+ ];
+ }
+
+ /**
+ * @dataProvider matchData
+ */
+ public function testMatch($input, $str, $matches)
+ {
+ $input = $this->getInput($input);
+ $filterOptions = new FilterOptions();
+ $filterOptions->bind($input);
+
+ $this->assertEquals($matches, $filterOptions->match($str));
+ }
+
+ public function matchData()
+ {
+ return [
+ ['', 'whatever', true],
+ ['--grep FOO', 'foo', false],
+ ['--grep foo', 'foo', true],
+ ['--grep foo', 'food', true],
+ ['--grep oo', 'Food', true],
+ ['--grep oo -i', 'FOOD', true],
+ ['--grep foo -v', 'food', false],
+ ['--grep foo -v', 'whatever', true],
+ ];
+ }
+
+ private function getInput($input)
+ {
+ $input = new StringInput($input);
+ $input->bind(new InputDefinition(FilterOptions::getOptions()));
+
+ return $input;
+ }
+}
diff --git a/test/Reflection/ReflectionClassConstantTest.php b/test/Reflection/ReflectionClassConstantTest.php
new file mode 100644
index 000000000..87d4f7520
--- /dev/null
+++ b/test/Reflection/ReflectionClassConstantTest.php
@@ -0,0 +1,81 @@
+getDeclaringClass();
+
+ $this->assertInstanceOf('ReflectionClass', $class);
+ $this->assertSame('Psy\Test\Reflection\ReflectionClassConstantTest', $class->getName());
+ $this->assertSame('CONSTANT_ONE', $refl->getName());
+ $this->assertSame('CONSTANT_ONE', (string) $refl);
+ $this->assertSame('one', $refl->getValue());
+ $this->assertNull($refl->getFileName());
+ $this->assertFalse($refl->getDocComment());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testUnknownConstantThrowsException()
+ {
+ new ReflectionClassConstant($this, 'UNKNOWN_CONSTANT');
+ }
+
+ public function testExport()
+ {
+ $ret = ReflectionClassConstant::export($this, 'CONSTANT_ONE', true);
+ $this->assertEquals($ret, 'Constant [ public string CONSTANT_ONE ] { one }');
+ }
+
+ public function testExportOutput()
+ {
+ $this->expectOutputString("Constant [ public string CONSTANT_ONE ] { one }\n");
+ ReflectionClassConstant::export($this, 'CONSTANT_ONE', false);
+ }
+
+ public function testModifiers()
+ {
+ $refl = new ReflectionClassConstant($this, 'CONSTANT_ONE');
+
+ $this->assertEquals(\ReflectionMethod::IS_PUBLIC, $refl->getModifiers());
+ $this->assertFalse($refl->isPrivate());
+ $this->assertFalse($refl->isProtected());
+ $this->assertTrue($refl->isPublic());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @dataProvider notYetImplemented
+ */
+ public function testNotYetImplemented($method)
+ {
+ $refl = new ReflectionClassConstant($this, 'CONSTANT_ONE');
+ $refl->$method();
+ }
+
+ public function notYetImplemented()
+ {
+ return [
+ ['getStartLine'],
+ ['getEndLine'],
+ ];
+ }
+}
diff --git a/test/Reflection/ReflectionConstantBCTest.php b/test/Reflection/ReflectionConstantBCTest.php
new file mode 100644
index 000000000..69d279709
--- /dev/null
+++ b/test/Reflection/ReflectionConstantBCTest.php
@@ -0,0 +1,26 @@
+assertInstanceOf('Psy\Reflection\ReflectionConstant', $refl);
+ $this->assertInstanceOf('Psy\Reflection\ReflectionClassConstant', $refl);
+ }
+}
diff --git a/test/Reflection/ReflectionConstantTest.php b/test/Reflection/ReflectionConstantTest.php
index 713a7e337..95328ff89 100644
--- a/test/Reflection/ReflectionConstantTest.php
+++ b/test/Reflection/ReflectionConstantTest.php
@@ -11,24 +11,61 @@
namespace Psy\Test\Reflection;
-use Psy\Reflection\ReflectionConstant;
+use Psy\Reflection\ReflectionConstant_;
+
+define('Psy\\Test\\Reflection\\SOME_CONSTANT', 'yep');
class ReflectionConstantTest extends \PHPUnit\Framework\TestCase
{
- const CONSTANT_ONE = 'one';
-
public function testConstruction()
{
- $refl = new ReflectionConstant($this, 'CONSTANT_ONE');
- $class = $refl->getDeclaringClass();
+ $refl = new ReflectionConstant_('Psy\\Test\\Reflection\\SOME_CONSTANT');
- $this->assertInstanceOf('ReflectionClass', $class);
- $this->assertSame('Psy\Test\Reflection\ReflectionConstantTest', $class->getName());
- $this->assertSame('CONSTANT_ONE', $refl->getName());
- $this->assertSame('CONSTANT_ONE', (string) $refl);
- $this->assertSame('one', $refl->getValue());
- $this->assertNull($refl->getFileName());
$this->assertFalse($refl->getDocComment());
+ $this->assertEquals('Psy\\Test\\Reflection\\SOME_CONSTANT', $refl->getName());
+ $this->assertEquals('Psy\\Test\\Reflection', $refl->getNamespaceName());
+ $this->assertEquals('yep', $refl->getValue());
+ $this->assertTrue($refl->inNamespace());
+ $this->assertEquals('Psy\\Test\\Reflection\\SOME_CONSTANT', (string) $refl);
+ $this->assertNull($refl->getFileName());
+ }
+
+ public function testBuiltInConstant()
+ {
+ $refl = new ReflectionConstant_('PHP_VERSION');
+
+ $this->assertEquals('PHP_VERSION', $refl->getName());
+ $this->assertEquals('PHP_VERSION', (string) $refl);
+ $this->assertEquals(PHP_VERSION, $refl->getValue());
+ $this->assertFalse($refl->inNamespace());
+ $this->assertSame('', $refl->getNamespaceName());
+ }
+
+ /**
+ * @dataProvider magicConstants
+ */
+ public function testIsMagicConstant($name, $is)
+ {
+ $this->assertEquals($is, ReflectionConstant_::isMagicConstant($name));
+ }
+
+ public function magicConstants()
+ {
+ return [
+ ['__LINE__', true],
+ ['__FILE__', true],
+ ['__DIR__', true],
+ ['__FUNCTION__', true],
+ ['__CLASS__', true],
+ ['__TRAIT__', true],
+ ['__METHOD__', true],
+ ['__NAMESPACE__', true],
+ ['__COMPILER_HALT_OFFSET__', true],
+ ['PHP_VERSION', false],
+ ['PHP_EOL', false],
+ ['Psy\\Test\\Reflection\\SOME_CONSTANT', false],
+ ['What if it isn\'t even a valid constant name?', false],
+ ];
}
/**
@@ -36,7 +73,25 @@ public function testConstruction()
*/
public function testUnknownConstantThrowsException()
{
- new ReflectionConstant($this, 'UNKNOWN_CONSTANT');
+ new ReflectionConstant_('UNKNOWN_CONSTANT');
+ }
+
+ public function testExport()
+ {
+ $ret = ReflectionConstant_::export('Psy\\Test\\Reflection\\SOME_CONSTANT', true);
+ $this->assertEquals($ret, 'Constant [ string Psy\\Test\\Reflection\\SOME_CONSTANT ] { yep }');
+ }
+
+ public function testExportOutput()
+ {
+ $this->expectOutputString("Constant [ string Psy\\Test\\Reflection\\SOME_CONSTANT ] { yep }\n");
+ ReflectionConstant_::export('Psy\\Test\\Reflection\\SOME_CONSTANT', false);
+ }
+
+ public function testGetFileName()
+ {
+ $refl = new ReflectionConstant_('Psy\\Test\\Reflection\\SOME_CONSTANT');
+ $this->assertNull($refl->getFileName());
}
/**
@@ -45,7 +100,7 @@ public function testUnknownConstantThrowsException()
*/
public function testNotYetImplemented($method)
{
- $refl = new ReflectionConstant($this, 'CONSTANT_ONE');
+ $refl = new ReflectionConstant_('Psy\\Test\\Reflection\\SOME_CONSTANT');
$refl->$method();
}
@@ -54,7 +109,6 @@ public function notYetImplemented()
return [
['getStartLine'],
['getEndLine'],
- ['export'],
];
}
}
diff --git a/test/Util/MirrorTest.php b/test/Util/MirrorTest.php
index 6caecb551..09976bdfd 100644
--- a/test/Util/MirrorTest.php
+++ b/test/Util/MirrorTest.php
@@ -36,7 +36,14 @@ public function testMirror()
$this->assertInstanceOf('ReflectionObject', $refl);
$refl = Mirror::get($this, 'FOO');
- $this->assertInstanceOf('Psy\Reflection\ReflectionConstant', $refl);
+ if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
+ $this->assertInstanceOf('ReflectionClassConstant', $refl);
+ } else {
+ $this->assertInstanceOf('Psy\Reflection\ReflectionClassConstant', $refl);
+ }
+
+ $refl = Mirror::get('PHP_VERSION');
+ $this->assertInstanceOf('Psy\Reflection\ReflectionConstant_', $refl);
$refl = Mirror::get($this, 'bar');
$this->assertInstanceOf('ReflectionProperty', $refl);