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)', + 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);