1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-10 08:52:39 +01:00

Add C# linter for Arcanist.

Summary:
Completes T3859.  This implements a C# linter for Arcanist, which in turn uses `cslint` from `cstools` to actually perform the linting.  `cslint` internally uses StyleCop in addition to it's own lint rules.

Unlike other linters, C# is a compiled language, which means that the StyleCop integration must be aware of the full project.  To this end, there is the `discovery` setting in `.arclint`.  This allows users to define mappings between C# files and the projects they belong to.  Here is an configuration for `.arclint` (and is the one we use):

```
{
  "linters": {
    "csharp": {
      "type": "csharp",
      "include": "(\\.cs$)",
      "binary": "cstools/cslint/bin/Debug/cslint.exe",
      "discovery": {
        "([^/]+)/(.*?)\\.cs": [
          "$1/$1.Linux.csproj"
        ],
        "([^\\\\]+)\\\\(.*?)\\.cs": [
          "$1\\$1.Windows.csproj"
        ]
      }
    }
  }
}
```

Test Plan: Tested under both Linux and Windows.  Changed some files, ran `arc lint` and it all worked correctly.

Reviewers: epriestley

Reviewed By: epriestley

CC: Korvin, aran, jamesr

Maniphest Tasks: T3859

Differential Revision: https://secure.phabricator.com/D7170
This commit is contained in:
James Rhodes 2013-10-01 11:37:26 -07:00 committed by epriestley
parent 0b8ea973ae
commit 02e4a690dd
2 changed files with 181 additions and 0 deletions

View file

@ -30,6 +30,7 @@ phutil_register_library_map(array(
'ArcanistBundleTestCase' => 'parser/__tests__/ArcanistBundleTestCase.php',
'ArcanistCSSLintLinter' => 'lint/linter/ArcanistCSSLintLinter.php',
'ArcanistCSSLintLinterTestCase' => 'lint/linter/__tests__/ArcanistCSSLintLinterTestCase.php',
'ArcanistCSharpLinter' => 'lint/linter/ArcanistCSharpLinter.php',
'ArcanistCallConduitWorkflow' => 'workflow/ArcanistCallConduitWorkflow.php',
'ArcanistCapabilityNotSupportedException' => 'workflow/exception/ArcanistCapabilityNotSupportedException.php',
'ArcanistChooseInvalidRevisionException' => 'exception/ArcanistChooseInvalidRevisionException.php',
@ -202,6 +203,7 @@ phutil_register_library_map(array(
'ArcanistBundleTestCase' => 'ArcanistTestCase',
'ArcanistCSSLintLinter' => 'ArcanistExternalLinter',
'ArcanistCSSLintLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistCSharpLinter' => 'ArcanistFutureLinter',
'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCapabilityNotSupportedException' => 'Exception',
'ArcanistChooseInvalidRevisionException' => 'Exception',

View file

@ -0,0 +1,179 @@
<?php
/**
* C# linter for Arcanist.
*
* @group linter
*/
final class ArcanistCSharpLinter extends ArcanistFutureLinter {
private $runtimeEngine;
private $cslintEngine;
private $cslintHintPath;
private $loaded;
private $discoveryMap;
public function getLinterName() {
return 'C#';
}
public function getLinterConfigurationName() {
return 'csharp';
}
public function getLinterConfigurationOptions() {
$options = parent::getLinterConfigurationOptions();
$options["discovery"] = 'map<string, list<string>>';
$options["binary"] = 'string';
return $options;
}
public function setLinterConfigurationValue($key, $value) {
switch ($key) {
case 'discovery':
$this->discoveryMap = $value;
return;
case 'binary':
$this->cslintHintPath = $value;
return;
}
parent::setLinterConfigurationValue($key, $value);
}
public function getLintCodeFromLinterConfigurationKey($code) {
return $code;
}
public function setCustomSeverityMap(array $map) {
foreach ($map as $code => $severity) {
if (substr($code, 0, 2) === "SA" && $severity == "disabled") {
throw new Exception(
"In order to keep StyleCop integration with IDEs and other tools ".
"consistent with Arcanist results, you aren't permitted to ".
"disable StyleCop rules within '.arclint'. ".
"Instead configure the severity using the StyleCop settings dialog ".
"(usually accessible from within your IDE). StyleCop settings ".
"for your project will be used when linting for Arcanist.");
}
}
return parent::setCustomSeverityMap($map);
}
public function getLintSeverityMap() {
return array();
}
public function getLintNameMap() {
return array();
}
/**
* Determines what executables and lint paths to use. Between platforms
* this also changes whether the lint engine is run under .NET or Mono. It
* also ensures that all of the required binaries are available for the lint
* to run successfully.
*
* @return void
*/
private function loadEnvironment() {
if ($this->loaded) {
return;
}
// Determine runtime engine (.NET or Mono).
if (phutil_is_windows()) {
$this->runtimeEngine = "";
} else if (Filesystem::binaryExists("mono")) {
$this->runtimeEngine = "mono ";
} else {
throw new Exception("Unable to find Mono and you are not on Windows!");
}
// Determine cslint path.
$cslint = $this->cslintHintPath;
if ($cslint !== null && file_exists($cslint)) {
$this->cslintEngine = Filesystem::resolvePath($cslint);
} else if (Filesystem::binaryExists("cslint.exe")) {
$this->cslintEngine = "cslint.exe";
} else {
throw new Exception("Unable to locate cslint.");
}
$this->loaded = true;
}
protected function buildFutures(array $paths) {
$this->loadEnvironment();
$futures = array();
foreach ($paths as $path) {
// %s won't pass through the JSON correctly
// under Windows. This is probably because not only
// does the JSON have quotation marks in the content,
// but because there'll be a lot of escaping and
// double escaping because the JSON also contains
// regular expressions. cslint supports passing the
// settings JSON through base64-encoded to mitigate
// this issue.
$futures[$path] = new ExecFuture(
"%C --settings-base64=%s -r=. %s",
$this->runtimeEngine.$this->cslintEngine,
base64_encode(json_encode($this->discoveryMap)),
$this->getEngine()->getFilePathOnDisk($path));
}
return $futures;
}
protected function resolveFuture($path, Future $future) {
list($rc, $stdout) = $future->resolve();
$results = json_decode($stdout);
if ($results === null || $results->Issues === null) {
return;
}
foreach ($results->Issues as $issue) {
$message = new ArcanistLintMessage();
$message->setPath($path);
$message->setLine($issue->LineNumber);
$message->setCode($issue->Index->Code);
$message->setName($issue->Index->Name);
$message->setChar($issue->Column);
$message->setOriginalText($issue->OriginalText);
$message->setReplacementText($issue->ReplacementText);
$message->setDescription(
vsprintf($issue->Index->Message, $issue->Parameters));
$severity = ArcanistLintSeverity::SEVERITY_ADVICE;
switch ($issue->Index->Severity) {
case 0:
$severity = ArcanistLintSeverity::SEVERITY_ADVICE;
break;
case 1:
$severity = ArcanistLintSeverity::SEVERITY_AUTOFIX;
break;
case 2:
$severity = ArcanistLintSeverity::SEVERITY_WARNING;
break;
case 3:
$severity = ArcanistLintSeverity::SEVERITY_ERROR;
break;
case 4:
$severity = ArcanistLintSeverity::SEVERITY_DISABLED;
break;
}
$severity_override = $this->getLintMessageSeverity($issue->Index->Code);
if ($severity_override !== null) {
$severity = $severity_override;
}
$message->setSeverity($severity);
$this->addLintMessage($message);
}
}
protected function getDefaultMessageSeverity($code) {
return null;
}
}