diff --git a/.arclint b/.arclint index 2da113bb..d6233a0f 100644 --- a/.arclint +++ b/.arclint @@ -31,10 +31,6 @@ "type": "phutil-library", "include": "(\\.php$)" }, - "phutil-xhpast": { - "type": "phutil-xhpast", - "include": "(\\.php$)" - }, "spelling": { "type": "spelling", "exclude": "(^resources/spelling/.*\\.json$)" diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6dd1abda..42c27b4a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/lint/linter/ArcanistPhutilXHPASTLinter.php b/src/lint/linter/ArcanistPhutilXHPASTLinter.php deleted file mode 100644 index 7607b5f2..00000000 --- a/src/lint/linter/ArcanistPhutilXHPASTLinter.php +++ /dev/null @@ -1,336 +0,0 @@ - - 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', - 'help' => pht( - 'Functions which should should be considered deprecated.'), - ), - 'phutil-xhpast.dynamic-string.functions' => array( - 'type' => 'optional map', - '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', - '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')); - } - } - } - -} diff --git a/src/lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php b/src/lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php deleted file mode 100644 index d773e1f5..00000000 --- a/src/lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php +++ /dev/null @@ -1,14 +0,0 @@ -setDeprecatedFunctions(array( - 'deprecated_function' => pht('This function is most likely deprecated.'), - )); - - $this->executeTestsInDirectory(dirname(__FILE__).'/phlxhp/', $linter); - } - -} diff --git a/src/lint/linter/__tests__/phlxhp/deprecated-function.lint-test b/src/lint/linter/__tests__/phlxhp/deprecated-function.lint-test deleted file mode 100644 index 25ce3c96..00000000 --- a/src/lint/linter/__tests__/phlxhp/deprecated-function.lint-test +++ /dev/null @@ -1,6 +0,0 @@ - '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, ), ); } diff --git a/src/lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php new file mode 100644 index 00000000..659bd30b --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php @@ -0,0 +1,44 @@ +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)')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php new file mode 100644 index 00000000..d712b3fe --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php @@ -0,0 +1,40 @@ +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')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php new file mode 100644 index 00000000..bc57bb2d --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php @@ -0,0 +1,57 @@ + array( + 'type' => 'optional map', + '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]); + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php new file mode 100644 index 00000000..64bfd87f --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php @@ -0,0 +1,54 @@ +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')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistUnsafeDynamicStringXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUnsafeDynamicStringXHPASTLinterRule.php new file mode 100644 index 00000000..be483f3c --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistUnsafeDynamicStringXHPASTLinterRule.php @@ -0,0 +1,103 @@ + array( + 'type' => 'optional map', + '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', + '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.'()')); + } + } + } + +}