Skip to content

Commit d13741b

Browse files
authored
add: Rule to inform user to follow silverstripe config doc practices (#8)
1 parent 7bb70cd commit d13741b

File tree

6 files changed

+162
-0
lines changed

6 files changed

+162
-0
lines changed

phpstan.neon

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ services:
8888
class: Syntro\SilverstripePHPStan\Type\HasMethodTypeSpecifyingExtension
8989
tags:
9090
- phpstan.typeSpecifier.methodTypeSpecifyingExtension
91+
# Rule informing the user to add the @config docblock
92+
-
93+
class: Syntro\SilverstripePHPStan\Rule\ReadWriteConfigPropertiesRule
94+
arguments:
95+
alwaysWrittenTags: %propertyAlwaysWrittenTags%
96+
alwaysReadTags: %propertyAlwaysReadTags%
97+
checkUninitializedProperties: %checkUninitializedProperties%
98+
tags:
99+
- phpstan.rules.rule
91100
# Special handling for configuration properties
92101
-
93102
class: Syntro\SilverstripePHPStan\Reflection\ReadWritePropertiesExtension
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Syntro\SilverstripePHPStan\Rule;
4+
5+
use PhpParser\Node;
6+
use PHPStan\ShouldNotHappenException;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Node\ClassPropertiesNode;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule;
11+
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
12+
use PHPStan\Type\MixedType;
13+
use PHPStan\Type\ObjectType;
14+
use PHPStan\Type\TypeUtils;
15+
use SilverStripe\Core\Config\Configurable;
16+
use SilverStripe\Core\Extension;
17+
18+
/**
19+
* Adds a rule to add a hint to never read, never written or unused properties,
20+
* instructing the user to add an "@config" docblock if necessary
21+
*/
22+
class ReadWriteConfigPropertiesRule extends UnusedPrivatePropertyRule /* @phpstan-ignore-line */
23+
{
24+
25+
public function processNode(Node $node, Scope $scope): array
26+
{
27+
$classReflection = $scope->getClassReflection();
28+
if ($classReflection->hasTraitUse(Configurable::class) || $classReflection->isSubclassOf(Extension::class)) {
29+
$errors = parent::processNode($node, $scope); /* @phpstan-ignore-line */
30+
$hints = [];
31+
/** @var \PHPStan\Rules\RuleErrors\RuleError11 $error */
32+
foreach ($errors as $error) {
33+
$message = $error->getMessage(); /* @phpstan-ignore-line */
34+
if (strpos($message, 'Static') !== 0 || strpos($message, 'never written, only read') !== false) {
35+
// NOTE(mleutenegger): 2022-04-30
36+
// In this case, the property wasn't static or the property
37+
// was never written, but only read via $this->property.
38+
// This should not actually happen with config properties.
39+
// In this case, we assume that the error is genuine and
40+
// dont print a hint. We might need to handle this case in
41+
// the future, when adding config rules.
42+
continue;
43+
}
44+
$nameFound = preg_match("/(\S+)::(\S+)/", $message, $names);
45+
if ($nameFound) {
46+
$tip = sprintf('See: %s', 'https://docs.silverstripe.org/en/4/developer_guides/configuration/configuration/');
47+
$hints[] = RuleErrorBuilder::message(sprintf('Have you forgotten to add "@config" for the property %s of the configurable class %s?', $names[2], $names[1]))->line($error->line)->tip($tip)->build();
48+
}
49+
}
50+
return $hints;
51+
}
52+
return [];
53+
}
54+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Syntro\SilverstripePHPStan\Tests\Rule\Data;
4+
5+
use SilverStripe\Core\Config\Configurable;
6+
7+
class Foo
8+
{
9+
use Configurable;
10+
11+
private static $this_should_be_config = 'a value';
12+
13+
/**
14+
* @config
15+
*/
16+
private static $this_is_config = 'another value';
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Syntro\SilverstripePHPStan\Tests\Rule\Data;
4+
5+
class NoFoo
6+
{
7+
private static $this_should_be_config = 'a value';
8+
9+
/**
10+
* @config
11+
*/
12+
private static $this_is_config = 'another value';
13+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Syntro\SilverstripePHPStan\Tests\Rule;
4+
5+
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
6+
use PHPStan\Rules\Properties\ReadWritePropertiesExtension;
7+
8+
class DirectReadWritePropertiesExtensionProvider implements ReadWritePropertiesExtensionProvider /* @phpstan-ignore-line */
9+
{
10+
11+
/**
12+
* @var ReadWritePropertiesExtension[]
13+
*/
14+
private $extensions;
15+
16+
/**
17+
* @param ReadWritePropertiesExtension[] $extensions
18+
*/
19+
public function __construct($extensions)
20+
{
21+
$this->extensions = $extensions;
22+
}
23+
24+
/**
25+
* @return ReadWritePropertiesExtension[]
26+
*/
27+
public function getExtensions(): array
28+
{
29+
return $this->extensions;
30+
}
31+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Syntro\SilverstripePHPStan\Tests\Rule;
4+
5+
use Syntro\SilverstripePHPStan\Rule\ReadWriteConfigPropertiesRule;
6+
use Syntro\SilverstripePHPStan\Reflection\ReadWritePropertiesExtension;
7+
use Syntro\SilverstripePHPStan\Tests\Rule\DirectReadWritePropertiesExtensionProvider;
8+
use PHPStan\Rules\Rule;
9+
10+
class ReadWriteConfigPropertiesRuleTest extends \PHPStan\Testing\RuleTestCase
11+
{
12+
13+
protected function getRule(): Rule
14+
{
15+
return new ReadWriteConfigPropertiesRule(
16+
new DirectReadWritePropertiesExtensionProvider([new ReadWritePropertiesExtension()]),
17+
[],
18+
[],
19+
true
20+
);
21+
}
22+
23+
public function testAddsHintsForConfigurable(): void
24+
{
25+
$this->analyse([__DIR__ . '/Data/ReadWritePropertiesConfig.php'], [
26+
[
27+
'Have you forgotten to add "@config" for the property $this_should_be_config of the configurable class Syntro\SilverstripePHPStan\Tests\Rule\Data\Foo?',
28+
11,
29+
'See: https://docs.silverstripe.org/en/4/developer_guides/configuration/configuration/',
30+
],
31+
]);
32+
}
33+
34+
public function testAddsNoHintsForNoConfigurable(): void
35+
{
36+
$this->analyse([__DIR__ . '/Data/ReadWritePropertiesNoConfig.php'], []);
37+
}
38+
}

0 commit comments

Comments
 (0)