Changeset View
Changeset View
Standalone View
Standalone View
src/lint/linter/ArcanistExternalLinter.php
<?php | <?php | ||||
/** | /** | ||||
* Base class for linters which operate by invoking an external program and | * Base class for linters which operate by invoking an external program and | ||||
* parsing results. | * parsing results. | ||||
* | * | ||||
* @task bin Interpreters, Binaries and Flags | * @task bin Interpreters, Binaries and Flags | ||||
* @task parse Parsing Linter Output | * @task parse Parsing Linter Output | ||||
* @task exec Executing the Linter | * @task exec Executing the Linter | ||||
*/ | */ | ||||
abstract class ArcanistExternalLinter extends ArcanistFutureLinter { | abstract class ArcanistExternalLinter extends ArcanistFutureLinter { | ||||
private $bin; | private $bin; | ||||
private $interpreter; | private $interpreter; | ||||
private $flags; | private $flags; | ||||
private $versionRequirement; | |||||
/* -( Interpreters, Binaries and Flags )----------------------------------- */ | /* -( Interpreters, Binaries and Flags )----------------------------------- */ | ||||
/** | /** | ||||
* Return the default binary name or binary path where the external linter | * Return the default binary name or binary path where the external linter | ||||
* lives. This can either be a binary name which is expected to be installed | * lives. This can either be a binary name which is expected to be installed | ||||
* in PATH (like "jshint"), or a relative path from the project root | * in PATH (like "jshint"), or a relative path from the project root | ||||
Show All 14 Lines | /* -( Interpreters, Binaries and Flags )----------------------------------- */ | ||||
* -g such-and-such`.", but will differ from linter to linter. | * -g such-and-such`.", but will differ from linter to linter. | ||||
* | * | ||||
* @return string Human readable install instructions | * @return string Human readable install instructions | ||||
* @task bin | * @task bin | ||||
*/ | */ | ||||
abstract public function getInstallInstructions(); | abstract public function getInstallInstructions(); | ||||
/** | /** | ||||
* Return a human-readable string describing how to upgrade the linter. | |||||
* | |||||
* @return string Human readable upgrade instructions | |||||
* @task bin | |||||
*/ | |||||
public function getUpgradeInstructions() { | |||||
return null; | |||||
} | |||||
/** | |||||
* Return true to continue when the external linter exits with an error code. | * Return true to continue when the external linter exits with an error code. | ||||
* By default, linters which exit with an error code are assumed to have | * By default, linters which exit with an error code are assumed to have | ||||
* failed. However, some linters exit with a specific code to indicate that | * failed. However, some linters exit with a specific code to indicate that | ||||
* lint messages were detected. | * lint messages were detected. | ||||
* | * | ||||
* If the linter sometimes raises errors during normal operation, override | * If the linter sometimes raises errors during normal operation, override | ||||
* this method and return true so execution continues when it exits with | * this method and return true so execution continues when it exits with | ||||
* a nonzero status. | * a nonzero status. | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | /* -( Interpreters, Binaries and Flags )----------------------------------- */ | ||||
* @task bin | * @task bin | ||||
*/ | */ | ||||
final public function setFlags(array $flags) { | final public function setFlags(array $flags) { | ||||
$this->flags = $flags; | $this->flags = $flags; | ||||
return $this; | return $this; | ||||
} | } | ||||
/** | /** | ||||
* Set the binary's version requirement. | |||||
* | |||||
* @param string Version requirement. | |||||
* @return this | |||||
* @task bin | |||||
*/ | |||||
final public function setVersionRequirement($version) { | |||||
$this->versionRequirement = trim($version); | |||||
return $this; | |||||
} | |||||
/** | |||||
* Return the binary or script to execute. This method synthesizes defaults | * Return the binary or script to execute. This method synthesizes defaults | ||||
* and configuration. You can override the binary with @{method:setBinary}. | * and configuration. You can override the binary with @{method:setBinary}. | ||||
* | * | ||||
* @return string Binary to execute. | * @return string Binary to execute. | ||||
* @task bin | * @task bin | ||||
*/ | */ | ||||
final public function getBinary() { | final public function getBinary() { | ||||
return coalesce($this->bin, $this->getDefaultBinary()); | return coalesce($this->bin, $this->getDefaultBinary()); | ||||
▲ Show 20 Lines • Show All 140 Lines • ▼ Show 20 Lines | if ($interpreter) { | ||||
pht( | pht( | ||||
'TO INSTALL: %s', | 'TO INSTALL: %s', | ||||
$this->getInstallInstructions()))); | $this->getInstallInstructions()))); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* If a binary version requirement has been specified, compare the version | |||||
* of the configured binary to the required version, and if the binary's | |||||
* version is not supported, throw an exception. | |||||
* | |||||
* @param string Version string to check. | |||||
* @return void | |||||
*/ | |||||
final protected function checkBinaryVersion($version) { | |||||
if (!$this->versionRequirement) { | |||||
return; | |||||
} | |||||
if (!$version) { | |||||
$message = pht( | |||||
'Linter %s requires %s version %s. Unable to determine the version '. | |||||
'that you have installed.', | |||||
get_class($this), | |||||
$this->getBinary()); | |||||
epriestley: This pattern has three "%s"'s but only two parameters (missing $verision)? | |||||
$instructions = $this->getUpgradeInstructions(); | |||||
if ($instructions) { | |||||
$message .= "\n".pht('TO UPGRADE: %s', $instructions); | |||||
} | |||||
throw new ArcanistMissingLinterException($message); | |||||
} | |||||
$operator = '=='; | |||||
$compare_to = $this->versionRequirement; | |||||
$matches = null; | |||||
if (preg_match('/^([<>]=?|==)\s*(.*)$/', $compare_to, $matches)) { | |||||
$operator = $matches[1]; | |||||
$compare_to = $matches[2]; | |||||
} | |||||
if (!version_compare($version, $compare_to, $operator)) { | |||||
Done Inline ActionsShould be $operator, not $compare_to? epriestley: Should be `$operator`, not `$compare_to`? | |||||
$message = pht( | |||||
'Linter %s requires %s version %s. You have version %s.', | |||||
get_class($this), | |||||
$this->getBinary(), | |||||
$this->versionRequirement, | |||||
$version); | |||||
$instructions = $this->getUpgradeInstructions(); | |||||
if ($instructions) { | |||||
$message .= "\n".pht('TO UPGRADE: %s', $instructions); | |||||
} | |||||
throw new ArcanistMissingLinterException($message); | |||||
} | |||||
} | |||||
/** | |||||
* Get the composed executable command, including the interpreter and binary | * Get the composed executable command, including the interpreter and binary | ||||
* but without flags or paths. This can be used to execute `--version` | * but without flags or paths. This can be used to execute `--version` | ||||
* commands. | * commands. | ||||
* | * | ||||
* @return string Command to execute the raw linter. | * @return string Command to execute the raw linter. | ||||
* @task exec | * @task exec | ||||
*/ | */ | ||||
final protected function getExecutableCommand() { | final protected function getExecutableCommand() { | ||||
Show All 33 Lines | try { | ||||
$this->checkBinaryConfiguration(); | $this->checkBinaryConfiguration(); | ||||
} catch (ArcanistMissingLinterException $e) { | } catch (ArcanistMissingLinterException $e) { | ||||
return null; | return null; | ||||
} | } | ||||
$version = $this->getVersion(); | $version = $this->getVersion(); | ||||
if ($version) { | if ($version) { | ||||
$this->checkBinaryVersion($version); | |||||
Not Done Inline ActionsI think this isn't really the best place to call this (conceptually, there is no guarantee we'll ever need to generate the cache version for a linter) , but I think a better place may not exist today. I'll make a note about this on T7045. epriestley: I think this isn't really the best place to call this (conceptually, there is no guarantee… | |||||
Not Done Inline ActionsYes, I'm not happy with this location either. I originally tried checkBinaryConfiguration(), but then we get into a infinite dependency loop because we need to use the binary to get the version (as I believe you also discovered). jparise: Yes, I'm not happy with this location either. I originally tried `checkBinaryConfiguration()`… | |||||
return $version.'-'.json_encode($this->getCommandFlags()); | return $version.'-'.json_encode($this->getCommandFlags()); | ||||
} else { | } else { | ||||
// Either we failed to parse the version number or the `getVersion` | // Either we failed to parse the version number or the `getVersion` | ||||
// function hasn't been implemented. | // function hasn't been implemented. | ||||
return json_encode($this->getCommandFlags()); | return json_encode($this->getCommandFlags()); | ||||
} | } | ||||
} | } | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | $options = array( | ||||
'the first one which exists will be used.'), | 'the first one which exists will be used.'), | ||||
), | ), | ||||
'flags' => array( | 'flags' => array( | ||||
'type' => 'optional list<string>', | 'type' => 'optional list<string>', | ||||
'help' => pht( | 'help' => pht( | ||||
'Provide a list of additional flags to pass to the linter on the '. | 'Provide a list of additional flags to pass to the linter on the '. | ||||
'command line.'), | 'command line.'), | ||||
), | ), | ||||
'version' => array( | |||||
'type' => 'optional string', | |||||
'help' => pht( | |||||
'Specify a version requirement for the binary. The version number '. | |||||
'may be prefixed with <, <=, >, >=, or == to specify the version '. | |||||
'comparison operator (default: ==).'), | |||||
), | |||||
); | ); | ||||
if ($this->shouldUseInterpreter()) { | if ($this->shouldUseInterpreter()) { | ||||
$options['interpreter'] = array( | $options['interpreter'] = array( | ||||
'type' => 'optional string | list<string>', | 'type' => 'optional string | list<string>', | ||||
'help' => pht( | 'help' => pht( | ||||
'Specify a string (or list of strings) identifying the interpreter '. | 'Specify a string (or list of strings) identifying the interpreter '. | ||||
'which should be used to invoke the linter binary. If you provide '. | 'which should be used to invoke the linter binary. If you provide '. | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | switch ($key) { | ||||
} | } | ||||
} | } | ||||
throw new Exception( | throw new Exception( | ||||
pht('None of the configured binaries can be located.')); | pht('None of the configured binaries can be located.')); | ||||
case 'flags': | case 'flags': | ||||
$this->setFlags($value); | $this->setFlags($value); | ||||
return; | return; | ||||
case 'version': | |||||
$this->setVersionRequirement($value); | |||||
return; | |||||
} | } | ||||
return parent::setLinterConfigurationValue($key, $value); | return parent::setLinterConfigurationValue($key, $value); | ||||
} | } | ||||
/** | /** | ||||
* Map a configuration lint code to an `arc` lint code. Primarily, this is | * Map a configuration lint code to an `arc` lint code. Primarily, this is | ||||
* intended for validation, but can also be used to normalize case or | * intended for validation, but can also be used to normalize case or | ||||
Show All 12 Lines |
This pattern has three "%s"'s but only two parameters (missing $verision)?