@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at recaptime-dev/main 179 lines 5.5 kB view raw
1<?php 2 3final class PhorgePHPParserExtractor extends PhpParser\NodeVisitorAbstract { 4 private static $knownTypes = array( 5 'PhabricatorEdgeType' => array( 6 'getTransactionAddString' => array(null, 'phutilnumber', null), 7 'getTransactionRemoveString' => array(null, 'phutilnumber', null), 8 'getTransactionEditString' => array( 9 null, 10 'phutilnumber', 11 'phutilnumber', 12 null, 13 'phutilnumber', 14 null, 15 ), 16 'getFeedAddString' => array(null, null, 'phutilnumber', null), 17 'getFeedRemoveString' => array(null, null, 'phutilnumber', null), 18 'getFeedEditString' => array( 19 null, 20 null, 21 'phutilnumber', 22 'phutilnumber', 23 null, 24 'phutilnumber', 25 null, 26 ), 27 ), 28 ); 29 private $classDecl = null; 30 private $classExtends = null; 31 private $methodDecl = null; 32 private $paramVars = array(); 33 private $filename = null; 34 private $results = array(); 35 private $warnings = array(); 36 private $variableAssignments = array(); 37 38 public function getWarnings() { 39 return $this->warnings; 40 } 41 42 public function getResults() { 43 return $this->results; 44 } 45 46 public function __construct($filename) { 47 $this->filename = $filename; 48 } 49 50 private function getName($node) { 51 $name = $node->name; 52 if (is_string($name)) { 53 return $name; 54 } 55 if ($name instanceof PhpParser\Node\Name || 56 $name instanceof PhpParser\Node\Identifier 57 ) { 58 return $name->name; 59 } 60 return null; 61 } 62 63 private function typeNode(PhpParser\Node $node, array $seen = array()) { 64 if ($node instanceof PhpParser\Node\Scalar\Int_) { 65 return 'number'; 66 } else if ($node instanceof PhpParser\Node\Expr\BinaryOp) { 67 // i.e count($foos) + count($bars) 68 $type1 = $this->typeNode($node->left, $seen); 69 $type2 = $this->typeNode($node->right, $seen); 70 if ($type1 === $type2) { 71 return $type1; 72 } 73 } else if ($node instanceof PhpParser\Node\Expr\FuncCall) { 74 switch ($this->getName($node)) { 75 case 'phutil_count': 76 return 'phutilnumber'; 77 case 'count': 78 return 'number'; 79 case 'phutil_person': 80 return 'person'; 81 } 82 } else if ($node instanceof PhpParser\Node\Expr\New_ ) { 83 if ($this->getName($node->class) == 'PhutilNumber') { 84 return 'phutilnumber'; 85 } 86 } else if ($node instanceof PhpParser\Node\Expr\Variable) { 87 $name = $this->getName($node); 88 if (isset($seen[$name])) { 89 return null; 90 } 91 if (isset($this->variableAssignments[$name])) { 92 return $this->typeNode( 93 $this->variableAssignments[$name], 94 $seen + array($name => true)); 95 } 96 $known = static::$knownTypes; 97 if (isset($known[$this->classExtends])) { 98 $class_data = $known[$this->classExtends]; 99 } else if (isset($known[$this->classDecl])) { 100 $class_data = $known[$this->classDecl]; 101 } 102 if (isset($class_data[$this->methodDecl])) { 103 $types = $class_data[$this->methodDecl]; 104 if ($name && isset($this->paramVars[$name])) { 105 if (isset($types[$this->paramVars[$name]])) { 106 return $types[$this->paramVars[$name]]; 107 } 108 } 109 } 110 } 111 return null; 112 } 113 114 public function enterNode(PhpParser\Node $node) { 115 if ($node instanceof PhpParser\Node\Stmt\Class_) { 116 $this->classDecl = $this->getName($node); 117 if ($node->extends) { 118 $this->classExtends = $this->getName($node->extends); 119 } 120 } else if ($node instanceof PhpParser\Node\Stmt\ClassMethod) { 121 $this->methodDecl = $this->getName($node); 122 $this->paramVars = array(); 123 $i = 0; 124 foreach ($node->params as $param) { 125 $name = $this->getName($param->var); 126 if ($name) { 127 $this->paramVars[$name] = $i; 128 } 129 $i++; 130 } 131 } else if ($node instanceof PhpParser\Node\Expr\Assign) { 132 if ($node->var instanceof PhpParser\Node\Expr\Variable) { 133 $name = $this->getName($node->var); 134 // $name could be null if this is a "variable variable" 135 // (like $$foo = bar) 136 if ($name) { 137 $this->variableAssignments[$name] = $node->expr; 138 } 139 } 140 } else if ($node instanceof PhpParser\Node\Expr\FuncCall) { 141 if ($this->getName($node) == 'pht') { 142 $types = array(); 143 $args = $node->args; 144 $first = array_shift($args); 145 try { 146 $key = id(new PhpParser\ConstExprEvaluator()) 147 ->evaluateSilently($first->value); 148 } catch (PhpParser\ConstExprEvaluationException $ex) { 149 $this->warnings[] = pht( 150 'Failed to evaluate pht() call on line %d in "%s": %s', 151 $first->getStartLine(), 152 $this->filename, 153 $ex->getMessage()); 154 return; 155 } 156 foreach ($args as $child) { 157 $types[] = $this->typeNode($child->value); 158 } 159 $this->results[] = array( 160 'string' => $key, 161 'line' => $first->getStartLine(), 162 'types' => $types, 163 'file' => $this->filename, 164 ); 165 } 166 } 167 } 168 169 public function leaveNode(PhpParser\Node $node) { 170 if ($node instanceof PhpParser\Node\Stmt\Class_) { 171 $this->classDecl = null; 172 $this->classExtends = null; 173 } else if ($node instanceof PhpParser\Node\Stmt\ClassMethod) { 174 $this->methodDecl = null; 175 $this->paramVars = array(); 176 $this->variableAssignments = array(); 177 } 178 } 179}