Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions doc/tasks/phpmd3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# PhpMd

The PhpMd3 task will sniff your code for bad coding standards.

It is the same as the `phpmd` task, but provides compatibility with the `3.x-dev` branch.
It will be merged into the `phpmd` task when the 3.x becomes stable.

***Composer***

```
composer require --dev phpmd/phpmd:3.x-dev
```

***Config***

The task lives under the `phpmd3` namespace and has following configurable parameters:

```yaml
# grumphp.yml
grumphp:
tasks:
phpmd3:
whitelist_patterns: []
exclude: []
report_format: text
ruleset: ['phpmd.xml.dist']
triggered_by: ['php']
```

**whitelist_patterns**

*Default: []*

This is a list of regex patterns that will filter files to validate. With this option you can skip files like tests. This option is used in relation with the parameter `triggered_by`.
For example: whitelist files in `src/FolderA/` and `src/FolderB/` you can use
```yaml
whitelist_patterns:
- /^src\/FolderA\/(.*)/
- /^src\/FolderB\/(.*)/
```

**exclude**

*Default: []*

This is a list of patterns that will be ignored by phpmd. With this option you can skip directories like tests. Leave this option blank to run phpmd for every php file.

**report_format**

*Default: text*

This sets the output [renderer](https://phpmd.org/documentation/#renderers) of phpmd.
Available formats: ansi, text.

**ruleset**

*Default: [phpmd.xml.dist]*

With this parameter you will be able to configure the rule/rulesets you want to use. You can use the standard
sets provided by PhpMd or you can configure your own xml configuration as described in the [PhpMd Documentation](https://phpmd.org/documentation/creating-a-ruleset.html)

The full list of rules/rulesets can be found at [PhpMd Rules](https://phpmd.org/rules/index.html)

**triggered_by**

*Default: [php]*

This is a list of extensions to be sniffed.
7 changes: 7 additions & 0 deletions resources/config/tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ services:
tags:
- {name: grumphp.task, task: phpmd}

GrumPHP\Task\PhpMd3:
arguments:
- '@process_builder'
- '@formatter.raw_process'
tags:
- {name: grumphp.task, task: phpmd3}

GrumPHP\Task\PhpMnd:
arguments:
- '@process_builder'
Expand Down
97 changes: 97 additions & 0 deletions src/Task/PhpMd3.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

declare(strict_types=1);

namespace GrumPHP\Task;

use GrumPHP\Formatter\ProcessFormatterInterface;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* @extends AbstractExternalTask<ProcessFormatterInterface>
*/
class PhpMd3 extends AbstractExternalTask
{
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
'whitelist_patterns' => [],
'exclude' => [],
'report_format' => 'text',
'ruleset' => ['phpmd.xml.dist'],
'triggered_by' => ['php'],
]);

$resolver->addAllowedTypes('whitelist_patterns', ['array']);
$resolver->addAllowedTypes('exclude', ['array']);
$resolver->addAllowedTypes('report_format', ['string']);
$resolver->addAllowedValues('report_format', ['text', 'ansi']);
$resolver->addAllowedTypes('ruleset', ['array']);
$resolver->addAllowedTypes('triggered_by', ['array']);

return ConfigOptionsResolver::fromOptionsResolver($resolver);
}

/**
* {@inheritdoc}
*/
public function canRunInContext(ContextInterface $context): bool
{
return $context instanceof GitPreCommitContext || $context instanceof RunContext;
}

/**
* {@inheritdoc}
*/
public function run(ContextInterface $context): TaskResultInterface
{
$config = $this->getConfig()->getOptions();

$whitelistPatterns = $config['whitelist_patterns'];
$extensions = $config['triggered_by'];

$files = $context->getFiles();
if (\count($whitelistPatterns)) {
$files = $files->paths($whitelistPatterns);
}
$files = $files->extensions($extensions);

if (0 === \count($files)) {
return TaskResult::createSkipped($this, $context);
}

$arguments = $this->processBuilder->createArgumentsForCommand('phpmd');
$arguments->add('analyze');
$arguments->addOptionalArgument('--format=%s', $config['report_format']);

foreach ($config['ruleset'] as $ruleset) {
$arguments->addOptionalArgument('--ruleset=%s', $ruleset);
}

foreach ($config['exclude'] as $exclude) {
$arguments->addOptionalArgument('--exclude=%s', $exclude);
}

foreach ($extensions as $extension) {
$arguments->addOptionalArgument('--suffixes=%s', $extension);
}

$arguments->addFiles($files);

$process = $this->processBuilder->buildProcess($arguments);
$process->run();

if (!$process->isSuccessful()) {
return TaskResult::createFailed($this, $context, $this->formatter->format($process));
}

return TaskResult::createPassed($this, $context);
}
}
173 changes: 173 additions & 0 deletions test/Unit/Task/PhpMdTest3.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

declare(strict_types=1);

namespace GrumPHPTest\Unit\Task;

use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
use GrumPHP\Task\PhpMd3;
use GrumPHP\Task\TaskInterface;
use GrumPHP\Test\Task\AbstractExternalTaskTestCase;

class PhpMdTest3 extends AbstractExternalTaskTestCase
{
protected function provideTask(): TaskInterface
{
return new PhpMd3(
$this->processBuilder->reveal(),
$this->formatter->reveal()
);
}

public static function provideConfigurableOptions(): iterable
{
yield 'defaults' => [
[],
[
'whitelist_patterns' => [],
'exclude' => [],
'report_format' => 'text',
'ruleset' => ['phpmd.xml.dist'],
'triggered_by' => ['php'],
]
];

yield 'invalidcase' => [
[
'whitelist_patterns' => 'thisisnotanarray'
],
null
];
}

public static function provideRunContexts(): iterable
{
yield 'run-context' => [
true,
self::mockContext(RunContext::class)
];

yield 'pre-commit-context' => [
true,
self::mockContext(GitPreCommitContext::class)
];

yield 'other' => [
false,
self::mockContext()
];
}

public static function provideFailsOnStuff(): iterable
{
yield 'exitCode1' => [
[],
self::mockContext(RunContext::class, ['hello.php']),
function () {
$this->mockProcessBuilder('phpmd', $process = self::mockProcess(1));
$this->formatter->format($process)->willReturn('nope');
},
'nope'
];
}

public static function providePassesOnStuff(): iterable
{
yield 'exitCode0' => [
[],
self::mockContext(RunContext::class, ['hello.php']),
function () {
$this->mockProcessBuilder('phpmd', self::mockProcess(0));
}
];
}

public static function provideSkipsOnStuff(): iterable
{
yield 'no-files' => [
[],
self::mockContext(RunContext::class),
function () {}
];
yield 'no-files-after-triggered-by' => [
[],
self::mockContext(RunContext::class, ['notaphpfile.txt']),
function () {}
];
yield 'no-files-after-whitelist' => [
[
'whitelist_patterns' => ['src/'],
],
self::mockContext(RunContext::class, ['test/file.php']),
function () {}
];
}

public static function provideExternalTaskRuns(): iterable
{
yield 'defaults' => [
[],
self::mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'phpmd',
[
'analyze',
'--format=text',
'--ruleset=phpmd.xml.dist',
'--suffixes=php',
'hello.php',
'hello2.php',
]
];

yield 'excludes' => [
[
'exclude' => ['hello.php', 'hello2.php'],
],
self::mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'phpmd',
[
'analyze',
'--format=text',
'--ruleset=phpmd.xml.dist',
'--exclude=hello.php',
'--exclude=hello2.php',
'--suffixes=php',
'hello.php',
'hello2.php',
]
];

yield 'rulesets' => [
[
'ruleset' => ['cleancode'],
],
self::mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'phpmd',
[
'analyze',
'--format=text',
'--ruleset=cleancode',
'--suffixes=php',
'hello.php',
'hello2.php',
]
];

yield 'report_formats' => [
[
'report_format' => 'ansi',
],
self::mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'phpmd',
[
'analyze',
'--format=ansi',
'--ruleset=phpmd.xml.dist',
'--suffixes=php',
'hello.php',
'hello2.php',
]
];
}
}