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

« back to all changes in this revision

Viewing changes to src/lint/linter/ArcanistPhutilLibraryLinter.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
/**
 
4
 * Applies lint rules for Phutil libraries. We enforce three rules:
 
5
 *
 
6
 *   # If you use a symbol, it must be defined somewhere.
 
7
 *   # If you define a symbol, it must not duplicate another definition.
 
8
 *   # If you define a class or interface in a file, it MUST be the only symbol
 
9
 *     defined in that file.
 
10
 */
 
11
final class ArcanistPhutilLibraryLinter extends ArcanistLinter {
 
12
 
 
13
  const LINT_UNKNOWN_SYMBOL      = 1;
 
14
  const LINT_DUPLICATE_SYMBOL    = 2;
 
15
  const LINT_ONE_CLASS_PER_FILE  = 3;
 
16
 
 
17
  public function getInfoName() {
 
18
    return 'Phutil Library Linter';
 
19
  }
 
20
 
 
21
  public function getInfoDescription() {
 
22
    return pht(
 
23
      'Make sure all the symbols use in a libphutil library are defined and '.
 
24
      'known. This linter is specific to PHP source in libphutil libraries.');
 
25
  }
 
26
 
 
27
  public function getLinterConfigurationName() {
 
28
    return 'phutil-library';
 
29
  }
 
30
 
 
31
  public function getLintNameMap() {
 
32
    return array(
 
33
      self::LINT_UNKNOWN_SYMBOL     => pht('Unknown Symbol'),
 
34
      self::LINT_DUPLICATE_SYMBOL   => pht('Duplicate Symbol'),
 
35
      self::LINT_ONE_CLASS_PER_FILE => pht('One Class Per File'),
 
36
    );
 
37
  }
 
38
 
 
39
  public function getLinterName() {
 
40
    return 'PHL';
 
41
  }
 
42
 
 
43
  public function getLinterPriority() {
 
44
    return 2.0;
 
45
  }
 
46
 
 
47
  public function willLintPaths(array $paths) {
 
48
    if (!xhpast_is_available()) {
 
49
      throw new Exception(xhpast_get_build_instructions());
 
50
    }
 
51
 
 
52
    // NOTE: For now, we completely ignore paths and just lint every library in
 
53
    // its entirety. This is simpler and relatively fast because we don't do any
 
54
    // detailed checks and all the data we need for this comes out of module
 
55
    // caches.
 
56
 
 
57
    $bootloader = PhutilBootloader::getInstance();
 
58
    $libs = $bootloader->getAllLibraries();
 
59
 
 
60
    // Load the up-to-date map for each library, without loading the library
 
61
    // itself. This means lint results will accurately reflect the state of
 
62
    // the working copy.
 
63
 
 
64
    $symbols = array();
 
65
    foreach ($libs as $lib) {
 
66
      $root = phutil_get_library_root($lib);
 
67
 
 
68
      try {
 
69
        $symbols[$lib] = id(new PhutilLibraryMapBuilder($root))
 
70
          ->buildFileSymbolMap();
 
71
      } catch (XHPASTSyntaxErrorException $ex) {
 
72
        // If the library contains a syntax error then there isn't much that we
 
73
        // can do.
 
74
        continue;
 
75
      }
 
76
    }
 
77
 
 
78
    $all_symbols = array();
 
79
    foreach ($symbols as $library => $map) {
 
80
      // Check for files which declare more than one class/interface in the same
 
81
      // file, or mix function definitions with class/interface definitions. We
 
82
      // must isolate autoloadable symbols to one per file so the autoloader
 
83
      // can't end up in an unresolvable cycle.
 
84
      foreach ($map as $file => $spec) {
 
85
        $have = idx($spec, 'have', array());
 
86
 
 
87
        $have_classes =
 
88
          idx($have, 'class', array()) +
 
89
          idx($have, 'interface', array());
 
90
        $have_functions = idx($have, 'function');
 
91
 
 
92
        if ($have_functions && $have_classes) {
 
93
          $function_list = implode(', ', array_keys($have_functions));
 
94
          $class_list = implode(', ', array_keys($have_classes));
 
95
          $this->raiseLintInLibrary(
 
96
            $library,
 
97
            $file,
 
98
            end($have_functions),
 
99
            self::LINT_ONE_CLASS_PER_FILE,
 
100
            "File '{$file}' mixes function ({$function_list}) and ".
 
101
            "class/interface ({$class_list}) definitions in the same file. ".
 
102
            "A file which declares a class or an interface MUST ".
 
103
            "declare nothing else.");
 
104
        } else if (count($have_classes) > 1) {
 
105
          $class_list = implode(', ', array_keys($have_classes));
 
106
          $this->raiseLintInLibrary(
 
107
            $library,
 
108
            $file,
 
109
            end($have_classes),
 
110
            self::LINT_ONE_CLASS_PER_FILE,
 
111
            "File '{$file}' declares more than one class or interface ".
 
112
            "({$class_list}). A file which declares a class or interface MUST ".
 
113
            "declare nothing else.");
 
114
        }
 
115
      }
 
116
 
 
117
      // Check for duplicate symbols: two files providing the same class or
 
118
      // function.
 
119
      foreach ($map as $file => $spec) {
 
120
        $have = idx($spec, 'have', array());
 
121
        foreach (array('class', 'function', 'interface') as $type) {
 
122
          $libtype = ($type == 'interface') ? 'class' : $type;
 
123
          foreach (idx($have, $type, array()) as $symbol => $offset) {
 
124
            if (empty($all_symbols[$libtype][$symbol])) {
 
125
              $all_symbols[$libtype][$symbol] = array(
 
126
                'library' => $library,
 
127
                'file'    => $file,
 
128
                'offset'  => $offset,
 
129
              );
 
130
              continue;
 
131
            }
 
132
 
 
133
            $osrc = $all_symbols[$libtype][$symbol]['file'];
 
134
            $olib = $all_symbols[$libtype][$symbol]['library'];
 
135
 
 
136
            $this->raiseLintInLibrary(
 
137
              $library,
 
138
              $file,
 
139
              $offset,
 
140
              self::LINT_DUPLICATE_SYMBOL,
 
141
              "Definition of {$type} '{$symbol}' in '{$file}' in library ".
 
142
              "'{$library}' duplicates prior definition in '{$osrc}' in ".
 
143
              "library '{$olib}'.");
 
144
          }
 
145
        }
 
146
      }
 
147
    }
 
148
 
 
149
    $types = array('class', 'function', 'interface', 'class/interface');
 
150
    foreach ($symbols as $library => $map) {
 
151
      // Check for unknown symbols: uses of classes, functions or interfaces
 
152
      // which are not defined anywhere. We reference the list of all symbols
 
153
      // we built up earlier.
 
154
      foreach ($map as $file => $spec) {
 
155
        $need = idx($spec, 'need', array());
 
156
        foreach ($types as $type) {
 
157
          $libtype = $type;
 
158
          if ($type == 'interface' || $type == 'class/interface') {
 
159
            $libtype = 'class';
 
160
          }
 
161
          foreach (idx($need, $type, array()) as $symbol => $offset) {
 
162
            if (!empty($all_symbols[$libtype][$symbol])) {
 
163
              // Symbol is defined somewhere.
 
164
              continue;
 
165
            }
 
166
 
 
167
            $libphutil_root = dirname(phutil_get_library_root('phutil'));
 
168
 
 
169
            $this->raiseLintInLibrary(
 
170
              $library,
 
171
              $file,
 
172
              $offset,
 
173
              self::LINT_UNKNOWN_SYMBOL,
 
174
              "Use of unknown {$type} '{$symbol}'. Common causes are:\n\n".
 
175
              "  - Your libphutil/ is out of date.\n".
 
176
              "    This is the most common cause.\n".
 
177
              "    Update this copy of libphutil: {$libphutil_root}\n".
 
178
              "\n".
 
179
              "  - Some other library is out of date.\n".
 
180
              "    Update the library this symbol appears in.\n".
 
181
              "\n".
 
182
              "  - This symbol is misspelled.\n".
 
183
              "    Spell the symbol name correctly.\n".
 
184
              "    Symbol name spelling is case-sensitive.\n".
 
185
              "\n".
 
186
              "  - This symbol was added recently.\n".
 
187
              "    Run `arc liberate` on the library it was added to.\n".
 
188
              "\n".
 
189
              "  - This symbol is external. Use `@phutil-external-symbol`.\n".
 
190
              "    Use `grep` to find usage examples of this directive.\n".
 
191
              "\n".
 
192
              "*** ALTHOUGH USUALLY EASY TO FIX, THIS IS A SERIOUS ERROR.\n".
 
193
              "*** THIS ERROR IS YOUR FAULT. YOU MUST RESOLVE IT.");
 
194
          }
 
195
        }
 
196
      }
 
197
    }
 
198
  }
 
199
 
 
200
  private function raiseLintInLibrary($library, $path, $offset, $code, $desc) {
 
201
    $root = phutil_get_library_root($library);
 
202
 
 
203
    $this->activePath = $root.'/'.$path;
 
204
    $this->raiseLintAtOffset($offset, $code, $desc);
 
205
  }
 
206
 
 
207
  public function lintPath($path) {
 
208
    return;
 
209
  }
 
210
 
 
211
  public function getCacheGranularity() {
 
212
    return self::GRANULARITY_GLOBAL;
 
213
  }
 
214
 
 
215
}