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