1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-02-19 02:08:37 +01:00
phorge-arcanist/src/lint/linter/ArcanistTextLinter.php

291 lines
7.3 KiB
PHP
Raw Normal View History

2011-01-09 15:22:25 -08:00
<?php
/**
* Enforces basic text file rules.
*/
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;
const LINT_BOF_WHITESPACE = 8;
const LINT_EOF_WHITESPACE = 9;
2011-01-09 15:22:25 -08:00
private $maxLineLength = 80;
public function getInfoName() {
return pht('Basic Text Linter');
}
public function getInfoDescription() {
return pht(
'Enforces basic text rules like line length, character encoding, '.
'and trailing whitespace.');
}
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;
}
public function getLinterConfigurationOptions() {
$options = array(
'text.max-line-length' => array(
'type' => 'optional int',
'help' => pht(
'Adjust the maximum line length before a warning is raised. By '.
'default, a warning is raised on lines exceeding 80 characters.'),
),
);
return $options + parent::getLinterConfigurationOptions();
}
2011-01-09 15:22:25 -08:00
public function setMaxLineLength($new_length) {
$this->maxLineLength = $new_length;
return $this;
}
public function setLinterConfigurationValue($key, $value) {
switch ($key) {
case 'text.max-line-length':
$this->setMaxLineLength($value);
return;
}
return parent::setLinterConfigurationValue($key, $value);
}
2011-01-09 15:22:25 -08:00
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,
self::LINT_TRAILING_WHITESPACE => ArcanistLintSeverity::SEVERITY_AUTOFIX,
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(
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_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) {
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);
$this->lintBOFWhitespace($path);
$this->lintEOFWhitespace($path);
2011-01-09 15:22:25 -08:00
}
protected function lintNewlines($path) {
$data = $this->getData($path);
$pos = strpos($this->getData($path), "\r");
2011-01-09 15:22:25 -08:00
if ($pos !== false) {
$this->raiseLintAtOffset(
0,
2011-01-09 15:22:25 -08:00
self::LINT_DOS_NEWLINE,
pht('You must use ONLY Unix linebreaks ("%s") in source code.', '\n'),
$data,
str_replace("\r\n", "\n", $data));
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,
pht('Configure your editor to use spaces for indentation.'),
2011-01-09 15:22:25 -08:00
"\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,
pht(
'This line is %s characters long, but the '.
'convention is %s characters.',
new PhutilNumber(strlen($line)),
$width),
2011-01-09 15:22:25 -08:00
$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,
pht('Files must end in a newline.'),
2011-01-09 15:22:25 -08:00
'',
"\n");
}
}
protected function lintCharset($path) {
$data = $this->getData($path);
$matches = null;
$bad = '[^\x09\x0A\x20-\x7E]';
2011-01-09 15:22:25 -08:00
$preg = preg_match_all(
"/{$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,
pht(
'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.'),
2011-01-09 15:22:25 -08:00
$string);
}
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,
pht(
'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,
'');
}
}
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,
pht(
'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,
pht(
'This file contains trailing whitespace at the end of the file. '.
'This is unnecessary and should be avoided when possible.'),
$string,
'');
}
2011-01-09 15:22:25 -08:00
}