Skip to content

Commit d58806c

Browse files
authored
Can disallow control structures like else, elseif, goto (#257)
Checking params inside ( ... ) doesn't work at the moment, so you can disallow all `declare()`s but can't re-allow e.g. `declare(strict-types = 1)`. If you try to disallow `else if` with the space, an exception will be thrown, because `else if` is parsed as `else` followed by `if`, so disallowing `else if` with the space wouldn't have the desired effect and the result would be unexpected. Close #68
2 parents 6d5ce7e + 5b5bef6 commit d58806c

38 files changed

+2602
-1
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
},
4141
"scripts": {
4242
"lint": "vendor/bin/parallel-lint --colors src/ tests/",
43-
"lint-7.x": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/functionCallsNamedParams.php --exclude tests/src/disallowed-allow/functionCallsNamedParams.php --exclude tests/src/disallowed/attributeUsages.php --exclude tests/src/disallowed-allow/attributeUsages.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php --exclude tests/src/Enums.php",
43+
"lint-7.x": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/functionCallsNamedParams.php --exclude tests/src/disallowed-allow/functionCallsNamedParams.php --exclude tests/src/disallowed/attributeUsages.php --exclude tests/src/disallowed-allow/attributeUsages.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php --exclude tests/src/Enums.php --exclude tests/src/disallowed/controlStructures.php --exclude tests/src/disallowed-allow/controlStructures.php",
4444
"lint-8.0": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php --exclude tests/src/Enums.php",
4545
"lint-8.1": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php",
4646
"lint-8.2": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php",

docs/custom-rules.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ There are several different types (and configuration keys) that can be disallowe
1010
6. `disallowedSuperglobals` - for usages of superglobal variables like `$GLOBALS` or `$_POST`
1111
7. `disallowedAttributes` - for attributes like `#[Entity(class: Foo::class, something: true)]`
1212
8. `disallowedEnums` - for enums, both pure & backed, like `Suit::Hearts` (like class constants, enums need to be split to `enum: Suit` & `case: Hearts` in the configuration, see notes below)
13+
9. `disallowedControlStructures` - for control structures like `if`, `else`, `elseif`, loops, `require` & `include`, and `goto`
1314

1415
Use them to add rules to your `phpstan.neon` config file. I like to use a separate file (`disallowed-calls.neon`) for these which I'll include later on in the main `phpstan.neon` config file. Here's an example, update to your needs:
1516

@@ -165,10 +166,36 @@ You can treat some language constructs as functions and disallow it in `disallow
165166

166167
To disallow naive object creation (`new ClassName()` or `new $classname`), disallow `NameSpace\ClassName::__construct` in `disallowedMethodCalls`. Works even when there's no constructor defined in that class.
167168

169+
You can also disallow control structures, see below.
170+
168171
### Constants
169172

170173
When [disallowing constants](disallowing-constants.md) please be aware of limitations and special requirements, see [docs](disallowing-constants.md).
171174

172175
### Enums
173176

174177
Similar to disallowing constants, enums have some limitations, see [docs](disallowing-enums.md).
178+
179+
### Control structures
180+
181+
You can forbid [control structures](https://www.php.net/language.control-structures) like `if`, `else`, `elseif` (don't write it as `else if` because `else if` is parsed as `else` followed by `if` producing unexpected results), loops, `break`, `continue`, `goto`, `require`, `include` (and the `_once` variants).
182+
183+
Currently, parameters are not checked, so it's not possible to disallow for example `declare`, but re-allow `declare(strict-types = 1)`.
184+
185+
You can use `controlStructure` or `structure` directives, and both can be specified as arrays:
186+
```neon
187+
parameters:
188+
disallowedControlStructures:
189+
-
190+
controlStructure:
191+
- 'elseif'
192+
- 'return'
193+
allowIn:
194+
- 'tests/foo/bar.php'
195+
-
196+
structure: 'goto'
197+
message: "restructure the program's flow"
198+
errorTip: 'https://xkcd.com/292/'
199+
allowIn:
200+
- 'tests/waldo.php'
201+
```

extension.neon

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ parameters:
1010
disallowedEnums: []
1111
disallowedSuperglobals: []
1212
disallowedAttributes: []
13+
disallowedControlStructures: []
1314

1415
parametersSchema:
1516
allowInRootDir: schema(string(), nullable())
@@ -224,13 +225,26 @@ parametersSchema:
224225
?errorTip: string(),
225226
])
226227
)
228+
disallowedControlStructures: listOf(
229+
structure([
230+
?controlStructure: anyOf(string(), listOf(string())),
231+
?structure: anyOf(string(), listOf(string())),
232+
?message: string(),
233+
?allowIn: listOf(string()),
234+
?allowExceptIn: list(string()),
235+
?disallowIn: list(string()),
236+
?errorIdentifier: string(),
237+
?errorTip: string(),
238+
])
239+
)
227240

228241
services:
229242
- Spaze\PHPStan\Rules\Disallowed\Allowed\Allowed
230243
- Spaze\PHPStan\Rules\Disallowed\Allowed\AllowedPath
231244
- Spaze\PHPStan\Rules\Disallowed\DisallowedAttributeFactory
232245
- Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory
233246
- Spaze\PHPStan\Rules\Disallowed\DisallowedConstantFactory
247+
- Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory
234248
- Spaze\PHPStan\Rules\Disallowed\DisallowedNamespaceFactory
235249
- Spaze\PHPStan\Rules\Disallowed\DisallowedSuperglobalFactory
236250
- Spaze\PHPStan\Rules\Disallowed\File\FilePath(rootDir: %filesRootDir%)
@@ -239,6 +253,7 @@ services:
239253
- Spaze\PHPStan\Rules\Disallowed\Normalizer\Normalizer
240254
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedAttributeRuleErrors
241255
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedConstantRuleErrors
256+
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedControlStructureRuleErrors
242257
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedMethodRuleErrors
243258
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedNamespaceRuleErrors
244259
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedCallsRuleErrors
@@ -312,3 +327,63 @@ services:
312327
factory: Spaze\PHPStan\Rules\Disallowed\Usages\AttributeUsages(disallowedAttributes: %disallowedAttributes%)
313328
tags:
314329
- phpstan.rules.rule
330+
-
331+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\BreakControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
332+
tags:
333+
- phpstan.rules.rule
334+
-
335+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\ContinueControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
336+
tags:
337+
- phpstan.rules.rule
338+
-
339+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\DeclareControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
340+
tags:
341+
- phpstan.rules.rule
342+
-
343+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\DoWhileControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
344+
tags:
345+
- phpstan.rules.rule
346+
-
347+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\ElseControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
348+
tags:
349+
- phpstan.rules.rule
350+
-
351+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\ElseIfControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
352+
tags:
353+
- phpstan.rules.rule
354+
-
355+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\ForControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
356+
tags:
357+
- phpstan.rules.rule
358+
-
359+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\ForeachControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
360+
tags:
361+
- phpstan.rules.rule
362+
-
363+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\GotoControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
364+
tags:
365+
- phpstan.rules.rule
366+
-
367+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\IfControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
368+
tags:
369+
- phpstan.rules.rule
370+
-
371+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\MatchControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
372+
tags:
373+
- phpstan.rules.rule
374+
-
375+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\RequireIncludeControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
376+
tags:
377+
- phpstan.rules.rule
378+
-
379+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\ReturnControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
380+
tags:
381+
- phpstan.rules.rule
382+
-
383+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\SwitchControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
384+
tags:
385+
- phpstan.rules.rule
386+
-
387+
factory: Spaze\PHPStan\Rules\Disallowed\ControlStructures\WhileControlStructure(disallowedControlStructures: @Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructureFactory::getDisallowedControlStructures(%disallowedControlStructures%))
388+
tags:
389+
- phpstan.rules.rule
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Spaze\PHPStan\Rules\Disallowed\ControlStructures;
5+
6+
use PhpParser\Node;
7+
use PhpParser\Node\Stmt\Break_;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleError;
11+
use PHPStan\ShouldNotHappenException;
12+
use Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructure;
13+
use Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedControlStructureRuleErrors;
14+
15+
/**
16+
* Reports on using the break control structure.
17+
*
18+
* @package Spaze\PHPStan\Rules\Disallowed
19+
* @implements Rule<Break_>
20+
*/
21+
class BreakControlStructure implements Rule
22+
{
23+
24+
/** @var DisallowedControlStructureRuleErrors */
25+
private $disallowedControlStructureRuleErrors;
26+
27+
/** @var list<DisallowedControlStructure> */
28+
private $disallowedControlStructures;
29+
30+
31+
/**
32+
* @param DisallowedControlStructureRuleErrors $disallowedControlStructureRuleErrors
33+
* @param list<DisallowedControlStructure> $disallowedControlStructures
34+
*/
35+
public function __construct(DisallowedControlStructureRuleErrors $disallowedControlStructureRuleErrors, array $disallowedControlStructures)
36+
{
37+
$this->disallowedControlStructureRuleErrors = $disallowedControlStructureRuleErrors;
38+
$this->disallowedControlStructures = $disallowedControlStructures;
39+
}
40+
41+
42+
public function getNodeType(): string
43+
{
44+
return Break_::class;
45+
}
46+
47+
48+
/**
49+
* @param Break_ $node
50+
* @param Scope $scope
51+
* @return list<RuleError>
52+
* @throws ShouldNotHappenException
53+
*/
54+
public function processNode(Node $node, Scope $scope): array
55+
{
56+
return $this->disallowedControlStructureRuleErrors->get($scope, 'break', $this->disallowedControlStructures);
57+
}
58+
59+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Spaze\PHPStan\Rules\Disallowed\ControlStructures;
5+
6+
use PhpParser\Node;
7+
use PhpParser\Node\Stmt\Continue_;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleError;
11+
use PHPStan\ShouldNotHappenException;
12+
use Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructure;
13+
use Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedControlStructureRuleErrors;
14+
15+
/**
16+
* Reports on using the continue control structure.
17+
*
18+
* @package Spaze\PHPStan\Rules\Disallowed
19+
* @implements Rule<Continue_>
20+
*/
21+
class ContinueControlStructure implements Rule
22+
{
23+
24+
/** @var DisallowedControlStructureRuleErrors */
25+
private $disallowedControlStructureRuleErrors;
26+
27+
/** @var list<DisallowedControlStructure> */
28+
private $disallowedControlStructures;
29+
30+
31+
/**
32+
* @param DisallowedControlStructureRuleErrors $disallowedControlStructureRuleErrors
33+
* @param list<DisallowedControlStructure> $disallowedControlStructures
34+
*/
35+
public function __construct(DisallowedControlStructureRuleErrors $disallowedControlStructureRuleErrors, array $disallowedControlStructures)
36+
{
37+
$this->disallowedControlStructureRuleErrors = $disallowedControlStructureRuleErrors;
38+
$this->disallowedControlStructures = $disallowedControlStructures;
39+
}
40+
41+
42+
public function getNodeType(): string
43+
{
44+
return Continue_::class;
45+
}
46+
47+
48+
/**
49+
* @param Continue_ $node
50+
* @param Scope $scope
51+
* @return list<RuleError>
52+
* @throws ShouldNotHappenException
53+
*/
54+
public function processNode(Node $node, Scope $scope): array
55+
{
56+
return $this->disallowedControlStructureRuleErrors->get($scope, 'continue', $this->disallowedControlStructures);
57+
}
58+
59+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Spaze\PHPStan\Rules\Disallowed\ControlStructures;
5+
6+
use PhpParser\Node;
7+
use PhpParser\Node\Stmt\Declare_;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleError;
11+
use PHPStan\ShouldNotHappenException;
12+
use Spaze\PHPStan\Rules\Disallowed\DisallowedControlStructure;
13+
use Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedControlStructureRuleErrors;
14+
15+
/**
16+
* Reports on using the declare control structure.
17+
*
18+
* @package Spaze\PHPStan\Rules\Disallowed
19+
* @implements Rule<Declare_>
20+
*/
21+
class DeclareControlStructure implements Rule
22+
{
23+
24+
/** @var DisallowedControlStructureRuleErrors */
25+
private $disallowedControlStructureRuleErrors;
26+
27+
/** @var list<DisallowedControlStructure> */
28+
private $disallowedControlStructures;
29+
30+
31+
/**
32+
* @param DisallowedControlStructureRuleErrors $disallowedControlStructureRuleErrors
33+
* @param list<DisallowedControlStructure> $disallowedControlStructures
34+
*/
35+
public function __construct(DisallowedControlStructureRuleErrors $disallowedControlStructureRuleErrors, array $disallowedControlStructures)
36+
{
37+
$this->disallowedControlStructureRuleErrors = $disallowedControlStructureRuleErrors;
38+
$this->disallowedControlStructures = $disallowedControlStructures;
39+
}
40+
41+
42+
public function getNodeType(): string
43+
{
44+
return Declare_::class;
45+
}
46+
47+
48+
/**
49+
* @param Declare_ $node
50+
* @param Scope $scope
51+
* @return list<RuleError>
52+
* @throws ShouldNotHappenException
53+
*/
54+
public function processNode(Node $node, Scope $scope): array
55+
{
56+
return $this->disallowedControlStructureRuleErrors->get($scope, 'declare', $this->disallowedControlStructures);
57+
}
58+
59+
}

0 commit comments

Comments
 (0)