4
* C# linter for Arcanist.
6
final class ArcanistCSharpLinter extends ArcanistLinter {
8
private $runtimeEngine;
10
private $cslintHintPath;
12
private $discoveryMap;
15
const SUPPORTED_VERSION = 1;
17
public function getLinterName() {
21
public function getLinterConfigurationName() {
25
public function getLinterConfigurationOptions() {
26
$options = parent::getLinterConfigurationOptions();
28
$options['discovery'] = array(
29
'type' => 'map<string, list<string>>',
30
'help' => pht('Provide a discovery map.'),
34
// TODO: This should probably be replaced with "bin" when this moves
35
// to extend ExternalLinter.
37
$options['binary'] = array(
39
'help' => pht('Override default binary.'),
45
public function setLinterConfigurationValue($key, $value) {
48
$this->discoveryMap = $value;
51
$this->cslintHintPath = $value;
54
parent::setLinterConfigurationValue($key, $value);
57
public function getLintCodeFromLinterConfigurationKey($code) {
61
public function setCustomSeverityMap(array $map) {
62
foreach ($map as $code => $severity) {
63
if (substr($code, 0, 2) === 'SA' && $severity == 'disabled') {
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.");
73
return parent::setCustomSeverityMap($map);
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.
84
private function loadEnvironment() {
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 ';
95
throw new Exception('Unable to find Mono and you are not on Windows!');
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';
105
throw new Exception('Unable to locate cslint.');
108
// Determine cslint version.
109
$ver_future = new ExecFuture(
111
$this->runtimeEngine.$this->cslintEngine);
112
list($err, $stdout, $stderr) = $ver_future->resolve();
115
'You are running an old version of cslint. Please '.
116
'upgrade to version '.self::SUPPORTED_VERSION.'.');
119
if ($ver < self::SUPPORTED_VERSION) {
121
'You are running an old version of cslint. Please '.
122
'upgrade to version '.self::SUPPORTED_VERSION.'.');
123
} else if ($ver > self::SUPPORTED_VERSION) {
125
'Arcanist does not support this version of cslint (it is '.
126
'newer). You can try upgrading Arcanist with `arc upgrade`.');
129
$this->loaded = true;
132
public function lintPath($path) {}
134
public function willLintPaths(array $paths) {
135
$this->loadEnvironment();
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.
147
foreach ($current_paths as $current_path) {
148
$total += strlen($current_path) + 3; // Quotes and space.
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
159
$futures[] = new ExecFuture(
160
'%C --settings-base64=%s -r=. %Ls',
161
$this->runtimeEngine.$this->cslintEngine,
162
base64_encode(json_encode($this->discoveryMap)),
164
$current_paths = array();
167
// Append the path to the current paths array.
168
$current_paths[] = $this->getEngine()->getFilePathOnDisk($path);
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)),
179
$current_paths = array();
182
$this->futures = $futures;
185
public function didRunLinters() {
186
if ($this->futures) {
187
foreach (Futures($this->futures)->limit(8) as $future) {
188
$this->resolveFuture($future);
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) {
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;
213
$message->setDescription($desc);
214
$severity = ArcanistLintSeverity::SEVERITY_ADVICE;
215
switch ($issue->Index->Severity) {
217
$severity = ArcanistLintSeverity::SEVERITY_ADVICE;
220
$severity = ArcanistLintSeverity::SEVERITY_AUTOFIX;
223
$severity = ArcanistLintSeverity::SEVERITY_WARNING;
226
$severity = ArcanistLintSeverity::SEVERITY_ERROR;
229
$severity = ArcanistLintSeverity::SEVERITY_DISABLED;
232
$severity_override = $this->getLintMessageSeverity($issue->Index->Code);
233
if ($severity_override !== null) {
234
$severity = $severity_override;
236
$message->setSeverity($severity);
237
$this->addLintMessage($message);
242
protected function getDefaultMessageSeverity($code) {