ClassMethodArgVisitor::extractKeyFromArgument()   D
last analyzed

Complexity

Conditions 21
Paths 20

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 27
c 1
b 0
f 1
dl 0
loc 41
rs 4.1666
cc 21
nc 20
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Efabrica\TranslationsAutomatization\Command\CheckFormKeys;
4
5
use PhpParser\Node;
6
use PhpParser\Node\Expr\Array_;
7
use PhpParser\Node\Expr\Closure;
8
use PhpParser\Node\Expr\MethodCall;
9
use PhpParser\Node\Expr\New_;
10
use PhpParser\Node\Identifier;
11
use PhpParser\Node\Scalar\String_;
12
use PhpParser\Node\Stmt\Return_;
13
use PhpParser\Node\Stmt\Use_;
14
use PhpParser\NodeVisitorAbstract;
15
16
class ClassMethodArgVisitor extends NodeVisitorAbstract
17
{
18
    private $keys = [];
19
20
    private $filePath;
21
22
    private $className;
23
24
    private $classArgposClassesMap = [];
25
26
    private $config;
27
28
    public function __construct(array &$keys, string $filePath, array $config)
29
    {
30
        $this->keys = &$keys;
31
        $this->filePath = $filePath;
32
        $this->className = (string)pathinfo($filePath, PATHINFO_FILENAME);
33
        $this->config = $config;
34
    }
35
36
    public function enterNode(Node $node)
37
    {
38
        $this->prepareUseClasses($node);
39
        // find new Class(arg1, arg2, arg3)
40
        if ($node instanceof New_ && isset($node->class) && in_array($node->class->name ?? null, $this->classArgposClassesMap, true)) {
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
41
            $className = $node->class->name;
42
            $argIndex = array_search($className, $this->classArgposClassesMap);
43
            $args = $node->args;
44
            if (isset($args[$argIndex]) && $args[$argIndex]->value instanceof String_) {
45
                $key = $args[$argIndex]->value->value;
46
                $this->addKey($args[$argIndex]->getStartLine(), $className, $key);
47
            }
48
        }
49
        // find in Class ->method(arg1, arg2, arg3)
50
        if ($node instanceof MethodCall &&
51
            $node->name instanceof Identifier) {
52
            $methodName = $node->name->name;
53
            foreach ($this->config['CLASS_ARGPOS_METHODS'] ?? [] as $classNamePart => $argposMethods) {
54
                // ALL classes OR Classes end with classNamePart
55
                if ($classNamePart !== 'ALL' && (strpos($this->className, $classNamePart) === false || substr($this->className, -strlen($classNamePart)) !== $classNamePart)) {
56
                    continue;
57
                }
58
                foreach ($argposMethods as $argIndex => $methods) {
59
                    if (in_array($methodName, $methods, true)) {
60
                        $this->extractKeyFromArgument($node, $argIndex, $classNamePart);
61
                    }
62
                }
63
            }
64
        }
65
    }
66
67
    private function prepareUseClasses(Node $node)
68
    {
69
        if ($node instanceof Use_) {
70
            foreach ($node->uses as $use) {
71
                $useName = $use->name->name;
72
                foreach ($this->config['ARGPOS_CLASSES'] ?? [] as $argIndex => $classes) {
73
                    if (in_array($useName, $classes)) {
74
                        $shortName = basename(str_replace('\\', '/', $useName));
75
                        $this->classArgposClassesMap[$argIndex] = $shortName;
76
                    }
77
                }
78
            }
79
        }
80
    }
81
82
    private function extractKeyFromArgument(MethodCall $node, int $argIndex, string $classNamePart): void
83
    {
84
        $args = $node->args;
85
        // find in funciton return array values
86
        if (isset($args[$argIndex]) && $args[$argIndex]->value instanceof Closure &&
87
            isset($args[$argIndex]->value) && isset($args[$argIndex]->value)
88
        ) {
89
            $method = $node->name->name;
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
90
            $closure = $args[$argIndex]->value;
91
            if ($closure->stmts !== null) {
92
                $return = reset($closure->stmts);
93
                if ($return instanceof Return_ && $return->expr instanceof Array_ && $return->expr->items !== null) {
94
                    $items = $return->expr->items;
95
                    foreach ($items as $item) {
96
                        if ($item->value instanceof String_) {
97
                            $key = $item->value->value;
98
                            $this->addKey($item->value->getAttribute('startLine'), $method, $key, null);
0 ignored issues
show
Bug introduced by
It seems like $item->value->getAttribute('startLine') can also be of type null; however, parameter $line of Efabrica\TranslationsAut...hodArgVisitor::addKey() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

98
                            $this->addKey(/** @scrutinizer ignore-type */ $item->value->getAttribute('startLine'), $method, $key, null);
Loading history...
99
                        }
100
                    }
101
                }
102
            }
103
        }
104
105
        if (isset($args[$argIndex]) && $args[$argIndex]->value instanceof String_) {
106
            $method = $node->name->name;
107
            $arg = null;
108
            if ($method === 'translate' && isset($args[$argIndex + 1]) && $args[$argIndex + 1]->value instanceof Node\Expr\Array_) {
109
                $arg = $args[$argIndex + 1]->value->items[0]->key->value;
110
            }
111
112
            $key = $args[$argIndex]->value->value;
113
            $allowEmptyTranslation = $this->config['ALLOW_EMPTY_TRANSLATION'] ?? [];
114
            if (
115
                array_key_exists($classNamePart, $allowEmptyTranslation) &&
116
                array_key_exists($argIndex, $allowEmptyTranslation[$classNamePart]) &&
117
                ($key === '' || $key === '--') &&
118
                (in_array($method, $allowEmptyTranslation[$classNamePart][$argIndex], true))
119
            ) {
120
                return;
121
            }
122
            $this->addKey($args[$argIndex]->getStartLine(), $method, $key, $arg);
123
        }
124
    }
125
126
    private function addKey(int $line, string $call, string $key, string $arg = null): void
127
    {
128
        $this->keys[] = [
129
            'file' => $this->filePath,
130
            'line' => $line,
131
            'call' => $call,
132
            'key' => $key,
133
            'arg' => $arg,
134
        ];
135
    }
136
}
137