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

« back to all changes in this revision

Viewing changes to src/lint/linter/ArcanistCSharpLinter.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
 * C# linter for Arcanist.
 
5
 */
 
6
final class ArcanistCSharpLinter extends ArcanistLinter {
 
7
 
 
8
  private $runtimeEngine;
 
9
  private $cslintEngine;
 
10
  private $cslintHintPath;
 
11
  private $loaded;
 
12
  private $discoveryMap;
 
13
  private $futures;
 
14
 
 
15
  const SUPPORTED_VERSION = 1;
 
16
 
 
17
  public function getLinterName() {
 
18
    return 'C#';
 
19
  }
 
20
 
 
21
  public function getLinterConfigurationName() {
 
22
    return 'csharp';
 
23
  }
 
24
 
 
25
  public function getLinterConfigurationOptions() {
 
26
    $options = parent::getLinterConfigurationOptions();
 
27
 
 
28
    $options['discovery'] = array(
 
29
      'type' => 'map<string, list<string>>',
 
30
      'help' => pht('Provide a discovery map.'),
 
31
    );
 
32
 
 
33
 
 
34
    // TODO: This should probably be replaced with "bin" when this moves
 
35
    // to extend ExternalLinter.
 
36
 
 
37
    $options['binary'] = array(
 
38
      'type' => 'string',
 
39
      'help' => pht('Override default binary.'),
 
40
    );
 
41
 
 
42
    return $options;
 
43
  }
 
44
 
 
45
  public function setLinterConfigurationValue($key, $value) {
 
46
    switch ($key) {
 
47
      case 'discovery':
 
48
        $this->discoveryMap = $value;
 
49
        return;
 
50
      case 'binary':
 
51
        $this->cslintHintPath = $value;
 
52
        return;
 
53
    }
 
54
    parent::setLinterConfigurationValue($key, $value);
 
55
  }
 
56
 
 
57
  public function getLintCodeFromLinterConfigurationKey($code) {
 
58
    return $code;
 
59
  }
 
60
 
 
61
  public function setCustomSeverityMap(array $map) {
 
62
    foreach ($map as $code => $severity) {
 
63
      if (substr($code, 0, 2) === 'SA' && $severity == 'disabled') {
 
64
        throw new Exception(
 
65
          "In order to keep StyleCop integration with IDEs and other tools ".
 
66
          "consistent with Arcanist results, you aren't permitted to ".
 
67
          "disable StyleCop rules within '.arclint'. ".
 
68
          "Instead configure the severity using the StyleCop settings dialog ".
 
69
          "(usually accessible from within your IDE). StyleCop settings ".
 
70
          "for your project will be used when linting for Arcanist.");
 
71
      }
 
72
    }
 
73
    return parent::setCustomSeverityMap($map);
 
74
  }
 
75
 
 
76
  /**
 
77
   * Determines what executables and lint paths to use. Between platforms
 
78
   * this also changes whether the lint engine is run under .NET or Mono. It
 
79
   * also ensures that all of the required binaries are available for the lint
 
80
   * to run successfully.
 
81
   *
 
82
   * @return void
 
83
   */
 
84
  private function loadEnvironment() {
 
85
    if ($this->loaded) {
 
86
      return;
 
87
    }
 
88
 
 
89
    // Determine runtime engine (.NET or Mono).
 
90
    if (phutil_is_windows()) {
 
91
      $this->runtimeEngine = '';
 
92
    } else if (Filesystem::binaryExists('mono')) {
 
93
      $this->runtimeEngine = 'mono ';
 
94
    } else {
 
95
      throw new Exception('Unable to find Mono and you are not on Windows!');
 
96
    }
 
97
 
 
98
    // Determine cslint path.
 
99
    $cslint = $this->cslintHintPath;
 
100
    if ($cslint !== null && file_exists($cslint)) {
 
101
      $this->cslintEngine = Filesystem::resolvePath($cslint);
 
102
    } else if (Filesystem::binaryExists('cslint.exe')) {
 
103
      $this->cslintEngine = 'cslint.exe';
 
104
    } else {
 
105
      throw new Exception('Unable to locate cslint.');
 
106
    }
 
107
 
 
108
    // Determine cslint version.
 
109
    $ver_future = new ExecFuture(
 
110
      '%C -v',
 
111
      $this->runtimeEngine.$this->cslintEngine);
 
112
    list($err, $stdout, $stderr) = $ver_future->resolve();
 
113
    if ($err !== 0) {
 
114
      throw new Exception(
 
115
        'You are running an old version of cslint. Please '.
 
116
        'upgrade to version '.self::SUPPORTED_VERSION.'.');
 
117
    }
 
118
    $ver = (int)$stdout;
 
119
    if ($ver < self::SUPPORTED_VERSION) {
 
120
      throw new Exception(
 
121
        'You are running an old version of cslint. Please '.
 
122
        'upgrade to version '.self::SUPPORTED_VERSION.'.');
 
123
    } else if ($ver > self::SUPPORTED_VERSION) {
 
124
      throw new Exception(
 
125
        'Arcanist does not support this version of cslint (it is '.
 
126
        'newer). You can try upgrading Arcanist with `arc upgrade`.');
 
127
    }
 
128
 
 
129
    $this->loaded = true;
 
130
  }
 
131
 
 
132
  public function lintPath($path) {}
 
133
 
 
134
  public function willLintPaths(array $paths) {
 
135
    $this->loadEnvironment();
 
136
 
 
137
    $futures = array();
 
138
 
 
139
    // Bulk linting up into futures, where the number of files
 
140
    // is based on how long the command is.
 
141
    $current_paths = array();
 
142
    foreach ($paths as $path) {
 
143
      // If the current paths for the command, plus the next path
 
144
      // is greater than 6000 characters (less than the Windows
 
145
      // command line limit), then finalize this future and add it.
 
146
      $total = 0;
 
147
      foreach ($current_paths as $current_path) {
 
148
        $total += strlen($current_path) + 3; // Quotes and space.
 
149
      }
 
150
      if ($total + strlen($path) > 6000) {
 
151
        // %s won't pass through the JSON correctly
 
152
        // under Windows. This is probably because not only
 
153
        // does the JSON have quotation marks in the content,
 
154
        // but because there'll be a lot of escaping and
 
155
        // double escaping because the JSON also contains
 
156
        // regular expressions. cslint supports passing the
 
157
        // settings JSON through base64-encoded to mitigate
 
158
        // this issue.
 
159
        $futures[] = new ExecFuture(
 
160
          '%C --settings-base64=%s -r=. %Ls',
 
161
          $this->runtimeEngine.$this->cslintEngine,
 
162
          base64_encode(json_encode($this->discoveryMap)),
 
163
          $current_paths);
 
164
        $current_paths = array();
 
165
      }
 
166
 
 
167
      // Append the path to the current paths array.
 
168
      $current_paths[] = $this->getEngine()->getFilePathOnDisk($path);
 
169
    }
 
170
 
 
171
    // If we still have paths left in current paths, then we need to create
 
172
    // a future for those too.
 
173
    if (count($current_paths) > 0) {
 
174
      $futures[] = new ExecFuture(
 
175
        '%C --settings-base64=%s -r=. %Ls',
 
176
        $this->runtimeEngine.$this->cslintEngine,
 
177
        base64_encode(json_encode($this->discoveryMap)),
 
178
        $current_paths);
 
179
      $current_paths = array();
 
180
    }
 
181
 
 
182
    $this->futures = $futures;
 
183
  }
 
184
 
 
185
  public function didRunLinters() {
 
186
    if ($this->futures) {
 
187
      foreach (Futures($this->futures)->limit(8) as $future) {
 
188
        $this->resolveFuture($future);
 
189
      }
 
190
    }
 
191
  }
 
192
 
 
193
  protected function resolveFuture(Future $future) {
 
194
    list($stdout) = $future->resolvex();
 
195
    $all_results = json_decode($stdout);
 
196
    foreach ($all_results as $results) {
 
197
      if ($results === null || $results->Issues === null) {
 
198
        return;
 
199
      }
 
200
      foreach ($results->Issues as $issue) {
 
201
        $message = new ArcanistLintMessage();
 
202
        $message->setPath($results->FileName);
 
203
        $message->setLine($issue->LineNumber);
 
204
        $message->setCode($issue->Index->Code);
 
205
        $message->setName($issue->Index->Name);
 
206
        $message->setChar($issue->Column);
 
207
        $message->setOriginalText($issue->OriginalText);
 
208
        $message->setReplacementText($issue->ReplacementText);
 
209
        $desc = @vsprintf($issue->Index->Message, $issue->Parameters);
 
210
        if ($desc === false) {
 
211
          $desc = $issue->Index->Message;
 
212
        }
 
213
        $message->setDescription($desc);
 
214
        $severity = ArcanistLintSeverity::SEVERITY_ADVICE;
 
215
        switch ($issue->Index->Severity) {
 
216
          case 0:
 
217
            $severity = ArcanistLintSeverity::SEVERITY_ADVICE;
 
218
            break;
 
219
          case 1:
 
220
            $severity = ArcanistLintSeverity::SEVERITY_AUTOFIX;
 
221
            break;
 
222
          case 2:
 
223
            $severity = ArcanistLintSeverity::SEVERITY_WARNING;
 
224
            break;
 
225
          case 3:
 
226
            $severity = ArcanistLintSeverity::SEVERITY_ERROR;
 
227
            break;
 
228
          case 4:
 
229
            $severity = ArcanistLintSeverity::SEVERITY_DISABLED;
 
230
            break;
 
231
        }
 
232
        $severity_override = $this->getLintMessageSeverity($issue->Index->Code);
 
233
        if ($severity_override !== null) {
 
234
          $severity = $severity_override;
 
235
        }
 
236
        $message->setSeverity($severity);
 
237
        $this->addLintMessage($message);
 
238
      }
 
239
    }
 
240
  }
 
241
 
 
242
  protected function getDefaultMessageSeverity($code) {
 
243
    return null;
 
244
  }
 
245
 
 
246
}