Completed
Pull Request — master (#241)
by
unknown
02:17
created

Parser   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 160
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 98.51%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 23
c 1
b 0
f 0
lcom 2
cbo 3
dl 0
loc 160
ccs 66
cts 67
cp 0.9851
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 4
A getClass() 0 11 2
A getCleanReflectionName() 0 4 1
A getMethods() 0 14 4
A getClassName() 0 16 3
B _searchForUnitTestClass() 0 17 6
A classNameMatchesFileName() 0 5 2
A invertSlashes() 0 4 1
1
<?php
2
namespace ParaTest\Parser;
3
4
class Parser
5
{
6
    /**
7
     * The path to the source file to parse
8
     *
9
     * @var string
10
     */
11
    private $path;
12
13
    /**
14
     * @var \ReflectionClass
15
     */
16
    private $refl;
17
18
    /**
19
     * Matches a test method beginning with the conventional "test"
20
     * word
21
     *
22
     * @var string
23
     */
24
    private static $testName = '/^test/';
25
26
    /**
27
     * A pattern for matching test methods that use the @test annotation
28
     *
29
     * @var string
30
     */
31
    private static $testAnnotation = '/@test\b/';
32
33 30
    public function __construct($srcPath)
34
    {
35 30
        if (!file_exists($srcPath)) {
36 1
            throw new \InvalidArgumentException("file not found: " . $srcPath);
37
        }
38
39 29
        $this->path = $srcPath;
40 29
        $declaredClasses = get_declared_classes();
41 29
        require_once($this->path);
42 29
        $class = $this->getClassName($this->path, $declaredClasses);
43 29
        if (!$class) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
44 2
            throw new NoClassInFileException();
45
        }
46
        try {
47 27
            $this->refl = new \ReflectionClass($class);
48 27
        } catch (\ReflectionException $e) {
49
            throw new \InvalidArgumentException("Unable to instantiate ReflectionClass. " . $class . " not found in: " . $srcPath);
50
        }
51 27
    }
52
53
    /**
54
     * Returns the fully constructed class
55
     * with methods or null if the class is abstract
56
     *
57
     * @return null|ParsedClass
58
     */
59 27
    public function getClass()
60
    {
61 27
        return ($this->refl->isAbstract())
62 27
            ? null
63 27
            : new ParsedClass(
64 27
                $this->refl->getDocComment(),
65 27
                $this->getCleanReflectionName(),
66 27
                $this->refl->getNamespaceName(),
67 27
                $this->getMethods()
68 27
            );
69
    }
70
71
    /**
72
     * Return reflection name with null bytes stripped
73
     *
74
     * @return string
75
     */
76 27
    private function getCleanReflectionName()
77
    {
78 27
        return str_replace("\x00", '', $this->refl->getName());
79
    }
80
81
    /**
82
     * Return all test methods present in the file
83
     *
84
     * @return array
85
     */
86 27
    private function getMethods()
87
    {
88 27
        $tests = array();
89 27
        $methods = $this->refl->getMethods(\ReflectionMethod::IS_PUBLIC);
90 27
        foreach ($methods as $method) {
91 27
            $hasTestName = preg_match(self::$testName, $method->getName());
92 27
            $hasTestAnnotation = preg_match(self::$testAnnotation, $method->getDocComment());
93 27
            $isTestMethod = $hasTestName || $hasTestAnnotation;
94 27
            if ($isTestMethod) {
95 25
                $tests[] = new ParsedFunction($method->getDocComment(), 'public', $method->getName());
96 25
            }
97 27
        }
98 27
        return $tests;
99
    }
100
101
    /**
102
     * Return the class name of the class contained
103
     * in the file
104
     *
105
     * @return string
106
     */
107 29
    private function getClassName($filename, $previousDeclaredClasses)
108
    {
109 29
        $filename = realpath($filename);
110 29
        $classes = get_declared_classes();
111 29
        $newClasses = array_values(array_diff($classes, $previousDeclaredClasses));
112
113 29
        $className = $this->_searchForUnitTestClass($newClasses, $filename);
114 29
        if (isset($className)) {
115 11
            return $className;
116
        }
117
118 22
        $className = $this->_searchForUnitTestClass($classes, $filename);
119 22
        if (isset($className)) {
120 20
            return $className;
121
        }
122 2
    }
123
124
    /**
125
     * Search for the name of the unit test
126
     * @param string[] $classes
127
     * @param string $filename
128
     * @return string|null
129
     */
130 29
    private function _searchForUnitTestClass(array $classes, $filename) {
131
        // TODO: After merging this PR or other PR for phpunit 6 support, keep only the applicable subclass name
132 29
        $matchingClassName = null;
133 29
        foreach ($classes as $className) {
134 29
            $class = new \ReflectionClass($className);
135 29
            if ($class->getFileName() == $filename) {
136 27
                if ($class->isSubclassOf('PHPUnit_Framework_TestCase')) {
137 27
                    if ($this->classNameMatchesFileName($filename, $className)) {
138 21
                        return $className;
139 17
                    } else if ($matchingClassName === null) {
140 17
                        $matchingClassName = $className;
141 17
                    }
142 17
                }
143 17
            }
144 26
        }
145 24
        return $matchingClassName;
146
    }
147
148
    /**
149
     * @param $filename
150
     * @param $className
151
     * @return bool
152
     */
153 27
    private function classNameMatchesFileName($filename, $className)
154
    {
155 27
        return strpos($filename, $className) !== false
156 27
            || strpos($filename, $this->invertSlashes($className)) !== false;
157
    }
158
159 17
    private function invertSlashes($className)
160
    {
161 17
        return str_replace('\\', '/', $className);
162
    }
163
}
164