4
* Enforces basic text file rules.
6
final class ArcanistTextLinter extends ArcanistLinter {
8
const LINT_DOS_NEWLINE = 1;
9
const LINT_TAB_LITERAL = 2;
10
const LINT_LINE_WRAP = 3;
11
const LINT_EOF_NEWLINE = 4;
12
const LINT_BAD_CHARSET = 5;
13
const LINT_TRAILING_WHITESPACE = 6;
14
const LINT_NO_COMMIT = 7;
15
const LINT_BOF_WHITESPACE = 8;
16
const LINT_EOF_WHITESPACE = 9;
18
private $maxLineLength = 80;
20
public function getInfoName() {
21
return pht('Basic Text Linter');
24
public function getInfoDescription() {
26
'Enforces basic text rules like line length, character encoding, '.
27
'and trailing whitespace.');
30
public function getLinterPriority() {
34
public function getLinterConfigurationOptions() {
36
'text.max-line-length' => array(
37
'type' => 'optional int',
39
'Adjust the maximum line length before a warning is raised. By '.
40
'default, a warning is raised on lines exceeding 80 characters.'),
44
return $options + parent::getLinterConfigurationOptions();
47
public function setMaxLineLength($new_length) {
48
$this->maxLineLength = $new_length;
52
public function setLinterConfigurationValue($key, $value) {
54
case 'text.max-line-length':
55
$this->setMaxLineLength($value);
59
return parent::setLinterConfigurationValue($key, $value);
62
public function getLinterName() {
66
public function getLinterConfigurationName() {
70
public function getLintSeverityMap() {
72
self::LINT_LINE_WRAP => ArcanistLintSeverity::SEVERITY_WARNING,
73
self::LINT_TRAILING_WHITESPACE => ArcanistLintSeverity::SEVERITY_AUTOFIX,
74
self::LINT_BOF_WHITESPACE => ArcanistLintSeverity::SEVERITY_AUTOFIX,
75
self::LINT_EOF_WHITESPACE => ArcanistLintSeverity::SEVERITY_AUTOFIX,
79
public function getLintNameMap() {
81
self::LINT_DOS_NEWLINE => pht('DOS Newlines'),
82
self::LINT_TAB_LITERAL => pht('Tab Literal'),
83
self::LINT_LINE_WRAP => pht('Line Too Long'),
84
self::LINT_EOF_NEWLINE => pht('File Does Not End in Newline'),
85
self::LINT_BAD_CHARSET => pht('Bad Charset'),
86
self::LINT_TRAILING_WHITESPACE => pht('Trailing Whitespace'),
87
self::LINT_NO_COMMIT => pht('Explicit %s', '@no'.'commit'),
88
self::LINT_BOF_WHITESPACE => pht('Leading Whitespace at BOF'),
89
self::LINT_EOF_WHITESPACE => pht('Trailing Whitespace at EOF'),
93
public function lintPath($path) {
94
if (!strlen($this->getData($path))) {
95
// If the file is empty, don't bother; particularly, don't require
96
// the user to add a newline.
100
$this->lintNewlines($path);
101
$this->lintTabs($path);
103
if ($this->didStopAllLinters()) {
107
$this->lintCharset($path);
109
if ($this->didStopAllLinters()) {
113
$this->lintLineLength($path);
114
$this->lintEOFNewline($path);
115
$this->lintTrailingWhitespace($path);
117
$this->lintBOFWhitespace($path);
118
$this->lintEOFWhitespace($path);
120
if ($this->getEngine()->getCommitHookMode()) {
121
$this->lintNoCommit($path);
125
protected function lintNewlines($path) {
126
$data = $this->getData($path);
127
$pos = strpos($this->getData($path), "\r");
129
if ($pos !== false) {
130
$this->raiseLintAtOffset(
132
self::LINT_DOS_NEWLINE,
133
pht('You must use ONLY Unix linebreaks ("%s") in source code.', '\n'),
135
str_replace("\r\n", "\n", $data));
137
if ($this->isMessageEnabled(self::LINT_DOS_NEWLINE)) {
138
$this->stopAllLinters();
143
protected function lintTabs($path) {
144
$pos = strpos($this->getData($path), "\t");
145
if ($pos !== false) {
146
$this->raiseLintAtOffset(
148
self::LINT_TAB_LITERAL,
149
pht('Configure your editor to use spaces for indentation.'),
154
protected function lintLineLength($path) {
155
$lines = explode("\n", $this->getData($path));
157
$width = $this->maxLineLength;
158
foreach ($lines as $line_idx => $line) {
159
if (strlen($line) > $width) {
160
$this->raiseLintAtLine(
163
self::LINT_LINE_WRAP,
165
'This line is %s characters long, but the '.
166
'convention is %s characters.',
167
new PhutilNumber(strlen($line)),
174
protected function lintEOFNewline($path) {
175
$data = $this->getData($path);
176
if (!strlen($data) || $data[strlen($data) - 1] != "\n") {
177
$this->raiseLintAtOffset(
179
self::LINT_EOF_NEWLINE,
180
pht('Files must end in a newline.'),
186
protected function lintCharset($path) {
187
$data = $this->getData($path);
190
$bad = '[^\x09\x0A\x20-\x7E]';
191
$preg = preg_match_all(
192
"/{$bad}(.*{$bad})?/",
195
PREG_OFFSET_CAPTURE);
201
foreach ($matches[0] as $match) {
202
list($string, $offset) = $match;
203
$this->raiseLintAtOffset(
205
self::LINT_BAD_CHARSET,
207
'Source code should contain only ASCII bytes with ordinal '.
208
'decimal values between 32 and 126 inclusive, plus linefeed. '.
209
'Do not use UTF-8 or other multibyte charsets.'),
213
if ($this->isMessageEnabled(self::LINT_BAD_CHARSET)) {
214
$this->stopAllLinters();
218
protected function lintTrailingWhitespace($path) {
219
$data = $this->getData($path);
222
$preg = preg_match_all(
226
PREG_OFFSET_CAPTURE);
232
foreach ($matches[0] as $match) {
233
list($string, $offset) = $match;
234
$this->raiseLintAtOffset(
236
self::LINT_TRAILING_WHITESPACE,
238
'This line contains trailing whitespace. Consider setting '.
239
'up your editor to automatically remove trailing whitespace, '.
240
'you will save time.'),
246
protected function lintBOFWhitespace($path) {
247
$data = $this->getData($path);
254
PREG_OFFSET_CAPTURE);
260
list($string, $offset) = $matches[0];
261
$this->raiseLintAtOffset(
263
self::LINT_BOF_WHITESPACE,
265
'This file contains leading whitespace at the beginning of the file. '.
266
'This is unnecessary and should be avoided when possible.'),
271
protected function lintEOFWhitespace($path) {
272
$data = $this->getData($path);
279
PREG_OFFSET_CAPTURE);
285
list($string, $offset) = $matches[0];
286
$this->raiseLintAtOffset(
288
self::LINT_EOF_WHITESPACE,
290
'This file contains trailing whitespace at the end of the file. '.
291
'This is unnecessary and should be avoided when possible.'),
296
private function lintNoCommit($path) {
297
$data = $this->getData($path);
299
$deadly = '@no'.'commit';
301
$offset = strpos($data, $deadly);
302
if ($offset !== false) {
303
$this->raiseLintAtOffset(
305
self::LINT_NO_COMMIT,
307
'This file is explicitly marked as "%s", which blocks commits.',