4
* A linter for LESSCSS files.
6
* This linter uses [[https://github.com/less/less.js | lessc]] to detect
7
* errors and potential problems in [[http://lesscss.org/ | LESS]] code.
9
final class ArcanistLesscLinter extends ArcanistExternalLinter {
11
const LINT_RUNTIME_ERROR = 1;
12
const LINT_ARGUMENT_ERROR = 2;
13
const LINT_FILE_ERROR = 3;
14
const LINT_NAME_ERROR = 4;
15
const LINT_OPERATION_ERROR = 5;
16
const LINT_PARSE_ERROR = 6;
17
const LINT_SYNTAX_ERROR = 7;
19
private $strictMath = false;
20
private $strictUnits = false;
22
public function getInfoName() {
26
public function getInfoURI() {
27
return 'https://lesscss.org/';
30
public function getInfoDescription() {
32
'Use the `--lint` mode provided by `lessc` to detect errors in Less '.
36
public function getLinterName() {
40
public function getLinterConfigurationName() {
44
public function getLinterConfigurationOptions() {
45
return parent::getLinterConfigurationOptions() + array(
46
'lessc.strict-math' => array(
47
'type' => 'optional bool',
49
'Enable strict math, which only processes mathematical expressions '.
50
'inside extraneous parentheses.'),
52
'lessc.strict-units' => array(
53
'type' => 'optional bool',
54
'help' => pht('Enable strict handling of units in expressions.'),
59
public function setLinterConfigurationValue($key, $value) {
61
case 'lessc.strict-math':
62
$this->strictMath = $value;
64
case 'lessc.strict-units':
65
$this->strictUnits = $value;
69
return parent::setLinterConfigurationValue($key, $value);
72
public function getLintNameMap() {
74
self::LINT_RUNTIME_ERROR => pht('Runtime Error'),
75
self::LINT_ARGUMENT_ERROR => pht('Argument Error'),
76
self::LINT_FILE_ERROR => pht('File Error'),
77
self::LINT_NAME_ERROR => pht('Name Error'),
78
self::LINT_OPERATION_ERROR => pht('Operation Error'),
79
self::LINT_PARSE_ERROR => pht('Parse Error'),
80
self::LINT_SYNTAX_ERROR => pht('Syntax Error'),
84
public function getDefaultBinary() {
88
public function getVersion() {
89
list($stdout) = execx('%C --version', $this->getExecutableCommand());
92
$regex = '/^lessc (?P<version>\d+\.\d+\.\d+)\b/';
93
if (preg_match($regex, $stdout, $matches)) {
94
$version = $matches['version'];
100
public function getInstallInstructions() {
101
return pht('Install lessc using `npm install -g less`.');
104
public function shouldExpectCommandErrors() {
108
public function supportsReadDataFromStdin() {
109
// Technically `lessc` can read data from standard input however, when doing
110
// so, relative imports cannot be resolved. Therefore, this functionality is
115
public function getReadDataFromStdinFilename() {
119
protected function getMandatoryFlags() {
123
'--strict-math='.($this->strictMath ? 'on' : 'off'),
124
'--strict-units='.($this->strictUnits ? 'on' : 'off'),
128
protected function parseLinterOutput($path, $err, $stdout, $stderr) {
129
$lines = phutil_split_lines($stderr, false);
132
foreach ($lines as $line) {
135
'/^(?P<name>\w+): (?P<description>.+) '.
136
'in (?P<path>.+|-) '.
137
'on line (?P<line>\d+), column (?P<column>\d+):$/',
142
switch ($matches['name']) {
144
$code = self::LINT_RUNTIME_ERROR;
147
case 'ArgumentError':
148
$code = self::LINT_ARGUMENT_ERROR;
152
$code = self::LINT_FILE_ERROR;
156
$code = self::LINT_NAME_ERROR;
159
case 'OperationError':
160
$code = self::LINT_OPERATION_ERROR;
164
$code = self::LINT_PARSE_ERROR;
168
$code = self::LINT_SYNTAX_ERROR;
172
throw new RuntimeException(pht(
173
'Unrecognized lint message code "%s".',
177
$code = $this->getLintCodeFromLinterConfigurationKey($matches['name']);
179
$message = new ArcanistLintMessage();
180
$message->setPath($path);
181
$message->setLine($matches['line']);
182
$message->setChar($matches['column']);
183
$message->setCode($this->getLintMessageFullCode($code));
184
$message->setSeverity($this->getLintMessageSeverity($code));
185
$message->setName($this->getLintMessageName($code));
186
$message->setDescription(ucfirst($matches['description']));
188
$messages[] = $message;
192
if ($err && !$messages) {