22
33namespace mglaman \PHPStanDrupal \Rules \Classes ;
44
5+ use Drupal \Component \Plugin \PluginManagerInterface ;
6+ use Drupal \Core \Plugin \DefaultPluginManager ;
57use PhpParser \Node ;
8+ use PhpParser \NodeFinder ;
69use PHPStan \Analyser \Scope ;
710use PHPStan \Reflection \ReflectionProvider ;
811use PHPStan \Rules \Rule ;
12+ use PHPStan \Rules \RuleErrorBuilder ;
913use PHPStan \Type \ObjectType ;
1014use function sprintf ;
1115
@@ -35,80 +39,78 @@ public function processNode(Node $node, Scope $scope): array
3539 if ($ node ->extends === null ) {
3640 return [];
3741 }
38- $ className = (string ) $ node ->namespacedName ;
39- $ pluginManagerType = new ObjectType ($ className );
40- $ pluginManagerInterfaceType = new ObjectType ('\Drupal\Component\Plugin\PluginManagerInterface ' );
42+ if (str_contains ($ node ->namespacedName ->toLowerString (), 'test ' )) {
43+ return [];
44+ }
45+
46+ $ pluginManagerType = $ scope ->resolveTypeByName ($ node ->namespacedName );
47+ $ pluginManagerInterfaceType = new ObjectType (PluginManagerInterface::class);
4148 if (!$ pluginManagerInterfaceType ->isSuperTypeOf ($ pluginManagerType )->yes ()) {
4249 return [];
4350 }
51+ $ defaultPluginManager = new ObjectType (DefaultPluginManager::class);
52+ if ($ defaultPluginManager ->equals ($ pluginManagerType )) {
53+ return [];
54+ }
55+
56+ $ constructorMethodNode = (new NodeFinder ())->findFirst ($ node ->stmts , static function (Node $ node ) {
57+ return $ node instanceof Node \Stmt \ClassMethod && $ node ->name ->toString () === '__construct ' ;
58+ });
59+ if (!$ constructorMethodNode instanceof Node \Stmt \ClassMethod) {
60+ return [];
61+ }
4462
4563 $ errors = [];
4664 if ($ this ->isYamlDiscovery ($ node )) {
47- $ errors = $ this ->inspectYamlPluginManager ($ node );
65+ $ errors = $ this ->inspectYamlPluginManager ($ node, $ constructorMethodNode );
4866 } else {
4967 // @todo inspect annotated plugin managers.
5068 }
5169
52- $ hasAlterInfoSet = false ;
53-
54- foreach ($ node ->stmts as $ stmt ) {
55- if ($ stmt instanceof Node \Stmt \ClassMethod && $ stmt ->name ->toString () === '__construct ' ) {
56- foreach ($ stmt ->stmts ?? [] as $ statement ) {
57- if ($ statement instanceof Node \Stmt \Expression) {
58- $ statement = $ statement ->expr ;
59- }
60- if ($ statement instanceof Node \Expr \MethodCall
61- && $ statement ->name instanceof Node \Identifier
62- && $ statement ->name ->name === 'alterInfo ' ) {
63- $ hasAlterInfoSet = true ;
64- }
65- }
66- }
67- }
70+ $ alterInfoMethodNode = (new NodeFinder ())->findFirst ($ constructorMethodNode ->stmts ?? [], static function (Node $ node ) {
71+ return $ node instanceof Node \Stmt \Expression
72+ && $ node ->expr instanceof Node \Expr \MethodCall
73+ && $ node ->expr ->name instanceof Node \Identifier
74+ && $ node ->expr ->name ->toString () === 'alterInfo ' ;
75+ });
6876
69- if (!$ hasAlterInfoSet ) {
70- $ errors [] = 'Plugin definitions cannot be altered. ' ;
77+ if ($ alterInfoMethodNode === null ) {
78+ $ errors [] = RuleErrorBuilder::message (
79+ 'Plugin managers should call alterInfo to allow plugin definitions to be altered. '
80+ )
81+ ->identifier ('plugin.manager.alterInfoMissing ' )
82+ ->tip ('For example, to invoke hook_mymodule_data_alter() call alterInfo with "mymodule_data". ' )
83+ ->line ($ node ->getStartLine ())
84+ ->build ();
7185 }
7286
7387 return $ errors ;
7488 }
7589
7690 private function isYamlDiscovery (Node \Stmt \Class_ $ class ): bool
7791 {
78- foreach ($ class ->stmts as $ stmt ) {
79- // YAML discovery plugin managers must override getDiscovery.
80- if ($ stmt instanceof Node \Stmt \ClassMethod && $ stmt ->name ->toString () === 'getDiscovery ' ) {
81- foreach ($ stmt ->stmts ?? [] as $ methodStmt ) {
82- if ($ methodStmt instanceof Node \Stmt \If_) {
83- foreach ($ methodStmt ->stmts as $ ifStmt ) {
84- if ($ ifStmt instanceof Node \Stmt \Expression) {
85- $ ifStmtExpr = $ ifStmt ->expr ;
86- if ($ ifStmtExpr instanceof Node \Expr \Assign) {
87- $ ifStmtExprVar = $ ifStmtExpr ->var ;
88- if ($ ifStmtExprVar instanceof Node \Expr \PropertyFetch
89- && $ ifStmtExprVar ->var instanceof Node \Expr \Variable
90- && $ ifStmtExprVar ->name instanceof Node \Identifier
91- && $ ifStmtExprVar ->name ->name === 'discovery '
92- ) {
93- $ ifStmtExprExpr = $ ifStmtExpr ->expr ;
94- if ($ ifStmtExprExpr instanceof Node \Expr \New_
95- && ($ ifStmtExprExpr ->class instanceof Node \Name)
96- && $ ifStmtExprExpr ->class ->toString () === 'Drupal\Core\Plugin\Discovery\YamlDiscovery ' ) {
97- return true ;
98- }
99- }
100- }
101- }
102- }
103- }
104- }
105- }
92+ $ nodeFinder = new NodeFinder ();
93+ $ getDiscoveryMethodNode = $ nodeFinder ->findFirst ($ class ->stmts , static function (Node $ node ) {
94+ return $ node instanceof Node \Stmt \ClassMethod && $ node ->name ->toString () === 'getDiscovery ' ;
95+ });
96+ if (!$ getDiscoveryMethodNode instanceof Node \Stmt \ClassMethod) {
97+ return false ;
98+ }
99+
100+ $ assignDiscovery = $ nodeFinder ->findFirstInstanceOf ($ getDiscoveryMethodNode ->stmts ?? [], Node \Expr \Assign::class);
101+ if ($ assignDiscovery === null ) {
102+ return false ;
103+ }
104+ if ($ assignDiscovery ->expr instanceof Node \Expr \New_
105+ && $ assignDiscovery ->expr ->class instanceof Node \Name
106+ && $ assignDiscovery ->expr ->class ->toString () === 'Drupal\Core\Plugin\Discovery\YamlDiscovery ' ) {
107+ return true ;
106108 }
107109
108110 return false ;
109111 }
110112
111- private function inspectYamlPluginManager (Node \Stmt \Class_ $ class ): array
113+ private function inspectYamlPluginManager (Node \Stmt \Class_ $ class, Node \ Stmt \ ClassMethod $ constructorMethodNode ): array
112114 {
113115 $ errors = [];
114116
@@ -119,20 +121,16 @@ private function inspectYamlPluginManager(Node\Stmt\Class_ $class): array
119121 if ($ constructor ->getDeclaringClass ()->getName () !== $ fqn ) {
120122 $ errors [] = sprintf ('%s must override __construct if using YAML plugins. ' , $ fqn );
121123 } else {
122- foreach ($ class ->stmts as $ stmt ) {
123- if ($ stmt instanceof Node \Stmt \ClassMethod && $ stmt ->name ->toString () === '__construct ' ) {
124- foreach ($ stmt ->stmts ?? [] as $ constructorStmt ) {
125- if ($ constructorStmt instanceof Node \Stmt \Expression) {
126- $ constructorStmt = $ constructorStmt ->expr ;
127- }
128- if ($ constructorStmt instanceof Node \Expr \StaticCall
129- && $ constructorStmt ->class instanceof Node \Name
130- && ((string )$ constructorStmt ->class === 'parent ' )
131- && $ constructorStmt ->name instanceof Node \Identifier
132- && $ constructorStmt ->name ->name === '__construct ' ) {
133- $ errors [] = sprintf ('YAML plugin managers should not invoke its parent constructor. ' );
134- }
135- }
124+ foreach ($ constructorMethodNode ->stmts ?? [] as $ constructorStmt ) {
125+ if ($ constructorStmt instanceof Node \Stmt \Expression) {
126+ $ constructorStmt = $ constructorStmt ->expr ;
127+ }
128+ if ($ constructorStmt instanceof Node \Expr \StaticCall
129+ && $ constructorStmt ->class instanceof Node \Name
130+ && ((string )$ constructorStmt ->class === 'parent ' )
131+ && $ constructorStmt ->name instanceof Node \Identifier
132+ && $ constructorStmt ->name ->name === '__construct ' ) {
133+ $ errors [] = 'YAML plugin managers should not invoke its parent constructor. ' ;
136134 }
137135 }
138136 }
0 commit comments