Changeset View
Standalone View
src/lint/linter/ArcanistESLintLinter.php
- This file was added.
<?php | |||||
final class ArcanistESLintLinter extends ArcanistExternalLinter { | |||||
protected $eslintenv; | |||||
protected $eslintconfig; | |||||
protected $eslintignore; | |||||
public function __construct() { | |||||
// This sets the min supported version of this | |||||
// linter. If you have set it in your .arcconfig or | |||||
// .arclint file it will overwrite this. However if | |||||
// the value is set lower it will cause the linter | |||||
// to break. | |||||
$this->setVersionRequirement('>=v1.0.0'); | |||||
michaeljs1990: This is the only way I see presently to set a min version. However as noted above the user can… | |||||
} | |||||
public function getInfoName() { | |||||
return 'ESLint'; | |||||
Done Inline ActionsLinter names are typically uppercase, consider changing this to ESLINT. joshuaspence: Linter names are typically uppercase, consider changing this to `ESLINT`. | |||||
} | |||||
Not Done Inline ActionsThis was actually changed since - see T9353. avivey: This was actually changed since - see T9353.
The idea is that the one-liner output from `arc… | |||||
public function getInfoURI() { | |||||
return 'https://www.eslint.org'; | |||||
} | |||||
public function getRuleDocumentationURI($rule_id) { | |||||
return $this->getInfoURI().'/docs/rules/'.$rule_id; | |||||
} | |||||
public function getInfoDescription() { | |||||
return pht('ESLint is a linter for JavaScript source files.'); | |||||
Done Inline ActionsTechnically this should be written as pht('Install ESLint using `%s`.', 'npm install -g eslint') because npm install -g eslint isn't really translatable. joshuaspence: Technically this should be written as `pht('Install ESLint using ```%s```.', 'npm install -g… | |||||
Not Done Inline ActionsHi! I tried using this linter, and configured it to use the eslint binary relative to my project (node_modules/.bin/eslint), and it wouldn't report the version (because I don't have eslint installed globally). Maybe the line should be this, instead? list($output) = execx('%C --version', $this->getExecutableCommand()); bspoon: Hi! I tried using this linter, and configured it to use the eslint binary relative to my… | |||||
Not Done Inline ActionsYou are correct, sorry I missed that. Will push up a change for it shortly. michaeljs1990: You are correct, sorry I missed that. Will push up a change for it shortly. | |||||
} | |||||
public function getVersion() { | |||||
list($stdout, $stderr) = execx( | |||||
'%C --version', $this->getExecutableCommand()); | |||||
Done Inline ActionsThis is the default value, just remove this method. joshuaspence: This is the default value, just remove this method. | |||||
$matches = array(); | |||||
$regex = '/^v(?P<version>\d+\.\d+\.\d+)$/'; | |||||
if (preg_match($regex, $stdout, $matches)) { | |||||
return $matches['version']; | |||||
} else { | |||||
return false; | |||||
} | |||||
} | |||||
public function getLinterName() { | |||||
return 'ESLINT'; | |||||
} | |||||
public function getLinterConfigurationName() { | |||||
return 'eslint'; | |||||
} | |||||
public function getDefaultBinary() { | |||||
return 'eslint'; | |||||
} | |||||
public function getInstallInstructions() { | |||||
return pht('Install ESLint using `%s`.', 'npm install -g eslint'); | |||||
} | |||||
public function getUpgradeInstructions() { | |||||
return pht('Upgrade ESLint using `%s`.', 'npm update -g eslint'); | |||||
} | |||||
protected function getMandatoryFlags() { | |||||
$options = array(); | |||||
$options[] = '--format=json'; | |||||
$options[] = '--no-color'; | |||||
if ($this->eslintenv) { | |||||
$options[] = '--env='.$this->eslintenv; | |||||
} | |||||
if ($this->eslintconfig) { | |||||
$options[] = '--config='.$this->eslintconfig; | |||||
} | |||||
if ($this->eslintignore) { | |||||
$options[] = '--ignore-path='.$this->eslintignore; | |||||
} | |||||
return $options; | |||||
} | |||||
public function getLinterConfigurationOptions() { | |||||
$options = array( | |||||
'eslint.eslintenv' => array( | |||||
'type' => 'optional string', | |||||
'help' => pht('enables specific environments.'), | |||||
), | |||||
'eslint.eslintconfig' => array( | |||||
'type' => 'optional string', | |||||
'help' => pht('config file to use the default is .eslint.'), | |||||
), | |||||
'eslint.eslintignore' => array( | |||||
'type' => 'optional string', | |||||
'help' => pht( | |||||
'ignore file to use. the default is .eslintignore.'), | |||||
), | |||||
); | |||||
return $options + parent::getLinterConfigurationOptions(); | |||||
} | |||||
public function getLintSeverityMap() { | |||||
return array( | |||||
2 => ArcanistLintSeverity::SEVERITY_ERROR, | |||||
1 => ArcanistLintSeverity::SEVERITY_WARNING, | |||||
); | |||||
} | |||||
public function setLinterConfigurationValue($key, $value) { | |||||
switch ($key) { | |||||
case 'eslint.eslintenv': | |||||
$this->eslintenv = $value; | |||||
return; | |||||
case 'eslint.eslintconfig': | |||||
$this->eslintconfig = $value; | |||||
return; | |||||
case 'eslint.eslintignore': | |||||
$this->eslintignore = $value; | |||||
return; | |||||
} | |||||
return parent::setLinterConfigurationValue($key, $value); | |||||
} | |||||
protected function canCustomizeLintSeverities() { | |||||
return true; | |||||
} | |||||
protected function parseLinterOutput($path, $err, $stdout, $stderr) { | |||||
$errors = null; | |||||
try { | |||||
$errors = phutil_json_decode($stdout); | |||||
} catch (PhutilJSONParserException $ex) { | |||||
// Something went wrong and we can't decode the output. | |||||
// Exit abnormally. | |||||
if (empty($stdout)) { | |||||
throw new PhutilProxyException( | |||||
pht('ESLint threw an error: %s', $stderr), | |||||
$ex | |||||
); | |||||
} else { | |||||
throw new PhutilProxyException( | |||||
pht('ESLint returned unparseable output: %s', $stdout), | |||||
$ex | |||||
); | |||||
} | |||||
} | |||||
$messages = array(); | |||||
foreach (idx($errors[0], 'messages') as $result) { | |||||
$message = new ArcanistLintMessage(); | |||||
$message->setPath($path); | |||||
$message->setLine(idx($result, 'line')); | |||||
$message->setChar(idx($result, 'column')); | |||||
$message->setDescription(idx($result, 'message')); | |||||
$message->setSeverity( | |||||
$this->getLintMessageSeverity(idx($result, 'severity'))); | |||||
// In case of a parsing error, eslint does not specify what rule | |||||
// failed | |||||
// Instead it sets fatal to true | |||||
if (idx($result, 'fatal')) { | |||||
$message->setCode('fatal'); | |||||
$message->setName('ParsingError'); | |||||
} else { | |||||
$rule_id = idx($result, 'ruleId'); | |||||
$message->setCode($rule_id); | |||||
$message->setName(idx($result, 'nodeType')); | |||||
$message->setDescription( | |||||
pht( | |||||
"%s\r\nSee documentation at %s", | |||||
idx($result, 'message'), | |||||
$this->getRuleDocumentationURI($rule_id))); | |||||
} | |||||
// Log files that ignored by ESLint | |||||
if (!$message->getName()) { | |||||
echo pht("Couldn't lint path: %s\r\n", $path); | |||||
continue; | |||||
} | |||||
$messages[] = $message; | |||||
} | |||||
return $messages; | |||||
} | |||||
} |
This is the only way I see presently to set a min version. However as noted above the user can potential mess this up by setting a lower version in .arclint or .arcconfig and I have no way to stop this presently.