├── .env
├── .gitignore
├── bin
└── phpctags
├── a.json
├── .ac-php-conf.json
├── composer.json
├── a.php
├── phpunit.xml.dist
├── .travis.yml
├── GitIgnore
├── Util.php
├── Rule.php
├── FileFinder.php
├── Ruleset.php
└── Pattern.php
├── t.sh
├── Makefile
├── gen_common_json.php
├── ChangeLog.md
├── README.md
├── composer.lock
├── bootstrap.php
├── deal_config.php
└── PHPCtags.php
/.env:
--------------------------------------------------------------------------------
1 | ls
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .test_fs
2 | phpctags
3 | build/
4 | vendor/
5 |
--------------------------------------------------------------------------------
/bin/phpctags:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
3 | =7.0",
8 | "nikic/php-parser": "^4.15"
9 | },
10 | "autoload": {
11 | "classmap": ["PHPCtags.php"]
12 | },
13 | "bin": ["bin/phpctags"]
14 | }
15 |
--------------------------------------------------------------------------------
/a.php:
--------------------------------------------------------------------------------
1 | v1
13 | }
14 | use Instance;
15 |
16 | use Concerns\InteractsWithContainer,
17 | Concerns\MakesHttpRequests,
18 | Concerns\ImpersonatesUsers;
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | tests/
7 |
8 |
9 |
10 |
11 | src
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - '7.3'
5 | - '7.2'
6 | - '7.1'
7 | - '7.0'
8 |
9 | git:
10 | depth: 1
11 |
12 | matrix:
13 | fast_finish: true
14 |
15 | cache:
16 | apt: true
17 | timeout: 604800
18 | directories:
19 | - $HOME/.composer/cache
20 |
21 | install:
22 | - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest"
23 | - travis_retry composer install $flags
24 |
25 | script:
26 | - true
27 |
28 | notifications:
29 | email: false
30 |
--------------------------------------------------------------------------------
/GitIgnore/Util.php:
--------------------------------------------------------------------------------
1 | _pattern = $pattern;
16 | $this->_isExclusion = $isExclusion;
17 | }
18 |
19 | /** @return true: include this file, false: exclude this file, null: rule does not apply to this file */
20 | public function match($path)
21 | {
22 | if (!is_string($path)) {
23 | throw new Exception(__METHOD__." expects a string; given ".Util::describe($path));
24 | }
25 | if ($this->_pattern->match($path)) {
26 | return $this->_isExclusion ? false : true;
27 | }
28 | return null;
29 | }
30 |
31 | public static function parse($str)
32 | {
33 | $isExclusion = false;
34 | if ($str[0] == '!') {
35 | $isExclusion = true;
36 | $str = substr($str, 1);
37 | }
38 | $pattern = Pattern::parse($str);
39 | return new self($pattern, $isExclusion);
40 | }
41 |
42 | public function isExclusion()
43 | {
44 | return $this->_isExclusion;
45 | }
46 |
47 | public function __toString()
48 | {
49 | return ($this->isExclusion() ? "!" : "") . $this->_pattern->getPatternString();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/gen_common_json.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | ruleset = $options['ruleset'];
8 | $this->invertRulesetResult = $options['invertRulesetResult'];
9 | $this->defaultResult = $options['defaultResult'];
10 | $this->includeDirectories = $options['includeDirectories'];
11 | $this->callback = $options['callback'];
12 | }
13 |
14 | protected function match($rootDir, $f)
15 | {
16 | $result = $this->ruleset->match($f);
17 | if ($this->invertRulesetResult and $result !== null) {
18 | $result = !$result;
19 | }
20 | return $result === null ? $this->defaultResult : $result;
21 | }
22 |
23 | protected function _findFiles($rootDir, $f)
24 | {
25 | if (preg_match('#/$#', $rootDir)) {
26 | throw new Exception("Root directory argument to _findFiles should not end with a slash; given «{$rootDir}»");
27 | }
28 | if (preg_match('#^/|/$#', $f)) {
29 | throw new Exception("Relative path argument to _findFiles should not start or end with a slash; given «{$f}»");
30 | }
31 | //echo "_findFiles($rootDir, $f)\n";
32 | $fullPath = $f == '' ? $rootDir : $rootDir.'/'.$f;
33 | if ($this->includeDirectories or !is_dir($fullPath)) {
34 | $result = $this->match($rootDir, $f);
35 | call_user_func($this->callback, $f, $result);
36 | }
37 | if (is_dir($fullPath)) {
38 | $dh = opendir($fullPath);
39 | while (($fn = readdir($dh)) !== false) {
40 | if ($fn == '.' or $fn == '..') {
41 | continue;
42 | }
43 | $this->_findFiles($rootDir, $f == '' ? $fn : $f.'/'.$fn);
44 | }
45 | closedir($dh);
46 | }
47 | }
48 |
49 | public function findFiles($dir)
50 | {
51 | self::_findFiles($dir, '');
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | phpctags
2 | ========
3 | Master [](https://travis-ci.org/xcwen/phpctags?branch=master)
4 |
5 | An enhanced php [ctags](http://ctags.sourceforge.net/) index file generator
6 | compatible with http://ctags.sourceforge.net/FORMAT.
7 |
8 | Using [PHP_Parser](https://github.com/nikic/PHP-Parser) as PHP syntax parsing
9 | backend, written in pure PHP. The generated ctags index file contains scope
10 | and access information about class's methods and properties.
11 |
12 | This tool was originally developed to enhance the PHP syntax outline surport
13 | for vim [tagbar](http://majutsushi.github.com/tagbar/) plugin. The enhaced
14 | functionality has been included into an addon plugin for tagbar as
15 | [tagbar-phpctags](https://github.com/techlivezheng/tagbar-phpctags).
16 |
17 | Enjoy!
18 |
19 | Installation
20 | ------------
21 |
22 | ## Download
23 |
24 | ```
25 | curl -Ss http://vim-php.com/phpctags/install/phpctags.phar > phpctags
26 | php ./phpctags
27 | ```
28 |
29 | ## Build
30 | > We currently only support building PHAR executable for \*nix like platform
31 | which provides `make` utility. If you are interested in building an executable
32 | for other platform, especially for Windows, please help yourself out. It
33 | should be easy though (Apologize for not being able to provide any help for
34 | this, I am really not a Windows guy), it also would be great if someone could
35 | provide a patch for this.
36 |
37 | Installation is simple, make sure you have PHP's PHAR extension enabled, then
38 | just run `make` in the root directory of the source, you will get a `phpctags`
39 | PHAR executable, add it to your `$PATH`, then you can invoke `phpctags`
40 | directly from anywhere.
41 |
42 | Requirements
43 | ------------
44 |
45 | * PHP CLI 5.3+
46 | * [PHP-Parser](https://github.com/nikic/PHP-Parser)
47 |
48 | Acknowledgements
49 | ----------------
50 |
51 | * [Snapi](https://github.com/sanpii) for composer support.
52 | * [DeMarko](https://github.com/DeMarko) for memory limit support.
53 | * [Sander Marechal](https://github.com/sandermarechal) for improve console support
54 | * [Mark Wu](https://github.com/markwu) for building a stand-alone PHAR executable
55 |
--------------------------------------------------------------------------------
/GitIgnore/Ruleset.php:
--------------------------------------------------------------------------------
1 | rules[] = $rule;
31 | }
32 |
33 | public function match($path)
34 | {
35 | $path=substr($path, $this->check_start_pos);
36 | if ($path===false) {
37 | return null;
38 | }
39 | if (!is_string($path)) {
40 | throw new Exception(__METHOD__." expects a string; given ".Util::describe($path));
41 | }
42 | $lastResult = null;
43 | foreach ($this->rules as $rule) {
44 |
45 | /** @var Rule $rule */
46 | $result = $rule->match($path);
47 | if ($result !== null) {
48 | $lastResult = $result;
49 | }
50 | }
51 | return $lastResult;
52 | }
53 |
54 | /**
55 | *@return self
56 | */
57 | public static function loadFromStrings($lines)
58 | {
59 | $rs = new self;
60 | foreach ($lines as $line) {
61 | $rs->addRule($line);
62 | }
63 | return $rs;
64 | }
65 |
66 | public static function loadFromString($str)
67 | {
68 | $lines = explode("\n", $str);
69 | return self::loadFromStrings($lines);
70 | }
71 |
72 | public static function loadFromFile($filename)
73 | {
74 | $rs = new self;
75 | $fh = fopen($filename);
76 | while (($line = fgets($fh))) {
77 | $rs->addRule($line);
78 | }
79 | fclose($fh);
80 | return $rs;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/GitIgnore/Pattern.php:
--------------------------------------------------------------------------------
1 | patternString = $pattern;
11 | $this->regex = $regex;
12 | }
13 |
14 | public function getPatternString()
15 | {
16 | return $this->patternString;
17 | }
18 |
19 | protected static function patternToRegex($pp)
20 | {
21 | preg_match_all('/\*\*|\*|\?|\[![^\]]+\]|\[[^\]]+\]|[^\*\?]/', $pp, $bifs);
22 | $regex = '';
23 | $start_close_flag=false;
24 | //print_r($bifs);
25 |
26 | foreach ($bifs[0] as $part) {
27 | if ($part == '**') {
28 | $regex .= ".*";
29 | } elseif ($part == '*') {
30 | $regex .= "[^/]*";
31 | } elseif ($part == '?') {
32 | $regex .= '?';
33 | } elseif ($part[0] == '[') {
34 | // Not exactly, but maybe close enough.
35 | // Maybe fnmatch is the thing to use
36 | if (@$part[1] == '!') {
37 | $part[1] = '^';
38 | }
39 | $regex .= $part;
40 | $start_close_flag=true;
41 | } else {
42 | $regex .= preg_quote($part, '#');
43 | }
44 | }
45 | return $regex;
46 | }
47 |
48 | public static function parse($pattern)
49 | {
50 | $r = self::patternToRegex($pattern);
51 | if (strlen($pattern) == 0) {
52 | throw new Exception("Zero-length pattern string passed to ".__METHOD__);
53 | }
54 | if ($pattern[0] == '/') {
55 | $r = '#^'.substr($r, 1).'.*#';
56 | } else {
57 | $r = '#(?:^|/)'.$r.'.*#';
58 | }
59 | return new self($pattern, $r);
60 | }
61 |
62 |
63 | public function match($path)
64 | {
65 | if (strlen($path) > 0 and $path[0] == '/') {
66 | throw new Exception("Paths passed to #match should not start with a slash; given: «".$path."»");
67 | }
68 | if (!is_string($path)) {
69 | throw new Exception(__METHOD__." expects a string; given ".Util::describe($path));
70 | }
71 | //echo "check [". $this->regex . "]=>". $path . "\n";
72 | return preg_match($this->regex, $path);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "e7e3e3130f4516a21cc7dc01658b749c",
8 | "packages": [
9 | {
10 | "name": "nikic/php-parser",
11 | "version": "v4.17.1",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/nikic/PHP-Parser.git",
15 | "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
20 | "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
21 | "shasum": "",
22 | "mirrors": [
23 | {
24 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
25 | "preferred": true
26 | }
27 | ]
28 | },
29 | "require": {
30 | "ext-tokenizer": "*",
31 | "php": ">=7.0"
32 | },
33 | "require-dev": {
34 | "ircmaxell/php-yacc": "^0.0.7",
35 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
36 | },
37 | "bin": [
38 | "bin/php-parse"
39 | ],
40 | "type": "library",
41 | "extra": {
42 | "branch-alias": {
43 | "dev-master": "4.9-dev"
44 | }
45 | },
46 | "autoload": {
47 | "psr-4": {
48 | "PhpParser\\": "lib/PhpParser"
49 | }
50 | },
51 | "notification-url": "https://packagist.org/downloads/",
52 | "license": [
53 | "BSD-3-Clause"
54 | ],
55 | "authors": [
56 | {
57 | "name": "Nikita Popov"
58 | }
59 | ],
60 | "description": "A PHP parser written in PHP",
61 | "keywords": [
62 | "parser",
63 | "php"
64 | ],
65 | "support": {
66 | "issues": "https://github.com/nikic/PHP-Parser/issues",
67 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
68 | },
69 | "time": "2023-08-13T19:53:39+00:00"
70 | }
71 | ],
72 | "packages-dev": [],
73 | "aliases": [],
74 | "minimum-stability": "stable",
75 | "stability-flags": [],
76 | "prefer-stable": false,
77 | "prefer-lowest": false,
78 | "platform": {
79 | "php": ">=7.0"
80 | },
81 | "platform-dev": [],
82 | "plugin-api-version": "2.3.0"
83 | }
84 |
--------------------------------------------------------------------------------
/bootstrap.php:
--------------------------------------------------------------------------------
1 | , https://github.com/techlivezheng/phpctags
24 | EOF;
25 |
26 | $options = getopt('aC:f:Nno:RuV', array(
27 | 'append::',
28 | 'debug',
29 | 'exclude:',
30 | 'excmd::',
31 | 'fields::',
32 | 'tags_dir::',
33 | 'save-common-el::',
34 | 'kinds::',
35 | 'format::',
36 | 'help',
37 | 'recurse::',
38 | 'sort::',
39 | 'rebuild::',
40 | 'realpath_flag::',
41 | 'verbose::',
42 | 'version',
43 | 'memory::',
44 | 'files::',
45 | 'config-file::',
46 | "test::",
47 | ));
48 |
49 | $options_info = <<<'EOF'
50 | phpctags currently only supports a subset of the original ctags' options.
51 |
52 | Usage: phpctags [options] [file(s)]
53 |
54 | -a Append the tags to an existing tag file.
55 | -f
56 | Write tags to specified file. Value of "-" writes tags to stdout
57 | ["tags"].
58 | -C
59 | Use a cache file to store tags for faster updates.
60 | -n Equivalent to --excmd=number.
61 | -N Equivalent to --excmd=pattern.
62 | -o Alternative for -f.
63 | -R Equivalent to --recurse.
64 | -u Equivalent to --sort=no.
65 | -V Equivalent to --verbose.
66 | --append=[yes|no]
67 | Should tags should be appended to existing tag file [no]?
68 | --debug
69 | phpctags only
70 | Repect PHP's error level configuration.
71 | --exclude=pattern
72 | Exclude files and directories matching 'pattern'.
73 | --excmd=number|pattern|mix
74 | Uses the specified type of EX command to locate tags [mix].
75 | --fields=[+|-]flags
76 | Include selected extension fields (flags: "afmikKlnsStz") [fks].
77 | --kinds=[+|-]flags
78 | Enable/disable tag kinds [cmfpvditn]
79 | --format=level
80 | Force output of specified tag file format [2].
81 | --help
82 | Print this option summary.
83 | --memory=[-1|bytes|KMG]
84 | phpctags only
85 | Set how many memories phpctags could use.
86 | --recurse=[yes|no]
87 | Recurse into directories supplied on command line [no].
88 | --sort=[yes|no|foldcase]
89 | Should tags be sorted (optionally ignoring case) [yes]?.
90 | --verbose=[yes|no]
91 | Enable verbose messages describing actions on each source file.
92 | --version
93 | Print version identifier to standard output.
94 | EOF;
95 |
96 | // prune options and its value from the $argv array
97 | $argv_ = array();
98 |
99 | foreach ($options as $option => $value) {
100 | foreach ($argv as $key => $chunk) {
101 | $regex = '/^'. (isset($option[1]) ? '--' : '-') . $option . '/';
102 | if ($chunk == $value && $argv[$key-1][0] == '-' || preg_match($regex, $chunk)) {
103 | array_push($argv_, $key);
104 | }
105 | }
106 | }
107 |
108 | while ($key = array_pop($argv_)) {
109 | unset($argv[$key]);
110 | }
111 |
112 | // option -v is an alternative to --verbose
113 | if (isset($options['V'])) {
114 | $options['verbose'] = 'yes';
115 | }
116 |
117 | if (isset($options['save-common-el'])) {
118 | $common_json_file=__DIR__. "/common.json";
119 |
120 | $json_data= json_decode(file_get_contents($common_json_file), true);
121 | save_as_el($options['save-common-el'], $json_data[0], $json_data[1], $json_data[2], $json_data[3]);
122 |
123 | exit;
124 | }
125 |
126 |
127 |
128 |
129 | if (isset($options['verbose'])) {
130 | if ($options['verbose'] === false || yes_or_no($options['verbose']) == 'yes') {
131 | $options['V'] = true;
132 | } elseif (yes_or_no($options['verbose']) != 'no') {
133 | die('phpctags: Invalid value for "verbose" option'.PHP_EOL);
134 | } else {
135 | $options['V'] = false;
136 | }
137 | } else {
138 | $options['V'] = false;
139 | }
140 |
141 | if (isset($options['debug'])) {
142 | $options['debug'] = true;
143 | } else {
144 | #error_reporting(0);
145 | }
146 |
147 | if (isset($options['help'])) {
148 | echo "Version: ".$version."\n\n".$copyright;
149 | echo PHP_EOL;
150 | echo PHP_EOL;
151 | echo $options_info;
152 | echo PHP_EOL;
153 | exit;
154 | }
155 |
156 | if (isset($options['version'])) {
157 | echo "Version: ".$version."\n\n".$copyright;
158 | echo PHP_EOL;
159 | exit;
160 | }
161 |
162 | array_shift($argv);
163 |
164 | // option -o is an alternative to -f
165 | if (isset($options['o']) && !isset($options['f'])) {
166 | $options['f'] = $options['o'];
167 | }
168 |
169 | // if both -n and -N options are given, use the last specified one
170 | if (isset($options['n']) && isset($options['N'])) {
171 | if (array_search('n', array_keys($options)) < array_search('N', array_keys($options))) {
172 | unset($options['n']);
173 | } else {
174 | unset($options['N']);
175 | }
176 | }
177 |
178 | // option -n is equivalent to --excmd=number
179 | if (isset($options['n']) && !isset($options['N'])) {
180 | $options['excmd'] = 'number';
181 | }
182 |
183 | // option -N is equivalent to --excmd=pattern
184 | if (isset($options['N']) && !isset($options['n'])) {
185 | $options['excmd'] = 'pattern';
186 | }
187 |
188 | if (!isset($options['excmd'])) {
189 | $options['excmd'] = 'pattern';
190 | }
191 | if (!isset($options['format'])) {
192 | $options['format'] = 2;
193 | }
194 | if (!isset($options['memory'])) {
195 | $options['memory'] = '1024M';
196 | }
197 | if (!isset($options['fields'])) {
198 | $options['fields'] = array('n', 'k', 's', 'a','i');
199 | } else {
200 | $options['fields'] = str_split($options['fields']);
201 | }
202 |
203 | if (!isset($options['kinds'])) {
204 | $options['kinds'] = array('c', 'm', 'f', 'p', 'd', 'v', 'i', 't', 'n','T');
205 | } else {
206 | $options['kinds'] = str_split($options['kinds']);
207 | }
208 |
209 |
210 | // handle -u or --sort options
211 | if (isset($options['sort'])) {
212 | // --sort or --sort=[Y,y,YES,Yes,yes]
213 | if ($options['sort'] === false || yes_or_no($options['sort']) == 'yes') {
214 | $options['sort'] = 'yes';
215 | // --sort=[N,n,NO,No,no]
216 | } elseif (yes_or_no($options['sort']) == 'no') {
217 | $options['sort'] = 'no';
218 | // --sort=foldcase, case insensitive sorting
219 | } elseif ($options['sort'] == 'foldcase') {
220 | $options['sort'] = 'foldcase';
221 | } else {
222 | die('phpctags: Invalid value for "sort" option'.PHP_EOL);
223 | }
224 | // option -n is equivalent to --sort=no
225 | } elseif (isset($options['u'])) {
226 | $options['sort'] = 'no';
227 | // sort the result by default
228 | } else {
229 | $options['sort'] = 'yes';
230 | }
231 |
232 | // if the memory limit option is set and is valid, adjust memory
233 | if (isset($options['memory'])) {
234 | $memory_limit = trim($options['memory']);
235 | if (isMemoryLimitValid($memory_limit)) {
236 | ini_set('memory_limit', $memory_limit);
237 | }
238 | }
239 |
240 | if (isset($options['append'])) {
241 | if ($options['append'] === false || yes_or_no($options['append']) == 'yes') {
242 | $options['a'] = false;
243 | } elseif (yes_or_no($options['append']) != 'no') {
244 | die('phpctags: Invalid value for "append" option'.PHP_EOL);
245 | }
246 | }
247 |
248 | if (isset($options['recurse'])) {
249 | if ($options['recurse'] === false || yes_or_no($options['recurse']) == 'yes') {
250 | $options['R'] = false;
251 | } elseif (yes_or_no($options['recurse']) != 'no') {
252 | die('phpctags: Invalid value for "recurse" option'.PHP_EOL);
253 | }
254 | }
255 |
256 |
257 | // if option -R is given and no file is specified, use current working directory
258 | if (isset($options['R']) && empty($argv)) {
259 | $argv[] = getcwd();
260 | }
261 |
262 | //
263 |
264 | try {
265 | //print_r($options);
266 | if ($options["config-file"]) {
267 | deal_config(
268 | $options["config-file"],
269 | yes_or_no($options['rebuild']) == 'yes',
270 | yes_or_no($options['realpath_flag']) == 'yes',
271 | $options["tags_dir"],
272 | yes_or_no(@$options['test']) == 'yes'
273 | );
274 |
275 | exit;
276 | } else {
277 | $ctags = new \app\PHPCtags($options);
278 | $ctags->addFiles($argv);
279 | $result = $ctags->export();
280 | }
281 | } catch (Exception $e) {
282 | echo $e->getMessage();
283 |
284 | exit;
285 | }
286 | // write to a specified file
287 | if (isset($options['f']) && $options['f'] !== '-') {
288 | $tagfile = fopen($options['f'], isset($options['a']) ? 'a' : 'w');
289 | // write to stdout only when instructed
290 | } elseif (isset($options['f']) && $options['f'] === '-') {
291 | $tagfile = fopen('php://stdout', 'w');
292 | // write to file 'tags' by default
293 | } else {
294 | $tagfile = fopen('tags', isset($options['a']) ? 'a' : 'w');
295 | }
296 |
297 | $mode = ($options['sort'] == 'yes' ? 1 : ($options['sort'] == 'foldcase' ? 2 : 0));
298 |
299 | if (!isset($options['a'])) {
300 | $tagline = << 0) {
330 | // memory limit provided in bytes
331 | return true;
332 | } elseif (preg_match("/\d+\s*[KMG]/", $memory_limit)) {
333 | // memory limit provided in human readable sizes
334 | // as specified here: http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
335 | return true;
336 | }
337 |
338 | return false;
339 | }
340 |
--------------------------------------------------------------------------------
/deal_config.php:
--------------------------------------------------------------------------------
1 | null,
11 | "tag-dir" => null,
12 | "filter"=> [
13 | "can-use-external-dir"=> true,
14 | "php-file-ext-list"=> [
15 | "php"
16 | ],
17 | "php-path-list"=> [
18 | "."
19 | ],
20 | "php-path-list-without-subdir"=> [],
21 | ]
22 | ], JSON_PRETTY_PRINT);
23 | file_put_contents($config_file, $json_data);
24 | }
25 | return json_decode($json_data, true);
26 | }
27 |
28 | function deal_tags($file_index, &$result, &$class_inherit_map, &$class_map, &$function_list, &$construct_map, &$class_define_map)
29 | {
30 | foreach ($result as &$item) {
31 | $kind=$item["kind"] ;
32 | $scope=$item["scope"];
33 | $name=$item["name"];
34 | //$file=$item["file"];
35 | $line=$item["line"];
36 | $file_pos=$file_index.":". $line;
37 | switch ($kind) {
38 | case "c":
39 | case "i":
40 | case "t":
41 | $class_name=$scope."\\".$name;
42 | if (isset($item["inherits"])) {
43 | if (isset($class_inherit_map[$class_name])) {
44 | $arr=$item["inherits"];
45 | foreach ($class_inherit_map[$class_name] as $inherit) {
46 | $arr[]= $inherit;
47 | }
48 | $class_inherit_map[$class_name] = $arr;
49 | } else {
50 | $class_inherit_map[$class_name] = $item["inherits"];
51 | }
52 | }
53 | $return_type=$class_name;
54 | $function_list[ ]= [ $kind , $class_name , "" , $file_pos , $return_type ] ;
55 | if (!isset($class_map[$class_name])) {
56 | $class_map[$class_name] =[];
57 | }
58 | $class_define_map[ $class_name ] =[ "", $file_pos];
59 |
60 | break;
61 |
62 | case "T":
63 | $class_name = $item["scope"];
64 | $class_inherit_map[$class_name][]= $item["type"] ;
65 | break;
66 | case "m":
67 | case "p":
68 | $class_name= $scope;
69 | $doc=@$item["args"];
70 | $return_type=@$item["type"];
71 | $access=$item["access"];
72 | $static =@$item["static"];
73 |
74 | $preg_str= "/\\\\$name\$/";
75 | if ($name=="__construct" || preg_match($preg_str, $class_name)) {
76 | $construct_map[ $class_name ] =[ $doc , $file_pos];
77 | }
78 |
79 | if ($kind=="m") {
80 | $name.="(";
81 | }
82 | $class_map[$class_name][] =[
83 | $kind , $name, $doc , $file_pos , $return_type, $class_name,$access , $static ];
84 |
85 | break;
86 |
87 | case "f":
88 | $function_name=$scope."\\".$name;
89 | $doc=@$item["args"];
90 | $return_type=@$item["type"];
91 | $function_list[ ]= [ $kind , $function_name."(" , $doc , $file_pos , $return_type ] ;
92 | break;
93 | case "d":
94 | $define_scope=$item["args"];
95 | $return_type=$item["type"];
96 | $access=$item["access"];
97 | $doc="";
98 | if ($define_scope =="class") {
99 | $class_name= $scope;
100 | $class_map[$class_name][] =[
101 | $kind , $name, $doc , $file_pos , $return_type,$class_name , $access, "" ];
102 | } else {
103 | if (is_array($name) && isset($name['name'])) {
104 | $name = $name['name'];
105 | }
106 | $define_name=$scope."\\".$name;
107 | $function_list[ ]= [ $kind , $define_name, $doc , $file_pos , $return_type ] ;
108 | }
109 |
110 | break;
111 |
112 |
113 | default:
114 | break;
115 | }
116 | }
117 | }
118 | function get_path($cur_dir, $path)
119 | {
120 | $path=trim($path);
121 | if ($path[0] =="/") {
122 | return $path;
123 | }
124 | //相对路径
125 | return normalizePath($cur_dir."/".$path);
126 | }
127 | function normalizePath($path)
128 | {
129 | $parts = array();// Array to build a new path from the good parts
130 | $path = str_replace('\\', '/', $path);// Replace backslashes with forwardslashes
131 | $path = preg_replace('/\/+/', '/', $path);// Combine multiple slashes into a single slash
132 | $segments = explode('/', $path);// Collect path segments
133 | $test = '';// Initialize testing variable
134 | foreach ($segments as $segment) {
135 | if ($segment != '.') {
136 | $test = array_pop($parts);
137 | if (is_null($test)) {
138 | $parts[] = $segment;
139 | } elseif ($segment == '..') {
140 | if ($test == '..') {
141 | $parts[] = $test;
142 | }
143 |
144 | if ($test == '..' || $test == '') {
145 | $parts[] = $segment;
146 | }
147 | } else {
148 | $parts[] = $test;
149 | $parts[] = $segment;
150 | }
151 | }
152 | }
153 | return implode('/', $parts);
154 | }
155 |
156 | function deal_file_tags($cache_flag, $cache_file_name, $test_flag, $rebuild_all_flag, $cur_work_dir, $obj_dir, $realpath_flag, $php_path_list, $php_path_list_without_subdir, $php_file_ext_list, $start_pecent, $max_percent, $ignore_ruleset)
157 | {
158 | //得到要处理的文件
159 | $file_list=[];
160 |
161 |
162 | $ctags = new \app\PHPCtags([]);
163 | $i=0;
164 |
165 | $class_map= [];
166 | $function_list= [];
167 | $class_inherit_map= [];
168 |
169 |
170 | $cache_file_count=0;
171 |
172 | if (!$test_flag) {
173 | if ($cache_flag) {
174 | $common_json_file=__DIR__. "/common.json";
175 | // } else {
176 | // $common_json_file= $cache_file_name;
177 | // }
178 | $json_data= json_decode(file_get_contents($common_json_file), true);
179 | $class_map= $json_data[0];//类信息
180 | $function_list= $json_data[1];//函数,常量
181 | $class_inherit_map= $json_data[2];//继承
182 | $file_list= $json_data[3];//原先的文件列表
183 | } else {
184 | $config_data= json_decode(file_get_contents($cache_file_name), true);
185 | if (isset($config_data["vendor_file_count"])) {
186 | $cache_file_count=$config_data["vendor_file_count"];
187 | }
188 | }
189 | }
190 | /** @var Ruleset $ignore_ruleset */
191 |
192 | foreach ($php_path_list as $dir) {
193 | if ($realpath_flag) {
194 | $dir= realpath($dir);
195 | } else {
196 | $dir= get_path($cur_work_dir, $dir);
197 | }
198 | if (strpos($dir, $cur_work_dir) !== 0) {
199 | $dir_len=strlen($dir);
200 | $ignore_ruleset->check_start_pos=$dir_len+1;
201 | }
202 | get_filter_file_list($cache_flag, $file_list, $dir, $php_file_ext_list, true, $ignore_ruleset);
203 | if (strpos($dir, $cur_work_dir) !== 0) {
204 | $cur_work_dir_len=strlen($cur_work_dir);
205 | $ignore_ruleset->check_start_pos=$cur_work_dir_len+1;
206 | }
207 | }
208 |
209 | foreach ($php_path_list_without_subdir as $dir) {
210 | if ($realpath_flag) {
211 | $dir= realpath($dir);
212 | } else {
213 | $dir= get_path($cur_work_dir, $dir);
214 | }
215 | if (strpos($dir, $cur_work_dir) !== 0) {
216 | $dir_len=strlen($dir);
217 | $ignore_ruleset->check_start_pos=$dir_len+1;
218 | }
219 |
220 | get_filter_file_list($cache_flag, $file_list, $dir, $php_file_ext_list, false, $ignore_ruleset);
221 | if (strpos($dir, $cur_work_dir) !== 0) {
222 | $cur_work_dir_len=strlen($cur_work_dir);
223 | $ignore_ruleset->check_start_pos=$cur_work_dir_len+1;
224 | }
225 | }
226 |
227 | $ret_file_list =[];
228 | foreach ($file_list as $file) {
229 | if (strpos($file, $cur_work_dir) !== 0 || !$ignore_ruleset->match($file)) {
230 | $ret_file_list[]=$file;
231 | } else {
232 | echo "filter-ignore:$file\n";
233 | }
234 | }
235 | $file_list=$ret_file_list;
236 |
237 |
238 | $deal_all_count=count($file_list);
239 |
240 |
241 | if ($cache_flag) {
242 | $tags_data='{}';
243 | } else {
244 | $tags_file="$obj_dir/tag-file-map.json";
245 | $tags_data=@file_get_contents($tags_file);
246 | if (!$tags_data) {
247 | $tags_data='{}';
248 | }
249 | }
250 |
251 |
252 | $tags_map = json_decode($tags_data, true);
253 | $find_time=time();
254 | $last_pecent=-1;
255 | $construct_map=[];
256 | $class_define_map=[];
257 |
258 | $result=null;
259 | for ($i= 0; $i< $deal_all_count; $i++) {
260 | $file_index=$cache_file_count+$i ;
261 | $src_file= $file_list[$i];
262 | $tag_key= $src_file;
263 |
264 | $md5="";
265 | $need_deal_flag= false;
266 | if ($rebuild_all_flag) {
267 | $need_deal_flag= true;
268 | } else {
269 | if (@$tags_map[$tag_key]["gen_time"] < filemtime($src_file)) {
270 | $md5= md5_file($src_file);
271 | if (@$tags_map[$tag_key]["md5"] != $md5) {
272 | $need_deal_flag=true;
273 | } else {
274 | $tags_map[$tag_key]["gen_time"]=time();
275 | }
276 | }
277 | }
278 |
279 | unset($result);
280 | if ($need_deal_flag) {
281 | if (!$md5) {
282 | // echo "src :$src_file\n";
283 | $md5= md5_file($src_file);
284 | }
285 | $pecent =($i/$deal_all_count)*$max_percent;
286 | if ($pecent != $last_pecent) {
287 | printf("%02d%% %s\n", $start_pecent+$pecent, $src_file);
288 | $last_pecent = $pecent;
289 | }
290 | $ctags->cleanFiles();
291 |
292 |
293 | try {
294 | $result = $ctags->process_single_file($src_file);
295 | if ($result !== false) {
296 | $tags_map[$tag_key] =[
297 | "find_time" => $find_time ,
298 | "gen_time" => time(),
299 | "md5" => $md5,
300 | "result" =>$result,
301 | ];
302 | }
303 | } catch (\Exception $e) {
304 | echo "PHPParser: {$e->getMessage()} - {$src_file}".PHP_EOL;
305 | $tags_map[$tag_key]["find_time"] = $find_time;
306 | $result= &$tags_map[$tag_key]["result"] ;
307 | }
308 | } else {
309 | $tags_map[$tag_key]["find_time"] = $find_time;
310 | $result= &$tags_map[$tag_key]["result"] ;
311 | }
312 |
313 | if ($result) {
314 | deal_tags($file_index, $result, $class_inherit_map, $class_map, $function_list, $construct_map, $class_define_map);
315 | }
316 | }
317 |
318 | construct_map_to_function_list($class_map, $construct_map, $class_inherit_map, $function_list, $class_define_map);
319 |
320 | //clean old file-data
321 | foreach ($tags_map as $key => &$item) {
322 | if ($item["find_time"] != $find_time) {
323 | unset($tags_map[$key]);
324 | }
325 | }
326 |
327 |
328 | if (! $cache_flag) {
329 | file_put_contents($tags_file, json_encode($tags_map, JSON_PRETTY_PRINT));
330 | }
331 |
332 | $json_flag= JSON_PRETTY_PRINT ;
333 | if ($test_flag) {
334 | //$json_flag= null;
335 | // if ($cache_flag) {
336 | // $out_file_name=$cache_file_name;
337 | // } else {
338 | // }
339 | $out_file_name= "$obj_dir/tags.json";
340 |
341 | file_put_contents($out_file_name, json_encode([
342 | $class_map, $function_list, $class_inherit_map , $file_list
343 | ], $json_flag));
344 | }
345 | if ($cache_flag) {
346 | save_as_el("$obj_dir/tags-vendor.el", $class_map, $function_list, $class_inherit_map, $file_list);
347 | file_put_contents($cache_file_name, json_encode([
348 | "gen_time" => time(),
349 | "vendor_file_count" => count($file_list) ,
350 | ], $json_flag));
351 | } else {
352 | save_as_el("$obj_dir/tags.el", $class_map, $function_list, $class_inherit_map, $file_list);
353 | }
354 | }
355 |
356 | function deal_config($config_file, $rebuild_all_flag, $realpath_flag, $need_tags_dir, $test_flag)
357 | {
358 | //echo " rebuild_all_flag :$rebuild_all_flag \n";
359 | $work_dir = dirname($config_file);
360 | $config = get_config($config_file);
361 | chdir($work_dir);
362 |
363 | $tag_dir = @$config["tag-dir"];
364 |
365 | $cur_work_dir=$work_dir;
366 | if ($tag_dir) { // find
367 | $obj_dir= get_path($cur_work_dir, $tag_dir);
368 | } else { // default
369 | $tag_dir=$need_tags_dir;
370 | if (strtoupper(substr(PHP_OS, 0, 3))==='WIN') {
371 | $work_dir = "/". preg_replace("/[\:\\ \t]/", "", $work_dir);
372 | }
373 | $obj_dir= $tag_dir."/tags". preg_replace("/[\/\\ \t]/", "-", $work_dir);
374 | }
375 |
376 | @mkdir($tag_dir, 0777, true);
377 | @mkdir($obj_dir, 0777, true);
378 |
379 | $filter = $config["filter"] ;
380 | //$can_use_external_dir = @$filter["can-use-external-dir"];
381 | $php_path_list = $filter ["php-path-list"];
382 | $php_file_ext_list = $filter ["php-file-ext-list"];
383 | $php_path_list_without_subdir = isset($filter ["php-path-list-without-subdir"])? $filter ["php-path-list-without-subdir"]:[];
384 | $ignore_ruleset_list = isset($filter ["ignore-ruleset"])?$filter ["ignore-ruleset"]:[
385 | "/vendor/**/[tT]ests/**/*.php"
386 | ];
387 |
388 | $ignore_ruleset=Ruleset::loadFromStrings($ignore_ruleset_list);
389 | $cur_work_dir_len=strlen($cur_work_dir);
390 | $ignore_ruleset->check_start_pos=$cur_work_dir_len+1;
391 |
392 | print_r($ignore_ruleset);
393 | //echo "realpath_flag :$realpath_flag \n";
394 |
395 |
396 | $cache_file_name= "$obj_dir/tags-cache-v3.json" ;
397 |
398 | $start_pecent=0;
399 | $max_percent=100;
400 | $composer_lock_file="$cur_work_dir/composer.lock";
401 | $cache_flag=false;
402 | if (!file_exists($cache_file_name) || $rebuild_all_flag) {
403 | $cache_flag=true;
404 | }
405 | if (is_readable($composer_lock_file) && is_readable($cache_file_name)) {
406 | if (filemtime($composer_lock_file) > filemtime($cache_file_name)) {
407 | //echo "check_time:". filemtime($composer_lock_file) ." :". filemtime($cache_file_name) . "\n";
408 | //user update composer , need rebuild
409 | $cache_flag=true;
410 | $rebuild_all_flag=true;
411 | }
412 | }
413 |
414 |
415 | if ($cache_flag) {
416 | $max_percent=50;
417 | deal_file_tags($cache_flag, $cache_file_name, $test_flag, $rebuild_all_flag, $cur_work_dir, $obj_dir, $realpath_flag, $php_path_list, $php_path_list_without_subdir, $php_file_ext_list, $start_pecent, $max_percent, $ignore_ruleset);
418 | $start_pecent=50;
419 | }
420 | $cache_flag=false;
421 | deal_file_tags($cache_flag, $cache_file_name, $test_flag, $rebuild_all_flag, $cur_work_dir, $obj_dir, $realpath_flag, $php_path_list, $php_path_list_without_subdir, $php_file_ext_list, $start_pecent, $max_percent, $ignore_ruleset);
422 | }
423 | function save_as_el($file_name, $class_map, $function_list, $class_inherit_map, $file_list)
424 | {
425 | $fp=fopen($file_name, "w");
426 | $str= "(setq g-ac-php-tmp-tags [\n(\n" ;
427 | //class_map
428 | foreach ($class_map as $class_name => &$c_field_list) {
429 | $class_name_str= addslashes($class_name);
430 | $str.= " (\"". $class_name_str ."\".[\n" ;
431 | foreach ($c_field_list as &$c_f_item) {
432 | //print_r( $c_f_item );
433 | $doc=addslashes($c_f_item[2]??"");
434 | $return_type_str=addslashes($c_f_item[4]??"");
435 | $name= is_string($c_f_item[1]) ? addslashes($c_f_item[1]) : '';
436 | $str.=" [\"{$c_f_item[0]}\" \"$name\" \"{$doc}\" \"{$c_f_item[3]}\" \"$return_type_str\" \"$class_name_str\" \"{$c_f_item[6]}\" \"{$c_f_item[7]}\" ]\n";
437 | }
438 | $str.= " ])\n" ;
439 | }
440 | $str.= ")\n" ;
441 | fwrite($fp, $str);
442 | $str="[\n";
443 |
444 | //[ $kind , $define_name, $doc , $file_pos , $return_type ] ;
445 | foreach ($function_list as &$f_item) {
446 | $doc=addslashes($f_item[2]??"");
447 | $function_name_str=addslashes($f_item[1]??"");
448 | $return_type_str=addslashes($f_item[4]??"");
449 | $str.=" [\"{$f_item[0]}\" \"$function_name_str\" \"{$doc}\" \"{$f_item[3]}\" \"$return_type_str\" ]\n";
450 | }
451 | $str.="]\n";
452 | fwrite($fp, $str);
453 |
454 | $str="(\n";
455 | foreach ($class_inherit_map as $class_name => &$i_list) {
456 | $class_name_str= addslashes($class_name);
457 | $str.= " (\"". $class_name_str ."\". [ " ;
458 | foreach ($i_list as &$i_item) {
459 | $str .= "\"". addslashes($i_item) ."\" " ;
460 | }
461 | $str.= "])\n" ;
462 | }
463 | $str.=")\n";
464 | fwrite($fp, $str);
465 |
466 |
467 |
468 | $str="[\n";
469 | foreach ($file_list as &$f_file) {
470 | $str.=" \"". addslashes($f_file) ."\"\n" ;
471 | }
472 | $str.="]\n";
473 | fwrite($fp, $str);
474 | fwrite($fp, "])\n");
475 | fclose($fp);
476 | }
477 |
478 |
479 | function construct_map_to_function_list(&$class_map, &$construct_map, &$class_inherit_map, &$function_list, &$class_define_map)
480 | {
481 | // construct_map => function_list
482 | $kind="f";
483 | foreach (array_keys($class_map) as $class_name) {
484 | $cur_map=[];
485 | $find_item=null;
486 | $parent_class=$class_name;
487 | $tmp_parent_class="";
488 | do {
489 | $find_item=@$construct_map[$parent_class];
490 | if ($find_item) {
491 | break;
492 | }
493 | $cur_map[$parent_class ]=true;
494 | $tmp_parent_class=$parent_class;
495 | $parent_class=@$class_inherit_map[$parent_class][0];
496 | if ($parent_class) {
497 | if ($parent_class[0]!="\\") { //cur namespace
498 | $parent_class=preg_replace("/[A-Za-z0-8_]*\$/", "", $tmp_parent_class). "$parent_class";
499 | }
500 | }
501 | } while ($parent_class && !isset($cur_map[$parent_class]));
502 | if (!$find_item) { //没有找到,就用类定义
503 | $find_item= @$class_define_map[$class_name ];
504 | }
505 | if ($find_item) {
506 | $function_list[]= [ $kind, $class_name."(" , $find_item[0] , $find_item[1], $class_name ] ;
507 | }
508 | }
509 | }
510 |
511 | function get_filter_file_list($cache_flag, &$file_list, $dir, $file_ext_list, $reduce_flag, $ignore_ruleset)
512 | {
513 | //vendor 里都是需要缓存的
514 | if (!$cache_flag) { //
515 | if (preg_match("/\/vendor\//", $dir)) {
516 | return;
517 | }
518 | }
519 |
520 |
521 | $dir_list=scandir($dir);
522 | foreach ($dir_list as $file) {
523 | if ($file[0]!='.') {
524 | $sub_file=$dir.'/'.$file;
525 | if (is_dir($sub_file) && $reduce_flag) {
526 | if (!$cache_flag) { //
527 | if ($file=="vendor") {
528 | continue;
529 | }
530 | } else { //vendor 里 test ,tests 目录不处理
531 | /**
532 | if (in_array(strtolower($file), ["test", "tests" ]) !==false) {
533 | continue;
534 | }
535 | */
536 | }
537 |
538 | /** @var Ruleset $ignore_ruleset */
539 |
540 | if (!$ignore_ruleset->match($sub_file."/")) {
541 | get_filter_file_list($cache_flag, $file_list, $sub_file, $file_ext_list, $reduce_flag, $ignore_ruleset);
542 | } else {
543 | echo "filter-ignore-dir:$sub_file\n";
544 | }
545 | } else {
546 | $file_path = pathinfo($file);
547 | if (in_array(@$file_path['extension'], $file_ext_list)) {
548 | if ($cache_flag) {
549 | if (preg_match("/\/vendor\//", $dir)) {
550 | $file_list[]= "$dir/$file";
551 | }
552 | } else {
553 | $file_list[]= "$dir/$file";
554 | }
555 | }
556 | }
557 | }
558 | }
559 | }
560 |
--------------------------------------------------------------------------------
/PHPCtags.php:
--------------------------------------------------------------------------------
1 | 'trait',
20 | 'c' => 'class',
21 | 'e' => 'enum',
22 | 'm' => 'method',
23 | 'f' => 'function',
24 | 'p' => 'property',
25 | 'd' => 'constant',
26 | 'v' => 'variable',
27 | 'i' => 'interface',
28 | 'n' => 'namespace',
29 | 'T' => 'usetrait',
30 | );
31 |
32 | /**
33 | * @var \PhpParser\Parser\Php7
34 | */
35 | private $mParser;
36 | private $mLines;
37 | private $mOptions;
38 | private $mUseConfig=array();
39 | private $tagdata;
40 | private $cachefile;
41 | private $filecount;
42 |
43 | public function __construct($options)
44 | {
45 | $this->mParser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
46 | $this->mOptions = $options;
47 | $this->filecount = 0;
48 | }
49 |
50 | public function setMFile($file)
51 | {
52 | $this->mFile=$file;
53 | //$this->mFileLines = file($file ) ;
54 | }
55 |
56 | public static function getMKinds()
57 | {
58 | return self::$mKinds;
59 | }
60 | public function cleanFiles()
61 | {
62 | $this->mLines=array();
63 | $this->mUseConfig=array();
64 | }
65 |
66 | public function setCacheFile($file)
67 | {
68 | $this->cachefile = $file;
69 | }
70 |
71 |
72 | private function getNodeAccess($node)
73 | {
74 | if ($node->isPrivate()) {
75 | return 'private';
76 | }
77 | if ($node->isProtected()) {
78 | return 'protected';
79 | }
80 | return 'public';
81 | }
82 |
83 | /**
84 | * stringSortByLine
85 | *
86 | * Sort a string based on its line delimiter
87 | *
88 | * @author Techlive Zheng
89 | *
90 | * @access public
91 | * @static
92 | *
93 | * @param string $str string to be sorted
94 | * @param boolean $foldcse case-insensitive sorting
95 | *
96 | * @return string sorted string
97 | **/
98 | public static function stringSortByLine($str, $foldcase = false)
99 | {
100 | $arr = explode("\n", $str);
101 | if (!$foldcase) {
102 | sort($arr, SORT_STRING);
103 | } else {
104 | sort($arr, SORT_STRING | SORT_FLAG_CASE);
105 | }
106 | $str = implode("\n", $arr);
107 | return $str;
108 | }
109 |
110 | private static function helperSortByLine($a, $b)
111 | {
112 | return $a['line'] > $b['line'] ? 1 : 0;
113 | }
114 |
115 |
116 | private function get_key_in_scope($scope, $key)
117 | {
118 | foreach ($scope as $item) {
119 | $value= @$item[$key];
120 | if ($value) {
121 | return $value ;
122 | }
123 | }
124 | return false;
125 | }
126 |
127 | private function getRealClassName($className, $scope)
128 | {
129 | if ($className=="\$this" || $className == "static" || $className =="self") {
130 | return 'self';
131 | // $namespace= $this-> get_key_in_scope($scope, "namespace");
132 | // $className= $this-> get_key_in_scope($scope, "class");
133 | // if ($namespace) {
134 | // return "\\$namespace\\$className";
135 | // } else {
136 | // return "\\$className";
137 | // }
138 | }
139 |
140 | if ($className[0] != "\\") {
141 | //系统本身类型
142 | if (in_array($className, ["string", "mixed", "int", "float", "double"])) {
143 | return $className;
144 | }
145 |
146 | $ret_arr=explode("\\", $className, 2);
147 | $pack_name=$ret_arr[0];
148 | if (count($ret_arr)==2) {
149 | if (isset($this->mUseConfig[ $pack_name])) {
150 | return $this->mUseConfig[$pack_name]."\\".$ret_arr[1] ;
151 | } else {
152 | return $className;
153 | }
154 | } else {
155 | if (isset($this->mUseConfig[$pack_name])) {
156 | return $this->mUseConfig[$pack_name] ;
157 | } else {
158 | $namespace= $this-> get_key_in_scope($scope, "namespace");
159 | if ($namespace) {
160 | return "\\$namespace\\$className";
161 | } else {
162 | return "\\$className";
163 | }
164 | }
165 | }
166 | } else {
167 | return $className;
168 | }
169 | }
170 |
171 | private function func_get_return_type($node, $scope)
172 | {
173 |
174 | if ($node->returnType instanceof \PhpParser\Node\NullableType) {
175 | $return_type="". $node->returnType->type ;
176 | } elseif ($node->returnType instanceof \PhpParser\Node\UnionType) {
177 | $return_type="". $node->returnType->types[0] ;
178 | } elseif ($node->returnType instanceof \PhpParser\Node) {
179 | $return_type="". $node->returnType->getType() ;
180 | } else {
181 | $return_type="". $node->returnType;
182 | }
183 |
184 |
185 | if (!$return_type) {
186 | if (preg_match("/@return[ \t]+([\$a-zA-Z0-9_\\\\]+)/", $node->getDocComment()??"", $matches)) {
187 | $return_type= $matches[1];
188 | }
189 | }
190 | if ($return_type) {
191 | $return_type=$this->getRealClassName($return_type, $scope);
192 | }
193 | return $return_type;
194 | }
195 | private function gen_args_default_str($node)
196 | {
197 |
198 |
199 | if ($node instanceof \PhpParser\Node\Scalar\LNumber) {
200 | /** @var \PhpParser\Node\Scalar\LNumber $node */
201 | $kind= $node->getAttribute("kind");
202 | switch ($kind) {
203 | case \PhpParser\Node\Scalar\LNumber::KIND_DEC:
204 | return strval($node->value);
205 | break;
206 | case \PhpParser\Node\Scalar\LNumber::KIND_OCT:
207 | return sprintf("0%o", $node->value) ;
208 | break;
209 | case \PhpParser\Node\Scalar\LNumber::KIND_HEX:
210 | return sprintf("0x%x", $node->value) ;
211 | break;
212 | case \PhpParser\Node\Scalar\LNumber::KIND_BIN:
213 | return sprintf("0b%b", $node->value) ;
214 | break;
215 | default:
216 | return strval($node->value);
217 | break;
218 | }
219 | } elseif ($node instanceof \PhpParser\Node\Scalar\String_) {
220 | return "'".$node->value."'";
221 | } elseif ($node instanceof \PhpParser\Node\Expr\ConstFetch) {
222 | return strval($node->name);
223 | } elseif ($node instanceof \PhpParser\Node\Expr\UnaryMinus) {
224 | return "" ;
225 | } elseif ($node instanceof \PhpParser\Node\Expr\BinaryOp\BitwiseOr) {
226 | return "" ;
227 | } elseif ($node instanceof \PhpParser\Node\Expr\Array_) {
228 | /** @var \PhpParser\Node\Expr\Array_ $node */
229 | if (count($node->items)==0) {
230 | return "[]" ;
231 | } else {
232 | return "array" ;
233 | }
234 | } else {
235 | return @strval($node->value);
236 | }
237 | }
238 |
239 | private function get_args($node)
240 | {
241 |
242 | $args_list=[];
243 |
244 | foreach ($node->getParams() as $param) {
245 | $ref_str="";
246 | if (@$param->byRef == 1) {
247 | //$ref_str="&";
248 | }
249 |
250 | if (!$param->var) {
251 | continue;
252 | }
253 |
254 | if ($param->default) {
255 | $def_str=$this->gen_args_default_str($param->default);
256 | $args_list[]="$ref_str\$".$param->var ->name."=$def_str";
257 | } else {
258 | $args_list[]="$ref_str\$".$param->var->name;
259 | }
260 | }
261 | return join(", ", $args_list);
262 | }
263 |
264 | private function struct($node, $reset = false, $parent = array())
265 | {
266 | static $scope = array();
267 | static $structs = array();
268 |
269 | if ($reset) {
270 | $structs = array();
271 | }
272 |
273 |
274 |
275 |
276 | $kind = $name = $line = $access = $extends = '';
277 | $static =false;
278 | $return_type="";
279 | $implements = array();
280 | $inherits=[];
281 | $args="";
282 |
283 |
284 | if (!empty($parent)) {
285 | array_push($scope, $parent);
286 | }
287 |
288 | if (is_array($node)) {
289 | foreach ($node as $subNode) {
290 | $this->struct($subNode);
291 | }
292 | } elseif ($node instanceof \PHPParser\Node\Stmt\UseUse) {
293 | $use_name=$node->name->toString();
294 | if ($use_name[0] != "\\") {
295 | $use_name="\\".$use_name;
296 | }
297 | if ($node->alias) {
298 | $this->mUseConfig[$node->alias->name]= $use_name ;
299 | } else {
300 | // \use think\console\Command; to : Command => \think\console\Command
301 | $tmp_arr=preg_split('/\\\\/', $use_name);
302 | $this->mUseConfig[ $tmp_arr[count($tmp_arr)-1] ]= $use_name ;
303 | }
304 | } elseif ($node instanceof \PhpParser\Node\Stmt\TraitUse) {
305 | foreach ($node ->traits as $trait) {
306 | $type= implode("\\", $trait->parts) ;
307 |
308 | $name = str_replace("\\", "_", $type) ;
309 | $line = $node->getLine();
310 |
311 | $return_type=$this->getRealClassName($type, $scope);
312 | $structs[] = array(
313 | //'file' => $this->mFile,
314 | 'kind' => "T",
315 | 'name' => $name,
316 | 'line' => $line ,
317 | 'scope' => $this->get_scope($scope) ,
318 | 'access' => "public",
319 | 'type' => $return_type,
320 | );
321 | }
322 | } elseif ($node instanceof \PHPParser\Node\Stmt\Use_) {
323 | foreach ($node as $subNode) {
324 | $this->struct($subNode);
325 | }
326 | } elseif ($node instanceof \PHPParser\Node\Stmt\Class_
327 | or $node instanceof \PHPParser\Node\Stmt\Trait_
328 | or $node instanceof \PHPParser\Node\Stmt\Enum_
329 | ) {
330 | $kind = 'c';
331 | //$name = $node->name;
332 | if ($node instanceof \PHPParser\Node\Stmt\Enum_) {
333 | $inherits[]="enum_";
334 | }
335 | $name = $node->name->name;
336 | $extends = @$node->extends;
337 | $implements = @$node->implements;
338 | $line = $node->getLine();
339 |
340 | $filed_scope=$scope;
341 | array_push($filed_scope, array('class' => $name ));
342 |
343 |
344 | $doc_item= $node->getDocComment() ;
345 | if ($doc_item) {
346 | $doc_start_line=$doc_item->getLine();
347 | $arr=explode("\n", ($doc_item->__toString()));
348 | foreach ($arr as $line_num => $line_str) {
349 | if (preg_match(
350 | "/@property(?:|-write|-read)[ \t]+([a-zA-Z0-9_\\\\]+)[ \t]+\\$?([a-zA-Z0-9_]+)/",
351 | $line_str,
352 | $matches
353 | )) {
354 | $field_name=$matches[2];
355 | $field_return_type= $this->getRealClassName($matches[1], $filed_scope);
356 | $structs[] = array(
357 | //'file' => $this->mFile,
358 | 'kind' => "p",
359 | 'name' => $field_name,
360 | 'line' => $doc_start_line+ $line_num ,
361 | 'scope' => $this->get_scope($filed_scope) ,
362 | 'access' => "public",
363 | 'type' => $field_return_type,
364 | );
365 | } elseif (preg_match(
366 | "/@method[ \t]+(static[ \t]+)*([^\\(]+)[ \t]+([a-zA-Z0-9_]+)[ \t]*\\((.*)\\)/",
367 | $line_str,
368 | $matches
369 | )) {
370 | //* @method static string imageUrl($width = 640, $height = 480, $category = null, $randomize = true)
371 | $static_flag=($matches[1]!="");
372 | $field_name=$matches[3];
373 | $args =$matches[4];
374 | $field_return_type= $this->getRealClassName($matches[2], $filed_scope);
375 | $structs[] = array(
376 | //'file' => $this->mFile,
377 | 'kind' => "m",
378 | 'name' => $field_name,
379 | 'line' => $doc_start_line+ $line_num ,
380 | 'scope' => $this->get_scope($filed_scope) ,
381 | 'access' => "public",
382 | 'type' => $field_return_type,
383 | 'static' => $static_flag,
384 | "args" => $args,
385 | );
386 | } elseif (preg_match(
387 | "/@use[ \t]+([a-zA-Z0-9_\\\\]+)/",
388 | $line_str,
389 | $matches
390 | )
391 | or preg_match(
392 | "/@mixin[ \t]+([a-zA-Z0-9_\\\\]+)/",
393 | $line_str,
394 | $matches
395 | )
396 | or (
397 | $extends && $extends->toString() =="Facade" &&
398 | preg_match(
399 | "/@see[ \t]+([a-zA-Z0-9_\\\\]+)/",
400 | $line_str,
401 | $matches
402 | ) )
403 |
404 | ) {
405 | //* @use classtype
406 |
407 | $type= $matches[1];
408 | $field_name = str_replace("\\", "_", $type) ;
409 | $field_return_type= $this->getRealClassName($type, $filed_scope);
410 |
411 | $structs[] = array(
412 | //'file' => $this->mFile,
413 | 'kind' => "T",
414 | 'name' => $field_name,
415 | 'line' => $doc_start_line+ $line_num ,
416 | 'scope' => $this->get_scope($filed_scope) ,
417 | 'access' => "public",
418 | 'type' => $field_return_type,
419 | );
420 | }
421 | }
422 | }
423 |
424 | foreach ($node as $key => $subNode) {
425 | if ($key=="stmts") {
426 | foreach ($subNode as $tmpNode) {
427 | $comments=$tmpNode->getAttribute("comments");
428 | if (is_array($comments)) {
429 | foreach ($comments as $comment) {
430 | if (preg_match(
431 | "/@var[ \t]+([a-zA-Z0-9_]+)[ \t]+\\$([a-zA-Z0-9_\\\\]+)/",
432 | $comment->getText(),
433 | $matches
434 | )) {
435 | $field_name=$matches[2];
436 | $field_return_type= $this->getRealClassName($matches[1], $filed_scope);
437 | $structs[] = array(
438 | // 'file' => $this->mFile,
439 | 'kind' => "p",
440 | 'name' => $field_name,
441 | 'line' => $comment->getLine() ,
442 | 'scope' => $this->get_scope($filed_scope) ,
443 | 'access' => "public",
444 | 'type' => $field_return_type,
445 | );
446 | }
447 | }
448 | }
449 | }
450 | }
451 | $this->struct($subNode, false, array('class' => $name));
452 | }
453 | } elseif ($node instanceof \PHPParser\Node\Stmt\Property) {
454 | $kind = 'p';
455 |
456 | $prop = $node->props[0];
457 | $name = $prop->name->name;
458 | $return_type="";
459 | if ($node->type && is_object($node->type)) {
460 | if (method_exists($node->type, '__toString')) {
461 | $return_type="". $node->type;
462 | }
463 | if ($node->type instanceof \PHPParser\Node\Name\FullyQualified) {
464 | $return_type= "\\".$return_type;
465 | }
466 | }
467 |
468 | $static=$node->isStatic();
469 | $line = $prop->getLine();
470 | // echo "return_type: " . $return_type ."\n" ;
471 | if (!$return_type) {
472 | if (preg_match("/@var[ \t]+([a-zA-Z0-9_\\\\|]+)/", $node->getDocComment()??"", $matches)) {
473 | $return_type=$this->getRealClassName($matches[1], $scope);
474 | }
475 | }
476 |
477 | $access = $this->getNodeAccess($node);
478 | } elseif ($node instanceof \PHPParser\Node\Stmt\ClassConst
479 | ) {
480 | $kind = 'd';
481 | $cons = $node->consts[0];
482 | $name = $cons->name->name;
483 | $line = $cons->getLine();
484 | $access = "public";
485 | $return_type="void";
486 | if (preg_match("/@var[ \t]+([a-zA-Z0-9_\\\\|]+)/", $node->getDocComment()??"", $matches)) {
487 | $return_type=$this->getRealClassName($matches[1], $scope);
488 | }
489 | $args="class";
490 | } elseif ($node instanceof \PHPParser\Node\Stmt\EnumCase) {
491 | $kind = 'p';
492 | $name = $node->name->name;
493 | $line = $node->getLine();
494 | $access = "public";
495 | $static = 1;
496 | $return_type= $this-> getRealClassName('static', $scope);
497 | $args="enum";
498 | } elseif ($node instanceof \PHPParser\Node\Stmt\ClassMethod) {
499 | $kind = 'm';
500 | $name = $node->name->name;
501 | $line = $node->getLine();
502 |
503 | $access = $this->getNodeAccess($node);
504 | $static =$node->isStatic();
505 | $return_type=$this->func_get_return_type($node, $scope);
506 |
507 | $args=$this->get_args($node);
508 |
509 |
510 |
511 | if ($name=="__construct") {
512 | $filed_scope=$scope;
513 | // array_push($filed_scope, array('class' => $name ));
514 | foreach ($node->getParams() as $param) {
515 | /** @var \PhpParser\Node\Param $param*/
516 |
517 | if (!$param->var) {
518 | continue;
519 | }
520 | $flags=$param->flags;
521 | if ($flags==0) { //public
522 | continue;
523 | }
524 |
525 | $field_access="public";
526 | if ($flags==2) { //
527 | $field_access="protected";
528 | } elseif ($flags==4) {
529 | $field_access="private";
530 | }
531 |
532 |
533 | $field_name=$param->var->name;
534 |
535 | $field_return_type="mixed";
536 | if ($param->type) {
537 | $cls = method_exists($param->type, 'toCodeString') ? $param->type->toCodeString() : $param->type->getType();
538 | $field_return_type= $this->getRealClassName($cls, $filed_scope);
539 | }
540 | $structs[] = array(
541 | //'file' => $this->mFile,
542 | 'kind' => "p",
543 | 'name' => $field_name,
544 | 'line' => $param->getLine() ,
545 | 'scope' => $this->get_scope($filed_scope) ,
546 | 'access' => $field_access,
547 | 'type' => $field_return_type,
548 | );
549 | }
550 | }
551 |
552 |
553 | /*
554 | foreach ($node as $subNode) {
555 | $this->struct($subNode, FALSE, array('method' => $name));
556 | }
557 | */
558 | } elseif ($node instanceof \PHPParser\Node\Stmt\If_) {
559 | foreach ($node as $subNode) {
560 | $this->struct($subNode);
561 | }
562 | } elseif ($node instanceof \PHPParser\Node\Expr\LogicalOr) {
563 | foreach ($node as $subNode) {
564 | $this->struct($subNode);
565 | }
566 | } elseif ($node instanceof \PHPParser\Node\Stmt\Const_) {
567 | $kind = 'd';
568 | $access = "public";
569 | $cons = $node->consts[0];
570 | $name = $cons->name;
571 | $line = $node->getLine();
572 |
573 | $return_type="void";
574 | if (preg_match("/@var[ \t]+([a-zA-Z0-9_\\\\|]+)/", $node->getDocComment()??"", $matches)) {
575 | $return_type=$this->getRealClassName($matches[1], $scope);
576 | }
577 |
578 | $args="namespace";
579 | } elseif ($node instanceof \PHPParser\Node\Stmt\Global_) {
580 | } elseif ($node instanceof \PHPParser\Node\Stmt\Static_) {
581 | //@todo
582 | } elseif ($node instanceof \PHPParser\Node\Stmt\Declare_) {
583 | //@todo
584 | } elseif ($node instanceof \PHPParser\Node\Stmt\TryCatch) {
585 | /*
586 | foreach ($node as $subNode) {
587 | $this->struct($subNode);
588 | }
589 | */
590 | } elseif ($node instanceof \PHPParser\Node\Stmt\Function_) {
591 | $kind = 'f';
592 | //$name = $node->name;
593 | $name = $node->name->name;
594 |
595 | // PS_UNRESERVE_PREFIX_
596 | if (!isset($scope["namespace"]) && substr($name, 0, 20) =="PS_UNRESERVE_PREFIX_") {
597 | $name=substr($name, 20);
598 | }
599 | $line = $node->getLine();
600 |
601 | $return_type = $this->func_get_return_type($node, $scope);
602 |
603 | $args=$this->get_args($node);
604 | } elseif ($node instanceof \PHPParser\Node\Stmt\Interface_) {
605 | $kind = 'i';
606 | //$name = $node->name;
607 | $name = $node->name->name;
608 | $extends = @$node->extends;
609 |
610 | $line = $node->getLine();
611 | foreach ($node as $subNode) {
612 | $this->struct($subNode, false, array('interface' => $name));
613 | }
614 | } elseif ($node instanceof \PHPParser\Node\Stmt\Trait_) {
615 | $kind = 't';
616 | //$name = $node->name;
617 | $name = $node->name->name;
618 | $line = $node->getLine();
619 | foreach ($node as $subNode) {
620 | $this->struct($subNode, false, array('trait' => $name));
621 | }
622 | } elseif ($node instanceof \PHPParser\Node\Stmt\Namespace_) {
623 | /*
624 | $kind = 'n';
625 | $line = $node->getLine();
626 | */
627 | $name = $node->name;
628 |
629 | foreach ($node as $subNode) {
630 | if ($name) {
631 | $this->struct($subNode, false, array('namespace' => $name));
632 | } else {
633 | $this->struct($subNode);
634 | }
635 | }
636 | } elseif ($node instanceof \PHPParser\Node\Expr\Assign_) {
637 | if (isset($node->var->name) && is_string($node->var->name)) {
638 | $kind = 'v';
639 | $node = $node->var;
640 | //$name = $node->name;
641 | $name = $node->name->name;
642 | $line = $node->getLine();
643 |
644 | $return_type="void";
645 | if (preg_match("/@var[ \t]+([a-zA-Z0-9_\\\\|]+)/", $node->getDocComment(), $matches)) {
646 | $return_type=$this->getRealClassName($matches[1], $scope);
647 | }
648 | }
649 | } elseif ($node instanceof \PHPParser\Node\Expr\AssignRef) {
650 | if (isset($node->var->name) && is_string($node->var->name)) {
651 | $kind = 'v';
652 | $node = $node->var;
653 | //$name = $node->name;
654 | $name = $node->name->name;
655 | $line = $node->getLine();
656 | $return_type="void";
657 | if (preg_match("/@var[ \t]+([a-zA-Z0-9_\\\\|]+)/", $node->getDocComment(), $matches)) {
658 | $return_type=$this->getRealClassName($matches[1], $scope);
659 | }
660 | }
661 | } elseif ($node instanceof \PhpParser\Node\Stmt\Expression) {
662 | $node= $node->expr;
663 | // print_r($node);
664 | if ($node instanceof \PhpParser\Node\Expr\FuncCall) {
665 | $name = @$node->name;
666 | // echo "call $name \n";
667 | switch ($name) {
668 | case 'define':
669 | $kind = 'd';
670 | $access = "public";
671 | $node = $node->args[0]->value;
672 | $name = $node->value;
673 | $line = $node->getLine();
674 | $return_type="void";
675 | if (preg_match("/@var[ \t]+([a-zA-Z0-9_\\\\|]+)/", $node->getDocComment(), $matches)) {
676 | $return_type=$this->getRealClassName($matches[1], $scope);
677 | }
678 | $args="namespace";
679 |
680 | break;
681 | case 'class_alias':
682 | $kind = 'c';
683 | $access = "public";
684 | $line = $node->getLine();
685 | $sub_node = $node->args[1]->value;
686 | $name = strval($sub_node->class);
687 | $sub_node_2 = $node->args[0]->value;
688 | $name2 = strval($sub_node_2->class);
689 |
690 |
691 | $inherits[]= $name2;
692 | // echo " check $name =>$name2 \n";
693 | break;
694 | }
695 | }
696 | } else {
697 | // we don't care the rest of them.
698 | }
699 |
700 | if (!empty($kind) && $kind !="n" && !empty($name) && !empty($line)) {
701 | $item=array(
702 | //'file' => $this->mFile,
703 | 'line' => $line,
704 | 'kind' => $kind,
705 | 'name' => $name,
706 | "scope" => $this->get_scope($scope),
707 | );
708 | if ($access) {
709 | $item["access"]= $access;
710 | }
711 | if ($return_type) {
712 | $item["type"]= $return_type;
713 | }
714 | $inherits= array_merge($this->get_inherits($extends, $implements, $scope), $inherits);
715 | if (!empty($inherits)) {
716 | $item["inherits"]= $inherits;
717 | }
718 | if ($args) {
719 | $item["args"]= $args;
720 | }
721 | if ($static) {
722 | $item["static"]= 1;
723 | }
724 |
725 |
726 | $structs[] = $item;
727 | }
728 |
729 | if (!empty($parent)) {
730 | array_pop($scope);
731 | }
732 | // print_r($structs);
733 |
734 | return $structs;
735 | }
736 |
737 |
738 |
739 |
740 |
741 | public function process_single_file($filename)
742 | {
743 | $this->setMFile((string) $filename);
744 | $file = file_get_contents($this->mFile);
745 | return $this->struct($this->mParser->parse($file), true);
746 | }
747 | public function get_inherits($extends, $implements, $scope)
748 | {
749 | $inherits = array();
750 | if (!empty($extends)) {
751 | if (is_array($extends)) {
752 | foreach ($extends as $item) {
753 | $inherits[] = $this->getRealClassName($item->toString(), $scope);
754 | }
755 | } else {
756 | $inherits[] = $this->getRealClassName($extends->toString(), $scope);
757 | }
758 | }
759 |
760 | if (!empty($implements)) {
761 | foreach ($implements as $interface) {
762 | $inherits[] = $this->getRealClassName($interface->toString(), $scope);
763 | }
764 | }
765 | return $inherits ;
766 | }
767 |
768 | public function get_scope($old_scope)
769 | {
770 | if (!empty($old_scope)) {
771 | $scope = array_pop($old_scope);
772 | list($type, $name) = [key($scope), current($scope)];
773 | switch ($type) {
774 | case 'class':
775 | case 'interface':
776 | case '':
777 | // n_* stuffs are namespace related scope variables
778 | // current > class > namespace
779 | $n_scope = array_pop($old_scope);
780 | if (!empty($n_scope)) {
781 | list($n_type, $n_name) = [key($n_scope), current($n_scope)];
782 | $s_str = '\\'. $n_name . '\\' . $name;
783 | } else {
784 | $s_str = '\\' . $name;
785 | }
786 |
787 | return $s_str;
788 | break;
789 | case 'method':
790 | // c_* stuffs are class related scope variables
791 | // current > method > class > namespace
792 | $c_scope = array_pop($scope);
793 | //list($c_type, $c_name) = [key($c_scope), current($c_scope)];
794 | $c_name= current($c_scope);
795 | $n_scope = array_pop($scope);
796 | if (!empty($n_scope)) {
797 | list($n_type, $n_name) = [key($n_scope), current($n_scope)];
798 | $s_str = '\\'. $n_name . '\\' . $c_name . '::' . $name;
799 | } else {
800 | $s_str = '\\'. $c_name . '::' . $name;
801 | }
802 |
803 | return $s_str;
804 | break;
805 | default:
806 | return "\\$name";
807 | break;
808 | }
809 | } else {
810 | return null;
811 | }
812 | }
813 | }
814 |
--------------------------------------------------------------------------------