Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F969412
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/lint/linter/ArcanistJSHintLinter.php b/src/lint/linter/ArcanistJSHintLinter.php
index ac6ecc31..9a066dae 100644
--- a/src/lint/linter/ArcanistJSHintLinter.php
+++ b/src/lint/linter/ArcanistJSHintLinter.php
@@ -1,163 +1,171 @@
<?php
/**
* Uses "JSHint" to detect errors and potential problems in JavaScript code.
* To use this linter, you must install jshint through NPM (Node Package
* Manager). You can configure different JSHint options on a per-file basis.
*
* If you have NodeJS installed you should be able to install jshint with
* ##npm install jshint -g## (don't forget the -g flag or NPM will install
* the package locally). If your system is unusual, you can manually specify
* the location of jshint and its dependencies by configuring these keys in
* your .arcconfig:
*
* lint.jshint.prefix
* lint.jshint.bin
*
* If you want to configure custom options for your project, create a JSON
* file with these options and add the path to the file to your .arcconfig
* by configuring this key:
*
* lint.jshint.config
*
* Example JSON file (config.json):
*
* {
* "predef": [ // Custom globals
* "myGlobalVariable",
* "anotherGlobalVariable"
* ],
*
* "es5": true, // Allow ES5 syntax
* "strict": true // Require strict mode
* }
*
* For more options see http://www.jshint.com/options/.
*
* @group linter
*/
final class ArcanistJSHintLinter extends ArcanistLinter {
const JSHINT_ERROR = 1;
public function getLinterName() {
return 'JSHint';
}
public function getLintSeverityMap() {
return array(
self::JSHINT_ERROR => ArcanistLintSeverity::SEVERITY_ERROR
);
}
public function getLintNameMap() {
return array(
self::JSHINT_ERROR => "JSHint Error"
);
}
public function getJSHintOptions() {
$working_copy = $this->getEngine()->getWorkingCopy();
$options = '--reporter '.dirname(realpath(__FILE__)).'/reporter.js';
$config = $working_copy->getConfig('lint.jshint.config');
if ($config !== null) {
$config = Filesystem::resolvePath(
$config,
$working_copy->getProjectRoot());
if (!Filesystem::pathExists($config)) {
throw new ArcanistUsageException(
"Unable to find custom options file defined by ".
"'lint.jshint.config'. Make sure that the path is correct.");
}
$options .= ' --config '.$config;
}
return $options;
}
private function getJSHintPath() {
$working_copy = $this->getEngine()->getWorkingCopy();
$prefix = $working_copy->getConfig('lint.jshint.prefix');
$bin = $working_copy->getConfig('lint.jshint.bin');
if ($bin === null) {
$bin = "jshint";
}
if ($prefix !== null) {
$bin = $prefix."/".$bin;
if (!Filesystem::pathExists($bin)) {
throw new ArcanistUsageException(
"Unable to find JSHint binary in a specified directory. Make sure ".
"that 'lint.jshint.prefix' and 'lint.jshint.bin' keys are set ".
"correctly. If you'd rather use a copy of JSHint installed ".
"globally, you can just remove these keys from your .arcconfig");
}
return $bin;
}
// Look for globally installed JSHint
list($err) = (phutil_is_windows()
? exec_manual('where %s', $bin)
: exec_manual('which %s', $bin));
if ($err) {
throw new ArcanistUsageException(
"JSHint does not appear to be installed on this system. Install it ".
"(e.g., with 'npm install jshint -g') or configure ".
"'lint.jshint.prefix' in your .arcconfig to point to the directory ".
"where it resides.");
}
return $bin;
}
public function willLintPaths(array $paths) {
+ if (!$this->isCodeEnabled(self::JSHINT_ERROR)) {
+ return;
+ }
+
$jshint_bin = $this->getJSHintPath();
$jshint_options = $this->getJSHintOptions();
$futures = array();
foreach ($paths as $path) {
$filepath = $this->getEngine()->getFilePathOnDisk($path);
$futures[$path] = new ExecFuture(
"%s %s %C",
$jshint_bin,
$filepath,
$jshint_options);
}
foreach (Futures($futures)->limit(8) as $path => $future) {
$this->results[$path] = $future->resolve();
}
}
public function lintPath($path) {
+ if (!$this->isCodeEnabled(self::JSHINT_ERROR)) {
+ return;
+ }
+
list($rc, $stdout, $stderr) = $this->results[$path];
if ($rc === 0) {
return;
}
$errors = json_decode($stdout);
if (!is_array($errors)) {
// Something went wrong and we can't decode the output. Exit abnormally.
throw new ArcanistUsageException(
"JSHint returned unparseable output.\n".
"stdout:\n\n{$stdout}".
"stderr:\n\n{$stderr}");
}
foreach ($errors as $err) {
$this->raiseLintAtLine(
$err->line,
$err->col,
self::JSHINT_ERROR,
$err->reason);
}
}
}
diff --git a/src/lint/linter/ArcanistPEP8Linter.php b/src/lint/linter/ArcanistPEP8Linter.php
index 735cfcfd..3e4a51a0 100644
--- a/src/lint/linter/ArcanistPEP8Linter.php
+++ b/src/lint/linter/ArcanistPEP8Linter.php
@@ -1,119 +1,125 @@
<?php
/**
* Uses "pep8.py" to enforce PEP8 rules for Python.
*
* @group linter
*/
final class ArcanistPEP8Linter extends ArcanistLinter {
public function willLintPaths(array $paths) {
return;
}
public function getLinterName() {
return 'PEP8';
}
public function getLintSeverityMap() {
return array();
}
public function getLintNameMap() {
return array();
}
public function getCacheVersion() {
list($stdout) = execx('%C --version', $this->getPEP8Path());
return $stdout.$this->getPEP8Options();
}
public function getPEP8Options() {
$working_copy = $this->getEngine()->getWorkingCopy();
$options = $working_copy->getConfig('lint.pep8.options');
if ($options === null) {
$options = $this->getConfig('options');
}
return $options;
}
public function getPEP8Path() {
$working_copy = $this->getEngine()->getWorkingCopy();
$prefix = $working_copy->getConfig('lint.pep8.prefix');
$bin = $working_copy->getConfig('lint.pep8.bin');
if ($bin === null && $prefix === null) {
$bin = csprintf('/usr/bin/env python %s',
phutil_get_library_root('arcanist').
'/../externals/pep8/pep8.py');
} else {
if ($bin === null) {
$bin = 'pep8';
}
if ($prefix !== null) {
if (!Filesystem::pathExists($prefix.'/'.$bin)) {
throw new ArcanistUsageException(
"Unable to find PEP8 binary in a specified directory. Make sure ".
"that 'lint.pep8.prefix' and 'lint.pep8.bin' keys are set ".
"correctly. If you'd rather use a copy of PEP8 installed ".
"globally, you can just remove these keys from your .arcconfig.");
}
$bin = csprintf("%s/%s", $prefix, $bin);
return $bin;
}
// Look for globally installed PEP8
list($err) = exec_manual('which %s', $bin);
if ($err) {
throw new ArcanistUsageException(
"PEP8 does not appear to be installed on this system. Install it ".
"(e.g., with 'easy_install pep8') or configure ".
"'lint.pep8.prefix' in your .arcconfig to point to the directory ".
"where it resides.");
}
}
return $bin;
}
public function lintPath($path) {
+ $severity = ArcanistLintSeverity::SEVERITY_WARNING;
+
+ if (!$this->getEngine()->isSeverityEnabled($severity)) {
+ return;
+ }
+
$pep8_bin = $this->getPEP8Path();
$options = $this->getPEP8Options();
list($rc, $stdout) = exec_manual(
"%C %C %s",
$pep8_bin,
$options,
$this->getEngine()->getFilePathOnDisk($path));
$lines = explode("\n", $stdout);
$messages = array();
foreach ($lines as $line) {
$matches = null;
if (!preg_match('/^(.*?):(\d+):(\d+): (\S+) (.*)$/', $line, $matches)) {
continue;
}
foreach ($matches as $key => $match) {
$matches[$key] = trim($match);
}
if (!$this->isMessageEnabled($matches[4])) {
continue;
}
$message = new ArcanistLintMessage();
$message->setPath($path);
$message->setLine($matches[2]);
$message->setChar($matches[3]);
$message->setCode($matches[4]);
$message->setName('PEP8 '.$matches[4]);
$message->setDescription($matches[5]);
- $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING);
+ $message->setSeverity($severity);
$this->addLintMessage($message);
}
}
}
diff --git a/src/lint/linter/ArcanistPhutilXHPASTLinter.php b/src/lint/linter/ArcanistPhutilXHPASTLinter.php
index bd98b3da..7d03bbbf 100644
--- a/src/lint/linter/ArcanistPhutilXHPASTLinter.php
+++ b/src/lint/linter/ArcanistPhutilXHPASTLinter.php
@@ -1,202 +1,213 @@
<?php
/**
* @group linter
*/
final class ArcanistPhutilXHPASTLinter extends ArcanistBaseXHPASTLinter {
const LINT_ARRAY_COMBINE = 2;
const LINT_DEPRECATED_FUNCTION = 3;
const LINT_UNSAFE_DYNAMIC_STRING = 4;
private $xhpastLinter;
private $deprecatedFunctions = array();
private $dynamicStringFunctions = array();
private $dynamicStringClasses = array();
public function setXHPASTLinter(ArcanistXHPASTLinter $linter) {
$this->xhpastLinter = $linter;
return $this;
}
public function setDeprecatedFunctions($map) {
$this->deprecatedFunctions = $map;
return $this;
}
public function setDynamicStringFunctions($map) {
$this->dynamicStringFunctions = $map;
return $this;
}
public function setDynamicStringClasses($map) {
$this->dynamicStringClasses = $map;
return $this;
}
public function setEngine(ArcanistLintEngine $engine) {
if (!$this->xhpastLinter) {
throw new Exception(
'Call setXHPASTLinter() before using ArcanistPhutilXHPASTLinter.');
}
$this->xhpastLinter->setEngine($engine);
return parent::setEngine($engine);
}
public function getLintNameMap() {
return array(
self::LINT_ARRAY_COMBINE => 'array_combine() Unreliable',
self::LINT_DEPRECATED_FUNCTION => 'Use of Deprecated Function',
self::LINT_UNSAFE_DYNAMIC_STRING => 'Unsafe Usage of Dynamic String',
);
}
public function getLintSeverityMap() {
$warning = ArcanistLintSeverity::SEVERITY_WARNING;
return array(
self::LINT_ARRAY_COMBINE => $warning,
self::LINT_DEPRECATED_FUNCTION => $warning,
self::LINT_UNSAFE_DYNAMIC_STRING => $warning,
);
}
public function getLinterName() {
return 'PHLXHP';
}
public function getCacheVersion() {
return 2;
}
public function willLintPaths(array $paths) {
$this->xhpastLinter->willLintPaths($paths);
}
public function lintPath($path) {
$tree = $this->xhpastLinter->getXHPASTTreeForPath($path);
if (!$tree) {
return;
}
$root = $tree->getRootNode();
- $this->lintArrayCombine($root);
- $this->lintUnsafeDynamicString($root);
- $this->lintDeprecatedFunctions($root);
+ $method_codes = array(
+ 'lintArrayCombine' => self::LINT_ARRAY_COMBINE,
+ 'lintUnsafeDynamicString' => self::LINT_UNSAFE_DYNAMIC_STRING,
+ 'lintDeprecatedFunctions' => self::LINT_DEPRECATED_FUNCTION,
+ );
+
+ foreach ($method_codes as $method => $codes) {
+ foreach ((array)$codes as $code) {
+ if ($this->isCodeEnabled($code)) {
+ call_user_func(array($this, $method), $root);
+ break;
+ }
+ }
+ }
}
private function lintUnsafeDynamicString($root) {
$safe = $this->dynamicStringFunctions + array(
'pht' => 0,
'hsprintf' => 0,
'csprintf' => 0,
'vcsprintf' => 0,
'execx' => 0,
'exec_manual' => 0,
'phutil_passthru' => 0,
'qsprintf' => 1,
'vqsprintf' => 1,
'queryfx' => 1,
'vqueryfx' => 1,
'queryfx_all' => 1,
'vqueryfx_all' => 1,
'queryfx_one' => 1,
);
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
$this->lintUnsafeDynamicStringCall($calls, $safe);
$safe = $this->dynamicStringClasses + array(
'ExecFuture' => 0,
);
$news = $root->selectDescendantsOfType('n_NEW');
$this->lintUnsafeDynamicStringCall($news, $safe);
}
private function lintUnsafeDynamicStringCall(
AASTNodeList $calls,
array $safe) {
$safe = array_combine(
array_map('strtolower', array_keys($safe)),
$safe);
foreach ($calls as $call) {
$name = $call->getChildByIndex(0)->getConcreteString();
$param = idx($safe, strtolower($name));
if ($param === null) {
continue;
}
$parameters = $call->getChildByIndex(1);
if (count($parameters->getChildren()) <= $param) {
continue;
}
$identifier = $parameters->getChildByIndex($param);
if (!$identifier->isConstantString()) {
$this->raiseLintAtNode(
$call,
self::LINT_UNSAFE_DYNAMIC_STRING,
"Parameter ".($param + 1)." of {$name}() should be a scalar string, ".
"otherwise it's not safe.");
}
}
}
private function lintArrayCombine($root) {
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
foreach ($function_calls as $call) {
$name = $call->getChildByIndex(0)->getConcreteString();
if (strcasecmp($name, 'array_combine') == 0) {
$parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
if (count($parameter_list->getChildren()) !== 2) {
// Wrong number of parameters, but raise that elsewhere if we want.
continue;
}
$first = $parameter_list->getChildByIndex(0);
$second = $parameter_list->getChildByIndex(1);
if ($first->getConcreteString() == $second->getConcreteString()) {
$this->raiseLintAtNode(
$call,
self::LINT_ARRAY_COMBINE,
'Prior to PHP 5.4, array_combine() fails when given empty '.
'arrays. Prefer to write array_combine(x, x) as array_fuse(x).');
}
}
}
}
private function lintDeprecatedFunctions($root) {
$map = $this->deprecatedFunctions + array(
'phutil_render_tag' =>
'The phutil_render_tag() function is deprecated and unsafe. '.
'Use phutil_tag() instead.',
);
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
foreach ($function_calls as $call) {
$name = $call->getChildByIndex(0)->getConcreteString();
$name = strtolower($name);
if (empty($map[$name])) {
continue;
}
$this->raiseLintAtNode(
$call,
self::LINT_DEPRECATED_FUNCTION,
$map[$name]);
}
}
}
diff --git a/src/lint/linter/ArcanistSpellingLinter.php b/src/lint/linter/ArcanistSpellingLinter.php
index 032e51e1..928bad43 100644
--- a/src/lint/linter/ArcanistSpellingLinter.php
+++ b/src/lint/linter/ArcanistSpellingLinter.php
@@ -1,157 +1,164 @@
<?php
/**
* Enforces basic spelling. Spelling inside code is actually pretty hard to
* get right without false positives. I take a conservative approach and
* just use a blacklisted set of words that are commonly spelled
* incorrectly.
*
* @group linter
*/
final class ArcanistSpellingLinter extends ArcanistLinter {
const LINT_SPELLING_PICKY = 0;
const LINT_SPELLING_IMPORTANT = 1;
private $partialWordRules;
private $wholeWordRules;
private $severity;
public function __construct($severity = self::LINT_SPELLING_PICKY) {
$this->severity = $severity;
$this->wholeWordRules = ArcanistSpellingDefaultData::getFullWordRules();
$this->partialWordRules =
ArcanistSpellingDefaultData::getPartialWordRules();
}
public function willLintPaths(array $paths) {
return;
}
public function getLinterName() {
return 'SPELL';
}
public function removeLintRule($word) {
foreach ($this->partialWordRules as $severity=>&$wordlist) {
unset($wordlist[$word]);
}
foreach ($this->wholeWordRules as $severity=>&$wordlist) {
unset($wordlist[$word]);
}
}
public function addPartialWordRule(
$incorrect_word,
$correct_word,
$severity=self::LINT_SPELLING_IMPORTANT) {
$this->partialWordRules[$severity][$incorrect_word] = $correct_word;
}
public function addWholeWordRule(
$incorrect_word,
$correct_word,
$severity=self::LINT_SPELLING_IMPORTANT) {
$this->wholeWordRules[$severity][$incorrect_word] = $correct_word;
}
public function getLintSeverityMap() {
return array(
self::LINT_SPELLING_PICKY => ArcanistLintSeverity::SEVERITY_WARNING,
self::LINT_SPELLING_IMPORTANT => ArcanistLintSeverity::SEVERITY_ERROR,
);
}
public function getLintNameMap() {
return array(
self::LINT_SPELLING_PICKY => 'Possible spelling mistake',
self::LINT_SPELLING_IMPORTANT => 'Possible spelling mistake',
);
}
public function lintPath($path) {
foreach ($this->partialWordRules as $severity => $wordlist) {
if ($severity >= $this->severity) {
+ if (!$this->isCodeEnabled($severity)) {
+ continue;
+ }
foreach ($wordlist as $misspell => $correct) {
$this->checkPartialWord($path, $misspell, $correct, $severity);
}
}
}
+
foreach ($this->wholeWordRules as $severity => $wordlist) {
if ($severity >= $this->severity) {
+ if (!$this->isCodeEnabled($severity)) {
+ continue;
+ }
foreach ($wordlist as $misspell => $correct) {
$this->checkWholeWord($path, $misspell, $correct, $severity);
}
}
}
}
protected function checkPartialWord($path, $word, $correct_word, $severity) {
$text = $this->getData($path);
$pos = 0;
while ($pos < strlen($text)) {
$next = stripos($text, $word, $pos);
if ($next === false) {
return;
}
$original = substr($text, $next, strlen($word));
$replacement = self::fixLetterCase($correct_word, $original);
$this->raiseLintAtOffset(
$next,
$severity,
sprintf(
"Possible spelling error. You wrote '%s', but did you mean '%s'?",
$word,
$correct_word
),
$original,
$replacement
);
$pos = $next + 1;
}
}
protected function checkWholeWord($path, $word, $correct_word, $severity) {
$text = $this->getData($path);
$matches = array();
$num_matches = preg_match_all(
'#\b' . preg_quote($word, '#') . '\b#i',
$text,
$matches,
PREG_OFFSET_CAPTURE
);
if (!$num_matches) {
return;
}
foreach ($matches[0] as $match) {
$original = $match[0];
$replacement = self::fixLetterCase($correct_word, $original);
$this->raiseLintAtOffset(
$match[1],
$severity,
sprintf(
"Possible spelling error. You wrote '%s', but did you mean '%s'?",
$word,
$correct_word
),
$original,
$replacement
);
}
}
public static function fixLetterCase($string, $case) {
if ($case == strtolower($case)) {
return strtolower($string);
}
if ($case == strtoupper($case)) {
return strtoupper($string);
}
if ($case == ucwords(strtolower($case))) {
return ucwords(strtolower($string));
}
return null;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 01:26 (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
547770
Default Alt Text
(19 KB)
Attached To
Mode
R118 Arcanist - fork
Attached
Detach File
Event Timeline
Log In to Comment