├── .ac-php-conf.json ├── .env ├── .gitignore ├── .travis.yml ├── ChangeLog.md ├── GitIgnore ├── FileFinder.php ├── Pattern.php ├── Rule.php ├── Ruleset.php └── Util.php ├── Makefile ├── PHPCtags.php ├── README.md ├── a.json ├── a.php ├── bin └── phpctags ├── bootstrap.php ├── buildPHAR.php ├── common.json ├── composer.json ├── composer.lock ├── deal_config.php ├── gen_common_json.php ├── phpunit.xml.dist └── t.sh /.ac-php-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "use-cscope": null, 3 | "tag-dir": null, 4 | "filter": { 5 | "php-file-ext-list": [ 6 | "php" 7 | ], 8 | "php-path-list": [ 9 | "." 10 | ], 11 | "php-path-list-without-subdir": [] 12 | } 13 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ls 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .test_fs 2 | phpctags 3 | build/ 4 | vendor/ 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | Version 0.6.0 2 | * Exclude phpctags when re-building phar 3 | * Fix typo in README.md 4 | * Fixed PHPUnit.xml.dist 5 | * Refactoring codebase 6 | * Refactroing testsuite 7 | * Now composer support an entry point to install in globally 8 | * Define new build flow 9 | 10 | Version 0.5.1 11 | ------------- 12 | 13 | * Fix building compatiblity with PHP 5.3.* 14 | thanks to grep-awesome@github 15 | 16 | Version 0.5 17 | ----------- 18 | 19 | * Add trait support 20 | thanks to Mark Wu 21 | * Add inherits support 22 | thanks to Mark Wu 23 | * Add namespace support 24 | thanks to Mark Wu 25 | * Add instruction to enable phar extension in README 26 | 27 | Version 0.4.2 28 | ------------- 29 | 30 | * Add assign reference support to phpctags 31 | thanks to Mark Wu 32 | 33 | Version 0.4.1 34 | ------------- 35 | 36 | * Add explaination for PHAR supported platforms 37 | 38 | Version 0.4 39 | ----------- 40 | 41 | * add tagline support 42 | thanks to Mark Wu 43 | * add a Makefile to build stand-alone PHAR executable 44 | thanks to Mark Wu 45 | * update PHPParser dependency to version 0.9.3 46 | 47 | Version 0.3 48 | ----------- 49 | 50 | * able to control the memroy size to be used 51 | thanks to Dannel Jurado 52 | * improved command line compatiblity to ctags 53 | thanks to Sander Marechal 54 | * bug fixes: #5 and #7 55 | 56 | Version 0.2 57 | ----------- 58 | 59 | * new kind descriptor 60 | * ctags flags support 61 | * excmd 62 | * fields 63 | * format 64 | * new test case layer 65 | * introduce debug mode 66 | * bug fixes: #3 and #4 67 | 68 | Version 0.1 69 | ----------- 70 | 71 | * ctags compatible 72 | * surppoted tokens 73 | * constant 74 | * variable 75 | * function 76 | * class 77 | * class method 78 | * class property 79 | * class constant 80 | * interface 81 | * scope field support 82 | * access field support 83 | -------------------------------------------------------------------------------- /GitIgnore/FileFinder.php: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /GitIgnore/Rule.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 | -------------------------------------------------------------------------------- /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/Util.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | phpctags 2 | ======== 3 | Master [![Build Status](https://travis-ci.org/xcwen/phpctags.svg)](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 | -------------------------------------------------------------------------------- /a.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "/home/jim/phpctags/a.php", 4 | "/home/jim/phpctags/a.tags" 5 | ] 6 | ] 7 | -------------------------------------------------------------------------------- /a.php: -------------------------------------------------------------------------------- 1 | v1 13 | } 14 | use Instance; 15 | 16 | use Concerns\InteractsWithContainer, 17 | Concerns\MakesHttpRequests, 18 | Concerns\ImpersonatesUsers; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bin/phpctags: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 3 | , 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 | -------------------------------------------------------------------------------- /buildPHAR.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 9 | parent::__construct($iterator); 10 | } 11 | 12 | public function accept () { 13 | $callback = $this->callback; 14 | return $callback(parent::current(), parent::key(), parent::getInnerIterator()); 15 | } 16 | 17 | public function getChildren () { 18 | return new self($this->getInnerIterator()->getChildren(), $this->callback); 19 | } 20 | } 21 | } 22 | 23 | $phar->buildFromIterator( 24 | new RecursiveIteratorIterator( 25 | new RecursiveCallbackFilterIterator( 26 | new RecursiveDirectoryIterator( 27 | getcwd(), 28 | FilesystemIterator::SKIP_DOTS 29 | ), 30 | function ($current) { 31 | $excludes = array( 32 | '.*', 33 | 'tags', 34 | 'ac-php/*', 35 | 'build/*', 36 | 'tests/*', 37 | 'Makefile', 38 | 'bin/phpctags', 39 | 'phpctags', 40 | 'buildPHAR.php', 41 | 'composer.lock', 42 | "vendor/nikic/php-parser/test/*", 43 | "vendor/nikic/php-parser/test_old/*", 44 | "vendor/nikic/php-parser/grammar", 45 | "vendor/nikic/php-parser/doc", 46 | "vendor/nikic/php-parser/bin", 47 | "vendor/nikic/php-parser/UPGRADE-1.0.md", 48 | "vendor/nikic/php-parser/README.md", 49 | "vendor/nikic/php-parser/phpunit.xml.dist", 50 | "vendor/nikic/php-parser/LICENSE", 51 | "vendor/nikic/php-parser/composer.json", 52 | "vendor/nikic/php-parser/CHANGELOG.md", 53 | "vendor/bin", 54 | "vendor/doctrine", 55 | "vendor/phpdocumentor", 56 | "vendor/phpspec", 57 | "vendor/phpunit", 58 | "vendor/sebastian", 59 | "vendor/symfony", 60 | "vendor/google/protobuf/[^p]*", 61 | "vendor/google/protobuf/proto*", 62 | "vendor/google/protobuf/post*", 63 | "vendor/google/protobuf/python", 64 | "vendor/google/protobuf/php/[^s]*", 65 | 'README.md', 66 | '.tags', 67 | "ChangeLog.md", 68 | ); 69 | 70 | foreach($excludes as $exclude) { 71 | if (fnmatch(getcwd().'/'.$exclude, $current->getPathName())) { 72 | return false; 73 | } 74 | } 75 | 76 | return true; 77 | } 78 | ) 79 | ), 80 | getcwd() 81 | ); 82 | 83 | $phar->setStub( 84 | "#!/usr/bin/env php\n".$phar->createDefaultStub('bootstrap.php') 85 | ); 86 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xcwen/phpctags", 3 | "description": "An enhanced php ctags index generator.", 4 | "keywords": ["ctags"], 5 | "type": "library", 6 | "require": { 7 | "php": ">=7.0", 8 | "nikic/php-parser": "^4.15" 9 | }, 10 | "autoload": { 11 | "classmap": ["PHPCtags.php"] 12 | }, 13 | "bin": ["bin/phpctags"] 14 | } 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gen_common_json.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | 2 | 3 | 4 | 5 | 6 | tests/ 7 | 8 | 9 | 10 | 11 | src 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /t.sh: -------------------------------------------------------------------------------- 1 | #php ./bootstrap.php --config-file=/home/jim/admin_yb1v1/.ac-php-conf.json --tags_dir=/home/jim/.ac-php --rebuild=yes --realpath_flag=false 2 | #php ./bootstrap.php --config-file=/home/jim/ac-php/commom_php/.ac-php-conf.json --tags_dir=/home/jim/.ac-php --rebuild=yes --realpath_flag=yes --test=yes 3 | #php ./bootstrap.php --config-file=/home/jim/phpctags/.ac-php-conf.json --tags_dir=/home/jim/.ac-php --rebuild=yes --realpath_flag=yes --test=no 4 | 5 | php ./bootstrap.php --config-file=/home/jim/ac-php/phptest/.ac-php-conf.json --tags_dir=/home/jim/.cache/ac-php --rebuild=yes --realpath_flag=yes 6 | #php ./bootstrap.php --save-common-el=/home/jim/b.el 7 | 8 | #php ./bootstrap.php --config-file=/home/jim/test/.ac-php-conf.json --tags_dir=/home/jim/.ac-php --rebuild=yes --realpath_flag=yes --test=no 9 | #php ./bootstrap.php --config-file=/home/jim/sd/.ac-php-conf.json --tags_dir=/home/jim/.ac-php --rebuild=yes --realpath_flag=yes --test=no 10 | #php ./bootstrap.php --config-file=/home/jim/aigc-php-core/src/.ac-php-conf.json --tags_dir=/home/jim/.ac-php --rebuild=yes --realpath_flag=no 11 | 12 | 13 | #php ./bootstrap.php --config-file=/home/jim/ac-php/phptest/.ac-php-conf.json --tags_dir=/home/jim/.ac-php --rebuild=yes --realpath_flag=no --test=no 14 | #php ./bootstrap.php --config-file=/home/jim/console_php_core/src/.ac-php-conf.json --tags_dir=/home/jim/.ac-php --rebuild=no --realpath_flag=no --test=no 15 | --------------------------------------------------------------------------------