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:
parent
0b8ea973ae
commit
02e4a690dd
2 changed files with 181 additions and 0 deletions
|
@ -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',
|
||||
|
|
179
src/lint/linter/ArcanistCSharpLinter.php
Normal file
179
src/lint/linter/ArcanistCSharpLinter.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue