Skip to content

Commit 21745de

Browse files
committed
Revamp Factories return type extension and argument type rule
1 parent 65ecfc0 commit 21745de

16 files changed

Lines changed: 141 additions & 365 deletions

docs/rules.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ using the `new` keyword. Instead, it directs users to use one of their named con
2323

2424
## Functions
2525

26+
### FactoriesFunctionArgumentTypeRule
27+
28+
**Class:** `CodeIgniter\PHPStan\Rules\Functions\FactoriesFunctionArgumentTypeRule`<br/>
29+
Fixable: No
30+
31+
This rule validates the class string passed to either the `config()` or `model()` function. It reports
32+
a passed string that does not resolve to a known class, and, when enabled, a resolved class that does not
33+
extend `CodeIgniter\Config\BaseConfig` or `CodeIgniter\Model` respectively. The rule is registered only
34+
when `codeigniter.checkArgumentTypeOfFactories` is `true`, and the per-function instance checks are
35+
toggled by `codeigniter.checkArgumentTypeOfConfig` and `codeigniter.checkArgumentTypeOfModel`.
36+
2637
### ServicesFunctionArgumentTypeRule
2738

2839
**Class:** `CodeIgniter\PHPStan\Rules\Functions\ServicesFunctionArgumentTypeRule`<br/>

extension.neon

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,31 @@ parameters:
66
universalObjectCratesClasses:
77
- CodeIgniter\Entity\Entity
88
codeigniter:
9+
additionalConfigNamespaces:
10+
- CodeIgniter\Config\
11+
additionalModelNamespaces: []
912
additionalServices: []
13+
checkArgumentTypeOfConfig: true
14+
checkArgumentTypeOfModel: true
15+
checkArgumentTypeOfFactories: true
1016

1117
parametersSchema:
1218
codeigniter: structure([
19+
additionalConfigNamespaces: listOf(string())
20+
additionalModelNamespaces: listOf(string())
1321
additionalServices: listOf(string())
22+
checkArgumentTypeOfConfig: bool()
23+
checkArgumentTypeOfModel: bool()
24+
checkArgumentTypeOfFactories: bool()
1425
])
1526

1627
services:
28+
factoriesReturnTypeHelper:
29+
class: CodeIgniter\PHPStan\Helpers\FactoriesReturnTypeHelper
30+
arguments:
31+
additionalConfigNamespaces: %codeigniter.additionalConfigNamespaces%
32+
additionalModelNamespaces: %codeigniter.additionalModelNamespaces%
33+
1734
reflectionHelperPrivateInvokerHelper:
1835
class: CodeIgniter\PHPStan\Helpers\ReflectionHelperPrivateInvokerHelper
1936

@@ -49,6 +66,11 @@ services:
4966
tags:
5067
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
5168

69+
-
70+
class: CodeIgniter\PHPStan\Type\FactoriesFunctionReturnTypeExtension
71+
tags:
72+
- phpstan.broker.dynamicFunctionReturnTypeExtension
73+
5274
-
5375
class: CodeIgniter\PHPStan\Type\ServicesFunctionReturnTypeExtension
5476
tags:
@@ -64,6 +86,16 @@ services:
6486
tags:
6587
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
6688

89+
-
90+
class: CodeIgniter\PHPStan\Rules\Functions\FactoriesFunctionArgumentTypeRule
91+
arguments:
92+
checkArgumentTypeOfConfig: %codeigniter.checkArgumentTypeOfConfig%
93+
checkArgumentTypeOfModel: %codeigniter.checkArgumentTypeOfModel%
94+
95+
conditionalTags:
96+
CodeIgniter\PHPStan\Rules\Functions\FactoriesFunctionArgumentTypeRule:
97+
phpstan.rules.rule: %codeigniter.checkArgumentTypeOfFactories%
98+
6799
rules:
68100
- CodeIgniter\PHPStan\Rules\Classes\FrameworkExceptionInstantiationRule
69101
- CodeIgniter\PHPStan\Rules\Superglobals\SuperglobalsGlobalAssignRule
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* the LICENSE file that was distributed with this source code.
1212
*/
1313

14-
namespace CodeIgniter\PHPStan\Type;
14+
namespace CodeIgniter\PHPStan\Helpers;
1515

1616
use PHPStan\Reflection\ReflectionProvider;
1717
use PHPStan\Type\IntersectionType;
@@ -34,10 +34,7 @@ final class FactoriesReturnTypeHelper
3434
/**
3535
* @var array<string, list<string>>
3636
*/
37-
private array $additionalNamespacesMap = [
38-
'config' => [],
39-
'model' => [],
40-
];
37+
private array $additionalNamespacesMap;
4138

4239
/**
4340
* @param list<string> $additionalConfigNamespaces
@@ -51,8 +48,8 @@ public function __construct(
5148
$cb = static fn (string $item): string => rtrim($item, '\\') . '\\';
5249

5350
$this->additionalNamespacesMap = [
54-
'config' => [...$this->additionalNamespacesMap['config'], ...array_map($cb, $additionalConfigNamespaces)],
55-
'model' => [...$this->additionalNamespacesMap['model'], ...array_map($cb, $additionalModelNamespaces)],
51+
'config' => array_map($cb, $additionalConfigNamespaces),
52+
'model' => array_map($cb, $additionalModelNamespaces),
5653
];
5754
}
5855

src/Rules/Functions/FactoriesFunctionArgumentTypeRule.php

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
use CodeIgniter\Config\BaseConfig;
1717
use CodeIgniter\Model;
18-
use CodeIgniter\PHPStan\Type\FactoriesReturnTypeHelper;
18+
use CodeIgniter\PHPStan\Helpers\FactoriesReturnTypeHelper;
1919
use PhpParser\Node;
2020
use PHPStan\Analyser\Scope;
2121
use PHPStan\Reflection\ParametersAcceptorSelector;
@@ -41,7 +41,7 @@ final class FactoriesFunctionArgumentTypeRule implements Rule
4141
/**
4242
* @var array<string, bool>
4343
*/
44-
private array $argumentTypeCheck = [];
44+
private array $argumentTypeCheck;
4545

4646
public function __construct(
4747
private readonly ReflectionProvider $reflectionProvider,
@@ -85,47 +85,48 @@ public function processNode(Node $node, Scope $scope): array
8585
return []; // caught elsewhere
8686
}
8787

88-
$returnType = $this->factoriesReturnTypeHelper->check($nameType, $function);
89-
88+
$returnType = $this->factoriesReturnTypeHelper->check($nameType, $function);
9089
$firstParameter = ParametersAcceptorSelector::selectFromArgs(
9190
$scope,
92-
$node->getArgs(),
91+
$args,
9392
$this->reflectionProvider->getFunction($nameNode, $scope)->getVariants(),
9493
)->getParameters()[0];
9594

9695
if ($returnType->isNull()->yes()) {
97-
$addTip = static function (RuleErrorBuilder $ruleErrorBuilder) use ($nameType, $function): RuleErrorBuilder {
98-
foreach ($nameType->getConstantStrings() as $constantStringType) {
99-
$ruleErrorBuilder->addTip(sprintf(
100-
'If %s is a valid class string, you can add its possible namespace(s) in <fg=cyan>codeigniter.additional%sNamespaces</> in your <fg=yellow>%%configurationFile%%</>.',
101-
$constantStringType->describe(VerbosityLevel::precise()),
102-
ucfirst($function),
103-
));
104-
}
105-
106-
return $ruleErrorBuilder;
107-
};
108-
109-
return [$addTip(RuleErrorBuilder::message(sprintf(
96+
$ruleErrorBuilder = RuleErrorBuilder::message(sprintf(
11097
'Parameter #1 $%s of function %s expects a valid class string, %s given.',
11198
$firstParameter->getName(),
11299
$function,
113100
$nameType->describe(VerbosityLevel::precise()),
114-
)))->identifier(sprintf('codeigniter.%sArgumentType', $function))->build()];
101+
))->identifier(sprintf('codeigniter.%sArgumentType', $function));
102+
103+
foreach ($nameType->getConstantStrings() as $constantStringType) {
104+
$ruleErrorBuilder->addTip(sprintf(
105+
'If %s is a valid class string, you can add its possible namespace(s) in <fg=cyan>codeigniter.additional%sNamespaces</> in your <fg=yellow>%%configurationFile%%</>.',
106+
$constantStringType->describe(VerbosityLevel::precise()),
107+
ucfirst($function),
108+
));
109+
}
110+
111+
return [$ruleErrorBuilder->build()];
115112
}
116113

117114
if (! (new ObjectType($this->instanceofMap[$function]))->isSuperTypeOf($returnType)->yes()) {
118115
if (! $this->argumentTypeCheck[$function]) {
119116
return [];
120117
}
121118

122-
return [RuleErrorBuilder::message(sprintf(
123-
'Argument #1 $%s (%s) passed to function %s does not extend %s.',
124-
$firstParameter->getName(),
125-
$nameType->describe(VerbosityLevel::precise()),
126-
$function,
127-
addcslashes($this->instanceofMap[$function], '\\'),
128-
))->identifier(sprintf('codeigniter.%sArgumentInstanceof', $function))->build()];
119+
return [
120+
RuleErrorBuilder::message(sprintf(
121+
'Argument #1 $%s (%s) passed to function %s does not extend %s.',
122+
$firstParameter->getName(),
123+
$nameType->describe(VerbosityLevel::precise()),
124+
$function,
125+
$this->instanceofMap[$function],
126+
))
127+
->identifier(sprintf('codeigniter.%sArgumentInstanceof', $function))
128+
->build(),
129+
];
129130
}
130131

131132
return [];

src/Rules/Functions/NoClassConstFetchOnFactoriesFunctions.php

Lines changed: 0 additions & 131 deletions
This file was deleted.

src/Type/FactoriesFunctionReturnTypeExtension.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\PHPStan\Type;
1515

16+
use CodeIgniter\PHPStan\Helpers\FactoriesReturnTypeHelper;
1617
use PhpParser\Node\Expr\FuncCall;
1718
use PHPStan\Analyser\Scope;
1819
use PHPStan\Reflection\FunctionReflection;
@@ -32,14 +33,14 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
3233

3334
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
3435
{
35-
$arguments = $functionCall->getArgs();
36+
$args = $functionCall->getArgs();
3637

37-
if ($arguments === []) {
38+
if ($args === []) {
3839
return null;
3940
}
4041

4142
return $this->factoriesReturnTypeHelper->check(
42-
$scope->getType($arguments[0]->value),
43+
$scope->getType($args[0]->value),
4344
$functionReflection->getName(),
4445
);
4546
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* the LICENSE file that was distributed with this source code.
1212
*/
1313

14-
namespace CodeIgniter\PHPStan\Tests\Fixtures\Type;
14+
namespace CodeIgniter\PHPStan\Tests\Fixtures;
1515

1616
use CodeIgniter\Model;
1717

0 commit comments

Comments
 (0)