1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types = 1); |
4
|
|
|
|
5
|
|
|
namespace PHPChunkit; |
6
|
|
|
|
7
|
|
|
use PHP_Token_Stream; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* @testClass PHPChunkit\Test\FileClassesHelperTest |
11
|
|
|
*/ |
12
|
|
|
class FileClassesHelper |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* Extract the classes in the given file |
16
|
|
|
* |
17
|
|
|
* Taken from https://github.com/composer/composer/blob/master/src/Composer/Autoload/ClassMapGenerator.php#L126 |
18
|
|
|
* |
19
|
|
|
* @param string $path The file to check |
20
|
|
|
* |
21
|
|
|
* @return array The found classes |
22
|
|
|
*/ |
23
|
2 |
|
public function getFileClasses(string $path) : array |
24
|
|
|
{ |
25
|
2 |
|
$extraTypes = PHP_VERSION_ID < 50400 ? '' : '|trait'; |
26
|
2 |
|
if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>=')) { |
27
|
|
|
$extraTypes .= '|enum'; |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
// Use @ here instead of Silencer to actively suppress 'unhelpful' output |
31
|
|
|
// @link https://github.com/composer/composer/pull/4886 |
32
|
2 |
|
$contents = @php_strip_whitespace($path); |
33
|
2 |
|
if (!$contents) { |
34
|
1 |
|
return []; |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
// return early if there is no chance of matching anything in this file |
38
|
1 |
|
if (!preg_match('{\b(?:class|interface'.$extraTypes.')\s}i', $contents)) { |
39
|
|
|
return array(); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
// strip heredocs/nowdocs |
43
|
1 |
|
$contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); |
44
|
|
|
// strip strings |
45
|
1 |
|
$contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); |
46
|
|
|
// strip leading non-php code if needed |
47
|
1 |
|
if (substr($contents, 0, 2) !== '<?') { |
48
|
|
|
$contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements); |
49
|
|
|
if ($replacements === 0) { |
50
|
|
|
return array(); |
51
|
|
|
} |
52
|
|
|
} |
53
|
|
|
// strip non-php blocks in the file |
54
|
1 |
|
$contents = preg_replace('{\?>.+<\?}s', '?><?', $contents); |
55
|
|
|
// strip trailing non-php code if needed |
56
|
1 |
|
$pos = strrpos($contents, '?>'); |
57
|
1 |
|
if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) { |
58
|
|
|
$contents = substr($contents, 0, $pos); |
59
|
|
|
} |
60
|
|
|
|
61
|
1 |
|
preg_match_all('{ |
62
|
|
|
(?: |
63
|
1 |
|
\b(?<![\$:>])(?P<type>class|interface'.$extraTypes.') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) |
64
|
|
|
| \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] |
65
|
|
|
) |
66
|
1 |
|
}ix', $contents, $matches); |
67
|
|
|
|
68
|
1 |
|
$classes = array(); |
69
|
1 |
|
$namespace = ''; |
70
|
|
|
|
71
|
1 |
|
for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { |
72
|
1 |
|
if (!empty($matches['ns'][$i])) { |
73
|
1 |
|
$namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]).'\\'; |
74
|
|
|
} else { |
75
|
1 |
|
$name = $matches['name'][$i]; |
76
|
|
|
// skip anon classes extending/implementing |
77
|
1 |
|
if ($name === 'extends' || $name === 'implements') { |
78
|
|
|
continue; |
79
|
|
|
} |
80
|
1 |
|
if ($name[0] === ':') { |
81
|
|
|
// This is an XHP class, https://github.com/facebook/xhp |
82
|
|
|
$name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1); |
83
|
1 |
|
} elseif ($matches['type'][$i] === 'enum') { |
84
|
|
|
// In Hack, something like: |
85
|
|
|
// enum Foo: int { HERP = '123'; } |
86
|
|
|
// The regex above captures the colon, which isn't part of |
87
|
|
|
// the class name. |
88
|
|
|
$name = rtrim($name, ':'); |
89
|
|
|
} |
90
|
1 |
|
$classes[] = ltrim($namespace.$name, '\\'); |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
|
94
|
1 |
|
return $classes; |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|