1<?php 2 3abstract class ArcanistXHPASTLinterRule extends Phobject { 4 5 private $linter = null; 6 private $lintID = null; 7 8 protected $version; 9 protected $windowsVersion; 10 11 final public static function loadAllRules() { 12 return id(new PhutilClassMapQuery()) 13 ->setAncestorClass(__CLASS__) 14 ->setUniqueMethod('getLintID') 15 ->execute(); 16 } 17 18 final public function getLintID() { 19 if ($this->lintID === null) { 20 $class = new ReflectionClass($this); 21 22 $const = $class->getConstant('ID'); 23 if ($const === false) { 24 throw new Exception( 25 pht( 26 '`%s` class `%s` must define an ID constant.', 27 __CLASS__, 28 get_class($this))); 29 } 30 31 if (!is_int($const)) { 32 throw new Exception( 33 pht( 34 '`%s` class `%s` has an invalid ID constant. '. 35 'ID must be an integer.', 36 __CLASS__, 37 get_class($this))); 38 } 39 40 $this->lintID = $const; 41 } 42 43 return $this->lintID; 44 } 45 46 abstract public function getLintName(); 47 48 public function getLintSeverity() { 49 return ArcanistLintSeverity::SEVERITY_ERROR; 50 } 51 52 public function getLinterConfigurationOptions() { 53 return array( 54 'xhpast.php-version' => array( 55 'type' => 'optional string', 56 'help' => pht('PHP version to target.'), 57 ), 58 'xhpast.php-version.windows' => array( 59 'type' => 'optional string', 60 'help' => pht('PHP version to target on Windows.'), 61 ), 62 ); 63 } 64 65 public function setLinterConfigurationValue($key, $value) { 66 switch ($key) { 67 case 'xhpast.php-version': 68 $this->version = $value; 69 return; 70 71 case 'xhpast.php-version.windows': 72 $this->windowsVersion = $value; 73 return; 74 } 75 } 76 77 abstract public function process(XHPASTNode $root); 78 79 final public function setLinter(ArcanistXHPASTLinter $linter) { 80 $this->linter = $linter; 81 return $this; 82 } 83 84 85/* -( Proxied Methods )---------------------------------------------------- */ 86 87 88 final public function getActivePath() { 89 return $this->linter->getActivePath(); 90 } 91 92 final public function getOtherLocation($offset, $path = null) { 93 return $this->linter->getOtherLocation($offset, $path); 94 } 95 96 final protected function raiseLintAtPath($desc) { 97 return $this->linter->raiseLintAtPath($this->getLintID(), $desc); 98 } 99 100 final public function raiseLintAtOffset( 101 $offset, 102 $description, 103 $original = null, 104 $replacement = null) { 105 106 $this->linter->raiseLintAtOffset( 107 $offset, 108 $this->getLintID(), 109 $description, 110 $original, 111 $replacement); 112 } 113 114 final protected function raiseLintAtToken( 115 XHPASTToken $token, 116 $description, 117 $replace = null) { 118 119 return $this->linter->raiseLintAtToken( 120 $token, 121 $this->getLintID(), 122 $description, 123 $replace); 124 } 125 126 final protected function raiseLintAtNode( 127 XHPASTNode $node, 128 $description, 129 $replace = null) { 130 131 return $this->linter->raiseLintAtNode( 132 $node, 133 $this->getLintID(), 134 $description, 135 $replace); 136 } 137 138 139/* -( Utility )------------------------------------------------------------ */ 140 141 142 /** 143 * Statically evaluate a boolean value from an XHP tree. 144 * 145 * TODO: Improve this and move it to XHPAST proper? 146 * 147 * @param string The "semantic string" of a single value. 148 * @return mixed `true` or `false` if the value could be evaluated 149 * statically; `null` if static evaluation was not possible. 150 */ 151 protected function evaluateStaticBoolean($string) { 152 switch (strtolower($string)) { 153 case '0': 154 case 'null': 155 case 'false': 156 return false; 157 case '1': 158 case 'true': 159 return true; 160 default: 161 return null; 162 } 163 } 164 165 /** 166 * Retrieve all anonymous closure(s). 167 * 168 * Returns all descendant nodes which represent an anonymous function 169 * declaration. 170 * 171 * @param XHPASTNode Root node. 172 * @return AASTNodeList 173 */ 174 protected function getAnonymousClosures(XHPASTNode $root) { 175 $func_decls = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION'); 176 $nodes = array(); 177 178 foreach ($func_decls as $func_decl) { 179 if ($func_decl->getChildByIndex(2)->getTypeName() == 'n_EMPTY') { 180 $nodes[] = $func_decl; 181 } 182 } 183 184 return AASTNodeList::newFromTreeAndNodes($root->getTree(), $nodes); 185 } 186 187 /** 188 * TODO 189 * 190 * @param XHPASTNode 191 * @return string 192 */ 193 protected function getConcreteVariableString(XHPASTNode $variable) { 194 $concrete = $variable->getConcreteString(); 195 196 // Strip off curly braces as in `$obj->{$property}`. 197 $concrete = trim($concrete, '{}'); 198 199 return $concrete; 200 } 201 202 /** 203 * Retrieve all calls to some specified function(s). 204 * 205 * Returns all descendant nodes which represent a function call to one of the 206 * specified functions. 207 * 208 * @param XHPASTNode Root node. 209 * @param list<string> Function names. 210 * @return AASTNodeList 211 */ 212 protected function getFunctionCalls(XHPASTNode $root, array $function_names) { 213 $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); 214 $nodes = array(); 215 216 foreach ($calls as $call) { 217 $node = $call->getChildByIndex(0); 218 $name = strtolower($node->getConcreteString()); 219 220 if (in_array($name, $function_names)) { 221 $nodes[] = $call; 222 } 223 } 224 225 return AASTNodeList::newFromTreeAndNodes($root->getTree(), $nodes); 226 } 227 228 /** 229 * Get class/method modifiers. 230 * 231 * @param XHPASTNode A node of type `n_CLASS_DECLARATION` or 232 * `n_METHOD_DECLARATION`. 233 * @return map<string, bool> Class/method modifiers. 234 */ 235 final protected function getModifiers(XHPASTNode $node) { 236 $modifier_list = $node->getChildByIndex(0); 237 238 switch ($modifier_list->getTypeName()) { 239 case 'n_CLASS_ATTRIBUTES': 240 case 'n_CLASS_MEMBER_MODIFIER_LIST': 241 case 'n_METHOD_MODIFIER_LIST': 242 break; 243 244 default: 245 return array(); 246 } 247 248 $modifiers = array(); 249 250 foreach ($modifier_list->selectDescendantsOfType('n_STRING') as $modifier) { 251 $modifiers[strtolower($modifier->getConcreteString())] = true; 252 } 253 254 return $modifiers; 255 } 256 257 /** 258 * Get PHP superglobals. 259 * 260 * @return list<string> 261 */ 262 public function getSuperGlobalNames() { 263 return array( 264 '$GLOBALS', 265 '$_SERVER', 266 '$_GET', 267 '$_POST', 268 '$_FILES', 269 '$_COOKIE', 270 '$_SESSION', 271 '$_REQUEST', 272 '$_ENV', 273 ); 274 } 275 276} 277