Skip to content

Commit e271af4

Browse files
committed
NodeCallbackInvoker
1 parent 0ec1862 commit e271af4

12 files changed

+162
-13
lines changed

src/Analyser/DirectInternalScopeFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Analyser;
44

5+
use PhpParser\Node;
56
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
67
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
78
use PHPStan\Node\Printer\ExprPrinter;
@@ -19,6 +20,7 @@ final class DirectInternalScopeFactory implements InternalScopeFactory
1920

2021
/**
2122
* @param int|array{min: int, max: int}|null $configPhpVersion
23+
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
2224
*/
2325
public function __construct(
2426
private ReflectionProvider $reflectionProvider,
@@ -34,6 +36,7 @@ public function __construct(
3436
private PhpVersion $phpVersion,
3537
private AttributeReflectionFactory $attributeReflectionFactory,
3638
private int|array|null $configPhpVersion,
39+
private $nodeCallback,
3740
private ConstantResolver $constantResolver,
3841
)
3942
{
@@ -75,6 +78,7 @@ public function create(
7578
$this->phpVersion,
7679
$this->attributeReflectionFactory,
7780
$this->configPhpVersion,
81+
$this->nodeCallback,
7882
$declareStrictTypes,
7983
$function,
8084
$namespace,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PhpParser\Node;
6+
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
7+
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
8+
use PHPStan\Node\Printer\ExprPrinter;
9+
use PHPStan\Parser\Parser;
10+
use PHPStan\Php\PhpVersion;
11+
use PHPStan\Reflection\AttributeReflectionFactory;
12+
use PHPStan\Reflection\InitializerExprTypeResolver;
13+
use PHPStan\Reflection\ReflectionProvider;
14+
use PHPStan\Rules\Properties\PropertyReflectionFinder;
15+
16+
final class DirectInternalScopeFactoryFactory implements InternalScopeFactoryFactory
17+
{
18+
19+
/**
20+
* @param int|array{min: int, max: int}|null $configPhpVersion
21+
*/
22+
public function __construct(
23+
private ReflectionProvider $reflectionProvider,
24+
private InitializerExprTypeResolver $initializerExprTypeResolver,
25+
private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
26+
private ExpressionTypeResolverExtensionRegistryProvider $expressionTypeResolverExtensionRegistryProvider,
27+
private ExprPrinter $exprPrinter,
28+
private TypeSpecifier $typeSpecifier,
29+
private PropertyReflectionFinder $propertyReflectionFinder,
30+
private Parser $parser,
31+
private NodeScopeResolver $nodeScopeResolver,
32+
private RicherScopeGetTypeHelper $richerScopeGetTypeHelper,
33+
private PhpVersion $phpVersion,
34+
private AttributeReflectionFactory $attributeReflectionFactory,
35+
private int|array|null $configPhpVersion,
36+
private ConstantResolver $constantResolver,
37+
)
38+
{
39+
}
40+
41+
/**
42+
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
43+
*/
44+
public function create(?callable $nodeCallback): DirectInternalScopeFactory
45+
{
46+
return new DirectInternalScopeFactory(
47+
$this->reflectionProvider,
48+
$this->initializerExprTypeResolver,
49+
$this->dynamicReturnTypeExtensionRegistryProvider,
50+
$this->expressionTypeResolverExtensionRegistryProvider,
51+
$this->exprPrinter,
52+
$this->typeSpecifier,
53+
$this->propertyReflectionFinder,
54+
$this->parser,
55+
$this->nodeScopeResolver,
56+
$this->richerScopeGetTypeHelper,
57+
$this->phpVersion,
58+
$this->attributeReflectionFactory,
59+
$this->configPhpVersion,
60+
$nodeCallback,
61+
$this->constantResolver,
62+
);
63+
}
64+
65+
}

src/Analyser/FileAnalyser.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ public function analyseFile(
9898
$parserNodes = $this->parser->parseFile($file);
9999
$linesToIgnore = $unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)];
100100
$temporaryFileErrors = [];
101-
$nodeCallback = function (Node $node, Scope $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors, $parserNodes): void {
101+
$nodeCallback = function (Node $node, $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors, $parserNodes): void {
102+
/** @var Scope&NodeCallbackInvoker $scope */
102103
if ($node instanceof Node\Stmt\Trait_) {
103104
foreach (array_keys($linesToIgnore[$file] ?? []) as $lineToIgnore) {
104105
if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) {
@@ -242,7 +243,7 @@ public function analyseFile(
242243
}
243244
};
244245

245-
$scope = $this->scopeFactory->create(ScopeContext::create($file));
246+
$scope = $this->scopeFactory->create(ScopeContext::create($file), $nodeCallback);
246247
$nodeCallback(new FileNode($parserNodes), $scope);
247248
$this->nodeScopeResolver->processNodes(
248249
$parserNodes,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PhpParser\Node;
6+
7+
interface InternalScopeFactoryFactory
8+
{
9+
10+
/**
11+
* @param callable(Node $node, Scope $scope): void $nodeCallback
12+
*/
13+
public function create(
14+
?callable $nodeCallback,
15+
): InternalScopeFactory;
16+
17+
}

src/Analyser/LazyInternalScopeFactory.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
namespace PHPStan\Analyser;
44

5-
use PHPStan\DependencyInjection\AutowiredService;
5+
use PhpParser\Node;
66
use PHPStan\DependencyInjection\Container;
7+
use PHPStan\DependencyInjection\GenerateFactory;
78
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
89
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
910
use PHPStan\Node\Printer\ExprPrinter;
@@ -15,15 +16,19 @@
1516
use PHPStan\Reflection\ReflectionProvider;
1617
use PHPStan\Rules\Properties\PropertyReflectionFinder;
1718

18-
#[AutowiredService(as: InternalScopeFactory::class)]
19+
#[GenerateFactory(interface: InternalScopeFactoryFactory::class, resultType: LazyInternalScopeFactory::class)]
1920
final class LazyInternalScopeFactory implements InternalScopeFactory
2021
{
2122

2223
/** @var int|array{min: int, max: int}|null */
2324
private int|array|null $phpVersion;
2425

26+
/**
27+
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
28+
*/
2529
public function __construct(
2630
private Container $container,
31+
private $nodeCallback,
2732
)
2833
{
2934
$this->phpVersion = $this->container->getParameter('phpVersion');
@@ -65,6 +70,7 @@ public function create(
6570
$this->container->getByType(PhpVersion::class),
6671
$this->container->getByType(AttributeReflectionFactory::class),
6772
$this->phpVersion,
73+
$this->nodeCallback,
6874
$declareStrictTypes,
6975
$function,
7076
$namespace,

src/Analyser/MutatingScope.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@
173173
use const PHP_INT_MAX;
174174
use const PHP_INT_MIN;
175175

176-
final class MutatingScope implements Scope
176+
final class MutatingScope implements Scope, NodeCallbackInvoker
177177
{
178178

179179
private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4;
@@ -200,6 +200,7 @@ final class MutatingScope implements Scope
200200

201201
/**
202202
* @param int|array{min: int, max: int}|null $configPhpVersion
203+
* @param callable(Node $node, Scope $scope): void|null $nodeCallback
203204
* @param array<string, ExpressionTypeHolder> $expressionTypes
204205
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
205206
* @param list<string> $inClosureBindScopeClasses
@@ -225,6 +226,7 @@ public function __construct(
225226
private PhpVersion $phpVersion,
226227
private AttributeReflectionFactory $attributeReflectionFactory,
227228
private int|array|null $configPhpVersion,
229+
private $nodeCallback = null,
228230
private bool $declareStrictTypes = false,
229231
private PhpFunctionFromParserNodeReflection|null $function = null,
230232
?string $namespace = null,
@@ -6456,4 +6458,14 @@ public function getPhpVersion(): PhpVersions
64566458
return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId()));
64576459
}
64586460

6461+
public function invokeNodeCallback(Node $node): void
6462+
{
6463+
$nodeCallback = $this->nodeCallback;
6464+
if ($nodeCallback === null) {
6465+
throw new ShouldNotHappenException('Node callback is not present in this scope');
6466+
}
6467+
6468+
$nodeCallback($node, $this);
6469+
}
6470+
64596471
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PhpParser\Node;
6+
7+
/**
8+
* The interface NodeCallbackInvoker can be typehinted in 2nd parameter of Rule::processNode():
9+
*
10+
* ```php
11+
* public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array
12+
* ```
13+
*
14+
* It can be used to invoke rules for virtual made-up nodes.
15+
*
16+
* For example: You're writing a rule for a method with declaration like:
17+
*
18+
* ```php
19+
* public static create(string $class, mixed ...$args)
20+
* ```
21+
*
22+
* And you'd like to check `Factory::create(Foo::class, 1, 2, 3)` as if it were
23+
* `new Foo(1, 2, 3)`.
24+
*
25+
* You can call `$scope->invokeNodeCallback(new New_(new Name($className), $args))`
26+
*
27+
* And PHPStan will call all the registered rules for New_, checking as if the instantiation
28+
* is actually in the code.
29+
*
30+
* @api
31+
*/
32+
interface NodeCallbackInvoker
33+
{
34+
35+
public function invokeNodeCallback(Node $node): void;
36+
37+
}

src/Analyser/ScopeFactory.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Analyser;
44

5+
use PhpParser\Node;
56
use PHPStan\DependencyInjection\AutowiredService;
67

78
/**
@@ -11,13 +12,16 @@
1112
final class ScopeFactory
1213
{
1314

14-
public function __construct(private InternalScopeFactory $internalScopeFactory)
15+
public function __construct(private InternalScopeFactoryFactory $internalScopeFactoryFactory)
1516
{
1617
}
1718

18-
public function create(ScopeContext $context): MutatingScope
19+
/**
20+
* @param callable(Node $node, Scope $scope): void $nodeCallback
21+
*/
22+
public function create(ScopeContext $context, ?callable $nodeCallback = null): MutatingScope
1923
{
20-
return $this->internalScopeFactory->create($context);
24+
return $this->internalScopeFactoryFactory->create($nodeCallback)->create($context);
2125
}
2226

2327
}

src/Rules/Playground/PromoteParameterRule.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Rules\Playground;
44

55
use PhpParser\Node;
6+
use PHPStan\Analyser\NodeCallbackInvoker;
67
use PHPStan\Analyser\Scope;
78
use PHPStan\DependencyInjection\Container;
89
use PHPStan\DependencyInjection\MissingServiceException;
@@ -87,7 +88,7 @@ private function getOriginalRule(): ?Rule
8788
return $this->originalRule = $originalRule;
8889
}
8990

90-
public function processNode(Node $node, Scope $scope): array
91+
public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array
9192
{
9293
if ($this->parameterValue) {
9394
return [];

src/Rules/Rule.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Rules;
44

55
use PhpParser\Node;
6+
use PHPStan\Analyser\NodeCallbackInvoker;
67
use PHPStan\Analyser\Scope;
78

89
/**
@@ -34,6 +35,6 @@ public function getNodeType(): string;
3435
* @param TNodeType $node
3536
* @return list<IdentifierRuleError>
3637
*/
37-
public function processNode(Node $node, Scope $scope): array;
38+
public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array;
3839

3940
}

0 commit comments

Comments
 (0)