update v 1.0.7.5

This commit is contained in:
Sujit Prasad
2016-06-13 20:41:55 +05:30
parent aa9786d829
commit 283d97e3ea
5078 changed files with 339851 additions and 175995 deletions

View File

@@ -18,7 +18,7 @@ tools:
excluded-paths: [ spec/*, integration/* ]
build_failure_conditions:
- 'issues.label("coding-style").exists'
- 'issues.label("coding-style").new.exists'
filter:
excluded_paths:

View File

@@ -24,6 +24,7 @@ matrix:
before_install:
- composer selfupdate
- if [ "$DEPENDENCIES" == "low" ]; then composer config disable-tls true; fi;
install:
- export COMPOSER_ROOT_VERSION=dev-master

View File

@@ -1,4 +1,27 @@
2.4.1 / 2016/01/01
2.5.0 / 2016-03-20
==================
* No changes from RC1
2.5.0-rc1 / 2016-03-12
======================
* Fixed bug with typehints in classes defined in spec file
2.5.0-beta / 2016-02-16
=======================
* Supports grouped Use statements
* Now shows path in error message when spec file doesn't contain a class
* Supports catching PHP 7 Errors in shouldThrow
* No longer attempts to generate methods with reserved names
* Fixed bug where bootstrapped classes could not be loaded after class generation
* Fixed bug where line numbers were incorrectly reported on PHP 7
* Fixed new methods being inserted incorrectly when strings included closing brace
* Dot formatter now shows spec count on last line
2.4.1 / 2016-01-01
==================
* Correctly handle nested class definitions
@@ -9,12 +32,12 @@
* Handle underscores correctly when using PSR-4
* Fixed HTML formatter
2.4.0 / 2015/11/28
2.4.0 / 2015-11-28
==================
* Improved docblock for beConstructedThrough()
2.4.0-rc1 / 2015/11/20
2.4.0-rc1 / 2015-11-20
======================
* No changes from RC1

View File

@@ -11,6 +11,12 @@ The main website with documentation is at `http://www.phpspec.net <http://www.ph
:target: https://scrutinizer-ci.com/g/phpspec/phpspec/build-status/master
:alt: Master Scrutinizer Quality Score
.. image:: https://ci.appveyor.com/api/projects/status/wce4nun9re76ocp6/branch/master?svg=true
:target: https://ci.appveyor.com/project/ciaranmcnulty/phpspec/branch/master
:alt: AppVeyor build status
Installing Dependencies
-----------------------

View File

@@ -7,17 +7,12 @@ environment:
matrix:
- PHP_DOWNLOAD_FILE: php-5.6.14-nts-Win32-VC11-x86.zip
matrix:
allow_failures:
- PHP_DOWNLOAD_FILE: php-5.6.14-nts-Win32-VC11-x86.zip
skip_commits:
message: /\[ci skip\]/
cache:
- c:\php -> appveyor.yml
- '%LOCALAPPDATA%\Composer'
- vendor
init:
- SET PATH=c:\php;%PATH%
@@ -48,4 +43,4 @@ test_script:
- cd c:\projects\phpspec
- php bin\phpspec run --format=pretty
- php vendor\phpunit\phpunit\phpunit --testdox
- php vendor\behat\behat\bin\behat --format=pretty --tags="~@php-version,@php5.4"
- php vendor\behat\behat\bin\behat --format=pretty --tags="~@php-version,@php5.4&&~@hhvm"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env php
<?php
define('PHPSPEC_VERSION', '2.4.1');
define('PHPSPEC_VERSION', '2.5.0');
if (is_file($autoload = getcwd() . '/vendor/autoload.php')) {
require $autoload;

View File

@@ -108,7 +108,10 @@ class ApplicationContext implements Context, MatchersProviderInterface
$this->addOptionToArguments($option, $arguments);
$this->lastExitCode = $this->tester->run($arguments, array('interactive' => (bool)$interactive));
$this->lastExitCode = $this->tester->run($arguments, array(
'interactive' => (bool)$interactive,
'decorated' => false,
));
}
/**

View File

@@ -7,6 +7,7 @@ use Behat\Gherkin\Node\TableNode;
use Matcher\FileExistsMatcher;
use Matcher\FileHasContentsMatcher;
use PhpSpec\Matcher\MatchersProviderInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
/**
@@ -51,7 +52,11 @@ class FilesystemContext implements Context, MatchersProviderInterface
*/
public function removeWorkingDirectory()
{
$this->filesystem->remove($this->workingDirectory);
try {
$this->filesystem->remove($this->workingDirectory);
} catch (IOException $e) {
//ignoring exception
}
}
/**

View File

@@ -40,8 +40,8 @@ class IsolatedProcessContext implements Context, SnippetAcceptingContext
$command = sprintf('%s %s', $this->buildPhpSpecCmd(), 'run');
$env = array(
'SHELL_INTERACTIVE' => true,
'HOME' => $_SERVER['HOME'],
'PATH' => $_SERVER['PATH']
'HOME' => getenv('HOME'),
'PATH' => getenv('PATH'),
);
$this->process = $process = new Process($command);
@@ -56,7 +56,13 @@ class IsolatedProcessContext implements Context, SnippetAcceptingContext
*/
protected function buildPhpSpecCmd()
{
return escapeshellcmd(__DIR__ . '/../../bin/phpspec');
$isWindows = DIRECTORY_SEPARATOR === '\\';
$cmd = escapeshellcmd('' . __DIR__ . '/../../bin/phpspec');
if ($isWindows) {
$cmd = 'php ' . $cmd;
}
return $cmd;
}
/**

View File

@@ -33,7 +33,7 @@ class ApplicationOutputMatcher implements MatcherInterface
public function positiveMatch($name, $subject, array $arguments)
{
$expected = $this->normalize($arguments[0]);
$actual = $this->normalize($subject->getDisplay());
$actual = $this->normalize($subject->getDisplay(true));
if (strpos($actual, $expected) === false) {
throw new FailureException(sprintf(
"Application output did not contain expected '%s'. Actual output:\n'%s'" ,

View File

@@ -0,0 +1,133 @@
@php-version @php7
Feature: Developer generates a collaborator
As a Developer
I want to automate creating collaborators
In order to avoid disrupting my workflow
Scenario: Being prompted but not generating a collaborator
Given the spec file "spec/CodeGeneration/CollaboratorExample1/Markdown1Spec.php" contains:
"""
<?php
namespace spec\CodeGeneration\CollaboratorExample1;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use CodeGeneration\CollaboratorExample1\{Tokenizer, Parser};
class Markdown1Spec extends ObjectBehavior
{
function it_interacts_with_a_collaborator(Parser $parser)
{
$parser->willReturn(true);
}
}
"""
And the class file "src/CodeGeneration/CollaboratorExample1/Markdown1.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorExample1;
class Markdown1
{
}
"""
When I run phpspec and answer "n" when asked if I want to generate the code
Then I should be prompted with:
"""
Would you like me to generate an interface
`CodeGeneration\CollaboratorExample1\Parser` for you?
[Y/n]
"""
Scenario: Asking for interface to be generated for second collaborator in group
Given the spec file "spec/CodeGeneration/CollaboratorExample2/Markdown1Spec.php" contains:
"""
<?php
namespace spec\CodeGeneration\CollaboratorExample2;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use CodeGeneration\CollaboratorExample2\{Tokenizer, Parser};
class Markdown1Spec extends ObjectBehavior
{
function it_interacts_with_a_collaborator(Parser $parser)
{
$parser->willReturn(true);
}
}
"""
And the class file "src/CodeGeneration/CollaboratorExample2/Markdown1.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorExample2;
class Markdown1
{
}
"""
When I run phpspec and answer "y" when asked if I want to generate the code
Then the class in "src/CodeGeneration/CollaboratorExample2/Parser.php" should contain:
"""
<?php
namespace CodeGeneration\CollaboratorExample2;
interface Parser
{
}
"""
Scenario: Asking for interface to be generated for the first collaborator in group
Given the spec file "spec/CodeGeneration/CollaboratorExample3/Markdown1Spec.php" contains:
"""
<?php
namespace spec\CodeGeneration\CollaboratorExample3;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use CodeGeneration\CollaboratorExample3\{Tokenizer, Parser};
class Markdown1Spec extends ObjectBehavior
{
function it_interacts_with_a_collaborator(Tokenizer $tokenizer)
{
$tokenizer->willReturn(true);
}
}
"""
And the class file "src/CodeGeneration/CollaboratorExample3/Markdown1.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorExample3;
class Markdown1
{
}
"""
When I run phpspec and answer "y" when asked if I want to generate the code
Then the class in "src/CodeGeneration/CollaboratorExample3/Tokenizer.php" should contain:
"""
<?php
namespace CodeGeneration\CollaboratorExample3;
interface Tokenizer
{
}
"""

View File

@@ -264,3 +264,48 @@ Feature: Developer generates a collaborator's method
"""
When I run phpspec and answer "n" when asked if I want to generate the code
Then I should not be prompted for code generation
Scenario: Being warned when a collaborator method is a restricted word
Given the spec file "spec/CodeGeneration/CollaboratorMethodExample6/MarkdownSpec.php" contains:
"""
<?php
namespace spec\CodeGeneration\CollaboratorMethodExample6;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use CodeGeneration\CollaboratorMethodExample1\Parser;
class MarkdownSpec extends ObjectBehavior
{
function it_interacts_with_a_collaborator(Parser $parser)
{
$parser->throw()->willReturn(true);
}
}
"""
And the class file "src/CodeGeneration/CollaboratorMethodExample6/Markdown.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorMethodExample6;
class Markdown
{
}
"""
And the class file "src/CodeGeneration/CollaboratorMethodExample6/Parser.php" contains:
"""
<?php
namespace CodeGeneration\CollaboratorMethodExample6;
interface Parser
{
}
"""
When I run phpspec and answer "n" when asked if I want to generate the code
Then I should see "I cannot generate the method 'throw' for you"

View File

@@ -371,3 +371,47 @@ Feature: Developer generates a method
}
"""
Scenario: Generating a method named with a restricted keyword
Given the spec file "spec/MyNamespace/RestrictedSpec.php" contains:
"""
<?php
namespace spec\MyNamespace;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RestrictedSpec extends ObjectBehavior
{
function it_tries_to_call_wrong_method()
{
$this->throw()->shouldReturn();
}
}
"""
And the class file "src/MyNamespace/Restricted.php" contains:
"""
<?php
namespace MyNamespace;
class Restricted
{
}
"""
When I run phpspec interactively
Then I should see "I cannot generate the method 'throw' for you"
And the class in "src/MyNamespace/Restricted.php" should contain:
"""
<?php
namespace MyNamespace;
class Restricted
{
}
"""

View File

@@ -396,7 +396,7 @@ Feature: Developer is shown diffs
"""
method call:
- methodTwo("value")
on Double\Diffs\DiffExample7\ClassBeingMocked\P13 was not expected, expected calls were:
on Double\Diffs\DiffExample7\ClassBeingMocked\P14 was not expected, expected calls were:
- methodOne(exact("value"))
"""
@@ -460,7 +460,7 @@ Feature: Developer is shown diffs
"""
method call:
- methodTwo("another value")
on Double\Diffs\DiffExample8\ClassBeingMocked\P14 was not expected, expected calls were:
on Double\Diffs\DiffExample8\ClassBeingMocked\P15 was not expected, expected calls were:
- methodTwo(exact("value"))
- methodOne(exact("another value"))
"""

View File

@@ -102,4 +102,27 @@ class TokenizedNamespaceResolverSpec extends ObjectBehavior
$this->resolve('Foo')->shouldReturn('Baz\Foo');
$this->resolve('Bar\Baz')->shouldReturn('Boz\Bar\Baz');
}
function it_resolves_types_from_grouped_use_statements()
{
$this->analyse('
<?php
namespace Baz;
use Boz\{Fiz, Buz};
class Foo
{
function it_something(Fiz $fiz, Buz $buz)
{
}
}
');
$this->resolve('Fiz')->shouldReturn('Boz\Fiz');
$this->resolve('Buz')->shouldReturn('Boz\Buz');
}
}

View File

@@ -25,7 +25,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar()
{
@@ -35,7 +35,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
')->shouldReturn('
<?php
class Foo
class FooSpec
{
public function bar()
{
@@ -50,7 +50,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(\Foo\Bar $bar)
{
@@ -60,9 +60,9 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
')->shouldReturn('
<?php
class Foo
class FooSpec
{
public function bar($bar)
public function bar( $bar)
{
}
}
@@ -75,7 +75,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(\Foo\Bar $bar)
{
@@ -89,9 +89,9 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
')->shouldReturn('
<?php
class Foo
class FooSpec
{
public function bar($bar)
public function bar( $bar)
{
new class($argument) implements InterfaceName
{
@@ -108,7 +108,7 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(Bar $bar, Baz $baz)
{
@@ -118,9 +118,9 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
')->shouldReturn('
<?php
class Foo
class FooSpec
{
public function bar($bar,$baz)
public function bar( $bar, $baz)
{
}
}
@@ -132,14 +132,14 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
{
$namespaceResolver->analyse(Argument::any())->shouldBeCalled();
$namespaceResolver->resolve('Foo')->willReturn('Foo');
$namespaceResolver->resolve('FooSpec')->willReturn('FooSpec');
$namespaceResolver->resolve('Foo\Bar')->willReturn('Foo\Bar');
$namespaceResolver->resolve('Baz')->willReturn('Baz');
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(Foo\Bar $bar, Baz $baz)
{
@@ -148,8 +148,8 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
');
$typeHintIndex->add('Foo', 'bar', '$bar', 'Foo\Bar')->shouldHaveBeenCalled();
$typeHintIndex->add('Foo', 'bar', '$baz', 'Baz')->shouldHaveBeenCalled();
$typeHintIndex->add('FooSpec', 'bar', '$bar', 'Foo\Bar')->shouldHaveBeenCalled();
$typeHintIndex->add('FooSpec', 'bar', '$baz', 'Baz')->shouldHaveBeenCalled();
}
function it_indexes_invalid_typehints(
@@ -159,13 +159,13 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
$e = new DisallowedScalarTypehintException();
$namespaceResolver->analyse(Argument::any())->shouldBeCalled();
$namespaceResolver->resolve('Foo')->willReturn('Foo');
$namespaceResolver->resolve('FooSpec')->willReturn('FooSpec');
$namespaceResolver->resolve('int')->willThrow($e);
$this->rewrite('
<?php
class Foo
class FooSpec
{
public function bar(int $bar)
{
@@ -174,7 +174,79 @@ class TokenizedTypeHintRewriterSpec extends ObjectBehavior
');
$typeHintIndex->addInvalid('Foo', 'bar', '$bar', $e)->shouldHaveBeenCalled();
$typeHintIndex->add('Foo', 'bar', '$bar', Argument::any())->shouldNotHaveBeenCalled();
$typeHintIndex->addInvalid('FooSpec', 'bar', '$bar', $e)->shouldHaveBeenCalled();
$typeHintIndex->add('FooSpec', 'bar', '$bar', Argument::any())->shouldNotHaveBeenCalled();
}
function it_preserves_line_numbers()
{
$this->rewrite('
<?php
class FooSpec
{
public function(
$foo,
array $bar,
Foo\Bar $arg3,
$arg4
)
{
}
}
')->shouldReturn('
<?php
class FooSpec
{
public function(
$foo,
array $bar,
$arg3,
$arg4
)
{
}
}
');
}
function it_do_not_remove_typehints_of_non_spec_classes()
{
$this->rewrite('
<?php
class FooSpec
{
public function bar(Bar $bar, Baz $baz)
{
}
}
class Bar
{
public function foo(Baz $baz)
{
}
}
')->shouldReturn('
<?php
class FooSpec
{
public function bar( $bar, $baz)
{
}
}
class Bar
{
public function foo(Baz $baz)
{
}
}
');
}
}

View File

@@ -62,6 +62,15 @@ class TokenizedCodeWriterSpec extends ObjectBehavior
$this->shouldThrow($exception)->during('insertAfterMethod', array($class, 'methodOne', ''));
}
function it_should_generate_a_method_in_a_class_with_a_string_containing_braces()
{
$class = $this->getClassWithBraceText();
$method = $this->getMethod();
$result = $this->getClassWithBraceTextAndNewMethod();
$this->insertMethodLastInClass($class, $method)->shouldReturn($result);
}
private function getSingleMethodClass()
{
return <<<SINGLE_METHOD_CLASS
@@ -232,4 +241,43 @@ final class MyClass
}
ONLY_NEW_METHOD_CLASS;
}
private function getClassWithBraceText()
{
return <<<'BRACE_TEXT_CLASS'
<?php
namespace MyNamespace;
final class MyClass
{
public function braceMethod()
{
return "{$foo}";
}
}
BRACE_TEXT_CLASS;
}
private function getClassWithBraceTextAndNewMethod()
{
return <<<'BRACE_TEXT_RESULT_CLASS'
<?php
namespace MyNamespace;
final class MyClass
{
public function braceMethod()
{
return "{$foo}";
}
public function newMethod()
{
return 'newSomething';
}
}
BRACE_TEXT_RESULT_CLASS;
}
}

View File

@@ -180,4 +180,36 @@ class IOSpec extends ObjectBehavior
$this->getBlockWidth()->shouldReturn(65);
}
function it_writes_a_message_about_broken_code(OutputInterface $output)
{
$message = 'Error message';
$output->writeln('<broken-bg> </broken-bg>')->shouldBeCalledTimes(2);
$output->writeln('<broken-bg>Error message </broken-bg>')->shouldBeCalled();
$output->writeln('')->shouldBeCalled();
$this->writeBrokenCodeBlock($message);
}
function it_wraps_long_broken_message(OutputInterface $output)
{
$message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pro maximus nulla eget libero rhoncus lacinia.';
$output->writeln('<broken-bg> </broken-bg>')->shouldBeCalledTimes(2);
$output->writeln('<broken-bg>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pro</broken-bg>')->shouldBeCalled();
$output->writeln('<broken-bg>maximus nulla eget libero rhoncus lacinia. </broken-bg>')->shouldBeCalled();
$output->writeln('')->shouldBeCalled();
$this->writeBrokenCodeBlock($message);
}
function it_indents_and_wraps_long_broken_message(OutputInterface $output)
{
$message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin maximus nulla eget libero rhoncus lacinia.';
$output->writeln('<broken-bg> </broken-bg>')->shouldBeCalledTimes(2);
$output->writeln('<broken-bg> Lorem ipsum dolor sit amet, consectetur adipiscing elit. </broken-bg>')->shouldBeCalled();
$output->writeln('<broken-bg> Proin maximus nulla eget libero rhoncus lacinia. </broken-bg>')->shouldBeCalled();
$output->writeln('')->shouldBeCalled();
$this->writeBrokenCodeBlock($message, 2);
}
}

View File

@@ -45,4 +45,12 @@ class SuiteEventSpec extends ObjectBehavior
$this->markAsWorthRerunning();
$this->isWorthRerunning()->shouldReturn(true);
}
function it_can_be_told_that_the_suite_is_no_longer_worth_rerunning()
{
$this->markAsWorthRerunning();
$this->markAsNotWorthRerunning();
$this->isWorthRerunning()->shouldReturn(false);
}
}

View File

@@ -26,6 +26,6 @@ class StringEngineSpec extends ObjectBehavior
</code>
DIFF;
$this->compare('string1', 'string2')->shouldReturn(str_replace("\n", PHP_EOL, $expected));
$this->compare('string1', 'string2')->shouldReturn($expected);
}
}

View File

@@ -11,19 +11,19 @@ use PhpSpec\Locator\ResourceInterface;
use PhpSpec\Locator\ResourceManager;
use PhpSpec\Locator\ResourceManagerInterface;
use PhpSpec\ObjectBehavior;
use PhpSpec\Util\NameCheckerInterface;
use Prophecy\Argument;
use Prophecy\Doubler\DoubleInterface;
use Prophecy\Exception\Doubler\MethodNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
class CollaboratorMethodNotFoundListenerSpec extends ObjectBehavior
{
function let(
IO $io, ResourceManagerInterface $resources, ExampleEvent $event,
MethodNotFoundException $exception, ResourceInterface $resource, GeneratorManager $generator
)
{
$this->beConstructedWith($io, $resources, $generator);
MethodNotFoundException $exception, ResourceInterface $resource, GeneratorManager $generator,
NameCheckerInterface $nameChecker
) {
$this->beConstructedWith($io, $resources, $generator, $nameChecker);
$event->getException()->willReturn($exception);
$io->isCodeGenerationEnabled()->willReturn(true);
@@ -32,6 +32,7 @@ class CollaboratorMethodNotFoundListenerSpec extends ObjectBehavior
$resources->createResource(Argument::any())->willReturn($resource);
$exception->getArguments()->willReturn(array());
$nameChecker->isNameValid('aMethod')->willReturn(true);
}
function it_is_an_event_subscriber()
@@ -72,7 +73,7 @@ class CollaboratorMethodNotFoundListenerSpec extends ObjectBehavior
function it_does_not_prompt_when_wrong_exception_is_thrown(IO $io, ExampleEvent $event, SuiteEvent $suiteEvent)
{
$event->getException()->willReturn(new RuntimeException());
$event->getException()->willReturn(new \RuntimeException());
$this->afterExample($event);
$this->afterSuite($suiteEvent);
@@ -153,6 +154,49 @@ class CollaboratorMethodNotFoundListenerSpec extends ObjectBehavior
$suiteEvent->markAsWorthRerunning()->shouldHaveBeenCalled();
}
function it_warns_if_a_method_name_is_wrong(
ExampleEvent $event,
SuiteEvent $suiteEvent,
IO $io,
NameCheckerInterface $nameChecker
) {
$exception = new MethodNotFoundException('Error', new DoubleOfInterface(), 'throw');
$event->getException()->willReturn($exception);
$nameChecker->isNameValid('throw')->willReturn(false);
$io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled();
$io->askConfirmation(Argument::any())->shouldNotBeCalled();
$this->afterExample($event);
$this->afterSuite($suiteEvent);
}
function it_prompts_and_warns_when_one_method_name_is_correct_but_other_reserved(
ExampleEvent $event,
SuiteEvent $suiteEvent,
IO $io,
NameCheckerInterface $nameChecker
) {
$this->callAfterExample($event, $nameChecker, 'throw', false);
$this->callAfterExample($event, $nameChecker, 'foo');
$io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled();
$io->askConfirmation(Argument::any())->shouldBeCalled();
$suiteEvent->markAsNotWorthRerunning()->shouldBeCalled();
$this->afterSuite($suiteEvent);
}
private function callAfterExample($event, $nameChecker, $method, $isNameValid = true)
{
$exception = new MethodNotFoundException('Error', new DoubleOfInterface(), $method);
$event->getException()->willReturn($exception);
$nameChecker->isNameValid($method)->willReturn($isNameValid);
$this->afterExample($event);
}
}
interface ExampleInterface {}

View File

@@ -11,22 +11,27 @@ use PhpSpec\CodeGenerator\GeneratorManager;
use PhpSpec\Event\ExampleEvent;
use PhpSpec\Event\SuiteEvent;
use PhpSpec\Exception\Fracture\MethodNotFoundException;
use PhpSpec\Util\NameCheckerInterface;
class MethodNotFoundListenerSpec extends ObjectBehavior
{
function let(IO $io, ResourceManager $resourceManager, GeneratorManager $generatorManager,
SuiteEvent $suiteEvent, ExampleEvent $exampleEvent)
{
function let(
IO $io,
ResourceManager $resourceManager,
GeneratorManager $generatorManager,
SuiteEvent $suiteEvent,
ExampleEvent $exampleEvent,
NameCheckerInterface $nameChecker
) {
$io->writeln(Argument::any())->willReturn();
$io->askConfirmation(Argument::any())->willReturn();
$this->beConstructedWith($io, $resourceManager, $generatorManager);
$this->beConstructedWith($io, $resourceManager, $generatorManager, $nameChecker);
$io->isCodeGenerationEnabled()->willReturn(true);
}
function it_does_not_prompt_for_method_generation_if_no_exception_was_thrown($exampleEvent, $suiteEvent, $io)
{
$io->isCodeGenerationEnabled()->willReturn(true);
$this->afterExample($exampleEvent);
$this->afterSuite($suiteEvent);
@@ -36,7 +41,6 @@ class MethodNotFoundListenerSpec extends ObjectBehavior
function it_does_not_prompt_for_method_generation_if_non_methodnotfoundexception_was_thrown($exampleEvent, $suiteEvent, $io, \InvalidArgumentException $exception)
{
$exampleEvent->getException()->willReturn($exception);
$io->isCodeGenerationEnabled()->willReturn(true);
$this->afterExample($exampleEvent);
$this->afterSuite($suiteEvent);
@@ -44,10 +48,16 @@ class MethodNotFoundListenerSpec extends ObjectBehavior
$io->askConfirmation(Argument::any())->shouldNotBeenCalled();
}
function it_prompts_for_method_generation_if_methodnotfoundexception_was_thrown_and_input_is_interactive($exampleEvent, $suiteEvent, $io, MethodNotFoundException $exception)
{
function it_prompts_for_method_generation_if_methodnotfoundexception_was_thrown_and_input_is_interactive(
$exampleEvent,
$suiteEvent,
$io,
NameCheckerInterface $nameChecker
) {
$exception = new MethodNotFoundException('Error', new \stdClass(), 'bar');
$exampleEvent->getException()->willReturn($exception);
$io->isCodeGenerationEnabled()->willReturn(true);
$nameChecker->isNameValid('bar')->willReturn(true);
$this->afterExample($exampleEvent);
$this->afterSuite($suiteEvent);
@@ -65,4 +75,42 @@ class MethodNotFoundListenerSpec extends ObjectBehavior
$io->askConfirmation(Argument::any())->shouldNotBeenCalled();
}
function it_warns_when_method_name_is_reserved(
$exampleEvent,
$suiteEvent,
IO $io,
NameCheckerInterface $nameChecker
) {
$this->callAfterExample($exampleEvent, $nameChecker, 'throw', false);
$io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled();
$this->afterSuite($suiteEvent);
}
function it_prompts_and_warns_when_one_method_name_is_correct_but_other_reserved(
$exampleEvent,
SuiteEvent $suiteEvent,
IO $io,
NameCheckerInterface $nameChecker
) {
$this->callAfterExample($exampleEvent, $nameChecker, 'throw', false);
$this->callAfterExample($exampleEvent, $nameChecker, 'foo');
$io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled();
$io->askConfirmation('Do you want me to create `stdClass::foo()` for you?')->shouldBeCalled();
$suiteEvent->markAsNotWorthRerunning()->shouldBeCalled();
$this->afterSuite($suiteEvent);
}
private function callAfterExample($exampleEvent, $nameChecker, $method, $isNameValid = true)
{
$exception = new MethodNotFoundException('Error', new \stdClass(), $method);
$exampleEvent->getException()->willReturn($exception);
$nameChecker->isNameValid($method)->willReturn($isNameValid);
$this->afterExample($exampleEvent);
}
}

View File

@@ -326,7 +326,7 @@ class PSR0LocatorSpec extends ObjectBehavior
$fs->getFileContents($filePath)->willReturn('no class definition');
$file->getRealPath()->willReturn($filePath);
$exception = new \RuntimeException('Spec file does not contains any class definition.');
$exception = new \RuntimeException(sprintf('Spec file "%s" does not contains any class definition.', $filePath));
$this->shouldThrow($exception)->duringFindResources($this->srcPath);
}

View File

@@ -99,11 +99,21 @@ class PSR0ResourceSpec extends ObjectBehavior
{
$this->beConstructedWith(array('usr', 'lib', 'config_test'), $locator);
$locator->getFullSrcPath()->willReturn('/local/');
$locator->getFullSpecPath()->willReturn('/local/spec/');
$locator->getFullSrcPath()->willReturn($this->convert_to_path('/local/'));
$locator->getFullSpecPath()->willReturn($this->convert_to_path('/local/spec/'));
$locator->isPSR4()->willReturn(true);
$this->getSrcFilename()->shouldReturn('/local/usr/lib/config_test.php');
$this->getSpecFilename()->shouldReturn('/local/spec/usr/lib/config_testSpec.php');
$this->getSrcFilename()->shouldReturn($this->convert_to_path('/local/usr/lib/config_test.php'));
$this->getSpecFilename()->shouldReturn($this->convert_to_path('/local/spec/usr/lib/config_testSpec.php'));
}
private function convert_to_path($path)
{
if ('/' === DIRECTORY_SEPARATOR) {
return $path;
}
return str_replace('/', DIRECTORY_SEPARATOR, $path);
}
}

View File

@@ -7,6 +7,7 @@ use Prophecy\Argument;
use PhpSpec\Wrapper\Unwrapper;
use PhpSpec\Formatter\Presenter\PresenterInterface;
use PhpSpec\Exception\Example\SkippingException;
use ArrayObject;
@@ -32,8 +33,28 @@ class ThrowMatcherSpec extends ObjectBehavior
$this->positiveMatch('throw', $arr, array('\Exception'))->during('ksort', array());
}
function it_accepts_a_method_during_which_an_error_should_be_thrown(ArrayObject $arr)
{
if (!class_exists('\Error')) {
throw new SkippingException('The class Error, introduced in PHP 7, does not exist');
}
$arr->ksort()->willThrow('\Error');
$this->positiveMatch('throw', $arr, array('\Error'))->during('ksort', array());
}
function it_accepts_a_method_during_which_an_exception_should_not_be_thrown(ArrayObject $arr)
{
$this->negativeMatch('throw', $arr, array('\Exception'))->during('ksort', array());
}
function it_accepts_a_method_during_which_an_error_should_not_be_thrown(ArrayObject $arr)
{
if (!class_exists('\Error')) {
throw new SkippingException('The class Error, introduced in PHP 7, does not exist');
}
$this->negativeMatch('throw', $arr, array('\Error'))->during('ksort', array());
}
}

View File

@@ -15,14 +15,12 @@ class CurrentExampleTrackerSpec extends ObjectBehavior
function it_should_set_a_message()
{
$currentExample = new CurrentExampleTracker();
$currentExample->setCurrentExample('test');
expect($currentExample->getCurrentExample())->toBe('test');
$this->setCurrentExample('test');
$this->getCurrentExample()->shouldBe('test');
}
function it_should_be_null_on_construction()
{
$currentExample = new CurrentExampleTracker();
expect($currentExample->getCurrentExample())->toBe(null);
$this->getCurrentExample()->shouldBe(null);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace spec\PhpSpec\Util;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ReservedWordsMethodNameCheckerSpec extends ObjectBehavior
{
function it_is_restriction_provider()
{
$this->shouldHaveType('PhpSpec\Util\NameCheckerInterface');
}
function it_returns_true_for_not_php_restricted_name()
{
$this->isNameValid('foo')->shouldReturn(true);
}
function it_returns_false_for_php_restricted_name()
{
$this->isNameValid('function')->shouldReturn(false);
}
function it_returns_false_for_php_predefined_constant()
{
$this->isNameValid('__CLASS__')->shouldReturn(false);
}
function it_returns_false_for_php_restricted_name_case_insensitive()
{
$this->isNameValid('instanceof')->shouldReturn(false);
$this->isNameValid('instanceOf')->shouldReturn(false);
}
}

View File

@@ -18,10 +18,12 @@ final class TokenizedNamespaceResolver implements NamespaceResolver
const STATE_DEFAULT = 0;
const STATE_READING_NAMESPACE = 1;
const STATE_READING_USE = 2;
const STATE_READING_USE_GROUP = 3;
private $state = self::STATE_DEFAULT;
private $currentNamespace;
private $currentUseGroup;
private $currentUse;
private $uses = array();
@@ -32,6 +34,7 @@ final class TokenizedNamespaceResolver implements NamespaceResolver
{
$this->state = self::STATE_DEFAULT;
$this->currentUse = null;
$this->currentUseGroup = null;
$this->uses = array();
$tokens = token_get_all($code);
@@ -48,12 +51,29 @@ final class TokenizedNamespaceResolver implements NamespaceResolver
$this->currentNamespace .= $token[1];
}
break;
case self::STATE_READING_USE_GROUP:
if ('}' == $token) {
$this->state = self::STATE_READING_USE;
$this->currentUseGroup = null;
}
elseif (',' == $token) {
$this->storeCurrentUse();
}
elseif (is_array($token)) {
$this->currentUse = $this->currentUseGroup . trim($token[1]);
}
break;
case self::STATE_READING_USE:
if (';' == $token) {
$this->storeCurrentUse();
$this->state = self::STATE_DEFAULT;
}
if (',' == $token) {
if ('{' == $token) {
$this->currentUseGroup = trim($this->currentUse);
$this->state = self::STATE_READING_USE_GROUP;
}
elseif (',' == $token) {
$this->storeCurrentUse();
}
elseif (is_array($token)) {

View File

@@ -74,6 +74,7 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
$this->currentClass = '';
$this->currentFunction = '';
}
/**
* @param array $tokens
* @return array $tokens
@@ -81,6 +82,13 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
private function stripTypeHints($tokens)
{
foreach ($tokens as $index => $token) {
if ($this->isToken($token, '{')) {
$this->currentBodyLevel++;
}
elseif ($this->isToken($token, '}')) {
$this->currentBodyLevel--;
}
switch ($this->state) {
case self::STATE_READING_ARGUMENTS:
if (')' == $token) {
@@ -103,24 +111,21 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
$this->state = self::STATE_READING_FUNCTION_BODY;
$this->currentBodyLevel = 1;
}
elseif ($this->tokenHasType($token, T_STRING) && !$this->currentClass) {
elseif ('}' == $token && $this->currentClass) {
$this->state = self::STATE_DEFAULT;
$this->currentClass = null;
}
elseif ($this->tokenHasType($token, T_STRING) && !$this->currentClass && $this->shouldExtractTokensOfClass($token[1])) {
$this->currentClass = $token[1];
}
elseif($this->tokenHasType($token, T_FUNCTION)) {
elseif ($this->tokenHasType($token, T_FUNCTION) && $this->currentClass) {
$this->state = self::STATE_READING_FUNCTION;
}
break;
case self::STATE_READING_FUNCTION_BODY:
if ('{' == $token) {
$this->currentBodyLevel++;
}
elseif ('}' == $token) {
$this->currentBodyLevel--;
if ($this->currentBodyLevel === 0) {
$this->currentFunction = '';
$this->state = self::STATE_READING_CLASS;
}
if ('}' == $token && $this->currentBodyLevel === 0) {
$this->currentFunction = '';
$this->state = self::STATE_READING_CLASS;
}
break;
@@ -155,7 +160,10 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
$typehint = '';
for ($i = $index - 1; in_array($tokens[$i][0], $this->typehintTokens); $i--) {
$typehint = $tokens[$i][1] . $typehint;
unset($tokens[$i]);
if (T_WHITESPACE !== $tokens[$i][0]) {
unset($tokens[$i]);
}
}
if ($typehint = trim($typehint)) {
@@ -189,4 +197,25 @@ final class TokenizedTypeHintRewriter implements TypeHintRewriter
{
return is_array($token) && $type == $token[0];
}
/**
* @param string $className
*
* @return bool
*/
private function shouldExtractTokensOfClass($className)
{
return substr($className, -4) == 'Spec';
}
/**
* @param array|string $token
* @param string $string
*
* @return bool
*/
private function isToken($token, $string)
{
return $token == $string || (is_array($token) && $token[1] == $string);
}
}

View File

@@ -87,11 +87,12 @@ final class TokenizedCodeWriter implements CodeWriter
$lastLines = array_slice($lines, $line);
$toInsert = trim($toInsert, "\n\r");
if ($leadingNewline) {
$toInsert = PHP_EOL . $toInsert;
$toInsert = "\n" . $toInsert;
}
array_unshift($lastLines, $toInsert);
array_splice($lines, $line, count($lines), $lastLines);
return implode(PHP_EOL, $lines);
return implode("\n", $lines);
}
/**
@@ -105,9 +106,10 @@ final class TokenizedCodeWriter implements CodeWriter
$line--;
$lines = explode("\n", $target);
$lastLines = array_slice($lines, $line);
array_unshift($lastLines, trim($toInsert, "\n\r") . PHP_EOL);
array_unshift($lastLines, trim($toInsert, "\n\r") . "\n");
array_splice($lines, $line, count($lines), $lastLines);
return implode(PHP_EOL, $lines);
return implode("\n", $lines);
}
/**
@@ -120,12 +122,13 @@ final class TokenizedCodeWriter implements CodeWriter
{
$tokens = token_get_all($class);
$searching = false;
$inString = false;
$searchPattern = array();
for ($i = count($tokens) - 1; $i >= 0; $i--) {
$token = $tokens[$i];
if ($token === '}') {
if ($token === '}' && !$inString) {
$searching = true;
continue;
}
@@ -134,6 +137,11 @@ final class TokenizedCodeWriter implements CodeWriter
continue;
}
if ($token === '"') {
$inString = !$inString;
continue;
}
if ($this->isWritePoint($token)) {
$line = $token[2];
return $this->insertStringAfterLine($class, $method, $line, $token[0] === T_COMMENT ?: $prependNewLine);
@@ -144,7 +152,8 @@ final class TokenizedCodeWriter implements CodeWriter
if ($token === '{') {
$search = implode('', $searchPattern);
$position = strpos($class, $search) + strlen($search) - 1;
return substr_replace($class, PHP_EOL . $method . PHP_EOL, $position, 0);
return substr_replace($class, "\n" . $method . "\n", $position, 0);
}
}
}
@@ -155,6 +164,6 @@ final class TokenizedCodeWriter implements CodeWriter
*/
private function isWritePoint($token)
{
return is_array($token) && ($token[1] === PHP_EOL || $token[0] === T_COMMENT);
return is_array($token) && ($token[1] === "\n" || $token[0] === T_COMMENT);
}
}

View File

@@ -20,7 +20,7 @@ use PhpSpec\CodeAnalysis\TokenizedTypeHintRewriter;
use PhpSpec\CodeAnalysis\VisibilityAccessInspector;
use PhpSpec\Console\Assembler\PresenterAssembler;
use PhpSpec\Process\Prerequisites\SuitePrerequisites;
use SebastianBergmann\Exporter\Exporter;
use PhpSpec\Util\ReservedWordsMethodNameChecker;
use PhpSpec\Process\ReRunner;
use PhpSpec\Util\MethodAnalyser;
use Symfony\Component\EventDispatcher\EventDispatcher;
@@ -151,7 +151,8 @@ class ContainerAssembler
return new Listener\CollaboratorMethodNotFoundListener(
$c->get('console.io'),
$c->get('locator.resource_manager'),
$c->get('code_generator')
$c->get('code_generator'),
$c->get('util.reserved_words_checker')
);
});
$container->setShared('event_dispatcher.listeners.named_constructor_not_found', function (ServiceContainer $c) {
@@ -165,7 +166,8 @@ class ContainerAssembler
return new Listener\MethodNotFoundListener(
$c->get('console.io'),
$c->get('locator.resource_manager'),
$c->get('code_generator')
$c->get('code_generator'),
$c->get('util.reserved_words_checker')
);
});
$container->setShared('event_dispatcher.listeners.stop_on_failure', function (ServiceContainer $c) {
@@ -195,6 +197,9 @@ class ContainerAssembler
$container->setShared('util.method_analyser', function () {
return new MethodAnalyser();
});
$container->setShared('util.reserved_words_checker', function () {
return new ReservedWordsMethodNameChecker();
});
$container->setShared('event_dispatcher.listeners.bootstrap', function (ServiceContainer $c) {
return new Listener\BootstrapListener(
$c->get('console.io')

View File

@@ -342,4 +342,26 @@ class IO implements IOInterface
}
return $width;
}
/**
* @param string $message
* @param int $indent
*/
public function writeBrokenCodeBlock($message, $indent = 0)
{
$message = wordwrap($message, $this->getBlockWidth() - ($indent * 2), "\n", true);
if ($indent) {
$message = $this->indentText($message, $indent);
}
$this->output->writeln("<broken-bg>".str_repeat(" ", $this->getBlockWidth())."</broken-bg>");
foreach (explode("\n", $message) as $line) {
$this->output->writeln("<broken-bg>".str_pad($line, $this->getBlockWidth(), ' ')."</broken-bg>");
}
$this->output->writeln("<broken-bg>".str_repeat(" ", $this->getBlockWidth())."</broken-bg>");
$this->output->writeln('');
}
}

View File

@@ -89,4 +89,9 @@ class SuiteEvent extends Event implements EventInterface
{
$this->worthRerunning = true;
}
public function markAsNotWorthRerunning()
{
$this->worthRerunning = false;
}
}

View File

@@ -61,13 +61,21 @@ class DotFormatter extends ConsoleFormatter
break;
}
if ($eventsCount % 50 === 0) {
$remainder = $eventsCount % 50;
$lastRow = $eventsCount === $this->examplesCount;
if ($remainder === 0 || $lastRow) {
$length = strlen((string) $this->examplesCount);
$format = sprintf(' %%%dd / %%%dd', $length, $length);
if ($lastRow) {
$io->write(str_repeat(' ', 50 - $remainder));
}
$io->write(sprintf($format, $eventsCount, $this->examplesCount));
if ($eventsCount !== $this->examplesCount) {
$io->writeLn();
$io->writeln();
}
}
}

View File

@@ -22,8 +22,8 @@ class StringEngine implements EngineInterface
public function compare($expected, $actual)
{
$expected = explode(PHP_EOL, (string) $expected);
$actual = explode(PHP_EOL, (string) $actual);
$expected = explode("\n", (string) $expected);
$actual = explode("\n", (string) $actual);
$diff = new \Diff($expected, $actual, array());
@@ -41,6 +41,6 @@ class StringEngine implements EngineInterface
}
}
return sprintf("<code>%s%s</code>", PHP_EOL, implode(PHP_EOL, $lines));
return sprintf("<code>%s%s</code>", "\n", implode("\n", $lines));
}
}

View File

@@ -19,7 +19,7 @@ class BootstrapListener implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array('beforeSuite' => 'beforeSuite');
return array('beforeSuite' => array('beforeSuite', 1100));
}
public function beforeSuite()

View File

@@ -19,6 +19,8 @@ use PhpSpec\Event\ExampleEvent;
use PhpSpec\Event\SuiteEvent;
use PhpSpec\Exception\Locator\ResourceCreationException;
use PhpSpec\Locator\ResourceManagerInterface;
use PhpSpec\Util\NameCheckerInterface;
use PhpSpec\Util\ReservedWordsMethodNameChecker;
use Prophecy\Argument\ArgumentsWildcard;
use Prophecy\Exception\Doubler\MethodNotFoundException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -47,16 +49,32 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
*/
private $generator;
/**
* @var NameCheckerInterface
*/
private $nameChecker;
/**
* @var array
*/
private $wrongMethodNames = array();
/**
* @param IO $io
* @param ResourceManagerInterface $resources
* @param GeneratorManager $generator
* @param NameCheckerInterface $nameChecker
*/
public function __construct(IO $io, ResourceManagerInterface $resources, GeneratorManager $generator)
{
public function __construct(
IO $io,
ResourceManagerInterface $resources,
GeneratorManager $generator,
NameCheckerInterface $nameChecker = null
) {
$this->io = $io;
$this->resources = $resources;
$this->generator = $generator;
$this->nameChecker = $nameChecker ?: new ReservedWordsMethodNameChecker();
}
/**
@@ -87,7 +105,9 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
$this->interfaces[$interface] = array();
}
$this->interfaces[$interface][$exception->getMethodName()] = $exception->getArguments();
$methodName = $exception->getMethodName();
$this->interfaces[$interface][$methodName] = $exception->getArguments();
$this->checkIfMethodNameAllowed($methodName);
}
/**
@@ -119,7 +139,6 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
public function afterSuite(SuiteEvent $event)
{
foreach ($this->interfaces as $interface => $methods) {
try {
$resource = $this->resources->createResource($interface);
} catch (ResourceCreationException $e) {
@@ -127,6 +146,10 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
}
foreach ($methods as $method => $arguments) {
if (in_array($method, $this->wrongMethodNames)) {
continue;
}
if ($this->io->askConfirmation(sprintf(self::PROMPT, $interface, $method))) {
$this->generator->generate(
$resource,
@@ -140,6 +163,11 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
}
}
}
if ($this->wrongMethodNames) {
$this->writeErrorMessage();
$event->markAsNotWorthRerunning();
}
}
/**
@@ -167,4 +195,19 @@ class CollaboratorMethodNotFoundListener implements EventSubscriberInterface
return $exception;
}
}
private function checkIfMethodNameAllowed($methodName)
{
if (!$this->nameChecker->isNameValid($methodName)) {
$this->wrongMethodNames[] = $methodName;
}
}
private function writeErrorMessage()
{
foreach ($this->wrongMethodNames as $methodName) {
$message = sprintf("I cannot generate the method '%s' for you because it is a reserved keyword", $methodName);
$this->io->writeBrokenCodeBlock($message, 2);
}
}
}

View File

@@ -13,6 +13,8 @@
namespace PhpSpec\Listener;
use PhpSpec\Util\ReservedWordsMethodNameChecker;
use PhpSpec\Util\NameCheckerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use PhpSpec\Console\IO;
use PhpSpec\Locator\ResourceManagerInterface;
@@ -27,12 +29,28 @@ class MethodNotFoundListener implements EventSubscriberInterface
private $resources;
private $generator;
private $methods = array();
private $wrongMethodNames = array();
/**
* @var NameCheckerInterface
*/
private $nameChecker;
public function __construct(IO $io, ResourceManagerInterface $resources, GeneratorManager $generator)
{
/**
* @param IO $io
* @param ResourceManagerInterface $resources
* @param GeneratorManager $generator
* @param NameCheckerInterface $nameChecker
*/
public function __construct(
IO $io,
ResourceManagerInterface $resources,
GeneratorManager $generator,
NameCheckerInterface $nameChecker = null
) {
$this->io = $io;
$this->resources = $resources;
$this->generator = $generator;
$this->nameChecker = $nameChecker ?: new ReservedWordsMethodNameChecker();
}
public static function getSubscribedEvents()
@@ -54,7 +72,9 @@ class MethodNotFoundListener implements EventSubscriberInterface
}
$classname = get_class($exception->getSubject());
$this->methods[$classname .'::'.$exception->getMethodName()] = $exception->getArguments();
$methodName = $exception->getMethodName();
$this->methods[$classname .'::'.$methodName] = $exception->getArguments();
$this->checkIfMethodNameAllowed($methodName);
}
public function afterSuite(SuiteEvent $event)
@@ -65,6 +85,11 @@ class MethodNotFoundListener implements EventSubscriberInterface
foreach ($this->methods as $call => $arguments) {
list($classname, $method) = explode('::', $call);
if (in_array($method, $this->wrongMethodNames)) {
continue;
}
$message = sprintf('Do you want me to create `%s()` for you?', $call);
try {
@@ -81,5 +106,25 @@ class MethodNotFoundListener implements EventSubscriberInterface
$event->markAsWorthRerunning();
}
}
if ($this->wrongMethodNames) {
$this->writeWrongMethodNameMessage();
$event->markAsNotWorthRerunning();
}
}
private function checkIfMethodNameAllowed($methodName)
{
if (!$this->nameChecker->isNameValid($methodName)) {
$this->wrongMethodNames[] = $methodName;
}
}
private function writeWrongMethodNameMessage()
{
foreach ($this->wrongMethodNames as $methodName) {
$message = sprintf("I cannot generate the method '%s' for you because it is a reserved keyword", $methodName);
$this->io->writeBrokenCodeBlock($message, 2);
}
}
}

View File

@@ -282,7 +282,7 @@ class PSR0Locator implements ResourceLocatorInterface
/**
* @param $path
*
*
* @return null|string
*/
private function findSpecClassname($path)
@@ -327,7 +327,7 @@ class PSR0Locator implements ResourceLocatorInterface
$classname = $this->findSpecClassname($path);
if (null === $classname) {
throw new \RuntimeException('Spec file does not contains any class definition.');
throw new \RuntimeException(sprintf('Spec file "%s" does not contains any class definition.', $path));
}
// Remove spec namespace from the begining of the classname.

View File

@@ -28,10 +28,12 @@ class ThrowMatcher implements MatcherInterface
* @var array
*/
private static $ignoredProperties = array('file', 'line', 'string', 'trace', 'previous');
/**
* @var Unwrapper
*/
private $unwrapper;
/**
* @var PresenterInterface
*/
@@ -43,9 +45,9 @@ class ThrowMatcher implements MatcherInterface
private $factory;
/**
* @param Unwrapper $unwrapper
* @param PresenterInterface $presenter
* @param ReflectionFactory $factory
* @param Unwrapper $unwrapper
* @param PresenterInterface $presenter
* @param ReflectionFactory|null $factory
*/
public function __construct(Unwrapper $unwrapper, PresenterInterface $presenter, ReflectionFactory $factory = null)
{
@@ -91,114 +93,134 @@ class ThrowMatcher implements MatcherInterface
}
/**
* @param callable $callable
* @param array $arguments
* @param null $exception
* @param callable $callable
* @param array $arguments
* @param null|object|string $exception
*
* @throws \PhpSpec\Exception\Example\FailureException
* @throws \PhpSpec\Exception\Example\NotEqualException
*/
public function verifyPositive($callable, array $arguments, $exception = null)
{
$exceptionThrown = null;
try {
call_user_func_array($callable, $arguments);
} catch (\Exception $e) {
if (null === $exception) {
return;
}
$exceptionThrown = $e;
} catch (\Throwable $e) {
$exceptionThrown = $e;
}
if (!$e instanceof $exception) {
throw new FailureException(sprintf(
if (!$exceptionThrown) {
throw new FailureException('Expected to get exception / throwable, none got.');
}
if (null === $exception) {
return;
}
if (!$exceptionThrown instanceof $exception) {
throw new FailureException(
sprintf(
'Expected exception of class %s, but got %s.',
$this->presenter->presentValue($exception),
$this->presenter->presentValue($e)
));
}
$this->presenter->presentValue($exceptionThrown)
)
);
}
if (is_object($exception)) {
$exceptionRefl = $this->factory->create($exception);
foreach ($exceptionRefl->getProperties() as $property) {
if (in_array($property->getName(), self::$ignoredProperties)) {
continue;
}
if (is_object($exception)) {
$exceptionRefl = $this->factory->create($exception);
foreach ($exceptionRefl->getProperties() as $property) {
if (in_array($property->getName(), self::$ignoredProperties, true)) {
continue;
}
$property->setAccessible(true);
$expected = $property->getValue($exception);
$actual = $property->getValue($e);
$property->setAccessible(true);
$expected = $property->getValue($exception);
$actual = $property->getValue($exceptionThrown);
if (null !== $expected && $actual !== $expected) {
throw new NotEqualException(sprintf(
if (null !== $expected && $actual !== $expected) {
throw new NotEqualException(
sprintf(
'Expected exception `%s` to be %s, but it is %s.',
$property->getName(),
$this->presenter->presentValue($expected),
$this->presenter->presentValue($actual)
), $expected, $actual);
}
), $expected, $actual
);
}
}
return;
}
throw new FailureException('Expected to get exception, none got.');
}
/**
* @param callable $callable
* @param array $arguments
* @param string|null $exception
* @param callable $callable
* @param array $arguments
* @param string|null|object $exception
*
* @throws \PhpSpec\Exception\Example\FailureException
*/
public function verifyNegative($callable, array $arguments, $exception = null)
{
$exceptionThrown = null;
try {
call_user_func_array($callable, $arguments);
} catch (\Exception $e) {
if (null === $exception) {
throw new FailureException(sprintf(
$exceptionThrown = $e;
} catch (\Throwable $e) {
$exceptionThrown = $e;
}
if ($exceptionThrown && null === $exception) {
throw new FailureException(
sprintf(
'Expected to not throw any exceptions, but got %s.',
$this->presenter->presentValue($e)
));
}
$this->presenter->presentValue($exceptionThrown)
)
);
}
if ($e instanceof $exception) {
$invalidProperties = array();
if (is_object($exception)) {
$exceptionRefl = $this->factory->create($exception);
foreach ($exceptionRefl->getProperties() as $property) {
if (in_array($property->getName(), self::$ignoredProperties)) {
continue;
}
if ($exceptionThrown && $exceptionThrown instanceof $exception) {
$invalidProperties = array();
if (is_object($exception)) {
$exceptionRefl = $this->factory->create($exception);
foreach ($exceptionRefl->getProperties() as $property) {
if (in_array($property->getName(), self::$ignoredProperties, true)) {
continue;
}
$property->setAccessible(true);
$expected = $property->getValue($exception);
$actual = $property->getValue($e);
$property->setAccessible(true);
$expected = $property->getValue($exception);
$actual = $property->getValue($exceptionThrown);
if (null !== $expected && $actual === $expected) {
$invalidProperties[] = sprintf(
' `%s`=%s',
$property->getName(),
$this->presenter->presentValue($expected)
);
}
if (null !== $expected && $actual === $expected) {
$invalidProperties[] = sprintf(
' `%s`=%s',
$property->getName(),
$this->presenter->presentValue($expected)
);
}
}
}
$withProperties = '';
if (count($invalidProperties) > 0) {
$withProperties = sprintf(
' with'.PHP_EOL.'%s,'.PHP_EOL,
implode(",\n", $invalidProperties)
);
}
$withProperties = '';
if (count($invalidProperties) > 0) {
$withProperties = sprintf(
' with'.PHP_EOL.'%s,'.PHP_EOL,
implode(",\n", $invalidProperties)
);
}
throw new FailureException(sprintf(
throw new FailureException(
sprintf(
'Expected to not throw %s exception%s but got it.',
$this->presenter->presentValue($exception),
$withProperties
));
}
)
);
}
}
@@ -226,7 +248,7 @@ class ThrowMatcher implements MatcherInterface
function ($method, $arguments) use ($check, $subject, $exception, $unwrapper) {
$arguments = $unwrapper->unwrapAll($arguments);
$methodName = $arguments[0];
$methodName = $arguments[0];
$arguments = isset($arguments[1]) ? $arguments[1] : array();
$callable = array($subject, $methodName);
@@ -253,7 +275,7 @@ class ThrowMatcher implements MatcherInterface
*/
private function getException(array $arguments)
{
if (0 == count($arguments)) {
if (0 === count($arguments)) {
return null;
}
@@ -261,15 +283,21 @@ class ThrowMatcher implements MatcherInterface
return $arguments[0];
}
if (is_object($arguments[0]) && $arguments[0] instanceof \Exception) {
return $arguments[0];
if (is_object($arguments[0])) {
if (class_exists('\Throwable') && $arguments[0] instanceof \Throwable) {
return $arguments[0];
} elseif ($arguments[0] instanceof \Exception) {
return $arguments[0];
}
}
throw new MatcherException(sprintf(
"Wrong argument provided in throw matcher.\n".
"Fully qualified classname or exception instance expected,\n".
"Got %s.",
$this->presenter->presentValue($arguments[0])
));
throw new MatcherException(
sprintf(
"Wrong argument provided in throw matcher.\n".
"Fully qualified classname or exception instance expected,\n".
"Got %s.",
$this->presenter->presentValue($arguments[0])
)
);
}
}

View File

@@ -32,6 +32,7 @@ use ArrayAccess;
* @method void beConstructedThrough($factoryMethod, array $constructorArguments = array())
* @method void beAnInstanceOf($class)
* @method void shouldHaveType($type)
* @method void shouldImplement($interface)
* @method Subject\Expectation\DuringCall shouldThrow($exception = null)
*/
class ObjectBehavior implements

View File

@@ -198,7 +198,7 @@ final class ClassFileAnalyser
for ($i = $index, $max = count($tokens); $i < $max; $i++) {
$token = $tokens[$i];
if ('{' === $token) {
if ('{' === $token || $this->isSpecialBraceToken($token)) {
$braceCount++;
continue;
}
@@ -212,6 +212,15 @@ final class ClassFileAnalyser
}
}
private function isSpecialBraceToken($token)
{
if (!is_array($token)) {
return false;
}
return $token[1] === "{";
}
/**
* @param mixed $token
* @return bool

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of PhpSpec, A php toolset to drive emergent
* design by specification.
*
* (c) Marcello Duarte <marcello.duarte@gmail.com>
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpSpec\Util;
interface NameCheckerInterface
{
/**
* @param string $name
*
* @return bool
*/
public function isNameValid($name);
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of PhpSpec, A php toolset to drive emergent
* design by specification.
*
* (c) Marcello Duarte <marcello.duarte@gmail.com>
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpSpec\Util;
final class ReservedWordsMethodNameChecker implements NameCheckerInterface
{
private $reservedWords = array(
'__halt_compiler',
'abstract',
'and',
'array',
'as',
'break',
'callable',
'case',
'catch',
'class',
'clone',
'const',
'continue',
'declare',
'default',
'die',
'do',
'echo',
'else',
'elseif',
'empty',
'enddeclare',
'endfor',
'endforeach',
'endif',
'endswitch',
'endwhile',
'eval',
'exit',
'extends',
'final',
'for',
'foreach',
'function',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'isset',
'list',
'namespace',
'new',
'or',
'print',
'private',
'protected',
'public',
'require',
'require_once',
'return',
'static',
'switch',
'throw',
'trait',
'try',
'unset',
'use',
'var',
'while',
'xor',
'__class__',
'__dir__',
'__file__',
'__function__',
'__line__',
'__method__',
'__namespace__',
'__trait__',
);
/**
* {@inheritdoc}
*/
public function isNameValid($name)
{
return !in_array(strtolower($name), $this->reservedWords);
}
}

View File

@@ -1,4 +1,4 @@
*.tgz
*.phar
bin
vendor
/composer.lock
/vendor

View File

@@ -1,6 +1,6 @@
language: php
php: [5.3, 5.4, 5.5, 5.6, 7, hhvm]
php: [5.3, 5.4, 5.5, 5.6, 7.0, hhvm]
sudo: false
@@ -14,6 +14,16 @@ branches:
- /^feature\/.*$/
- /^optimization\/.*$/
before_script: travis_retry composer install --no-interaction
matrix:
fast_finish: true
include:
- php: '7.0'
env: PHPDOCUMENTOR_REFLECTION_DOCBLOCK="^2.0"
before_script:
- if [ -n "$PHPDOCUMENTOR_REFLECTION_DOCBLOCK" ]; then
composer require "phpdocumentor/reflection-docblock:${PHPDOCUMENTOR_REFLECTION_DOCBLOCK}" --no-update;
fi;
- travis_retry composer update --no-interaction
script: vendor/bin/phpspec run -fpretty -v

View File

@@ -1,3 +1,10 @@
1.6.1 / 2016-06-07
==================
* Ignored empty method names in invalid `@method` phpdoc
* Fixed the mocking of SplFileObject
* Added compatibility with phpdocumentor/reflection-docblock 3
1.6.0 / 2016-02-15
==================

View File

@@ -16,16 +16,17 @@
"email": "marcello.duarte@gmail.com"
}
],
"require": {
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "~2.0",
"sebastian/comparator": "~1.1",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
"sebastian/comparator": "^1.1",
"doctrine/instantiator": "^1.0.2",
"sebastian/recursion-context": "~1.0"
"sebastian/recursion-context": "^1.0"
},
"require-dev": {
"phpspec/phpspec": "~2.0"
"phpspec/phpspec": "^2.0"
},
"autoload": {
@@ -36,7 +37,7 @@
"extra": {
"branch-alias": {
"dev-master": "1.5.x-dev"
"dev-master": "1.6.x-dev"
}
}
}

720
vendor/phpspec/prophecy/composer.lock generated vendored
View File

@@ -1,720 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "0cb11a8d8108277277f9931f6e4fa947",
"content-hash": "d2c6dc828e1217dfa154bc2dbb431edb",
"packages": [
{
"name": "doctrine/instantiator",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
"shasum": ""
},
"require": {
"php": ">=5.3,<8.0-DEV"
},
"require-dev": {
"athletic/athletic": "~0.1.8",
"ext-pdo": "*",
"ext-phar": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "http://ocramius.github.com/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://github.com/doctrine/instantiator",
"keywords": [
"constructor",
"instantiate"
],
"time": "2015-06-14 21:17:01"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
"reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"suggest": {
"dflydev/markdown": "~1.0",
"erusev/parsedown": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-0": {
"phpDocumentor": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "mike.vanriel@naenius.com"
}
],
"time": "2015-02-03 12:10:50"
},
{
"name": "sebastian/comparator",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
"reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/diff": "~1.2",
"sebastian/exporter": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "http://www.github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"time": "2015-07-26 15:48:44"
},
{
"name": "sebastian/diff",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "863df9687835c62aa423a22412d26fa2ebde3fd3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3",
"reference": "863df9687835c62aa423a22412d26fa2ebde3fd3",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Diff implementation",
"homepage": "http://www.github.com/sebastianbergmann/diff",
"keywords": [
"diff"
],
"time": "2015-02-22 15:13:53"
},
{
"name": "sebastian/exporter",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "7ae5513327cb536431847bcc0c10edba2701064e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
"reference": "7ae5513327cb536431847bcc0c10edba2701064e",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"time": "2015-06-21 07:55:53"
},
{
"name": "sebastian/recursion-context",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "994d4a811bafe801fb06dccbee797863ba2792ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba",
"reference": "994d4a811bafe801fb06dccbee797863ba2792ba",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2015-06-21 08:04:50"
}
],
"packages-dev": [
{
"name": "phpspec/php-diff",
"version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/phpspec/php-diff.git",
"reference": "30e103d19519fe678ae64a60d77884ef3d71b28a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/php-diff/zipball/30e103d19519fe678ae64a60d77884ef3d71b28a",
"reference": "30e103d19519fe678ae64a60d77884ef3d71b28a",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-0": {
"Diff": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Chris Boulton",
"homepage": "http://github.com/chrisboulton",
"role": "Original developer"
}
],
"description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).",
"time": "2013-11-01 13:02:21"
},
{
"name": "phpspec/phpspec",
"version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/phpspec.git",
"reference": "36635a903bdeb54899d7407bc95610501fd98559"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/phpspec/zipball/36635a903bdeb54899d7407bc95610501fd98559",
"reference": "36635a903bdeb54899d7407bc95610501fd98559",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.1",
"php": ">=5.3.3",
"phpspec/php-diff": "~1.0.0",
"phpspec/prophecy": "~1.4",
"sebastian/exporter": "~1.0",
"symfony/console": "~2.3",
"symfony/event-dispatcher": "~2.1",
"symfony/finder": "~2.1",
"symfony/process": "^2.6",
"symfony/yaml": "~2.1"
},
"require-dev": {
"behat/behat": "^3.0.11",
"bossa/phpspec2-expect": "~1.0",
"phpunit/phpunit": "~4.4",
"symfony/filesystem": "~2.1"
},
"suggest": {
"phpspec/nyan-formatters": "~1.0 Adds Nyan formatters"
},
"bin": [
"bin/phpspec"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
}
},
"autoload": {
"psr-0": {
"PhpSpec": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
},
{
"name": "Marcello Duarte",
"homepage": "http://marcelloduarte.net/"
}
],
"description": "Specification-oriented BDD framework for PHP 5.3+",
"homepage": "http://phpspec.net/",
"keywords": [
"BDD",
"SpecBDD",
"TDD",
"spec",
"specification",
"testing",
"tests"
],
"time": "2015-09-07 07:07:37"
},
{
"name": "symfony/console",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "5efd632294c8320ea52492db22292ff853a43766"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5efd632294c8320ea52492db22292ff853a43766",
"reference": "5efd632294c8320ea52492db22292ff853a43766",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1",
"symfony/process": "~2.1"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-10-20 14:38:46"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87a5db5ea887763fa3a31a5471b512ff1596d9b8",
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.0,>=2.0.5",
"symfony/dependency-injection": "~2.6",
"symfony/expression-language": "~2.6",
"symfony/stopwatch": "~2.3"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/finder",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d",
"reference": "2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/process",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "4a959dd4e19c2c5d7512689413921e0a74386ec7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/4a959dd4e19c2c5d7512689413921e0a74386ec7",
"reference": "4a959dd4e19c2c5d7512689413921e0a74386ec7",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2015-10-23 14:47:27"
},
{
"name": "symfony/yaml",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@@ -46,6 +46,18 @@ class MagicCallPatchSpec extends ObjectBehavior
$this->apply($node);
}
/**
* @param \Prophecy\Doubler\Generator\Node\ClassNode $node
*/
function it_ignores_empty_methods_from_phpdoc($node)
{
$node->getParentClass()->willReturn('spec\Prophecy\Doubler\ClassPatch\MagicalApiInvalidMethodDefinition');
$node->addMethod(new MethodNode(''))->shouldNotBeCalled();
$this->apply($node);
}
/**
* @param \Prophecy\Doubler\Generator\Node\ClassNode $node
*/
@@ -79,6 +91,15 @@ class MagicalApi
}
}
/**
* @method void invalidMethodDefinition
* @method void
* @method
*/
class MagicalApiInvalidMethodDefinition
{
}
/**
* @method void undefinedMethod()
* @method void definedMethod()

View File

@@ -88,4 +88,20 @@ class SplFileInfoPatchSpec extends ObjectBehavior
$this->apply($node);
}
/**
* @param \Prophecy\Doubler\Generator\Node\ClassNode $node
* @param \Prophecy\Doubler\Generator\Node\MethodNode $method
*/
function it_should_supply_a_file_for_a_spl_file_object($node, $method)
{
$node->hasMethod('__construct')->willReturn(true);
$node->getMethod('__construct')->willReturn($method);
$node->getParentClass()->willReturn('SplFileObject');
$method->setCode(Argument::that(function($value) {
return strpos($value, '.php') !== false;
}))->shouldBeCalled();
$this->apply($node);
}
}

View File

@@ -11,17 +11,27 @@
namespace Prophecy\Doubler\ClassPatch;
use phpDocumentor\Reflection\DocBlock;
use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\PhpDocumentor\ClassAndInterfaceTagRetriever;
use Prophecy\PhpDocumentor\MethodTagRetrieverInterface;
/**
* Discover Magical API using "@method" PHPDoc format.
*
* @author Thomas Tourlourat <thomas@tourlourat.com>
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Théo FIDRY <theo.fidry@gmail.com>
*/
class MagicCallPatch implements ClassPatchInterface
{
private $tagRetriever;
public function __construct(MethodTagRetrieverInterface $tagRetriever = null)
{
$this->tagRetriever = null === $tagRetriever ? new ClassAndInterfaceTagRetriever() : $tagRetriever;
}
/**
* Support any class
*
@@ -44,21 +54,17 @@ class MagicCallPatch implements ClassPatchInterface
$parentClass = $node->getParentClass();
$reflectionClass = new \ReflectionClass($parentClass);
$phpdoc = new DocBlock($reflectionClass->getDocComment());
$tagList = $phpdoc->getTagsByName('method');
$interfaces = $reflectionClass->getInterfaces();
foreach($interfaces as $interface) {
$phpdoc = new DocBlock($interface);
$tagList = array_merge($tagList, $phpdoc->getTagsByName('method'));
}
$tagList = $this->tagRetriever->getTagList($reflectionClass);
foreach($tagList as $tag) {
$methodName = $tag->getMethodName();
if (empty($methodName)) {
continue;
}
if (!$reflectionClass->hasMethod($methodName)) {
$methodNode = new MethodNode($tag->getMethodName());
$methodNode = new MethodNode($methodName);
$methodNode->setStatic($tag->isStatic());
$node->addMethod($methodNode);

View File

@@ -56,6 +56,13 @@ class SplFileInfoPatch implements ClassPatchInterface
if ($this->nodeIsDirectoryIterator($node)) {
$constructor->setCode('return parent::__construct("' . __DIR__ . '");');
return;
}
if ($this->nodeIsSplFileObject($node)) {
$constructor->setCode('return parent::__construct("' . __FILE__ .'");');
return;
}
@@ -79,7 +86,20 @@ class SplFileInfoPatch implements ClassPatchInterface
private function nodeIsDirectoryIterator(ClassNode $node)
{
$parent = $node->getParentClass();
return 'DirectoryIterator' === $parent
|| is_subclass_of($parent, 'DirectoryIterator');
}
/**
* @param ClassNode $node
* @return boolean
*/
private function nodeIsSplFileObject(ClassNode $node)
{
$parent = $node->getParentClass();
return 'SplFileObject' === $parent
|| is_subclass_of($parent, 'SplFileObject');
}
}

View File

@@ -1,10 +1,4 @@
<?php
/**
* Created by PhpStorm.
* User: jeff
* Date: 25/08/2015
* Time: 19:14
*/
namespace Prophecy\Exception\Doubler;

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Prophecy.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
* Marcello Duarte <marcello.duarte@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Prophecy\PhpDocumentor;
use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag;
use phpDocumentor\Reflection\DocBlock\Tags\Method;
/**
* @author Théo FIDRY <theo.fidry@gmail.com>
*
* @internal
*/
final class ClassAndInterfaceTagRetriever implements MethodTagRetrieverInterface
{
private $classRetriever;
public function __construct(MethodTagRetrieverInterface $classRetriever = null)
{
if (null !== $classRetriever) {
$this->classRetriever = $classRetriever;
return;
}
$this->classRetriever = class_exists('phpDocumentor\Reflection\DocBlockFactory') && class_exists('phpDocumentor\Reflection\Types\ContextFactory')
? new ClassTagRetriever()
: new LegacyClassTagRetriever()
;
}
/**
* @param \ReflectionClass $reflectionClass
*
* @return LegacyMethodTag[]|Method[]
*/
public function getTagList(\ReflectionClass $reflectionClass)
{
return array_merge(
$this->classRetriever->getTagList($reflectionClass),
$this->getInterfacesTagList($reflectionClass)
);
}
/**
* @param \ReflectionClass $reflectionClass
*
* @return LegacyMethodTag[]|Method[]
*/
private function getInterfacesTagList(\ReflectionClass $reflectionClass)
{
$interfaces = $reflectionClass->getInterfaces();
$tagList = array();
foreach($interfaces as $interface) {
$tagList = array_merge($tagList, $this->classRetriever->getTagList($interface));
}
return $tagList;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Prophecy.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
* Marcello Duarte <marcello.duarte@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Prophecy\PhpDocumentor;
use phpDocumentor\Reflection\DocBlock\Tags\Method;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Types\ContextFactory;
/**
* @author Théo FIDRY <theo.fidry@gmail.com>
*
* @internal
*/
final class ClassTagRetriever implements MethodTagRetrieverInterface
{
private $docBlockFactory;
private $contextFactory;
public function __construct()
{
$this->docBlockFactory = DocBlockFactory::createInstance();
$this->contextFactory = new ContextFactory();
}
/**
* @param \ReflectionClass $reflectionClass
*
* @return Method[]
*/
public function getTagList(\ReflectionClass $reflectionClass)
{
try {
$phpdoc = $this->docBlockFactory->create(
$reflectionClass,
$this->contextFactory->createFromReflector($reflectionClass)
);
return $phpdoc->getTagsByName('method');
} catch (\InvalidArgumentException $e) {
return array();
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Prophecy.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
* Marcello Duarte <marcello.duarte@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Prophecy\PhpDocumentor;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag;
/**
* @author Théo FIDRY <theo.fidry@gmail.com>
*
* @internal
*/
final class LegacyClassTagRetriever implements MethodTagRetrieverInterface
{
/**
* @param \ReflectionClass $reflectionClass
*
* @return LegacyMethodTag[]
*/
public function getTagList(\ReflectionClass $reflectionClass)
{
$phpdoc = new DocBlock($reflectionClass->getDocComment());
return $phpdoc->getTagsByName('method');
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Prophecy.
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
* Marcello Duarte <marcello.duarte@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Prophecy\PhpDocumentor;
use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag;
use phpDocumentor\Reflection\DocBlock\Tags\Method;
/**
* @author Théo FIDRY <theo.fidry@gmail.com>
*
* @internal
*/
interface MethodTagRetrieverInterface
{
/**
* @param \ReflectionClass $reflectionClass
*
* @return LegacyMethodTag[]|Method[]
*/
public function getTagList(\ReflectionClass $reflectionClass);
}

View File

@@ -86,6 +86,7 @@ class MethodProphecy
case 'Traversable':
case 'Generator':
// Remove eval() when minimum version >=5.5
/** @var callable $generator */
$generator = eval('return function () { yield; };');
return $generator();