1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-29 02:02:40 +01:00

Fold ArcanistPhutilXHPASTLinter into ArcanistXHPASTLinter

Summary: I've been thinking about this for a while... why not just fold `ArcanistPhutilXHPASTLinter` into `ArcanistXHPASTLinter`?

Test Plan: `arc unit`

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: Korvin

Differential Revision: https://secure.phabricator.com/D13867
This commit is contained in:
Joshua Spence 2015-11-13 07:08:40 +11:00 committed by Joshua Spence
parent dd0deb2407
commit 9dd6eafb52
39 changed files with 437 additions and 407 deletions

View file

@ -31,10 +31,6 @@
"type": "phutil-library",
"include": "(\\.php$)"
},
"phutil-xhpast": {
"type": "phutil-xhpast",
"include": "(\\.php$)"
},
"spelling": {
"type": "spelling",
"exclude": "(^resources/spelling/.*\\.json$)"

View file

@ -13,6 +13,7 @@ phutil_register_library_map(array(
'ArcanistAliasWorkflow' => 'workflow/ArcanistAliasWorkflow.php',
'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php',
'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php',
'ArcanistArrayCombineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php',
'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayIndexSpacingXHPASTLinterRule.php',
'ArcanistArraySeparatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArraySeparatorXHPASTLinterRule.php',
'ArcanistArrayValueXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayValueXHPASTLinterRule.php',
@ -40,6 +41,7 @@ phutil_register_library_map(array(
'ArcanistCheckstyleXMLLintRenderer' => 'lint/renderer/ArcanistCheckstyleXMLLintRenderer.php',
'ArcanistChmodLinter' => 'lint/linter/ArcanistChmodLinter.php',
'ArcanistChmodLinterTestCase' => 'lint/linter/__tests__/ArcanistChmodLinterTestCase.php',
'ArcanistClassExtendsObjectXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php',
'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassFilenameMismatchXHPASTLinterRule.php',
'ArcanistClassNameLiteralXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassNameLiteralXHPASTLinterRule.php',
'ArcanistCloseRevisionWorkflow' => 'workflow/ArcanistCloseRevisionWorkflow.php',
@ -71,6 +73,7 @@ phutil_register_library_map(array(
'ArcanistCpplintLinterTestCase' => 'lint/linter/__tests__/ArcanistCpplintLinterTestCase.php',
'ArcanistDeclarationParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDeclarationParenthesesXHPASTLinterRule.php',
'ArcanistDefaultParametersXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php',
'ArcanistDeprecationXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php',
'ArcanistDiffChange' => 'parser/diff/ArcanistDiffChange.php',
'ArcanistDiffChangeType' => 'parser/diff/ArcanistDiffChangeType.php',
'ArcanistDiffHunk' => 'parser/diff/ArcanistDiffHunk.php',
@ -203,9 +206,7 @@ phutil_register_library_map(array(
'ArcanistPhpunitTestResultParser' => 'unit/parser/ArcanistPhpunitTestResultParser.php',
'ArcanistPhrequentWorkflow' => 'workflow/ArcanistPhrequentWorkflow.php',
'ArcanistPhutilLibraryLinter' => 'lint/linter/ArcanistPhutilLibraryLinter.php',
'ArcanistPhutilXHPASTLinter' => 'lint/linter/ArcanistPhutilXHPASTLinter.php',
'ArcanistPhutilXHPASTLinterStandard' => 'lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php',
'ArcanistPhutilXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php',
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php',
'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPregQuoteMisuseXHPASTLinterRule.php',
'ArcanistPuppetLintLinter' => 'lint/linter/ArcanistPuppetLintLinter.php',
@ -214,6 +215,7 @@ phutil_register_library_map(array(
'ArcanistPyFlakesLinterTestCase' => 'lint/linter/__tests__/ArcanistPyFlakesLinterTestCase.php',
'ArcanistPyLintLinter' => 'lint/linter/ArcanistPyLintLinter.php',
'ArcanistPyLintLinterTestCase' => 'lint/linter/__tests__/ArcanistPyLintLinterTestCase.php',
'ArcanistRaggedClassTreeEdgeXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php',
'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php',
'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php',
'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php',
@ -265,6 +267,7 @@ phutil_register_library_map(array(
'ArcanistUnitWorkflow' => 'workflow/ArcanistUnitWorkflow.php',
'ArcanistUnnecessaryFinalModifierXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnnecessaryFinalModifierXHPASTLinterRule.php',
'ArcanistUnnecessarySemicolonXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnnecessarySemicolonXHPASTLinterRule.php',
'ArcanistUnsafeDynamicStringXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnsafeDynamicStringXHPASTLinterRule.php',
'ArcanistUpgradeWorkflow' => 'workflow/ArcanistUpgradeWorkflow.php',
'ArcanistUploadWorkflow' => 'workflow/ArcanistUploadWorkflow.php',
'ArcanistUsageException' => 'exception/ArcanistUsageException.php',
@ -305,6 +308,7 @@ phutil_register_library_map(array(
'ArcanistAliasWorkflow' => 'ArcanistWorkflow',
'ArcanistAmendWorkflow' => 'ArcanistWorkflow',
'ArcanistAnoidWorkflow' => 'ArcanistWorkflow',
'ArcanistArrayCombineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistArraySeparatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistArrayValueXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -332,6 +336,7 @@ phutil_register_library_map(array(
'ArcanistCheckstyleXMLLintRenderer' => 'ArcanistLintRenderer',
'ArcanistChmodLinter' => 'ArcanistLinter',
'ArcanistChmodLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistClassExtendsObjectXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistClassNameLiteralXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistCloseRevisionWorkflow' => 'ArcanistWorkflow',
@ -363,6 +368,7 @@ phutil_register_library_map(array(
'ArcanistCpplintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistDeclarationParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDefaultParametersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDeprecationXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDiffChange' => 'Phobject',
'ArcanistDiffChangeType' => 'Phobject',
'ArcanistDiffHunk' => 'Phobject',
@ -495,9 +501,7 @@ phutil_register_library_map(array(
'ArcanistPhpunitTestResultParser' => 'ArcanistTestResultParser',
'ArcanistPhrequentWorkflow' => 'ArcanistWorkflow',
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
'ArcanistPhutilXHPASTLinter' => 'ArcanistBaseXHPASTLinter',
'ArcanistPhutilXHPASTLinterStandard' => 'ArcanistLinterStandard',
'ArcanistPhutilXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPuppetLintLinter' => 'ArcanistExternalLinter',
@ -506,6 +510,7 @@ phutil_register_library_map(array(
'ArcanistPyFlakesLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistPyLintLinter' => 'ArcanistExternalLinter',
'ArcanistPyLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistRaggedClassTreeEdgeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistRepositoryAPI' => 'Phobject',
'ArcanistRepositoryAPIMiscTestCase' => 'PhutilTestCase',
'ArcanistRepositoryAPIStateTestCase' => 'PhutilTestCase',
@ -557,6 +562,7 @@ phutil_register_library_map(array(
'ArcanistUnitWorkflow' => 'ArcanistWorkflow',
'ArcanistUnnecessaryFinalModifierXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistUnnecessarySemicolonXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistUnsafeDynamicStringXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistUpgradeWorkflow' => 'ArcanistWorkflow',
'ArcanistUploadWorkflow' => 'ArcanistWorkflow',
'ArcanistUsageException' => 'Exception',

View file

@ -1,336 +0,0 @@
<?php
final class ArcanistPhutilXHPASTLinter extends ArcanistBaseXHPASTLinter {
const LINT_ARRAY_COMBINE = 2;
const LINT_DEPRECATED_FUNCTION = 3;
const LINT_UNSAFE_DYNAMIC_STRING = 4;
const LINT_RAGGED_CLASSTREE_EDGE = 5;
const LINT_EXTENDS_PHOBJECT = 6;
private $deprecatedFunctions = array();
private $dynamicStringFunctions = array();
private $dynamicStringClasses = array();
public function getInfoName() {
return 'XHPAST/libphutil Lint';
}
public function getInfoDescription() {
return pht(
'Use XHPAST to run libphutil-specific rules on a PHP library. This '.
'linter is intended for use in Phabricator libraries and extensions.');
}
public function getLintNameMap() {
return array(
self::LINT_ARRAY_COMBINE =>
pht('%s Unreliable', 'array_combine()'),
self::LINT_DEPRECATED_FUNCTION =>
pht('Use of Deprecated Function'),
self::LINT_UNSAFE_DYNAMIC_STRING =>
pht('Unsafe Usage of Dynamic String'),
self::LINT_RAGGED_CLASSTREE_EDGE =>
pht('Class Not %s Or %s', 'abstract', 'final'),
self::LINT_EXTENDS_PHOBJECT =>
pht('Class Not Extending %s', 'Phobject'),
);
}
public function getLinterName() {
return 'PHLXHP';
}
public function getLinterConfigurationName() {
return 'phutil-xhpast';
}
public function getLintSeverityMap() {
$advice = ArcanistLintSeverity::SEVERITY_ADVICE;
$warning = ArcanistLintSeverity::SEVERITY_WARNING;
return array(
self::LINT_ARRAY_COMBINE => $warning,
self::LINT_DEPRECATED_FUNCTION => $warning,
self::LINT_UNSAFE_DYNAMIC_STRING => $warning,
self::LINT_RAGGED_CLASSTREE_EDGE => $warning,
self::LINT_EXTENDS_PHOBJECT => $advice,
);
}
public function getLinterConfigurationOptions() {
$options = array(
'phutil-xhpast.deprecated.functions' => array(
'type' => 'optional map<string, string>',
'help' => pht(
'Functions which should should be considered deprecated.'),
),
'phutil-xhpast.dynamic-string.functions' => array(
'type' => 'optional map<string, string>',
'help' => pht(
'Functions which should should not be used because they represent '.
'the unsafe usage of dynamic strings.'),
),
'phutil-xhpast.dynamic-string.classes' => array(
'type' => 'optional map<string, string>',
'help' => pht(
'Classes which should should not be used because they represent the '.
'unsafe usage of dynamic strings.'),
),
);
return $options + parent::getLinterConfigurationOptions();
}
public function setLinterConfigurationValue($key, $value) {
switch ($key) {
case 'phutil-xhpast.deprecated.functions':
$this->setDeprecatedFunctions($value);
return;
case 'phutil-xhpast.dynamic-string.functions':
$this->setDynamicStringFunctions($value);
return;
case 'phutil-xhpast.dynamic-string.classes':
$this->setDynamicStringClasses($value);
return;
default:
parent::setLinterConfigurationValue($key, $value);
return;
}
}
public function getVersion() {
// The version number should be incremented whenever a new rule is added.
return '3';
}
protected function resolveFuture($path, Future $future) {
$tree = $this->getXHPASTLinter()->getXHPASTTreeForPath($path);
if (!$tree) {
return;
}
$root = $tree->getRootNode();
$method_codes = array(
'lintArrayCombine' => self::LINT_ARRAY_COMBINE,
'lintUnsafeDynamicString' => self::LINT_UNSAFE_DYNAMIC_STRING,
'lintDeprecatedFunctions' => self::LINT_DEPRECATED_FUNCTION,
'lintRaggedClasstreeEdges' => self::LINT_RAGGED_CLASSTREE_EDGE,
'lintClassExtendsPhobject' => self::LINT_EXTENDS_PHOBJECT,
);
foreach ($method_codes as $method => $codes) {
foreach ((array)$codes as $code) {
if ($this->isCodeEnabled($code)) {
call_user_func(array($this, $method), $root);
break;
}
}
}
}
/* -( Setters )------------------------------------------------------------ */
public function setDeprecatedFunctions(array $map) {
$this->deprecatedFunctions = $map;
return $this;
}
public function setDynamicStringClasses(array $map) {
$this->dynamicStringClasses = $map;
return $this;
}
public function setDynamicStringFunctions(array $map) {
$this->dynamicStringFunctions = $map;
return $this;
}
/* -( Linter Rules )------------------------------------------------------- */
private function lintUnsafeDynamicString(XHPASTNode $root) {
$safe = $this->dynamicStringFunctions + array(
'pht' => 0,
'hsprintf' => 0,
'jsprintf' => 0,
'hgsprintf' => 0,
'csprintf' => 0,
'vcsprintf' => 0,
'execx' => 0,
'exec_manual' => 0,
'phutil_passthru' => 0,
'qsprintf' => 1,
'vqsprintf' => 1,
'queryfx' => 1,
'queryfx_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,
pht(
"Parameter %d of %s should be a scalar string, ".
"otherwise it's not safe.",
$param + 1,
$name.'()'));
}
}
}
private function lintArrayCombine(XHPASTNode $root) {
$function_calls = $this->getFunctionCalls($root, array('array_combine'));
foreach ($function_calls as $call) {
$name = $call->getChildByIndex(0)->getConcreteString();
$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,
pht(
'Prior to PHP 5.4, `%s` fails when given empty arrays. '.
'Prefer to write `%s` as `%s`.',
'array_combine()',
'array_combine(x, x)',
'array_fuse(x)'));
}
}
}
private function lintDeprecatedFunctions(XHPASTNode $root) {
$map = $this->deprecatedFunctions;
$function_calls = $this->getFunctionCalls($root, array_keys($map));
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]);
}
}
private function lintRaggedClasstreeEdges(XHPASTNode $root) {
$parser = new PhutilDocblockParser();
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
foreach ($classes as $class) {
$is_final = false;
$is_abstract = false;
$is_concrete_extensible = false;
$attributes = $class->getChildOfType(0, 'n_CLASS_ATTRIBUTES');
foreach ($attributes->getChildren() as $child) {
if ($child->getConcreteString() == 'final') {
$is_final = true;
}
if ($child->getConcreteString() == 'abstract') {
$is_abstract = true;
}
}
$docblock = $class->getDocblockToken();
if ($docblock) {
list($text, $specials) = $parser->parse($docblock->getValue());
$is_concrete_extensible = idx($specials, 'concrete-extensible');
}
if (!$is_final && !$is_abstract && !$is_concrete_extensible) {
$this->raiseLintAtNode(
$class->getChildOfType(1, 'n_CLASS_NAME'),
self::LINT_RAGGED_CLASSTREE_EDGE,
pht(
"This class is neither '%s' nor '%s', and does not have ".
"a docblock marking it '%s'.",
'final',
'abstract',
'@concrete-extensible'));
}
}
}
private function lintClassExtendsPhobject(XHPASTNode $root) {
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
foreach ($classes as $class) {
// TODO: This doesn't quite work for namespaced classes (see T8534).
$name = $class->getChildOfType(1, 'n_CLASS_NAME');
$extends = $class->getChildByIndex(2);
if ($name->getConcreteString() == 'Phobject') {
continue;
}
if ($extends->getTypeName() == 'n_EMPTY') {
$this->raiseLintAtNode(
$class,
self::LINT_EXTENDS_PHOBJECT,
pht(
'Classes should extend from %s or from some other class. '.
'All classes (except for %s itself) should have a base class.',
'Phobject',
'Phobject'));
}
}
}
}

View file

@ -1,14 +0,0 @@
<?php
final class ArcanistPhutilXHPASTLinterTestCase extends ArcanistLinterTestCase {
public function testLinter() {
$linter = new ArcanistPhutilXHPASTLinter();
$linter->setDeprecatedFunctions(array(
'deprecated_function' => pht('This function is most likely deprecated.'),
));
$this->executeTestsInDirectory(dirname(__FILE__).'/phlxhp/', $linter);
}
}

View file

@ -1,6 +0,0 @@
<?php
deprecated_function();
modern_function();
~~~~~~~~~~
warning:3:1

View file

@ -1,27 +0,0 @@
<?php
pht('a');
pht("a");
pht('a'.'b');
pht(f());
pht();
pht($a);
pht('a'.$a);
pht('$a');
pht("$a");
pht('%s', $a);
pht(<<<EOT
a
EOT
);
pht(<<<EOT
$a
EOT
);
~~~~~~~~~~
warning:5:1
warning:7:1
warning:8:1
warning:10:1
warning:18:1

View file

@ -1,13 +0,0 @@
<?php
csprintf('x');
csprintf($x);
qsprintf();
qsprintf('x');
qsprintf('x', 'y');
qsprintf('x', $y);
~~~~~~~~~~
warning:4:1
warning:9:1

View file

@ -3,4 +3,4 @@
array_combine($x, $x);
array_combine($x, $y);
~~~~~~~~~~
warning:3:1
disabled:3:1

View file

@ -25,7 +25,9 @@ $foo(&$myvar);
array_walk(array(), function () use (&$x) {});
MyClass::myfunc(array(&$x, &$y));
~~~~~~~~~~
disabled:3:1
error:3:7 XHP19
disabled:3:7
error:10:8
error:13:15
error:16:17

View file

@ -12,6 +12,8 @@ class MyClass {
}
}
~~~~~~~~~~
disabled:3:1
disabled:3:7
error:3:7
advice:5:12
advice:9:10

View file

@ -9,5 +9,7 @@ class Foo {
~~~~~~~~~~
warning:3:8
warning:4:7
disabled:6:1
error:6:7
disabled:6:7
warning:7:9

View file

@ -28,6 +28,7 @@ f(function ($x)use($z) {});
warning:4:14
warning:5:11
warning:8:15
disabled:10:1
error:10:13
warning:13:23
warning:16:31

View file

@ -9,5 +9,7 @@ class MyClass {
}
~~~~~~~~~~
warning:4:13
disabled:7:1
error:7:7
disabled:7:7
warning:8:27

View file

@ -0,0 +1,15 @@
<?php
deprecated_function();
modern_function();
~~~~~~~~~~
warning:3:1
~~~~~~~~~~
~~~~~~~~~~
{
"config": {
"xhpast.deprecated.functions": {
"deprecated_function": "This function is deprecated."
}
}
}

View file

@ -3,6 +3,7 @@
final class Foo {};
$x = null;;
~~~~~~~~~~
disabled:3:1
error:3:13 XHP19
advice:3:19
advice:4:11

View file

@ -4,4 +4,4 @@ abstract class A extends Phobject {}
final class B extends A {}
final class C {}
~~~~~~~~~~
advice:5:1
disabled:5:1

View file

@ -11,6 +11,7 @@ final class Foo {
private $z;
}
~~~~~~~~~~
disabled:3:1
error:3:13 XHP19
advice:5:3
advice:6:3

View file

@ -11,6 +11,8 @@ class SomeClass {
abstract final public function baz() {}
}
~~~~~~~~~~
disabled:3:1
disabled:3:7
error:3:7 XHP19
error:5:10
error:6:10

View file

@ -9,6 +9,8 @@ class Foo {
static final public function foobar() {}
}
~~~~~~~~~~
disabled:3:1
disabled:3:7
error:3:7 XHP19
advice:5:3
advice:7:3

View file

@ -50,6 +50,7 @@ function j() {
$mIxEdCaSe = 1;
}
~~~~~~~~~~
disabled:3:1
warning:3:13
warning:4:9
warning:4:9
@ -63,6 +64,7 @@ warning:6:25
warning:9:11
warning:11:10
warning:11:13
disabled:13:1
warning:21:13
warning:24:3
warning:25:3

View file

@ -16,4 +16,5 @@ final class MyInvalidClass {
}
}
~~~~~~~~~~
disabled:9:1
error:11:5

View file

@ -3,3 +3,5 @@
abstract class A {}
final class F {}
~~~~~~~~~~
disabled:3:1
disabled:4:1

View file

@ -33,6 +33,7 @@ warning:14:10
warning:14:19
warning:15:12
warning:15:15
disabled:16:1
error:16:13 XHP19 Class-Filename Mismatch
warning:17:21
warning:17:24

View file

@ -0,0 +1,39 @@
<?php
pht('a');
pht("a");
pht('a'.'b');
pht(f());
pht();
pht($a);
pht('a'.$a);
pht('$a');
pht("$a");
pht('%s', $a);
pht(<<<EOT
a
EOT
);
pht(<<<EOT
$a
EOT
);
~~~~~~~~~~
advice:4:5
error:6:1
error:7:1
error:8:1
error:9:1
error:11:1
error:19:1
~~~~~~~~~~
~~~~~~~~~~
{
"config": {
"xhpast.dynamic-string.functions": {
"pht": 0
}
}
}

View file

@ -9,4 +9,4 @@ final class C extends Phobject {}
*/
class D extends Phobject {}
~~~~~~~~~~
warning:3:7
disabled:3:7

View file

@ -25,6 +25,7 @@ MyClass :: myMethod();
SomeReallyLongClassName
::someMethod();
~~~~~~~~~~
disabled:3:7
error:3:7
advice:7:5
advice:12:10

View file

@ -6,5 +6,6 @@ final class Platypus {
}
}
~~~~~~~~~~
disabled:3:1
error:3:13 XHP19 Class-Filename Mismatch
error:4:19

View file

@ -89,6 +89,7 @@ warning:48:3
warning:53:3
warning:57:3
warning:66:3
disabled:69:5
warning:71:3
warning:75:3
~~~~~~~~~~

View file

@ -24,4 +24,9 @@ abstract class SomeAbstractClass {
abstract public function __toString();
}
~~~~~~~~~~
disabled:3:1
disabled:3:7
error:6:7
disabled:13:1
disabled:13:7
disabled:23:1

View file

@ -190,6 +190,7 @@ warning:9:3
error:28:3
error:30:3
error:36:3
disabled:40:1
error:42:5
error:43:7
error:44:5
@ -202,7 +203,9 @@ error:87:3 This stuff is basically testing the lexer/parser for function decls.
error:104:15 Variables in instance derefs should be checked, static should not.
error:118:3 isset() and empty() should not trigger errors.
error:122:3 Should only warn once in this function.
disabled:136:1
error:144:8
disabled:147:1
error:150:9
error:164:9
error:171:5

View file

@ -5,5 +5,6 @@ final class Foo {
final public function baz() {}
}
~~~~~~~~~~
disabled:3:1
error:3:13 XHP19
advice:5:3

View file

@ -9,5 +9,6 @@ final class A {
}
}
~~~~~~~~~~
disabled:3:1
error:3:13 XHP19 Class-Filename Mismatch
error:8:5 Use of $this in a static method.

View file

@ -0,0 +1,15 @@
<?php
csprintf('x');
csprintf($x);
~~~~~~~~~~
error:4:1
~~~~~~~~~~
~~~~~~~~~~
{
"config": {
"xhpast.dynamic-string.functions": {
"csprintf": 0
}
}
}

View file

@ -26,8 +26,31 @@ final class ArcanistPhutilXHPASTLinterStandard
'The `%s` function should be avoided. It is potentially unsafe '.
'and makes debugging more difficult.',
'eval'),
),
'xhpast.php-version' => '5.2.3',
'xhpast.php-version.windows' => '5.3.0',
'xhpast.dynamic-string.classes' => array(
'ExecFuture' => 0,
),
'xhpast.dynamic-string.functions' => array(
'pht' => 0,
'hsprintf' => 0,
'jsprintf' => 0,
'hgsprintf' => 0,
'csprintf' => 0,
'vcsprintf' => 0,
'execx' => 0,
'exec_manual' => 0,
'phutil_passthru' => 0,
'qsprintf' => 1,
'vqsprintf' => 1,
'queryfx' => 1,
'queryfx_all' => 1,
'queryfx_one' => 1,
),
);
}

View file

@ -0,0 +1,44 @@
<?php
final class ArcanistArrayCombineXHPASTLinterRule
extends ArcanistXHPASTLinterRule {
const ID = 84;
public function getLintName() {
return pht('%s Unreliable', 'array_combine()');
}
public function getLintSeverity() {
return ArcanistLintSeverity::SEVERITY_DISABLED;
}
public function process(XHPASTNode $root) {
$function_calls = $this->getFunctionCalls($root, array('array_combine'));
foreach ($function_calls as $call) {
$name = $call->getChildByIndex(0)->getConcreteString();
$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,
pht(
'Prior to PHP 5.4, `%s` fails when given empty arrays. '.
'Prefer to write `%s` as `%s`.',
'array_combine()',
'array_combine(x, x)',
'array_fuse(x)'));
}
}
}
}

View file

@ -0,0 +1,40 @@
<?php
final class ArcanistClassExtendsObjectXHPASTLinterRule
extends ArcanistXHPASTLinterRule {
const ID = 88;
public function getLintName() {
return pht('Class Not Extending %s', 'Phobject');
}
public function getLintSeverity() {
return ArcanistLintSeverity::SEVERITY_DISABLED;
}
public function process(XHPASTNode $root) {
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
foreach ($classes as $class) {
// TODO: This doesn't quite work for namespaced classes (see T8534).
$name = $class->getChildOfType(1, 'n_CLASS_NAME');
$extends = $class->getChildByIndex(2);
if ($name->getConcreteString() == 'Phobject') {
continue;
}
if ($extends->getTypeName() == 'n_EMPTY') {
$this->raiseLintAtNode(
$class,
pht(
'Classes should extend from %s or from some other class. '.
'All classes (except for %s itself) should have a base class.',
'Phobject',
'Phobject'));
}
}
}
}

View file

@ -0,0 +1,57 @@
<?php
final class ArcanistDeprecationXHPASTLinterRule
extends ArcanistXHPASTLinterRule {
const ID = 85;
private $deprecatedFunctions = array();
public function getLintName() {
return pht('Use of Deprecated Function');
}
public function getLintSeverity() {
return ArcanistLintSeverity::SEVERITY_WARNING;
}
public function getLinterConfigurationOptions() {
return parent::getLinterConfigurationOptions() + array(
'xhpast.deprecated.functions' => array(
'type' => 'optional map<string, string>',
'help' => pht(
'Functions which should should be considered deprecated.'),
),
);
}
public function setLinterConfigurationValue($key, $value) {
switch ($key) {
case 'xhpast.deprecated.functions':
$this->deprecatedFunctions = $value;
return;
default:
return parent::getLinterConfigurationOptions();
}
}
public function process(XHPASTNode $root) {
$map = $this->deprecatedFunctions;
$function_calls = $this->getFunctionCalls($root, array_keys($map));
foreach ($function_calls as $call) {
$name = $call
->getChildByIndex(0)
->getConcreteString();
$name = strtolower($name);
if (empty($map[$name])) {
continue;
}
$this->raiseLintAtNode($call, $map[$name]);
}
}
}

View file

@ -0,0 +1,54 @@
<?php
final class ArcanistRaggedClassTreeEdgeXHPASTLinterRule
extends ArcanistXHPASTLinterRule {
const ID = 87;
public function getLintName() {
return pht('Class Not %s Or %s', 'abstract', 'final');
}
public function getLintSeverity() {
return ArcanistLintSeverity::SEVERITY_DISABLED;
}
public function process(XHPASTNode $root) {
$parser = new PhutilDocblockParser();
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
foreach ($classes as $class) {
$is_final = false;
$is_abstract = false;
$is_concrete_extensible = false;
$attributes = $class->getChildOfType(0, 'n_CLASS_ATTRIBUTES');
foreach ($attributes->getChildren() as $child) {
if ($child->getConcreteString() == 'final') {
$is_final = true;
}
if ($child->getConcreteString() == 'abstract') {
$is_abstract = true;
}
}
$docblock = $class->getDocblockToken();
if ($docblock) {
list($text, $specials) = $parser->parse($docblock->getValue());
$is_concrete_extensible = idx($specials, 'concrete-extensible');
}
if (!$is_final && !$is_abstract && !$is_concrete_extensible) {
$this->raiseLintAtNode(
$class->getChildOfType(1, 'n_CLASS_NAME'),
pht(
"This class is neither '%s' nor '%s', and does not have ".
"a docblock marking it '%s'.",
'final',
'abstract',
'@concrete-extensible'));
}
}
}
}

View file

@ -0,0 +1,103 @@
<?php
final class ArcanistUnsafeDynamicStringXHPASTLinterRule
extends ArcanistXHPASTLinterRule {
const ID = 86;
private $dynamicStringFunctions = array();
private $dynamicStringClasses = array();
public function getLintName() {
return pht('Unsafe Usage of Dynamic String');
}
public function getLintSeverity() {
return ArcanistLintSeverity::SEVERITY_ERROR;
}
public function getLinterConfigurationOptions() {
$options = array(
'xhpast.dynamic-string.classes' => array(
'type' => 'optional map<string, string>',
'help' => pht(
'Classes which should should not be used because they represent the '.
'unsafe usage of dynamic strings.'),
),
'xhpast.dynamic-string.functions' => array(
'type' => 'optional map<string, string>',
'help' => pht(
'Functions which should should not be used because they represent '.
'the unsafe usage of dynamic strings.'),
),
);
return $options + parent::getLinterConfigurationOptions();
}
public function setLinterConfigurationValue($key, $value) {
switch ($key) {
case 'xhpast.dynamic-string.classes':
$this->dynamicStringClasses = $value;
return;
case 'xhpast.dynamic-string.functions':
$this->dynamicStringFunctions = $value;
return;
default:
parent::setLinterConfigurationValue($key, $value);
return;
}
}
public function process(XHPASTNode $root) {
$this->lintUnsafeDynamicStringClasses($root);
$this->lintUnsafeDynamicStringFunctions($root);
}
private function lintUnsafeDynamicStringClasses(XHPASTNode $root) {
$news = $root->selectDescendantsOfType('n_NEW');
$this->lintUnsafeDynamicStringCall($news, $this->dynamicStringClasses);
}
private function lintUnsafeDynamicStringFunctions(XHPASTNode $root) {
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
$this->lintUnsafeDynamicStringCall($calls, $this->dynamicStringFunctions);
}
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,
pht(
"Parameter %d of %s should be a scalar string, ".
"otherwise it's not safe.",
$param + 1,
$name.'()'));
}
}
}
}