~ubuntu-branches/ubuntu/wily/phabricator/wily

« back to all changes in this revision

Viewing changes to src/lint/linter/ArcanistPhutilXHPASTLinter.php

  • Committer: Package Import Robot
  • Author(s): Richard Sellam
  • Date: 2014-11-01 23:20:06 UTC
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: package-import@ubuntu.com-20141101232006-mvlnp0cil67tsboe
Tags: upstream-0~git20141101/arcanist
Import upstream version 0~git20141101, component arcanist

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
final class ArcanistPhutilXHPASTLinter extends ArcanistBaseXHPASTLinter {
 
4
 
 
5
  const LINT_ARRAY_COMBINE          = 2;
 
6
  const LINT_DEPRECATED_FUNCTION    = 3;
 
7
  const LINT_UNSAFE_DYNAMIC_STRING  = 4;
 
8
  const LINT_RAGGED_CLASSTREE_EDGE  = 5;
 
9
 
 
10
  private $deprecatedFunctions    = array();
 
11
  private $dynamicStringFunctions = array();
 
12
  private $dynamicStringClasses   = array();
 
13
 
 
14
  public function getInfoName() {
 
15
    return 'XHPAST/libphutil Lint';
 
16
  }
 
17
 
 
18
  public function getInfoDescription() {
 
19
    return pht(
 
20
      'Use XHPAST to run libphutil-specific rules on a PHP library. This '.
 
21
      'linter is intended for use in Phabricator libraries and extensions.');
 
22
  }
 
23
 
 
24
  public function setDeprecatedFunctions(array $map) {
 
25
    $this->deprecatedFunctions = $map;
 
26
    return $this;
 
27
  }
 
28
 
 
29
  public function setDynamicStringFunctions(array $map) {
 
30
    $this->dynamicStringFunctions = $map;
 
31
    return $this;
 
32
  }
 
33
 
 
34
  public function setDynamicStringClasses(array $map) {
 
35
    $this->dynamicStringClasses = $map;
 
36
    return $this;
 
37
  }
 
38
 
 
39
  public function getLintNameMap() {
 
40
    return array(
 
41
      self::LINT_ARRAY_COMBINE          => pht(
 
42
        '%s Unreliable',
 
43
        'array_combine()'),
 
44
      self::LINT_DEPRECATED_FUNCTION    => pht(
 
45
        'Use of Deprecated Function'),
 
46
      self::LINT_UNSAFE_DYNAMIC_STRING  => pht(
 
47
        'Unsafe Usage of Dynamic String'),
 
48
      self::LINT_RAGGED_CLASSTREE_EDGE  => pht(
 
49
        'Class Not %s Or %s',
 
50
        'abstract',
 
51
        'final'),
 
52
    );
 
53
  }
 
54
 
 
55
  public function getLintSeverityMap() {
 
56
    $warning = ArcanistLintSeverity::SEVERITY_WARNING;
 
57
    return array(
 
58
      self::LINT_ARRAY_COMBINE          => $warning,
 
59
      self::LINT_DEPRECATED_FUNCTION    => $warning,
 
60
      self::LINT_UNSAFE_DYNAMIC_STRING  => $warning,
 
61
      self::LINT_RAGGED_CLASSTREE_EDGE  => $warning,
 
62
    );
 
63
  }
 
64
 
 
65
  public function getLinterName() {
 
66
    return 'PHLXHP';
 
67
  }
 
68
 
 
69
  public function getLinterConfigurationName() {
 
70
    return 'phutil-xhpast';
 
71
  }
 
72
 
 
73
  public function getVersion() {
 
74
    // The version number should be incremented whenever a new rule is added.
 
75
    return '3';
 
76
  }
 
77
 
 
78
  public function getLinterConfigurationOptions() {
 
79
    $options = array(
 
80
      'phutil-xhpast.deprecated.functions' => array(
 
81
        'type' => 'optional map<string, string>',
 
82
        'help' => pht(
 
83
          'Functions which should should be considered deprecated.'),
 
84
      ),
 
85
      'phutil-xhpast.dynamic-string.functions' => array(
 
86
        'type' => 'optional map<string, string>',
 
87
        'help' => pht(
 
88
          'Functions which should should not be used because they represent '.
 
89
          'the unsafe usage of dynamic strings.'),
 
90
      ),
 
91
      'phutil-xhpast.dynamic-string.classes' => array(
 
92
        'type' => 'optional map<string, string>',
 
93
        'help' => pht(
 
94
          'Classes which should should not be used because they represent the '.
 
95
          'unsafe usage of dynamic strings.'),
 
96
      ),
 
97
    );
 
98
 
 
99
    return $options + parent::getLinterConfigurationOptions();
 
100
  }
 
101
 
 
102
  public function setLinterConfigurationValue($key, $value) {
 
103
    switch ($key) {
 
104
      case 'phutil-xhpast.deprecated.functions':
 
105
        $this->setDeprecatedFunctions($value);
 
106
        return;
 
107
      case 'phutil-xhpast.dynamic-string.functions':
 
108
        $this->setDynamicStringFunctions($value);
 
109
        return;
 
110
      case 'phutil-xhpast.dynamic-string.classes':
 
111
        $this->setDynamicStringClasses($value);
 
112
        return;
 
113
    }
 
114
 
 
115
    return parent::setLinterConfigurationValue($key, $value);
 
116
  }
 
117
 
 
118
  protected function resolveFuture($path, Future $future) {
 
119
    $tree = $this->getXHPASTLinter()->getXHPASTTreeForPath($path);
 
120
    if (!$tree) {
 
121
      return;
 
122
    }
 
123
 
 
124
    $root = $tree->getRootNode();
 
125
 
 
126
    $method_codes = array(
 
127
      'lintArrayCombine' => self::LINT_ARRAY_COMBINE,
 
128
      'lintUnsafeDynamicString' => self::LINT_UNSAFE_DYNAMIC_STRING,
 
129
      'lintDeprecatedFunctions' => self::LINT_DEPRECATED_FUNCTION,
 
130
      'lintRaggedClasstreeEdges' => self::LINT_RAGGED_CLASSTREE_EDGE,
 
131
    );
 
132
 
 
133
    foreach ($method_codes as $method => $codes) {
 
134
      foreach ((array)$codes as $code) {
 
135
        if ($this->isCodeEnabled($code)) {
 
136
          call_user_func(array($this, $method), $root);
 
137
          break;
 
138
        }
 
139
      }
 
140
    }
 
141
  }
 
142
 
 
143
  private function lintUnsafeDynamicString(XHPASTNode $root) {
 
144
    $safe = $this->dynamicStringFunctions + array(
 
145
      'pht' => 0,
 
146
 
 
147
      'hsprintf' => 0,
 
148
      'jsprintf' => 0,
 
149
 
 
150
      'hgsprintf' => 0,
 
151
 
 
152
      'csprintf' => 0,
 
153
      'vcsprintf' => 0,
 
154
      'execx' => 0,
 
155
      'exec_manual' => 0,
 
156
      'phutil_passthru' => 0,
 
157
 
 
158
      'qsprintf' => 1,
 
159
      'vqsprintf' => 1,
 
160
      'queryfx' => 1,
 
161
      'vqueryfx' => 1,
 
162
      'queryfx_all' => 1,
 
163
      'vqueryfx_all' => 1,
 
164
      'queryfx_one' => 1,
 
165
    );
 
166
 
 
167
    $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
 
168
    $this->lintUnsafeDynamicStringCall($calls, $safe);
 
169
 
 
170
    $safe = $this->dynamicStringClasses + array(
 
171
      'ExecFuture' => 0,
 
172
    );
 
173
 
 
174
    $news = $root->selectDescendantsOfType('n_NEW');
 
175
    $this->lintUnsafeDynamicStringCall($news, $safe);
 
176
  }
 
177
 
 
178
  private function lintUnsafeDynamicStringCall(
 
179
    AASTNodeList $calls,
 
180
    array $safe) {
 
181
 
 
182
    $safe = array_combine(
 
183
      array_map('strtolower', array_keys($safe)),
 
184
      $safe);
 
185
 
 
186
    foreach ($calls as $call) {
 
187
      $name = $call->getChildByIndex(0)->getConcreteString();
 
188
      $param = idx($safe, strtolower($name));
 
189
 
 
190
      if ($param === null) {
 
191
        continue;
 
192
      }
 
193
 
 
194
      $parameters = $call->getChildByIndex(1);
 
195
      if (count($parameters->getChildren()) <= $param) {
 
196
        continue;
 
197
      }
 
198
 
 
199
      $identifier = $parameters->getChildByIndex($param);
 
200
      if (!$identifier->isConstantString()) {
 
201
        $this->raiseLintAtNode(
 
202
          $call,
 
203
          self::LINT_UNSAFE_DYNAMIC_STRING,
 
204
          pht(
 
205
            "Parameter %d of %s should be a scalar string, ".
 
206
            "otherwise it's not safe.",
 
207
            $param + 1,
 
208
            $name.'()'));
 
209
      }
 
210
    }
 
211
  }
 
212
 
 
213
  private function lintArrayCombine(XHPASTNode $root) {
 
214
    $function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
 
215
    foreach ($function_calls as $call) {
 
216
      $name = $call->getChildByIndex(0)->getConcreteString();
 
217
      if (strcasecmp($name, 'array_combine') == 0) {
 
218
        $parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
 
219
        if (count($parameter_list->getChildren()) !== 2) {
 
220
          // Wrong number of parameters, but raise that elsewhere if we want.
 
221
          continue;
 
222
        }
 
223
 
 
224
        $first = $parameter_list->getChildByIndex(0);
 
225
        $second = $parameter_list->getChildByIndex(1);
 
226
 
 
227
        if ($first->getConcreteString() == $second->getConcreteString()) {
 
228
          $this->raiseLintAtNode(
 
229
            $call,
 
230
            self::LINT_ARRAY_COMBINE,
 
231
            pht(
 
232
              'Prior to PHP 5.4, `%s` fails when given empty arrays. '.
 
233
              'Prefer to write `%s` as `%s`.',
 
234
              'array_combine()',
 
235
              'array_combine(x, x)',
 
236
              'array_fuse(x)'));
 
237
        }
 
238
      }
 
239
    }
 
240
  }
 
241
 
 
242
  private function lintDeprecatedFunctions(XHPASTNode $root) {
 
243
    $map = $this->deprecatedFunctions;
 
244
 
 
245
    $function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
 
246
    foreach ($function_calls as $call) {
 
247
      $name = $call->getChildByIndex(0)->getConcreteString();
 
248
 
 
249
      $name = strtolower($name);
 
250
      if (empty($map[$name])) {
 
251
        continue;
 
252
      }
 
253
 
 
254
      $this->raiseLintAtNode(
 
255
        $call,
 
256
        self::LINT_DEPRECATED_FUNCTION,
 
257
        $map[$name]);
 
258
    }
 
259
  }
 
260
 
 
261
  private function lintRaggedClasstreeEdges(XHPASTNode $root) {
 
262
    $parser = new PhutilDocblockParser();
 
263
 
 
264
    $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
 
265
    foreach ($classes as $class) {
 
266
      $is_final = false;
 
267
      $is_abstract = false;
 
268
      $is_concrete_extensible = false;
 
269
 
 
270
      $attributes = $class->getChildOfType(0, 'n_CLASS_ATTRIBUTES');
 
271
      foreach ($attributes->getChildren() as $child) {
 
272
        if ($child->getConcreteString() == 'final') {
 
273
          $is_final = true;
 
274
        }
 
275
        if ($child->getConcreteString() == 'abstract') {
 
276
          $is_abstract = true;
 
277
        }
 
278
      }
 
279
 
 
280
      $docblock = $class->getDocblockToken();
 
281
      if ($docblock) {
 
282
        list($text, $specials) = $parser->parse($docblock->getValue());
 
283
        $is_concrete_extensible = idx($specials, 'concrete-extensible');
 
284
      }
 
285
 
 
286
      if (!$is_final && !$is_abstract && !$is_concrete_extensible) {
 
287
        $this->raiseLintAtNode(
 
288
          $class->getChildOfType(1, 'n_CLASS_NAME'),
 
289
          self::LINT_RAGGED_CLASSTREE_EDGE,
 
290
          pht(
 
291
            "This class is neither '%s' nor '%s', and does not have ".
 
292
            "a docblock marking it '%s'.",
 
293
            'final',
 
294
            'abstract',
 
295
            '@concrete-extensible'));
 
296
      }
 
297
    }
 
298
  }
 
299
 
 
300
}