From eda731bb84dadbe478b2d8ef2c59a5ef1c2f1008 Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Wed, 12 Sep 2018 22:32:01 -0400 Subject: [PATCH 01/25] feat: Add additional info option --- doc/parameters.md | 23 ++++++++++++++++++++ resources/config/parameters.yml | 1 + spec/Configuration/GrumPHPSpec.php | 8 +++++++ spec/Console/Helper/TaskRunnerHelperSpec.php | 1 + src/Configuration/GrumPHP.php | 8 +++++++ src/Console/Helper/TaskRunnerHelper.php | 11 ++++++++++ 6 files changed, 52 insertions(+) diff --git a/doc/parameters.md b/doc/parameters.md index ed7968292..e2daea85f 100644 --- a/doc/parameters.md +++ b/doc/parameters.md @@ -13,6 +13,7 @@ parameters: process_async_limit: 10 process_async_wait: 1000 process_timeout: 60 + additonal_info: ~ ascii: failed: resource/grumphp-grumpy.txt succeeded: resource/grumphp-happy.txt @@ -108,6 +109,28 @@ If you've got tools that run more then 60 seconds, you can increase this paramet It is also possible to disable the timeout by setting the value to `null`. When receiving a `Symfony\Component\Process\Exception\ProcessTimedOutException` during the execution of GrumPHP, you probably need to increment this setting. +**additional_info** + +*Default: null* + +This parameter will display additional information at the end of a `success` *or* `error` task. + +```yaml +# grumphp.yml +parameters: + additional_info: "\nTo get full documentation for the project!\nVisit https://docs.example.com\n" +``` + +*Example Result:* +``` +GrumPHP is sniffing your code! +Running task 1/1: Phpcs... ✔ + +To get full documentation for the project! +Visit https://docs.example.com + +``` + **ascii** *Default: {failed: grumphp-grumpy.txt, succeeded: grumphp-happy.txt}* diff --git a/resources/config/parameters.yml b/resources/config/parameters.yml index 4fc8fe315..f62204fa0 100644 --- a/resources/config/parameters.yml +++ b/resources/config/parameters.yml @@ -3,6 +3,7 @@ parameters: git_dir: . hooks_dir: ~ hooks_preset: local + additional_info: ~ tasks: [] testsuites: [] stop_on_failure: false diff --git a/spec/Configuration/GrumPHPSpec.php b/spec/Configuration/GrumPHPSpec.php index cf16f3a4e..568f766e5 100644 --- a/spec/Configuration/GrumPHPSpec.php +++ b/spec/Configuration/GrumPHPSpec.php @@ -136,4 +136,12 @@ function it_should_know_all_testsuites(ContainerInterface $container) $container->getParameter('grumphp.testsuites')->willReturn($testSuites = new TestSuiteCollection()); $this->getTestSuites()->shouldBe($testSuites); } + + function it_knows_the_additional_info(ContainerInterface $container) + { + $container->getParameter('additional_info') + ->willReturn('https://docs.example.com'); + + $this->getAdditionalInfo()->shouldReturn('https://docs.example.com'); + } } diff --git a/spec/Console/Helper/TaskRunnerHelperSpec.php b/spec/Console/Helper/TaskRunnerHelperSpec.php index 7fb743322..04bd15a9c 100644 --- a/spec/Console/Helper/TaskRunnerHelperSpec.php +++ b/spec/Console/Helper/TaskRunnerHelperSpec.php @@ -39,6 +39,7 @@ function let( $runnerContext->hasTestSuite()->willReturn(false); $runnerContext->skipSuccessOutput()->willReturn(false); + $config->getAdditionalInfo()->willReturn(null); $config->hideCircumventionTip()->willReturn(false); } diff --git a/src/Configuration/GrumPHP.php b/src/Configuration/GrumPHP.php index 203aa1b01..5d4026c94 100644 --- a/src/Configuration/GrumPHP.php +++ b/src/Configuration/GrumPHP.php @@ -101,6 +101,14 @@ public function getProcessTimeout() return (float) $timeout; } + /** + * @return null|string + */ + public function getAdditionalInfo() + { + return $this->container->getParameter('additional_info'); + } + /** * @return array */ diff --git a/src/Console/Helper/TaskRunnerHelper.php b/src/Console/Helper/TaskRunnerHelper.php index 7e67f7fdf..5c2601450 100644 --- a/src/Console/Helper/TaskRunnerHelper.php +++ b/src/Console/Helper/TaskRunnerHelper.php @@ -126,6 +126,8 @@ private function returnErrorMessages(OutputInterface $output, array $errorMessag ); } + $this->returnAdditionalInfo($output); + return self::CODE_ERROR; } @@ -145,6 +147,7 @@ private function returnSuccessMessage(OutputInterface $output, array $warnings) $this->returnWarningMessages($output, $warnings); + $this->returnAdditionalInfo($output); return self::CODE_SUCCESS; } @@ -160,6 +163,14 @@ private function returnWarningMessages($output, array $warningMessages) } } + /** + * @param OutputInterface $output + */ + private function returnAdditionalInfo(OutputInterface $output) + { + is_null($this->config->getAdditionalInfo()) ?: $output->writeln($this->config->getAdditionalInfo()); + } + /** * {@inheritdoc} */ From e849351a8eca5479370c477285581e7ad52591da Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Thu, 13 Sep 2018 08:56:42 -0400 Subject: [PATCH 02/25] feat: Add option to disable all banners by setting ascii to null --- doc/parameters.md | 10 +++++++++- src/Configuration/GrumPHP.php | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/parameters.md b/doc/parameters.md index ed7968292..bee0c8990 100644 --- a/doc/parameters.md +++ b/doc/parameters.md @@ -132,7 +132,15 @@ parameters: - resource/grumphp-happy3.txt ``` -To disable banner set ascii images path to `~`: +To disable all banners set ascii to `~`: + +```yaml +# grumphp.yml +parameters: + ascii: ~ +``` + +To disable a specific banner set ascii image path to `~`: ```yaml # grumphp.yml diff --git a/src/Configuration/GrumPHP.php b/src/Configuration/GrumPHP.php index 203aa1b01..12b6e98a3 100644 --- a/src/Configuration/GrumPHP.php +++ b/src/Configuration/GrumPHP.php @@ -166,6 +166,10 @@ public function getTestSuites() */ public function getAsciiContentPath($resource) { + if (is_null($this->container->getParameter('ascii'))) { + return null; + } + $paths = $this->container->getParameter('ascii'); if (!array_key_exists($resource, $paths)) { return null; From b2ee6775debdde44d4a09e637d6c126bc1de24db Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Thu, 13 Sep 2018 15:25:14 -0400 Subject: [PATCH 03/25] feat(CommitMessage): Add ability to enforce type scope conventions This is a well-known convention. Ref: https://conventionalcommits.org/spec/v1.0.0-beta.2.html --- doc/tasks/git_commit_message.md | 76 +++++++++++ spec/Task/Git/CommitMessageSpec.php | 199 ++++++++++++++++++++++++++++ src/Task/Git/CommitMessage.php | 114 +++++++++++++++- 3 files changed, 384 insertions(+), 5 deletions(-) diff --git a/doc/tasks/git_commit_message.md b/doc/tasks/git_commit_message.md index d8eaecfb2..3c6eeaffe 100644 --- a/doc/tasks/git_commit_message.md +++ b/doc/tasks/git_commit_message.md @@ -10,8 +10,12 @@ parameters: git_commit_message: allow_empty_message: false enforce_capitalized_subject: true + enforce_no_subject_punctuations: false enforce_no_subject_trailing_period: true enforce_single_lined_subject: true + enforce_type_scope_conventions: false + types: [] + scopes: [] max_body_width: 72 max_subject_width: 60 matchers: @@ -33,6 +37,12 @@ Controls whether or not empty commit messages are allowed. Ensures that the commit message subject line starts with a capital letter. +**enforce_no_subject_punctuations** + +*Default: false* + +Ensures that the commit message subject line doesn't have any punctuations characters. + **enforce_no_subject_trailing_period** *Default: true* @@ -95,3 +105,69 @@ additional_modifiers: 'u' additional_modifiers: 'xu' ``` + +**type_scope_conventions** + +*Default: []* + +Enable a commonly used convention for the subject line. + +Format is as follows: +``` +type[(scope)]: subject +``` +*The scope is optional* + +Good examples: +``` +fix: Security issue with password hashing +fix(Password): Security issue with password hashing +``` + +**types** + +*Default: []* + +*To be used with `type_scope_conventions`* + +Specify a list of acceptable types. Default allows ***all*** types. + +Add one or multiple types like: +```yaml +type_scope_conventions: + - types: + - build + - ci + - chore + - docs + - feat + - fix + - perf + - refactor + - revert + - style + - test + - scopes: [] +``` + +**scopes** + +*Default: []* + +*To be used with `type_scope_conventions`* + +Specify a list of acceptable scopes. Default allows ***all*** scopes. + +Add one or multiple scopes like: +```yaml +type_scope_conventions: + - types: [] + - scopes: + - api + - index + - user + - language + - browser + - environment +``` + diff --git a/spec/Task/Git/CommitMessageSpec.php b/spec/Task/Git/CommitMessageSpec.php index 6dd9a9b5d..3f4107219 100644 --- a/spec/Task/Git/CommitMessageSpec.php +++ b/spec/Task/Git/CommitMessageSpec.php @@ -42,6 +42,8 @@ function it_should_have_configurable_options() $options->getDefinedOptions()->shouldContain('multiline'); $options->getDefinedOptions()->shouldContain('matchers'); $options->getDefinedOptions()->shouldContain('additional_modifiers'); + $options->getDefinedOptions()->shouldContain('enforce_no_subject_punctuations'); + $options->getDefinedOptions()->shouldContain('type_scope_conventions'); } function it_is_initializable() @@ -606,4 +608,201 @@ function it_should_pass_when_subject_does_not_contain_a_trailing_period(GrumPHP $result->shouldBeAnInstanceOf(TaskResultInterface::class); $result->isPassed()->shouldBe(true); } + + function it_should_pass_when_enforce_type_scope_conventions_is_false( + GrumPHP $grumPHP, + GitCommitMsgContext $context + ) { + $grumPHP->getTaskConfiguration('git_commit_message')->willReturn([ + 'allow_empty_message' => false, + 'enforce_capitalized_subject' => false, + 'enforce_no_subject_trailing_period' => false, + 'enforce_single_lined_subject' => true, + 'type_scope_conventions' => [], + ]); + + $commitMessage = <<<'MSG' +this subject does not follow the type scope conventions + +pass because we set enforce_type_scope_conventions to false + +And footer #12 +MSG; + + $context->getCommitMessage()->willReturn($commitMessage); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(true); + } + + function it_should_fail_when_type_scope_conventions_does_not_follow_conventions( + GrumPHP $grumPHP, + GitCommitMsgContext $context + ) { + $grumPHP->getTaskConfiguration('git_commit_message')->willReturn([ + 'allow_empty_message' => false, + 'enforce_capitalized_subject' => false, + 'enforce_no_subject_trailing_period' => false, + 'enforce_single_lined_subject' => true, + 'type_scope_conventions' => [ + 'types' => [] + ], + ]); + + $commitMessage = <<<'MSG' +this subject does not follow the type scope conventions + +The body ... + +And footer #12 +MSG; + + $context->getCommitMessage()->willReturn($commitMessage); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(false); + } + + function it_should_fail_when_type_scope_conventions_does_not_use_an_available_type( + GrumPHP $grumPHP, + GitCommitMsgContext $context + ) { + $grumPHP->getTaskConfiguration('git_commit_message')->willReturn([ + 'allow_empty_message' => false, + 'enforce_capitalized_subject' => false, + 'enforce_no_subject_trailing_period' => false, + 'enforce_single_lined_subject' => true, + 'type_scope_conventions' => [ + 'types' => ['fix'] + ], + ]); + + $commitMessage = <<<'MSG' +docs: this type is not in the available types array + +The body ... + +And footer #12 +MSG; + + $context->getCommitMessage()->willReturn($commitMessage); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(false); + } + + function it_should_pass_when_type_scope_conventions_does_use_an_available_type( + GrumPHP $grumPHP, + GitCommitMsgContext $context + ) { + $grumPHP->getTaskConfiguration('git_commit_message')->willReturn([ + 'allow_empty_message' => false, + 'enforce_capitalized_subject' => false, + 'enforce_no_subject_trailing_period' => false, + 'enforce_single_lined_subject' => true, + 'type_scope_conventions' => [ + 'types' => ['fix'] + ], + ]); + + $commitMessage = <<<'MSG' +fix: this type is in the available types array + +The body ... + +And footer #12 +MSG; + + $context->getCommitMessage()->willReturn($commitMessage); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(true); + } + + function it_should_fail_when_type_scope_conventions_does_not_use_an_available_scope( + GrumPHP $grumPHP, + GitCommitMsgContext $context + ) { + $grumPHP->getTaskConfiguration('git_commit_message')->willReturn([ + 'allow_empty_message' => false, + 'enforce_capitalized_subject' => false, + 'enforce_no_subject_trailing_period' => false, + 'enforce_single_lined_subject' => true, + 'type_scope_conventions' => [ + 'scopes' => ['user'] + ], + ]); + + $commitMessage = <<<'MSG' +fix(index): this scope is not in the available scopes array + +The body ... + +And footer #12 +MSG; + + $context->getCommitMessage()->willReturn($commitMessage); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(false); + } + + function it_should_pass_when_type_scope_conventions_does_use_an_available_scope( + GrumPHP $grumPHP, + GitCommitMsgContext $context + ) { + $grumPHP->getTaskConfiguration('git_commit_message')->willReturn([ + 'allow_empty_message' => false, + 'enforce_capitalized_subject' => false, + 'enforce_no_subject_trailing_period' => false, + 'enforce_single_lined_subject' => true, + 'type_scope_conventions' => [ + 'scopes' => ['user'] + ], + ]); + + $commitMessage = <<<'MSG' +fix(user): this scope is in the available scopes array + +The body ... + +And footer #12 +MSG; + + $context->getCommitMessage()->willReturn($commitMessage); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(true); + } + + function it_should_fail_if_subject_contains_punctuations(GrumPHP $grumPHP, GitCommitMsgContext $context) + { + $grumPHP->getTaskConfiguration('git_commit_message')->willReturn([ + 'allow_empty_message' => false, + 'enforce_capitalized_subject' => false, + 'enforce_no_subject_trailing_period' => false, + 'enforce_single_lined_subject' => true, + 'enforce_no_subject_punctuations' => true, + ]); + + $commitMessage = <<<'MSG' +fix(user): this subject has punctuations! + +The body ... + +And footer #12 ? +MSG; + + $context->getCommitMessage()->willReturn($commitMessage); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(false); + } } diff --git a/src/Task/Git/CommitMessage.php b/src/Task/Git/CommitMessage.php index e116a7313..e86b41820 100644 --- a/src/Task/Git/CommitMessage.php +++ b/src/Task/Git/CommitMessage.php @@ -9,6 +9,7 @@ use GrumPHP\Task\Context\GitCommitMsgContext; use GrumPHP\Task\TaskInterface; use GrumPHP\Util\Regex; +use GrumPHP\Util\Str; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -21,6 +22,11 @@ class CommitMessage implements TaskInterface */ private $grumPHP; + /** + * @var array $exceptions + */ + private $exceptions = []; + /** * @param GrumPHP $grumPHP */ @@ -56,18 +62,22 @@ public function getConfigurableOptions() $resolver->setDefaults([ 'allow_empty_message' => false, 'enforce_capitalized_subject' => true, + 'enforce_no_subject_punctuations' => false, 'enforce_no_subject_trailing_period' => true, 'enforce_single_lined_subject' => true, 'max_body_width' => 72, 'max_subject_width' => 60, 'case_insensitive' => true, 'multiline' => true, + 'type_scope_conventions' => [], 'matchers' => [], - 'additional_modifiers' => '' + 'additional_modifiers' => '', ]); $resolver->addAllowedTypes('allow_empty_message', ['bool']); + $resolver->addAllowedTypes('type_scope_conventions', ['array']); $resolver->addAllowedTypes('enforce_capitalized_subject', ['bool']); + $resolver->addAllowedTypes('enforce_no_subject_punctuations', ['bool']); $resolver->addAllowedTypes('enforce_no_subject_trailing_period', ['bool']); $resolver->addAllowedTypes('enforce_single_lined_subject', ['bool']); $resolver->addAllowedTypes('max_body_width', ['int']); @@ -99,7 +109,6 @@ public function run(ContextInterface $context) { $config = $this->getConfiguration(); $commitMessage = $context->getCommitMessage(); - $exceptions = []; if (!(bool) $config['allow_empty_message'] && trim($commitMessage) === '') { return TaskResult::createFailed( @@ -125,6 +134,14 @@ public function run(ContextInterface $context) ); } + if ((bool) $config['enforce_no_subject_punctuations'] && $this->subjectHasPunctuations($context)) { + return TaskResult::createFailed( + $this, + $context, + 'Please omit all punctuations from commit message subject.' + ); + } + if ((bool) $config['enforce_no_subject_trailing_period'] && $this->subjectHasTrailingPeriod($context)) { return TaskResult::createFailed( $this, @@ -133,16 +150,20 @@ public function run(ContextInterface $context) ); } + if ((bool) $this->enforceTypeScopeConventions() && !$this->followsTypeScopeConventions($context)) { + return TaskResult::createFailed($this, $context, implode(PHP_EOL, $this->exceptions)); + } + foreach ($config['matchers'] as $ruleName => $rule) { try { $this->runMatcher($config, $commitMessage, $rule, $ruleName); } catch (RuntimeException $e) { - $exceptions[] = $e->getMessage(); + $this->exceptions[] = $e->getMessage(); } } - if (count($exceptions)) { - return TaskResult::createFailed($this, $context, implode(PHP_EOL, $exceptions)); + if (count($this->exceptions)) { + return TaskResult::createFailed($this, $context, implode(PHP_EOL, $this->exceptions)); } return $this->enforceTextWidth($context); @@ -235,6 +256,27 @@ private function getSpecialPrefixLength($string) return mb_strlen($match[0]); } + /** + * @param ContextInterface $context + * + * @return bool + */ + private function subjectHasPunctuations(ContextInterface $context) + { + $commitMessage = $context->getCommitMessage(); + $subject = strtok($commitMessage, PHP_EOL); + + if (trim($commitMessage) === '') { + return false; + } + + if (Str::contains($subject, ['.', '!', '?', ','])) { + return true; + } + + return false; + } + /** * @param ContextInterface $context * @@ -332,4 +374,66 @@ private function getCommitMessageLinesWithoutComments($commitMessage) return strpos($line, '#') !== 0; })); } + + private function enforceTypeScopeConventions() + { + $config = $this->getConfiguration(); + + if (!is_array($config['type_scope_conventions'])) { + return false; + } + + if (!in_array('types', array_keys($config['type_scope_conventions'])) + && !in_array('scopes', array_keys($config['type_scope_conventions'])) + ) { + return false; + } + + return true; + } + + /** + * @param ContextInterface $context + * + * @return bool + */ + private function followsTypeScopeConventions($context) + { + $config = $this->getConfiguration(); + $commitMessage = $context->getCommitMessage(); + + $types = isset($config['type_scope_conventions']['types']) + ? $config['type_scope_conventions']['types'] + : []; + + $scopes = isset($config['type_scope_conventions']['scopes']) + ? $config['type_scope_conventions']['scopes'] + : []; + + $typesPattern = '([a-zA-Z0-9]+)'; + $scopesPattern = '(:\s|(\(.+\)?:\s))'; + $subjectPattern = '([a-zA-Z0-9-_ #@\'\/\\"]+)'; + $mergePattern = '(Merge branch \'.+\'\s.+|Merge remote-tracking branch \'.+\'|Merge pull request #\d+\s.+)'; + + if (count($types) > 0) { + $types = implode($types, '|'); + $typesPattern = '(' . $types . ')'; + } + + if (count($scopes) > 0) { + $scopes = implode($scopes, '|'); + $scopesPattern = '(:\s|(\(' . $scopes . '\)?:\s))'; + } + + $rule = '/^' . $typesPattern . $scopesPattern . $subjectPattern . '|' . $mergePattern . '/'; + + try { + $this->runMatcher($config, $commitMessage, $rule, 'Invalid Type/Scope Format'); + } catch (RuntimeException $e) { + $this->exceptions[] = $e->getMessage(); + return false; + } + + return true; + } } From 22f75c820cb799c0fd81e9df4f053ca1b1e1fa7c Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Thu, 13 Sep 2018 15:18:52 -0400 Subject: [PATCH 04/25] feat(Util): Add string helper class with contains method --- src/Util/Str.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/Util/Str.php diff --git a/src/Util/Str.php b/src/Util/Str.php new file mode 100644 index 000000000..b05061723 --- /dev/null +++ b/src/Util/Str.php @@ -0,0 +1,17 @@ + Date: Fri, 21 Sep 2018 10:54:02 -0400 Subject: [PATCH 05/25] style: Update to satisfy a style preference --- src/Configuration/GrumPHP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Configuration/GrumPHP.php b/src/Configuration/GrumPHP.php index 12b6e98a3..8055a1772 100644 --- a/src/Configuration/GrumPHP.php +++ b/src/Configuration/GrumPHP.php @@ -166,7 +166,7 @@ public function getTestSuites() */ public function getAsciiContentPath($resource) { - if (is_null($this->container->getParameter('ascii'))) { + if (null === $this->container->getParameter('ascii')) { return null; } From e775083fc7de94431c034e54f25bf22eac8da217 Mon Sep 17 00:00:00 2001 From: Gabriel Somoza Date: Fri, 21 Sep 2018 17:27:28 +0200 Subject: [PATCH 06/25] Allow PHPLint to handle large number of files. --- spec/Task/PhpLintSpec.php | 9 +++++++-- src/Task/PhpLint.php | 10 ++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/spec/Task/PhpLintSpec.php b/spec/Task/PhpLintSpec.php index 649c71acb..ec6d2e7b8 100644 --- a/spec/Task/PhpLintSpec.php +++ b/spec/Task/PhpLintSpec.php @@ -15,6 +15,7 @@ use PhpSpec\ObjectBehavior; use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Process\InputStream; use Symfony\Component\Process\Process; class PhpLintSpec extends ObjectBehavior @@ -60,7 +61,9 @@ function it_runs_the_linter(ProcessBuilder $processBuilder, Process $process, Co $processBuilder->createArgumentsForCommand('parallel-lint')->willReturn($arguments); $processBuilder->buildProcess($arguments)->willReturn($process); - $process->run()->shouldBeCalled(); + $process->setInput(null)->shouldBeCalled()->withArguments([new \Prophecy\Argument\Token\TypeToken(InputStream::class)]); + $process->start()->shouldBeCalled(); + $process->wait()->shouldBeCalled(); $process->isSuccessful()->willReturn(true); $context->getFiles()->willReturn(new FilesCollection([ @@ -78,7 +81,9 @@ function it_throws_exception_if_the_process_fails(ProcessBuilder $processBuilder $processBuilder->createArgumentsForCommand('parallel-lint')->willReturn($arguments); $processBuilder->buildProcess($arguments)->willReturn($process); - $process->run()->shouldBeCalled(); + $process->setInput(null)->shouldBeCalled()->withArguments([new \Prophecy\Argument\Token\TypeToken(InputStream::class)]);; + $process->start()->shouldBeCalled(); + $process->wait()->shouldBeCalled(); $process->isSuccessful()->willReturn(false); $context->getFiles()->willReturn(new FilesCollection([ diff --git a/src/Task/PhpLint.php b/src/Task/PhpLint.php index d35047513..5ca333ef3 100644 --- a/src/Task/PhpLint.php +++ b/src/Task/PhpLint.php @@ -7,6 +7,7 @@ use GrumPHP\Task\Context\GitPreCommitContext; use GrumPHP\Task\Context\RunContext; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Process\InputStream; /** * PHP parallel lint task. @@ -60,10 +61,15 @@ public function run(ContextInterface $context) $arguments->add('--no-colors'); $arguments->addOptionalArgumentWithSeparatedValue('-j', $config['jobs']); $arguments->addArgumentArrayWithSeparatedValue('--exclude', $config['exclude']); - $arguments->addFiles($files); + $arguments->add('--stdin'); + $inputStream = new InputStream(); $process = $this->processBuilder->buildProcess($arguments); - $process->run(); + $process->setInput($inputStream); + $process->start(); + $inputStream->write(\implode($files->toArray(), PHP_EOL)); + $inputStream->close(); + $process->wait(); if (!$process->isSuccessful()) { return TaskResult::createFailed($this, $context, $this->formatter->format($process)); From 18d3339deb16872a8a0a5c5bf4c30538fa4e6df4 Mon Sep 17 00:00:00 2001 From: Leigh Bicknell Date: Tue, 25 Sep 2018 16:50:37 +0100 Subject: [PATCH 07/25] Fix issue when .git file has absolute path --- src/Console/Helper/PathsHelper.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Console/Helper/PathsHelper.php b/src/Console/Helper/PathsHelper.php index f112cfcd2..30986a81d 100644 --- a/src/Console/Helper/PathsHelper.php +++ b/src/Console/Helper/PathsHelper.php @@ -170,7 +170,8 @@ public function getGitHooksDir() if (is_file($gitRepoPath)) { $fileContent = $this->fileSystem->readFromFileInfo(new SplFileInfo($gitRepoPath)); if (preg_match('/gitdir:\s+(\S+)/', $fileContent, $matches)) { - return $this->getRelativePath($gitPath . $matches[1] . '/hooks/'); + $relativePath = $this->getRelativePath($matches[1]); + return $this->getRelativePath($gitPath . $relativePath . '/hooks/'); } } From aa6d1ee5ab70d8408b731d2b645f76291f6687ce Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Fri, 28 Sep 2018 09:14:13 -0400 Subject: [PATCH 08/25] fix: Update overlooked documentation referencing an old proposal --- doc/tasks/git_commit_message.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/tasks/git_commit_message.md b/doc/tasks/git_commit_message.md index 3c6eeaffe..33b1c1f40 100644 --- a/doc/tasks/git_commit_message.md +++ b/doc/tasks/git_commit_message.md @@ -13,9 +13,7 @@ parameters: enforce_no_subject_punctuations: false enforce_no_subject_trailing_period: true enforce_single_lined_subject: true - enforce_type_scope_conventions: false - types: [] - scopes: [] + type_scope_conventions: [] max_body_width: 72 max_subject_width: 60 matchers: From 3217efd75d9a477b8f820553add23d6ed8f3a45d Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Fri, 28 Sep 2018 10:00:05 -0400 Subject: [PATCH 09/25] feat: Add private getSubjectLine() to extract duplicated logic --- src/Task/Git/CommitMessage.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Task/Git/CommitMessage.php b/src/Task/Git/CommitMessage.php index e86b41820..a1ac33f18 100644 --- a/src/Task/Git/CommitMessage.php +++ b/src/Task/Git/CommitMessage.php @@ -436,4 +436,17 @@ private function followsTypeScopeConventions($context) return true; } + + /** + * Gets a clean subject line from the commit message + * + * @param $context + * @return string + */ + private function getSubjectLine($context) + { + $commitMessage = $context->getCommitMessage(); + $lines = $this->getCommitMessageLinesWithoutComments($commitMessage); + return (string) $lines[0]; + } } From b3182857cabc6ef3d075d5c41dbdaf75a05c0483 Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Fri, 28 Sep 2018 10:00:57 -0400 Subject: [PATCH 10/25] refact: Update methods to make use of getSubjectLine method --- src/Task/Git/CommitMessage.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Task/Git/CommitMessage.php b/src/Task/Git/CommitMessage.php index a1ac33f18..a7c24a185 100644 --- a/src/Task/Git/CommitMessage.php +++ b/src/Task/Git/CommitMessage.php @@ -263,14 +263,13 @@ private function getSpecialPrefixLength($string) */ private function subjectHasPunctuations(ContextInterface $context) { - $commitMessage = $context->getCommitMessage(); - $subject = strtok($commitMessage, PHP_EOL); + $subjectLine = $this->getSubjectLine($context); - if (trim($commitMessage) === '') { + if (trim($subjectLine) === '') { return false; } - if (Str::contains($subject, ['.', '!', '?', ','])) { + if (Str::contains($subjectLine, ['.', '!', '?', ','])) { return true; } @@ -284,15 +283,13 @@ private function subjectHasPunctuations(ContextInterface $context) */ private function subjectHasTrailingPeriod(ContextInterface $context) { - $commitMessage = $context->getCommitMessage(); + $subjectLine = $this->getSubjectLine($context); - if (trim($commitMessage) === '') { + if (trim($subjectLine) === '') { return false; } - $lines = $this->getCommitMessageLinesWithoutComments($commitMessage); - - if (mb_substr(rtrim($lines[0]), -1) !== '.') { + if (mb_substr(rtrim($subjectLine), -1) !== '.') { return false; } @@ -400,7 +397,7 @@ private function enforceTypeScopeConventions() private function followsTypeScopeConventions($context) { $config = $this->getConfiguration(); - $commitMessage = $context->getCommitMessage(); + $subjectLine = $this->getSubjectLine($context); $types = isset($config['type_scope_conventions']['types']) ? $config['type_scope_conventions']['types'] @@ -428,7 +425,7 @@ private function followsTypeScopeConventions($context) $rule = '/^' . $typesPattern . $scopesPattern . $subjectPattern . '|' . $mergePattern . '/'; try { - $this->runMatcher($config, $commitMessage, $rule, 'Invalid Type/Scope Format'); + $this->runMatcher($config, $subjectLine, $rule, 'Invalid Type/Scope Format'); } catch (RuntimeException $e) { $this->exceptions[] = $e->getMessage(); return false; From d31d927dafc46cf194ac5f12cf81af4a39605b9a Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Fri, 28 Sep 2018 10:27:02 -0400 Subject: [PATCH 11/25] style: Update to satisfy a style preference --- src/Console/Helper/TaskRunnerHelper.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Console/Helper/TaskRunnerHelper.php b/src/Console/Helper/TaskRunnerHelper.php index 5c2601450..7821b7344 100644 --- a/src/Console/Helper/TaskRunnerHelper.php +++ b/src/Console/Helper/TaskRunnerHelper.php @@ -168,7 +168,9 @@ private function returnWarningMessages($output, array $warningMessages) */ private function returnAdditionalInfo(OutputInterface $output) { - is_null($this->config->getAdditionalInfo()) ?: $output->writeln($this->config->getAdditionalInfo()); + if (null !== $this->config->getAdditionalInfo()) { + $output->writeln($this->config->getAdditionalInfo()); + } } /** From 2de742761c1857f9e3a8a79363f215b75bfb428b Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Fri, 28 Sep 2018 10:44:44 -0400 Subject: [PATCH 12/25] refact: Add docblock to define parameter types and Refactor method name Add strict type to $needles to force an array of needles Update reference to the method in consideration to renaming --- src/Task/Git/CommitMessage.php | 2 +- src/Util/Str.php | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Task/Git/CommitMessage.php b/src/Task/Git/CommitMessage.php index a7c24a185..9946088b2 100644 --- a/src/Task/Git/CommitMessage.php +++ b/src/Task/Git/CommitMessage.php @@ -269,7 +269,7 @@ private function subjectHasPunctuations(ContextInterface $context) return false; } - if (Str::contains($subjectLine, ['.', '!', '?', ','])) { + if (Str::containsOneOf($subjectLine, ['.', '!', '?', ','])) { return true; } diff --git a/src/Util/Str.php b/src/Util/Str.php index b05061723..ca672b8de 100644 --- a/src/Util/Str.php +++ b/src/Util/Str.php @@ -4,9 +4,16 @@ class Str { - public static function contains($haystack, $needles) + /** + * String contains one of the provided needles + * + * @param string $haystack + * @param array $needles + * @return bool + */ + public static function containsOneOf($haystack, array $needles) { - foreach ((array) $needles as $needle) { + foreach ($needles as $needle) { if ($needle !== '' && mb_strpos($haystack, $needle) !== false) { return true; } From 0056460816fd622c9986c5eb1dd8de82e29a467c Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Mon, 1 Oct 2018 09:32:20 -0400 Subject: [PATCH 13/25] refact: Remove private $exceptions declaration per discussion Per @veewee: "I would avoid the local state since the run method can theoretically be called multiple times which causes an issue and we don't really need this state internally. A try/catch over the if statement will solve the issue you are addressing as well." Ref https://github.com/phpro/grumphp/pull/541#discussion_r221252425 --- src/Task/Git/CommitMessage.php | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Task/Git/CommitMessage.php b/src/Task/Git/CommitMessage.php index 9946088b2..1cc946667 100644 --- a/src/Task/Git/CommitMessage.php +++ b/src/Task/Git/CommitMessage.php @@ -22,11 +22,6 @@ class CommitMessage implements TaskInterface */ private $grumPHP; - /** - * @var array $exceptions - */ - private $exceptions = []; - /** * @param GrumPHP $grumPHP */ @@ -109,6 +104,7 @@ public function run(ContextInterface $context) { $config = $this->getConfiguration(); $commitMessage = $context->getCommitMessage(); + $exceptions = []; if (!(bool) $config['allow_empty_message'] && trim($commitMessage) === '') { return TaskResult::createFailed( @@ -150,20 +146,25 @@ public function run(ContextInterface $context) ); } - if ((bool) $this->enforceTypeScopeConventions() && !$this->followsTypeScopeConventions($context)) { - return TaskResult::createFailed($this, $context, implode(PHP_EOL, $this->exceptions)); + + if ((bool) $this->enforceTypeScopeConventions()) { + try { + $this->checkTypeScopeConventions($context); + } catch (RuntimeException $e) { + $exceptions[] = $e->getMessage(); + } } foreach ($config['matchers'] as $ruleName => $rule) { try { $this->runMatcher($config, $commitMessage, $rule, $ruleName); } catch (RuntimeException $e) { - $this->exceptions[] = $e->getMessage(); + $exceptions[] = $e->getMessage(); } } - if (count($this->exceptions)) { - return TaskResult::createFailed($this, $context, implode(PHP_EOL, $this->exceptions)); + if (count($exceptions)) { + return TaskResult::createFailed($this, $context, implode(PHP_EOL, $exceptions)); } return $this->enforceTextWidth($context); @@ -392,9 +393,10 @@ private function enforceTypeScopeConventions() /** * @param ContextInterface $context * - * @return bool + * @return void; + * @throws RuntimeException */ - private function followsTypeScopeConventions($context) + private function checkTypeScopeConventions($context) { $config = $this->getConfiguration(); $subjectLine = $this->getSubjectLine($context); @@ -427,11 +429,8 @@ private function followsTypeScopeConventions($context) try { $this->runMatcher($config, $subjectLine, $rule, 'Invalid Type/Scope Format'); } catch (RuntimeException $e) { - $this->exceptions[] = $e->getMessage(); - return false; + throw $e; } - - return true; } /** From 8ee9e9895f1886a6397d44cdc7902fb8d1d8f00c Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Tue, 2 Oct 2018 09:09:57 -0400 Subject: [PATCH 14/25] refact: Remove useless if statement --- src/Task/Git/CommitMessage.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Task/Git/CommitMessage.php b/src/Task/Git/CommitMessage.php index 1cc946667..5f82e0c11 100644 --- a/src/Task/Git/CommitMessage.php +++ b/src/Task/Git/CommitMessage.php @@ -270,11 +270,7 @@ private function subjectHasPunctuations(ContextInterface $context) return false; } - if (Str::containsOneOf($subjectLine, ['.', '!', '?', ','])) { - return true; - } - - return false; + return Str::containsOneOf($subjectLine, ['.', '!', '?', ',']); } /** From e2e2393936e52cc061480c8e436d54f362f8913c Mon Sep 17 00:00:00 2001 From: poppabear8883 Date: Tue, 2 Oct 2018 09:15:39 -0400 Subject: [PATCH 15/25] refact: Refactor to cleanup the method --- src/Task/Git/CommitMessage.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Task/Git/CommitMessage.php b/src/Task/Git/CommitMessage.php index 5f82e0c11..8aaee6114 100644 --- a/src/Task/Git/CommitMessage.php +++ b/src/Task/Git/CommitMessage.php @@ -373,17 +373,9 @@ private function enforceTypeScopeConventions() { $config = $this->getConfiguration(); - if (!is_array($config['type_scope_conventions'])) { - return false; - } - - if (!in_array('types', array_keys($config['type_scope_conventions'])) - && !in_array('scopes', array_keys($config['type_scope_conventions'])) - ) { - return false; - } + $conventionsKeys = array_keys($config['type_scope_conventions']); - return true; + return in_array('types', $conventionsKeys) || in_array('scopes', $conventionsKeys); } /** From 60b62befe97e1a655c60807c79aeb30148499c12 Mon Sep 17 00:00:00 2001 From: Gabriel Somoza Date: Thu, 18 Oct 2018 09:53:23 +0200 Subject: [PATCH 16/25] Bump minor version requirement of symfony/process to ~3.2 Because \Symfony\Components\Process\InputStream doesn't exist in previous versions. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2254cffc3..e170a0ba2 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "symfony/filesystem": "~2.7|~3.0|~4.0", "symfony/finder": "~2.7|~3.0|~4.0", "symfony/options-resolver": "~2.7|~3.0|~4.0", - "symfony/process": "~2.7|~3.0|~4.0", + "symfony/process": "~3.2|~4.0", "symfony/yaml": "~2.7|~3.0|~4.0" }, "require-dev": { From aa46ac3d21b22a8c60db8f68a8232138d8716d25 Mon Sep 17 00:00:00 2001 From: Jeffrey Cafferata Date: Thu, 18 Oct 2018 14:03:23 +0200 Subject: [PATCH 17/25] - Change the mistakes to `null` instead of `~`. - Added escaping for the correct markdown presentation. --- doc/tasks/composer_require_checker.md | 6 +++--- doc/tasks/phpstan.md | 4 ++-- doc/tasks/psalm.md | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/tasks/composer_require_checker.md b/doc/tasks/composer_require_checker.md index 6fb486145..270278ead 100644 --- a/doc/tasks/composer_require_checker.md +++ b/doc/tasks/composer_require_checker.md @@ -23,13 +23,13 @@ parameters: **composer_file** -*Default: ~* +*Default: null* The composer.json of your code base that should be checked. **config_file** -*Default: ~* +*Default: null* Composer Require Checker is configured to whitelist some symbols by default. You can now override this configuration with your own and tell GrumPHP to use that configuration file instead. @@ -44,6 +44,6 @@ This option is only available in version 0.2.0 of `maglnet/composer-require-chec **triggered_by** -*Default: ['composer.json', 'composer.lock', '*.php']* +*Default: ['composer.json', 'composer.lock', '\*.php']* This is a list of file names that should trigger this task. diff --git a/doc/tasks/phpstan.md b/doc/tasks/phpstan.md index 45fcebec8..a7411d6dd 100644 --- a/doc/tasks/phpstan.md +++ b/doc/tasks/phpstan.md @@ -24,13 +24,13 @@ parameters: **autoload_file** -*Default: ~* +*Default: null* With this parameter you can specify the path your project's additional autoload file path. **configuration** -*Default: ~* +*Default: null* With this parameter you can specify the path your project's configuration file. diff --git a/doc/tasks/psalm.md b/doc/tasks/psalm.md index c5d7e30df..29e2d97a8 100644 --- a/doc/tasks/psalm.md +++ b/doc/tasks/psalm.md @@ -25,7 +25,7 @@ parameters: **config** -*Default: ~* +*Default: null* With this parameter you can specify the path your project's configuration file. @@ -48,7 +48,7 @@ With this parameter you can run Psalm without using the cache file. **report** -*Default: ~* +*Default: null* With this path you can specify the path your psalm report file From 94c2e8ec621a237e8f008987261c2a29734a6e9b Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Fri, 26 Oct 2018 06:59:21 +0200 Subject: [PATCH 18/25] Add PHP 7.2 EOL to PHP version checker --- doc/tasks/phpversion.md | 2 +- resources/config/util.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/tasks/phpversion.md b/doc/tasks/phpversion.md index 77346124d..7b8a90931 100644 --- a/doc/tasks/phpversion.md +++ b/doc/tasks/phpversion.md @@ -9,7 +9,7 @@ It lives under the `phpversion` namespace and has following configurable paramet parameters: tasks: phpversion: - project: '7.0' + project: '7.2' ``` **project** diff --git a/resources/config/util.yml b/resources/config/util.yml index 3a90d423d..c1765487d 100644 --- a/resources/config/util.yml +++ b/resources/config/util.yml @@ -13,3 +13,4 @@ services: '5.6': '2018-12-31 23:59:59' '7.0': '2018-12-03 23:59:59' '7.1': '2019-12-01 23:59:59' + '7.2': '2020-11-30 23:59:59' From 18730ba9eb3bf7cebcdd6f8655b298deb99c3c72 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Fri, 26 Oct 2018 14:27:41 +0200 Subject: [PATCH 19/25] Host our own version of the symfony XSDs --- test/fixtures/linters/xml/xsd-url-invalid.xml | 2 +- test/fixtures/linters/xml/xsd-url-valid.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/linters/xml/xsd-url-invalid.xml b/test/fixtures/linters/xml/xsd-url-invalid.xml index feb75ef92..dc8e8f7b3 100644 --- a/test/fixtures/linters/xml/xsd-url-invalid.xml +++ b/test/fixtures/linters/xml/xsd-url-invalid.xml @@ -2,7 +2,7 @@ diff --git a/test/fixtures/linters/xml/xsd-url-valid.xml b/test/fixtures/linters/xml/xsd-url-valid.xml index 7d1ea74d5..8932d18d6 100644 --- a/test/fixtures/linters/xml/xsd-url-valid.xml +++ b/test/fixtures/linters/xml/xsd-url-valid.xml @@ -2,7 +2,7 @@ From 2dd294cb45fb6d469f22f5761d952f23839c54c7 Mon Sep 17 00:00:00 2001 From: Damien Merchier Date: Fri, 30 Mar 2018 17:23:57 +0200 Subject: [PATCH 20/25] Add phpunit bridge --- README.md | 1 + composer.json | 1 + doc/tasks.md | 2 + doc/tasks/phpunitbridge.md | 56 ++++++++++++++ resources/config/tasks.yml | 9 +++ spec/Task/PhpunitBridgeSpec.php | 126 ++++++++++++++++++++++++++++++++ src/Task/PhpunitBridge.php | 81 ++++++++++++++++++++ 7 files changed, 276 insertions(+) create mode 100644 doc/tasks/phpunitbridge.md create mode 100644 spec/Task/PhpunitBridgeSpec.php create mode 100644 src/Task/PhpunitBridge.php diff --git a/README.md b/README.md index cfa569b17..85aaa0e49 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ parameters: phpspec: ~ phpstan: ~ phpunit: ~ + phpunitbridge: ~ phpversion: ~ progpilot: ~ psalm: ~ diff --git a/composer.json b/composer.json index 2254cffc3..dc4438fe9 100644 --- a/composer.json +++ b/composer.json @@ -57,6 +57,7 @@ "sensiolabs/security-checker": "Lets GrumPHP be sure that there are no known security issues.", "squizlabs/php_codesniffer": "Lets GrumPHP sniff on your code.", "sstalle/php7cc": "Lets GrumPHP check PHP 5.3 - 5.6 code compatibility with PHP 7.", + "symfony/phpunit-bridge": "Lets GrumPHP run your unit tests with the phpunit-bridge of Symfony.", "vimeo/psalm": "Lets GrumPHP discover errors in your code without running it." }, "authors": [ diff --git a/doc/tasks.md b/doc/tasks.md index c2c8e5e00..c80e770c0 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -44,6 +44,7 @@ parameters: phpspec: ~ phpstan: ~ phpunit: ~ + phpunitbridge: ~ phpversion: ~ progpilot: ~ psalm: ~ @@ -95,6 +96,7 @@ Every task has it's own default configuration. It is possible to overwrite the p - [Phpspec](tasks/phpspec.md) - [PHPStan](tasks/phpstan.md) - [Phpunit](tasks/phpunit.md) +- [Phpunit bridge](tasks/phpunitbridge.md) - [PhpVersion](tasks/phpversion.md) - [Progpilot](tasks/progpilot.md) - [Psalm](tasks/psalm.md) diff --git a/doc/tasks/phpunitbridge.md b/doc/tasks/phpunitbridge.md new file mode 100644 index 000000000..150cd8979 --- /dev/null +++ b/doc/tasks/phpunitbridge.md @@ -0,0 +1,56 @@ +# Phpunit bridge + +The Phpunit Bridge task will run your unit tests thanks to the Symfony Phpunit Bridge. + +***Composer*** + +``` +composer require --dev symfony/phpunit-bridge +``` + +***Config*** + +The task lives under the `phpunitbridge` namespace and has following configurable parameters: + +```yaml +# grumphp.yml +parameters: + tasks: + phpunitbridge: + config_file: ~ + testsuite: ~ + group: [] + always_execute: false +``` + +**config_file** + +*Default: null* + +If your phpunit.xml file is located at an exotic location, you can specify your custom config file location with this option. +This option is set to `null` by default. +This means that `phpunit.xml` or `phpunit.xml.dist` are automatically loaded if one of them exist in the current directory. + + +**testsuite** + +*Default: null* + +If you wish to only run tests from a certain Suite. +`testsuite: unit` + + +**group** + +*Default: array()* + +If you wish to only run tests from a certain Group. +`group: [fast,quick,small]` + + +**always_execute** + +*Default: false* + +Always run the whole test suite, even if no PHP files were changed. + diff --git a/resources/config/tasks.yml b/resources/config/tasks.yml index 356454e92..8ead48e4f 100644 --- a/resources/config/tasks.yml +++ b/resources/config/tasks.yml @@ -327,6 +327,15 @@ services: tags: - {name: grumphp.task, config: phpunit} + task.phpunitbridge: + class: GrumPHP\Task\PhpunitBridge + arguments: + - '@config' + - '@process_builder' + - '@formatter.raw_process' + tags: + - {name: grumphp.task, config: phpunitbridge} + task.phpversion: class: GrumPHP\Task\PhpVersion arguments: diff --git a/spec/Task/PhpunitBridgeSpec.php b/spec/Task/PhpunitBridgeSpec.php new file mode 100644 index 000000000..68145817b --- /dev/null +++ b/spec/Task/PhpunitBridgeSpec.php @@ -0,0 +1,126 @@ +getTaskConfiguration('phpunitbridge')->willReturn([]); + $this->beConstructedWith($grumPHP, $processBuilder, $formatter); + } + + function it_is_initializable() + { + $this->shouldHaveType(PhpunitBridge::class); + } + + function it_should_have_a_name() + { + $this->getName()->shouldBe('phpunitbridge'); + } + + function it_should_have_configurable_options() + { + $options = $this->getConfigurableOptions(); + $options->shouldBeAnInstanceOf(OptionsResolver::class); + $options->getDefinedOptions()->shouldContain('config_file'); + $options->getDefinedOptions()->shouldContain('testsuite'); + $options->getDefinedOptions()->shouldContain('group'); + $options->getDefinedOptions()->shouldContain('always_execute'); + } + + function it_should_run_in_git_pre_commit_context(GitPreCommitContext $context) + { + $this->canRunInContext($context)->shouldReturn(true); + } + + function it_should_run_in_run_context(RunContext $context) + { + $this->canRunInContext($context)->shouldReturn(true); + } + + function it_does_not_do_anything_if_there_are_no_files(ProcessBuilder $processBuilder, ContextInterface $context) + { + $processBuilder->buildProcess('phpunitbridge')->shouldNotBeCalled(); + $processBuilder->buildProcess()->shouldNotBeCalled(); + $context->getFiles()->willReturn(new FilesCollection()); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->getResultCode()->shouldBe(TaskResult::SKIPPED); + } + + function it_runs_if_there_are_no_files_but_always_execute_is_passed(GrumPHP $grumPHP, Process $process, ProcessBuilder $processBuilder, ContextInterface $context) + { + $grumPHP->getTaskConfiguration('phpunitbridge')->willReturn([ + 'always_execute' => true, + ]); + + $arguments = new ProcessArgumentsCollection(); + $processBuilder->createArgumentsForCommand('simple-phpunit')->willReturn($arguments); + $processBuilder->buildProcess($arguments)->willReturn($process); + + $process->run()->shouldBeCalled(); + $process->isSuccessful()->willReturn(true); + + $context->getFiles()->willReturn(new FilesCollection()); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(true); + } + + function it_runs_the_suite(ProcessBuilder $processBuilder, Process $process, ContextInterface $context) + { + $arguments = new ProcessArgumentsCollection(); + $processBuilder->createArgumentsForCommand('simple-phpunit')->willReturn($arguments); + $processBuilder->buildProcess($arguments)->willReturn($process); + + $process->run()->shouldBeCalled(); + $process->isSuccessful()->willReturn(true); + + $context->getFiles()->willReturn(new FilesCollection([ + new SplFileInfo('test.php', '.', 'test.php') + ])); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(true); + } + + function it_throws_exception_if_the_process_fails(ProcessBuilder $processBuilder, Process $process, ContextInterface $context) + { + $arguments = new ProcessArgumentsCollection(); + $processBuilder->createArgumentsForCommand('simple-phpunit')->willReturn($arguments); + $processBuilder->buildProcess($arguments)->willReturn($process); + + $process->run()->shouldBeCalled(); + $process->isSuccessful()->willReturn(false); + + $context->getFiles()->willReturn(new FilesCollection([ + new SplFileInfo('test.php', '.', 'test.php') + ])); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(false); + } +} diff --git a/src/Task/PhpunitBridge.php b/src/Task/PhpunitBridge.php new file mode 100644 index 000000000..74e6f808b --- /dev/null +++ b/src/Task/PhpunitBridge.php @@ -0,0 +1,81 @@ +setDefaults([ + 'config_file' => null, + 'testsuite' => null, + 'group' => [], + 'always_execute' => false, + ]); + + $resolver->addAllowedTypes('config_file', ['null', 'string']); + $resolver->addAllowedTypes('testsuite', ['null', 'string']); + $resolver->addAllowedTypes('group', ['array']); + $resolver->addAllowedTypes('always_execute', ['bool']); + + return $resolver; + } + + /** + * {@inheritdoc} + */ + public function canRunInContext(ContextInterface $context) + { + return ($context instanceof GitPreCommitContext || $context instanceof RunContext); + } + + /** + * {@inheritdoc} + */ + public function run(ContextInterface $context) + { + $config = $this->getConfiguration(); + + $files = $context->getFiles()->name('*.php'); + if (0 === count($files) && !$config['always_execute']) { + return TaskResult::createSkipped($this, $context); + } + + $arguments = $this->processBuilder->createArgumentsForCommand('simple-phpunit'); + $arguments->addOptionalArgument('--configuration=%s', $config['config_file']); + $arguments->addOptionalArgument('--testsuite=%s', $config['testsuite']); + $arguments->addOptionalCommaSeparatedArgument('--group=%s', $config['group']); + + $process = $this->processBuilder->buildProcess($arguments); + $process->run(); + + if (!$process->isSuccessful()) { + return TaskResult::createFailed($this, $context, $this->formatter->format($process)); + } + + return TaskResult::createPassed($this, $context); + } +} From 744cb6831cc5a464f1c23e36d1bd37c73833d055 Mon Sep 17 00:00:00 2001 From: Lander Vanderstraeten Date: Tue, 30 Oct 2018 10:57:50 +0100 Subject: [PATCH 21/25] Add missing property --- doc/tasks/git_branch_name.md | 1 + spec/Task/Git/BranchNameSpec.php | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/tasks/git_branch_name.md b/doc/tasks/git_branch_name.md index 9ab7b6bf2..886df88d5 100644 --- a/doc/tasks/git_branch_name.md +++ b/doc/tasks/git_branch_name.md @@ -11,6 +11,7 @@ parameters: matchers: Branch name must contain JIRA issue number: /JIRA-\d+/ additional_modifiers: '' + allow_detached_head: true ``` **matchers** diff --git a/spec/Task/Git/BranchNameSpec.php b/spec/Task/Git/BranchNameSpec.php index bf24165df..bb06b14fa 100644 --- a/spec/Task/Git/BranchNameSpec.php +++ b/spec/Task/Git/BranchNameSpec.php @@ -36,6 +36,7 @@ function it_should_have_configurable_options() $options->shouldBeAnInstanceOf(OptionsResolver::class); $options->getDefinedOptions()->shouldContain('matchers'); $options->getDefinedOptions()->shouldContain('additional_modifiers'); + $options->getDefinedOptions()->shouldContain('allow_detached_head'); } function it_is_initializable() From db583c72ae7867a9469df9329b0ceb13d8e0bc57 Mon Sep 17 00:00:00 2001 From: Lander Vanderstraeten Date: Wed, 31 Oct 2018 09:27:17 +0100 Subject: [PATCH 22/25] Revert "Host our own version of the symfony XSDs" --- test/fixtures/linters/xml/xsd-url-invalid.xml | 2 +- test/fixtures/linters/xml/xsd-url-valid.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/linters/xml/xsd-url-invalid.xml b/test/fixtures/linters/xml/xsd-url-invalid.xml index dc8e8f7b3..feb75ef92 100644 --- a/test/fixtures/linters/xml/xsd-url-invalid.xml +++ b/test/fixtures/linters/xml/xsd-url-invalid.xml @@ -2,7 +2,7 @@ diff --git a/test/fixtures/linters/xml/xsd-url-valid.xml b/test/fixtures/linters/xml/xsd-url-valid.xml index 8932d18d6..7d1ea74d5 100644 --- a/test/fixtures/linters/xml/xsd-url-valid.xml +++ b/test/fixtures/linters/xml/xsd-url-valid.xml @@ -2,7 +2,7 @@ From 26eb82b42c8d90d5b94727322a29c8ee56dbcfd8 Mon Sep 17 00:00:00 2001 From: Jeffrey Cafferata Date: Wed, 14 Nov 2018 13:13:28 +0100 Subject: [PATCH 23/25] Add ignore patterns for PHPLint task --- doc/tasks/phplint.md | 7 +++++++ spec/Task/PhpLintSpec.php | 1 + src/Task/PhpLint.php | 8 +++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/tasks/phplint.md b/doc/tasks/phplint.md index 7a6e20086..8512c2cb1 100644 --- a/doc/tasks/phplint.md +++ b/doc/tasks/phplint.md @@ -17,6 +17,7 @@ parameters: phplint: exclude: [] jobs: ~ + ignore_patterns: [] triggered_by: ['php', 'phtml', 'php3', 'php4', 'php5'] ``` **exclude** @@ -34,6 +35,12 @@ The number of jobs you wish to use for parallel processing. If no number is given, it is left up to parallel-lint itself, which currently defaults to 10. +**ignore_patterns** + +*Default: []* + +This is a list of patterns that will be ignored by PHPLint. Leave this option blank to run PHPLint for every php file. + **trigered_by** *Default: ['php', 'phtml', 'php3', 'php4', 'php5']* diff --git a/spec/Task/PhpLintSpec.php b/spec/Task/PhpLintSpec.php index 649c71acb..dabcefbd1 100644 --- a/spec/Task/PhpLintSpec.php +++ b/spec/Task/PhpLintSpec.php @@ -41,6 +41,7 @@ function it_should_have_configurable_options() $options->shouldBeAnInstanceOf(OptionsResolver::class); $options->getDefinedOptions()->shouldContain('jobs'); $options->getDefinedOptions()->shouldContain('exclude'); + $options->getDefinedOptions()->shouldContain('ignore_patterns'); $options->getDefinedOptions()->shouldContain('triggered_by'); } diff --git a/src/Task/PhpLint.php b/src/Task/PhpLint.php index d35047513..b30cecf17 100644 --- a/src/Task/PhpLint.php +++ b/src/Task/PhpLint.php @@ -30,11 +30,13 @@ public function getConfigurableOptions() $resolver->setDefaults([ 'jobs' => null, 'exclude' => [], + 'ignore_patterns' => [], 'triggered_by' => ['php', 'phtml', 'php3', 'php4', 'php5'], ]); $resolver->setAllowedTypes('jobs', ['int', 'null']); $resolver->setAllowedTypes('exclude', 'array'); + $resolver->addAllowedTypes('ignore_patterns', ['array']); $resolver->setAllowedTypes('triggered_by', 'array'); return $resolver; @@ -54,7 +56,11 @@ public function canRunInContext(ContextInterface $context) public function run(ContextInterface $context) { $config = $this->getConfiguration(); - $files = $context->getFiles()->extensions($config['triggered_by']); + + $files = $context + ->getFiles() + ->notPaths($config['ignore_patterns']) + ->extensions($config['triggered_by']); $arguments = $this->processBuilder->createArgumentsForCommand('parallel-lint'); $arguments->add('--no-colors'); From 13119bc79815ce91247b531361f646347a7065d0 Mon Sep 17 00:00:00 2001 From: Vesquen Date: Thu, 29 Nov 2018 16:50:48 +0100 Subject: [PATCH 24/25] Added parameter to toggle show info (#573) * Added parameter to toggle show info * Removed duplicated config parameter --- doc/tasks/psalm.md | 7 +++++++ spec/Task/PsalmSpec.php | 1 + src/Task/Psalm.php | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/doc/tasks/psalm.md b/doc/tasks/psalm.md index 29e2d97a8..366924dc9 100644 --- a/doc/tasks/psalm.md +++ b/doc/tasks/psalm.md @@ -20,6 +20,7 @@ parameters: report: ~ threads: 1 triggered_by: ['php'] + show_info: false ``` @@ -65,3 +66,9 @@ This parameter defines on how many threads Psalm's analysis stage is ran. *Default: [php]* This is a list of extensions to be sniffed. + +**show_info** + +*Default: false* + +Show non-exception parser findings \ No newline at end of file diff --git a/spec/Task/PsalmSpec.php b/spec/Task/PsalmSpec.php index 48d4d557f..19aeaf852 100644 --- a/spec/Task/PsalmSpec.php +++ b/spec/Task/PsalmSpec.php @@ -46,6 +46,7 @@ function it_should_have_configurable_options() $options->getDefinedOptions()->shouldContain('report'); $options->getDefinedOptions()->shouldContain('threads'); $options->getDefinedOptions()->shouldContain('triggered_by'); + $options->getDefinedOptions()->shouldContain('show_info'); } function it_should_run_in_git_pre_commit_context(GitPreCommitContext $context) diff --git a/src/Task/Psalm.php b/src/Task/Psalm.php index a23765361..0f5c3cbd0 100644 --- a/src/Task/Psalm.php +++ b/src/Task/Psalm.php @@ -37,13 +37,16 @@ public function getConfigurableOptions() 'report' => null, 'threads' => null, 'triggered_by' => ['php'], + 'show_info' => false, ]); + $resolver->addAllowedTypes('config', ['null', 'string']); $resolver->addAllowedTypes('ignore_patterns', ['array']); $resolver->addAllowedTypes('no_cache', ['bool']); $resolver->addAllowedTypes('report', ['null', 'string']); $resolver->addAllowedTypes('threads', ['null', 'int']); $resolver->addAllowedTypes('triggered_by', ['array']); + $resolver->addAllowedTypes('show_info', ['bool']); return $resolver; } @@ -77,6 +80,7 @@ public function run(ContextInterface $context) $arguments->addOptionalArgument('--report=%s', $config['report']); $arguments->addOptionalArgument('--no-cache', $config['no_cache']); $arguments->addOptionalArgument('--threads=%d', $config['threads']); + $arguments->addOptionalBooleanArgument('--show-info=%s', $config['show_info'], 'true', 'false'); if ($context instanceof GitPreCommitContext) { $arguments->addFiles($files); From 6be5019a4bfed889f6776530256b006c27182a04 Mon Sep 17 00:00:00 2001 From: Lander Vanderstraeten Date: Fri, 30 Nov 2018 12:31:45 +0100 Subject: [PATCH 25/25] Add php unit bridge --- spec/Task/PhpunitBridgeSpec.php | 9 ++++++++- src/Task/PhpunitBridge.php | 26 +++++--------------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/spec/Task/PhpunitBridgeSpec.php b/spec/Task/PhpunitBridgeSpec.php index 68145817b..a69c72125 100644 --- a/spec/Task/PhpunitBridgeSpec.php +++ b/spec/Task/PhpunitBridgeSpec.php @@ -106,8 +106,15 @@ function it_runs_the_suite(ProcessBuilder $processBuilder, Process $process, Con $result->isPassed()->shouldBe(true); } - function it_throws_exception_if_the_process_fails(ProcessBuilder $processBuilder, Process $process, ContextInterface $context) + function it_throws_exception_if_the_process_fails( + ProcessBuilder $processBuilder, + Process $process, + ContextInterface $context, + ProcessFormatterInterface $formatter + ) { + $formatter->format($process)->willReturn('format string'); + $arguments = new ProcessArgumentsCollection(); $processBuilder->createArgumentsForCommand('simple-phpunit')->willReturn($arguments); $processBuilder->buildProcess($arguments)->willReturn($process); diff --git a/src/Task/PhpunitBridge.php b/src/Task/PhpunitBridge.php index 74e6f808b..10776b7ff 100644 --- a/src/Task/PhpunitBridge.php +++ b/src/Task/PhpunitBridge.php @@ -3,30 +3,20 @@ namespace GrumPHP\Task; use GrumPHP\Runner\TaskResult; +use GrumPHP\Runner\TaskResultInterface; use GrumPHP\Task\Context\ContextInterface; use GrumPHP\Task\Context\GitPreCommitContext; use GrumPHP\Task\Context\RunContext; use Symfony\Component\OptionsResolver\OptionsResolver; -/** - * PhpunitBridge task - * - * @link https://symfony.com/doc/current/components/phpunit_bridge.html - */ class PhpunitBridge extends AbstractExternalTask { - /** - * @return string - */ - public function getName() + public function getName(): string { return 'phpunitbridge'; } - /** - * @return OptionsResolver - */ - public function getConfigurableOptions() + public function getConfigurableOptions(): OptionsResolver { $resolver = new OptionsResolver(); $resolver->setDefaults([ @@ -44,18 +34,12 @@ public function getConfigurableOptions() return $resolver; } - /** - * {@inheritdoc} - */ - public function canRunInContext(ContextInterface $context) + public function canRunInContext(ContextInterface $context): bool { return ($context instanceof GitPreCommitContext || $context instanceof RunContext); } - /** - * {@inheritdoc} - */ - public function run(ContextInterface $context) + public function run(ContextInterface $context): TaskResultInterface { $config = $this->getConfiguration();