Passed
Pull Request — master (#68)
by Björn
03:35
created

IncludeCollector::getIncluded()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
rs 10
c 0
b 0
f 0
ccs 6
cts 6
cp 1
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
namespace ComposerRequireChecker\NodeVisitor;
4
5
use FilesystemIterator;
6
use InvalidArgumentException;
7
use PhpParser\Node;
8
use PhpParser\Node\Expr;
9
use PhpParser\Node\Expr\BinaryOp\Concat;
10
use PhpParser\Node\Expr\ConstFetch;
11
use PhpParser\Node\Expr\Include_;
12
use PhpParser\Node\Expr\Variable;
13
use PhpParser\Node\Scalar\MagicConst\Dir;
14
use PhpParser\Node\Scalar\MagicConst\File;
15
use PhpParser\Node\Scalar\String_;
16
use PhpParser\NodeVisitorAbstract;
17
use RecursiveDirectoryIterator;
18
use RecursiveIteratorIterator;
19
20
final class IncludeCollector extends NodeVisitorAbstract
21
{
22
    /**
23
     * @var Expr[]
24
     */
25
    private $included = [];
26
27
    /**
28
     * {@inheritDoc}
29
     */
30 2
    public function beforeTraverse(array $nodes)
31
    {
32 2
        $this->included = [];
33 2
        return parent::beforeTraverse($nodes);
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::beforeTraverse($nodes) targeting PhpParser\NodeVisitorAbstract::beforeTraverse() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
34
    }
35
36
    /**
37
     * @param string $file
38
     * @return string[]
39
     */
40 11
    public function getIncluded(string $file): array
41
    {
42 11
        $included = [];
43 11
        foreach ($this->included as $exp) {
44
            try {
45 9
                $this->computePath($included, $this->processIncludePath($exp, $file), $file);
46 9
            } catch (InvalidArgumentException $exception) {
47
                // not sure there's anything sensible to do here
48
            }
49
        }
50 11
        return $included;
51
    }
52
53
    /**
54
     * @param array $included
55
     * @param string $path
56
     * @param string $self
57
     * @return void
58
     */
59 8
    private function computePath(array &$included, string $path, string $self)
60
    {
61 8
        if (!preg_match('#^([A-Z]:)?/#i', str_replace('\\', '/', $path))) {
62 4
            $path = dirname($self) . '/' . $path;
63
        }
64 8
        if (false === strpos($path, '{var}')) {
65 6
            $included[] = $path;
66 6
            return;
67
        }
68 2
        $regex = $this->pathWithVarToRegex($path);
69 2
        $self = str_replace('\\', '/', $self);
70 2
        foreach (new RecursiveIteratorIterator(
71 2
            new RecursiveDirectoryIterator(
72 2
                explode('{var}', $path)[0],
73 2
                FilesystemIterator::CURRENT_AS_PATHNAME | FilesystemIterator::SKIP_DOTS
74
            )
75
        ) as $file) {
76 2
            $rfile = str_replace('\\', '/', $file);
77 2
            if ($rfile !== $self && preg_match('/\\.php$/i', $rfile) && preg_match($regex, $rfile)) {
78 2
                $included[] = $file;
79
            }
80
        }
81 2
    }
82
83
    /**
84
     * @param string $path
85
     * @return string
86
     */
87 2
    private function pathWithVarToRegex(string $path): string
88
    {
89 2
        $parts = explode('{var}', $path);
90 2
        $regex = [];
91 2
        foreach ($parts as $part) {
92 2
            $regex[] = preg_quote(str_replace('\\', '/', $part), '/');
93
        }
94 2
        return '/^' . implode('.+', $regex) . '$/';
95
    }
96
97
    /**
98
     * @param string|Expr $exp
99
     * @param string $file
100
     * @return string
101
     * @throws InvalidArgumentException
102
     */
103 9
    private function processIncludePath($exp, string $file): string
104
    {
105 9
        if (is_string($exp)) {
106 2
            return $exp;
107
        }
108 7
        if ($exp instanceof String_) {
109 6
            return $exp->value;
110
        }
111 6
        if ($exp instanceof Concat) {
112 5
            return $this->processIncludePath($exp->left, $file) . $this->processIncludePath($exp->right, $file);
113
        }
114 6
        return $this->replaceInIncludePath($exp, $file);
115
    }
116
117
    /**
118
     * @param Expr $exp
119
     * @param string $file
120
     * @return string
121
     * @throws InvalidArgumentException
122
     */
123 6
    private function replaceInIncludePath($exp, string $file)
124
    {
125 6
        if ($exp instanceof Dir) {
126 2
            return dirname($file);
127
        }
128 5
        if ($exp instanceof File) {
129 1
            return $file;
130
        }
131 4
        if ($exp instanceof ConstFetch && "$exp->name" === 'DIRECTORY_SEPARATOR') {
132 1
            return DIRECTORY_SEPARATOR;
133
        }
134 3
        if ($exp instanceof Variable || $exp instanceof ConstFetch) {
135 2
            return '{var}';
136
        }
137 1
        throw new InvalidArgumentException('can\'t yet handle ' . $exp->getType());
138
    }
139
140
    /**
141
     * {@inheritDoc}
142
     */
143 6
    public function enterNode(Node $node)
144
    {
145 6
        if ($node instanceof Include_) {
146 4
            $this->included[] = $node->expr;
147
        }
148 6
    }
149
}
150