Changeset View
Changeset View
Standalone View
Standalone View
src/lint/linter/ArcanistTextLinter.php
Show First 20 Lines • Show All 84 Lines • ▼ Show 20 Lines | return array( | ||||
self::LINT_BAD_CHARSET => pht('Bad Charset'), | self::LINT_BAD_CHARSET => pht('Bad Charset'), | ||||
self::LINT_TRAILING_WHITESPACE => pht('Trailing Whitespace'), | self::LINT_TRAILING_WHITESPACE => pht('Trailing Whitespace'), | ||||
self::LINT_BOF_WHITESPACE => pht('Leading Whitespace at BOF'), | self::LINT_BOF_WHITESPACE => pht('Leading Whitespace at BOF'), | ||||
self::LINT_EOF_WHITESPACE => pht('Trailing Whitespace at EOF'), | self::LINT_EOF_WHITESPACE => pht('Trailing Whitespace at EOF'), | ||||
self::LINT_EMPTY_FILE => pht('Empty File'), | self::LINT_EMPTY_FILE => pht('Empty File'), | ||||
); | ); | ||||
} | } | ||||
public function lintPath($path) { | protected function lintPath(ArcanistWorkingCopyPath $path) { | ||||
$this->lintEmptyFile($path); | $this->lintEmptyFile($path); | ||||
if (!strlen($this->getData($path))) { | $data = $path->getData(); | ||||
if (!strlen($data)) { | |||||
// If the file is empty, don't bother; particularly, don't require | // If the file is empty, don't bother; particularly, don't require | ||||
// the user to add a newline. | // the user to add a newline. | ||||
return; | return; | ||||
} | } | ||||
if ($this->didStopAllLinters()) { | if ($this->didStopAllLinters()) { | ||||
return; | return; | ||||
} | } | ||||
Show All 14 Lines | protected function lintPath(ArcanistWorkingCopyPath $path) { | ||||
$this->lintLineLength($path); | $this->lintLineLength($path); | ||||
$this->lintEOFNewline($path); | $this->lintEOFNewline($path); | ||||
$this->lintTrailingWhitespace($path); | $this->lintTrailingWhitespace($path); | ||||
$this->lintBOFWhitespace($path); | $this->lintBOFWhitespace($path); | ||||
$this->lintEOFWhitespace($path); | $this->lintEOFWhitespace($path); | ||||
} | } | ||||
protected function lintEmptyFile($path) { | private function lintEmptyFile(ArcanistWorkingCopyPath $path) { | ||||
$data = $this->getData($path); | // If this file has any content, it isn't empty. | ||||
$data = $path->getData(); | |||||
if (!preg_match('/^\s*$/', $data)) { | |||||
return; | |||||
} | |||||
// It is reasonable for certain file types to be completely empty, | // It is reasonable for certain file types to be completely empty, | ||||
// so they are excluded here. | // so they are excluded here. | ||||
switch ($filename = basename($this->getActivePath())) { | $basename = $path->getBasename(); | ||||
case '__init__.py': | |||||
return; | |||||
default: | // Allow empty "__init__.py", as this is legitimate in Python. | ||||
if (strlen($filename) && $filename[0] == '.') { | if ($basename === '__init__.py') { | ||||
return; | return; | ||||
} | } | ||||
// Allow empty ".gitkeep" and similar files. | |||||
if (isset($filename[0]) && $filename[0] == '.') { | |||||
return; | |||||
} | } | ||||
if (preg_match('/^\s*$/', $data)) { | |||||
$this->raiseLintAtPath( | $this->raiseLintAtPath( | ||||
self::LINT_EMPTY_FILE, | self::LINT_EMPTY_FILE, | ||||
pht("Empty files usually don't serve any useful purpose.")); | pht('Empty files usually do not serve any useful purpose.')); | ||||
$this->stopAllLinters(); | $this->stopAllLinters(); | ||||
} | } | ||||
} | |||||
protected function lintNewlines($path) { | private function lintNewlines(ArcanistWorkingCopyPath $path) { | ||||
$data = $this->getData($path); | $data = $path->getData(); | ||||
$pos = strpos($this->getData($path), "\r"); | $pos = strpos($data, "\r"); | ||||
if ($pos !== false) { | if ($pos !== false) { | ||||
$this->raiseLintAtOffset( | $this->raiseLintAtOffset( | ||||
0, | 0, | ||||
self::LINT_DOS_NEWLINE, | self::LINT_DOS_NEWLINE, | ||||
pht('You must use ONLY Unix linebreaks ("%s") in source code.', '\n'), | pht('You must use ONLY Unix linebreaks ("%s") in source code.', '\n'), | ||||
$data, | $data, | ||||
str_replace("\r\n", "\n", $data)); | str_replace("\r\n", "\n", $data)); | ||||
if ($this->isMessageEnabled(self::LINT_DOS_NEWLINE)) { | if ($this->isMessageEnabled(self::LINT_DOS_NEWLINE)) { | ||||
$this->stopAllLinters(); | $this->stopAllLinters(); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintTabs($path) { | private function lintTabs(ArcanistWorkingCopyPath $path) { | ||||
amckinley: I didn't know this was a default lint rule, but I'm so glad to see it. | |||||
$pos = strpos($this->getData($path), "\t"); | $data = $path->getData(); | ||||
$pos = strpos($data, "\t"); | |||||
if ($pos !== false) { | if ($pos !== false) { | ||||
$this->raiseLintAtOffset( | $this->raiseLintAtOffset( | ||||
$pos, | $pos, | ||||
self::LINT_TAB_LITERAL, | self::LINT_TAB_LITERAL, | ||||
pht('Configure your editor to use spaces for indentation.'), | pht('Configure your editor to use spaces for indentation.'), | ||||
"\t"); | "\t"); | ||||
} | } | ||||
} | } | ||||
protected function lintLineLength($path) { | private function lintLineLength(ArcanistWorkingCopyPath $path) { | ||||
$lines = explode("\n", $this->getData($path)); | $lines = $path->getDataAsLines(); | ||||
$width = $this->maxLineLength; | $width = $this->maxLineLength; | ||||
foreach ($lines as $line_idx => $line) { | foreach ($lines as $line_idx => $line) { | ||||
$line = rtrim($line, "\n"); | |||||
if (strlen($line) > $width) { | if (strlen($line) > $width) { | ||||
$this->raiseLintAtLine( | $this->raiseLintAtLine( | ||||
$line_idx + 1, | $line_idx + 1, | ||||
1, | 1, | ||||
self::LINT_LINE_WRAP, | self::LINT_LINE_WRAP, | ||||
pht( | pht( | ||||
'This line is %s characters long, but the '. | 'This line is %s characters long, but the '. | ||||
'convention is %s characters.', | 'convention is %s characters.', | ||||
new PhutilNumber(strlen($line)), | new PhutilNumber(strlen($line)), | ||||
$width), | $width), | ||||
$line); | $line); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintEOFNewline($path) { | private function lintEOFNewline(ArcanistWorkingCopyPath $path) { | ||||
$data = $this->getData($path); | $data = $path->getData(); | ||||
if (!strlen($data) || $data[strlen($data) - 1] != "\n") { | if (!strlen($data) || $data[strlen($data) - 1] != "\n") { | ||||
$this->raiseLintAtOffset( | $this->raiseLintAtOffset( | ||||
strlen($data), | strlen($data), | ||||
self::LINT_EOF_NEWLINE, | self::LINT_EOF_NEWLINE, | ||||
pht('Files must end in a newline.'), | pht('Files must end in a newline.'), | ||||
'', | '', | ||||
"\n"); | "\n"); | ||||
} | } | ||||
} | } | ||||
protected function lintCharset($path) { | private function lintCharset(ArcanistWorkingCopyPath $path) { | ||||
$data = $this->getData($path); | $data = $path->getData(); | ||||
$matches = null; | $matches = null; | ||||
$bad = '[^\x09\x0A\x20-\x7E]'; | $bad = '[^\x09\x0A\x20-\x7E]'; | ||||
$preg = preg_match_all( | $preg = preg_match_all( | ||||
"/{$bad}(.*{$bad})?/", | "/{$bad}(.*{$bad})?/", | ||||
$data, | $data, | ||||
$matches, | $matches, | ||||
PREG_OFFSET_CAPTURE); | PREG_OFFSET_CAPTURE); | ||||
Show All 14 Lines | foreach ($matches[0] as $match) { | ||||
$string); | $string); | ||||
} | } | ||||
if ($this->isMessageEnabled(self::LINT_BAD_CHARSET)) { | if ($this->isMessageEnabled(self::LINT_BAD_CHARSET)) { | ||||
$this->stopAllLinters(); | $this->stopAllLinters(); | ||||
} | } | ||||
} | } | ||||
protected function lintTrailingWhitespace($path) { | private function lintTrailingWhitespace(ArcanistWorkingCopyPath $path) { | ||||
$data = $this->getData($path); | $data = $path->getData(); | ||||
$matches = null; | $matches = null; | ||||
$preg = preg_match_all( | $preg = preg_match_all( | ||||
'/[[:blank:]]+$/m', | '/[[:blank:]]+$/m', | ||||
$data, | $data, | ||||
$matches, | $matches, | ||||
PREG_OFFSET_CAPTURE); | PREG_OFFSET_CAPTURE); | ||||
Show All 10 Lines | foreach ($matches[0] as $match) { | ||||
'This line contains trailing whitespace. Consider setting '. | 'This line contains trailing whitespace. Consider setting '. | ||||
'up your editor to automatically remove trailing whitespace, '. | 'up your editor to automatically remove trailing whitespace, '. | ||||
'you will save time.'), | 'you will save time.'), | ||||
$string, | $string, | ||||
''); | ''); | ||||
} | } | ||||
} | } | ||||
protected function lintBOFWhitespace($path) { | private function lintBOFWhitespace(ArcanistWorkingCopyPath $path) { | ||||
$data = $this->getData($path); | $data = $path->getData(); | ||||
$matches = null; | $matches = null; | ||||
$preg = preg_match( | $preg = preg_match( | ||||
'/^\s*\n/', | '/^\s*\n/', | ||||
$data, | $data, | ||||
$matches, | $matches, | ||||
PREG_OFFSET_CAPTURE); | PREG_OFFSET_CAPTURE); | ||||
if (!$preg) { | if (!$preg) { | ||||
return; | return; | ||||
} | } | ||||
list($string, $offset) = $matches[0]; | list($string, $offset) = $matches[0]; | ||||
$this->raiseLintAtOffset( | $this->raiseLintAtOffset( | ||||
$offset, | $offset, | ||||
self::LINT_BOF_WHITESPACE, | self::LINT_BOF_WHITESPACE, | ||||
pht( | pht( | ||||
'This file contains leading whitespace at the beginning of the file. '. | 'This file contains leading whitespace at the beginning of the file. '. | ||||
'This is unnecessary and should be avoided when possible.'), | 'This is unnecessary and should be avoided when possible.'), | ||||
$string, | $string, | ||||
''); | ''); | ||||
} | } | ||||
protected function lintEOFWhitespace($path) { | private function lintEOFWhitespace(ArcanistWorkingCopyPath $path) { | ||||
$data = $this->getData($path); | $data = $path->getData(); | ||||
$matches = null; | $matches = null; | ||||
$preg = preg_match( | $preg = preg_match( | ||||
'/(?<=\n)\s+$/', | '/(?<=\n)\s+$/', | ||||
$data, | $data, | ||||
$matches, | $matches, | ||||
PREG_OFFSET_CAPTURE); | PREG_OFFSET_CAPTURE); | ||||
Show All 14 Lines |
I didn't know this was a default lint rule, but I'm so glad to see it.