2011-01-09 15:22:25 -08:00
|
|
|
<?php
|
|
|
|
|
2011-02-19 11:36:08 -08:00
|
|
|
/**
|
|
|
|
* Enforces basic text file rules.
|
|
|
|
*
|
|
|
|
* @group linter
|
|
|
|
*/
|
2012-01-31 12:07:05 -08:00
|
|
|
final class ArcanistTextLinter extends ArcanistLinter {
|
2011-01-09 15:22:25 -08:00
|
|
|
|
|
|
|
const LINT_DOS_NEWLINE = 1;
|
|
|
|
const LINT_TAB_LITERAL = 2;
|
|
|
|
const LINT_LINE_WRAP = 3;
|
|
|
|
const LINT_EOF_NEWLINE = 4;
|
|
|
|
const LINT_BAD_CHARSET = 5;
|
|
|
|
const LINT_TRAILING_WHITESPACE = 6;
|
2011-02-16 17:26:51 -08:00
|
|
|
const LINT_NO_COMMIT = 7;
|
2014-01-13 18:05:42 -08:00
|
|
|
const LINT_BOF_WHITESPACE = 8;
|
|
|
|
const LINT_EOF_WHITESPACE = 9;
|
2011-01-09 15:22:25 -08:00
|
|
|
|
|
|
|
private $maxLineLength = 80;
|
|
|
|
|
Ready more linters and linter functions for .arclint
Summary:
Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`.
- **Ruby**: Make this an ExternalLinter.
- **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`.
- **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files).
- **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this).
- **Severity**: Move this `.arclint` config option up to top level.
- **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class.
- **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them.
- **Spelling**: clean up some dead / test-only / unconventional code.
- **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`.
Test Plan:
https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94
Ran unit tests.
Reviewers: btrahan
Reviewed By: btrahan
CC: Firehed, aran
Maniphest Tasks: T2039, T3186
Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 05:37:10 -07:00
|
|
|
public function getLinterPriority() {
|
|
|
|
return 0.5;
|
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
public function setMaxLineLength($new_length) {
|
|
|
|
$this->maxLineLength = $new_length;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLinterName() {
|
|
|
|
return 'TXT';
|
|
|
|
}
|
|
|
|
|
Ready more linters and linter functions for .arclint
Summary:
Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`.
- **Ruby**: Make this an ExternalLinter.
- **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`.
- **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files).
- **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this).
- **Severity**: Move this `.arclint` config option up to top level.
- **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class.
- **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them.
- **Spelling**: clean up some dead / test-only / unconventional code.
- **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`.
Test Plan:
https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94
Ran unit tests.
Reviewers: btrahan
Reviewed By: btrahan
CC: Firehed, aran
Maniphest Tasks: T2039, T3186
Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 05:37:10 -07:00
|
|
|
public function getLinterConfigurationName() {
|
|
|
|
return 'text';
|
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
public function getLintSeverityMap() {
|
|
|
|
return array(
|
|
|
|
self::LINT_LINE_WRAP => ArcanistLintSeverity::SEVERITY_WARNING,
|
2012-04-06 12:23:19 -07:00
|
|
|
self::LINT_TRAILING_WHITESPACE => ArcanistLintSeverity::SEVERITY_AUTOFIX,
|
2014-01-13 18:05:42 -08:00
|
|
|
self::LINT_BOF_WHITESPACE => ArcanistLintSeverity::SEVERITY_AUTOFIX,
|
|
|
|
self::LINT_EOF_WHITESPACE => ArcanistLintSeverity::SEVERITY_AUTOFIX,
|
2011-01-09 15:22:25 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLintNameMap() {
|
|
|
|
return array(
|
Ready more linters and linter functions for .arclint
Summary:
Ref T3186. Ref T2039. Continues work on readying linters for `.arclint`.
- **Ruby**: Make this an ExternalLinter.
- **Priority**: Currently, linters have an implicit "correct" order (notably, the "NoLint" linter needs to run before other linters). Make this explicit by introducing `getLinterPriority()`.
- **Binaries**: Currently, linters manually reject binary files. Instead, reject binary files by default (linters can override this if they do want to lint binary files).
- **Deleted Files**: Currently, linters manually reject deleted files (usually in engines). Instead, reject deleted files by default (linters can override this).
- **Severity**: Move this `.arclint` config option up to top level.
- **willLintPaths()**: This method is abstract, but almost all linters provide a trivial implementation. Provide a trivial implementation in the base class.
- **getLintSeverityMap()/getLintNameMap()**: A bunch of linters have empty implementations; these are redundant. Remove them.
- **Spelling**: clean up some dead / test-only / unconventional code.
- **`.arclint`**: Allow the filename, generated, nolint, text, spelling and ruby linters to be configured via `.arclint`.
Test Plan:
https://github.com/epriestley/arclint-examples/commit/458beca3d65b64d52ed612904ae66eb837118b94
Ran unit tests.
Reviewers: btrahan
Reviewed By: btrahan
CC: Firehed, aran
Maniphest Tasks: T2039, T3186
Differential Revision: https://secure.phabricator.com/D6805
2013-08-26 05:37:10 -07:00
|
|
|
self::LINT_DOS_NEWLINE => pht('DOS Newlines'),
|
|
|
|
self::LINT_TAB_LITERAL => pht('Tab Literal'),
|
|
|
|
self::LINT_LINE_WRAP => pht('Line Too Long'),
|
|
|
|
self::LINT_EOF_NEWLINE => pht('File Does Not End in Newline'),
|
|
|
|
self::LINT_BAD_CHARSET => pht('Bad Charset'),
|
|
|
|
self::LINT_TRAILING_WHITESPACE => pht('Trailing Whitespace'),
|
|
|
|
self::LINT_NO_COMMIT => pht('Explicit %s', '@no'.'commit'),
|
2014-01-13 18:05:42 -08:00
|
|
|
self::LINT_BOF_WHITESPACE => pht('Leading Whitespace at BOF'),
|
|
|
|
self::LINT_EOF_WHITESPACE => pht('Trailing Whitespace at EOF'),
|
2011-01-09 15:22:25 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function lintPath($path) {
|
2011-03-22 11:08:59 -07:00
|
|
|
if (!strlen($this->getData($path))) {
|
|
|
|
// If the file is empty, don't bother; particularly, don't require
|
|
|
|
// the user to add a newline.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
$this->lintNewlines($path);
|
|
|
|
$this->lintTabs($path);
|
|
|
|
|
|
|
|
if ($this->didStopAllLinters()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->lintCharset($path);
|
|
|
|
|
|
|
|
if ($this->didStopAllLinters()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->lintLineLength($path);
|
|
|
|
$this->lintEOFNewline($path);
|
|
|
|
$this->lintTrailingWhitespace($path);
|
2011-02-16 17:26:51 -08:00
|
|
|
|
2014-01-13 18:05:42 -08:00
|
|
|
$this->lintBOFWhitespace($path);
|
|
|
|
$this->lintEOFWhitespace($path);
|
|
|
|
|
2011-02-16 17:26:51 -08:00
|
|
|
if ($this->getEngine()->getCommitHookMode()) {
|
|
|
|
$this->lintNoCommit($path);
|
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function lintNewlines($path) {
|
|
|
|
$pos = strpos($this->getData($path), "\r");
|
|
|
|
if ($pos !== false) {
|
|
|
|
$this->raiseLintAtOffset(
|
|
|
|
$pos,
|
|
|
|
self::LINT_DOS_NEWLINE,
|
|
|
|
'You must use ONLY Unix linebreaks ("\n") in source code.',
|
|
|
|
"\r");
|
2011-11-16 21:53:47 -08:00
|
|
|
if ($this->isMessageEnabled(self::LINT_DOS_NEWLINE)) {
|
|
|
|
$this->stopAllLinters();
|
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function lintTabs($path) {
|
|
|
|
$pos = strpos($this->getData($path), "\t");
|
|
|
|
if ($pos !== false) {
|
|
|
|
$this->raiseLintAtOffset(
|
|
|
|
$pos,
|
|
|
|
self::LINT_TAB_LITERAL,
|
|
|
|
'Configure your editor to use spaces for indentation.',
|
|
|
|
"\t");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function lintLineLength($path) {
|
|
|
|
$lines = explode("\n", $this->getData($path));
|
|
|
|
|
|
|
|
$width = $this->maxLineLength;
|
|
|
|
foreach ($lines as $line_idx => $line) {
|
|
|
|
if (strlen($line) > $width) {
|
|
|
|
$this->raiseLintAtLine(
|
|
|
|
$line_idx + 1,
|
|
|
|
1,
|
|
|
|
self::LINT_LINE_WRAP,
|
|
|
|
'This line is '.number_format(strlen($line)).' characters long, '.
|
|
|
|
'but the convention is '.$width.' characters.',
|
|
|
|
$line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function lintEOFNewline($path) {
|
|
|
|
$data = $this->getData($path);
|
|
|
|
if (!strlen($data) || $data[strlen($data) - 1] != "\n") {
|
|
|
|
$this->raiseLintAtOffset(
|
|
|
|
strlen($data),
|
|
|
|
self::LINT_EOF_NEWLINE,
|
|
|
|
"Files must end in a newline.",
|
|
|
|
'',
|
|
|
|
"\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function lintCharset($path) {
|
|
|
|
$data = $this->getData($path);
|
|
|
|
|
|
|
|
$matches = null;
|
2012-11-05 15:52:19 -08:00
|
|
|
$bad = '[^\x09\x0A\x20-\x7E]';
|
2011-01-09 15:22:25 -08:00
|
|
|
$preg = preg_match_all(
|
2012-11-05 15:52:19 -08:00
|
|
|
"/{$bad}(.*{$bad})?/",
|
2011-01-09 15:22:25 -08:00
|
|
|
$data,
|
|
|
|
$matches,
|
|
|
|
PREG_OFFSET_CAPTURE);
|
|
|
|
|
|
|
|
if (!$preg) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($matches[0] as $match) {
|
|
|
|
list($string, $offset) = $match;
|
|
|
|
$this->raiseLintAtOffset(
|
|
|
|
$offset,
|
|
|
|
self::LINT_BAD_CHARSET,
|
|
|
|
'Source code should contain only ASCII bytes with ordinal decimal '.
|
|
|
|
'values between 32 and 126 inclusive, plus linefeed. Do not use UTF-8 '.
|
|
|
|
'or other multibyte charsets.',
|
|
|
|
$string);
|
|
|
|
}
|
|
|
|
|
2011-11-16 21:53:47 -08:00
|
|
|
if ($this->isMessageEnabled(self::LINT_BAD_CHARSET)) {
|
|
|
|
$this->stopAllLinters();
|
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function lintTrailingWhitespace($path) {
|
|
|
|
$data = $this->getData($path);
|
|
|
|
|
|
|
|
$matches = null;
|
|
|
|
$preg = preg_match_all(
|
|
|
|
'/ +$/m',
|
|
|
|
$data,
|
|
|
|
$matches,
|
|
|
|
PREG_OFFSET_CAPTURE);
|
|
|
|
|
|
|
|
if (!$preg) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($matches[0] as $match) {
|
|
|
|
list($string, $offset) = $match;
|
|
|
|
$this->raiseLintAtOffset(
|
|
|
|
$offset,
|
|
|
|
self::LINT_TRAILING_WHITESPACE,
|
2012-11-01 11:09:11 -07:00
|
|
|
'This line contains trailing whitespace. Consider setting up your '.
|
|
|
|
'editor to automatically remove trailing whitespace, you will save '.
|
|
|
|
'time.',
|
2011-01-09 15:22:25 -08:00
|
|
|
$string,
|
|
|
|
'');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-13 18:05:42 -08:00
|
|
|
protected function lintBOFWhitespace($path) {
|
|
|
|
$data = $this->getData($path);
|
|
|
|
|
|
|
|
$matches = null;
|
|
|
|
$preg = preg_match(
|
|
|
|
'/^\s*\n/',
|
|
|
|
$data,
|
|
|
|
$matches,
|
|
|
|
PREG_OFFSET_CAPTURE);
|
|
|
|
|
|
|
|
if (!$preg) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($string, $offset) = $matches[0];
|
|
|
|
$this->raiseLintAtOffset(
|
|
|
|
$offset,
|
|
|
|
self::LINT_BOF_WHITESPACE,
|
|
|
|
'This file contains leading whitespace at the beginning of the file. ' .
|
|
|
|
'This is unnecessary and should be avoided when possible.',
|
|
|
|
$string,
|
|
|
|
'');
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function lintEOFWhitespace($path) {
|
|
|
|
$data = $this->getData($path);
|
|
|
|
|
|
|
|
$matches = null;
|
|
|
|
$preg = preg_match(
|
|
|
|
'/(?<=\n)\s+$/',
|
|
|
|
$data,
|
|
|
|
$matches,
|
|
|
|
PREG_OFFSET_CAPTURE);
|
|
|
|
|
|
|
|
if (!$preg) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($string, $offset) = $matches[0];
|
|
|
|
$this->raiseLintAtOffset(
|
|
|
|
$offset,
|
|
|
|
self::LINT_EOF_WHITESPACE,
|
|
|
|
'This file contains trailing whitespace at the end of the file. This ' .
|
|
|
|
'is unnecessary and should be avoided when possible.',
|
|
|
|
$string,
|
|
|
|
'');
|
|
|
|
}
|
|
|
|
|
2011-02-16 17:26:51 -08:00
|
|
|
private function lintNoCommit($path) {
|
|
|
|
$data = $this->getData($path);
|
|
|
|
|
|
|
|
$deadly = '@no'.'commit';
|
|
|
|
|
|
|
|
$offset = strpos($data, $deadly);
|
|
|
|
if ($offset !== false) {
|
|
|
|
$this->raiseLintAtOffset(
|
|
|
|
$offset,
|
|
|
|
self::LINT_NO_COMMIT,
|
|
|
|
'This file is explicitly marked as "'.$deadly.'", which blocks '.
|
|
|
|
'commits.',
|
|
|
|
$deadly);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|