public function visit(NodeTraversal $t, \PHPParser_Node $n, \PHPParser_Node $parent = null) { if ($n instanceof \PHPParser_Node_Stmt_Property) { $containerNode = NodeUtil::getContainer($n)->get(); $class = $this->typeRegistry->getClassByNode($containerNode); if ($class->isInterface()) { $this->phpFile->addComment($n->getLine(), Comment::error('basic_semantics.property_on_interface', 'In PHP, declaring a method on an interface is not yet allowed.')); } } if (NodeUtil::isMethodContainer($n)) { $class = $this->typeRegistry->getClassByNode($n); if ($class instanceof Clazz && !$class->isAbstract()) { $abstractMethods = array(); foreach ($class->getMethods() as $method) { assert($method instanceof ClassMethod); /** @var $method ClassMethod */ if ($method->isAbstract()) { $abstractMethods[] = $method->getMethod()->getName(); } } if (!empty($abstractMethods)) { switch (count($abstractMethods)) { case 1: $this->phpFile->addComment($n->getLine(), Comment::error('basic_semantics.abstract_method_on_non_abstract_class', 'There is one abstract method ``%method%`` in this class; you could implement it, or declare this class as abstract.', array('method' => reset($abstractMethods)))); break; default: $this->phpFile->addComment($n->getLine(), Comment::error('basic_semantics.abstract_methods_on_non_abstract_class', 'There is at least one abstract method in this class. Maybe declare it as abstract, or implement the remaining methods: %methods%', array('methods' => implode(', ', $abstractMethods)))); } } } } }
public function testAdd() { $col = new \Scrutinizer\PhpAnalyzer\Model\CommentCollection(); $a = \Scrutinizer\PhpAnalyzer\Model\Comment::error('foo', 'bar'); $b = \Scrutinizer\PhpAnalyzer\Model\Comment::error('foo', 'bar'); $col->add(1, $a); $col->add(1, $b); $this->assertCount(1, $col->all(1)); }
private function checkUnusedMethod(NodeTraversal $t, \PHPParser_Node_Stmt_ClassMethod $node) { if ((\PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE & $node->type) === 0) { return; } $classType = $t->getScope()->getTypeOfThis()->restrictByNotNull(); if (($method = $classType->toMaybeObjectType()->getMethod($node->name)) && 0 === count($method->getInMethodCallSites())) { $this->phpFile->addComment($node->getLine(), Comment::warning('cleanup.unused_method', 'This method is unused, and could be removed.')); } }
public function shouldTraverse(NodeTraversal $t, \PHPParser_Node $node, \PHPParser_Node $parent = null) { $graphNode = $t->getControlFlowGraph()->getNode($node); if (null !== $graphNode && GraphReachability::UNREACHABLE === $graphNode->getAttribute(GraphReachability::ATTR_REACHABILITY)) { // Only report error when there are some line number informations. // There are synthetic nodes with no line number informations, nodes // introduced by other passes (although not likely since this pass should // be executed early) or some PHPParser bug. if (-1 !== $node->getLine()) { $this->file->addComment($node->getLine(), Comment::warning('usage.unreachable_code', '``%unreachable_code%`` is not reachable.', array('unreachable_code' => $this->prettyPrinter->prettyPrint(array($node))))); // From now on, we are going to assume the user fixed the error and not // give more warnings related to code sections reachable from this node. $r = new GraphReachability($t->getControlFlowGraph()); $r->recompute($node); // Saves time by not traversing children. return false; } } return true; }
private static function createPhpFile($name, $content) { $file = new PhpFile($name, $content); try { $ast = self::$phpParser->parse(new \PHPParser_Lexer($content)); } catch (\PHPParser_Error $parserEx) { // This at least allows to run all the passes. For those that // need an AST to work, they will obviously not do anything // useful, but maybe some can. // TODO: Implement some heuristics to attempt to fix the code. $ast = array(new BlockNode(array())); $lineNb = $parserEx->getRawLine(); if (-1 === $lineNb) { // This is such a serious error that we at least need to // report it somehow even if the line is off. $file->addComment(1, Comment::error('unparsable_code', "Cannot point out a specific line, but got the following parsing error when trying this code:\n\n%message%", array('message' => $parserEx->getRawMessage()))); } else { $file->addComment($lineNb, Comment::error('unparsable_code_with_line', "This code did not parse for me. Apparently, there is an error somewhere around this line:\n\n%message%", array('message' => $parserEx->getRawMessage()))); } } $traverser = new \PHPParser_NodeTraverser(); $traverser->addVisitor(new \PHPParser_NodeVisitor_NameResolver()); $traverser->addVisitor(new NormalizingNodeVisitor()); $ast = $traverser->traverse($ast); // Wrap the AST in a block node if it has more than one root if (count($ast) > 1) { $ast = array(new BlockNode($ast)); } else { if (0 === count($ast)) { $ast = array(new BlockNode(array())); } } $traverser = new \PHPParser_NodeTraverser(); $traverser->addVisitor(new \PHPParser_NodeVisitor_NodeConnector()); // do not assign the AST here as the traverser clones nodes resulting // in different references for "parent", "next", "previous", etc. $traverser->traverse($ast); $file->setAst($ast[0]); return $file; }
public function visit(NodeTraversal $t, \PHPParser_Node $node, \PHPParser_Node $parent = null) { if (!NodeUtil::isCallLike($node)) { return; } if (null === ($function = $this->typeRegistry->getCalledFunctionByNode($node))) { return; } foreach ($function->getParameters() as $param) { if (!$param->isPassedByRef()) { continue; } $index = $param->getIndex(); if (!isset($node->args[$index])) { continue; } $arg = $node->args[$index]; if (!NodeUtil::canBePassedByRef($arg->value)) { $this->phpFile->addComment($arg->getLine(), Comment::error('usage.non_referencable_arg', '``%argument%`` cannot be passed to ``%function_name%()`` as the parameter ``$%parameter_name%`` expects a reference.', array('argument' => self::$prettyPrinter->prettyPrintExpr($arg->value), 'function_name' => $function->getName(), 'parameter_name' => $param->getName()))); } } }
private function handleEmptyCatch(\PHPParser_Node_Stmt_Catch $node) { if (!$this->getSetting('non_commented_empty_catch_block')) { return; } if (count($node->stmts) > 0) { return; } if (-1 === ($line = $node->getLine())) { return; } $code = explode("\n", $this->phpFile->getContent()); $previousBlock = $node->getAttribute('parent')->stmts; foreach ($previousBlock as $lastChild) { } $previousLineEnd = count($previousBlock) > 0 ? $lastChild->getLine() : $previousBlock->getLine(); // Not sure how readable that code is supposed to be, but it doesn't seem to look good. if ($previousLineEnd === $node->getLine()) { $this->phpFile->addComment($previousLineEnd, Comment::warning('coding_style.unreadable_catch', 'Consider moving this CATCH statement to a new line.')); return; } // Start scanning for a comment one line after the last statement of the previous block. $foundComment = $afterCatch = $inCatchBlock = $terminateOnNextNonWhitespace = $afterCondition = false; for ($i = $previousLineEnd, $c = count($code); $i < $c; $i++) { if (empty($code[$i])) { continue; } // Might break for some weird inlined HTML, but honestly who does that these days? $lineTokens = token_get_all('<?php ' . $code[$i]); foreach ($lineTokens as $token) { if (!is_array($token)) { if ($terminateOnNextNonWhitespace) { break 2; } if ($afterCondition && '{' === $token) { $inCatchBlock = true; continue; } if ($inCatchBlock && '}' === $token) { $terminateOnNextNonWhitespace = true; continue; } if ($afterCatch && ')' === $token) { $afterCondition = true; continue; } // no whitespace and after the catch block means something like // catch (\Exception $ex) 'foo'; if ($afterCondition && !$inCatchBlock) { break; } continue; } if (\T_DOC_COMMENT === $token[0] || \T_COMMENT === $token[0]) { $foundComment = true; break 2; } if (\T_WHITESPACE === $token[0]) { continue; } if ($terminateOnNextNonWhitespace) { break 2; } if (\T_CATCH === $token[0]) { $afterCatch = true; continue; } } } if (!$foundComment) { $this->phpFile->addComment($node->getLine(), Comment::warning('suspicious_code.empty_catch_block', 'Consider adding a comment why this CATCH block is empty.')); } }
private function checkParameters(\PHPParser_Node $node, AbstractFunction $function, array $args, \Scrutinizer\PhpAnalyzer\Model\MethodContainer $clazz = null) { $missingArgs = $this->argumentChecker->getMissingArguments($function, $args, $clazz); if (!empty($missingArgs)) { if (count($missingArgs) > 1) { $this->phpFile->addComment($node->getLine(), Comment::error('usage.missing_multiple_required_arguments', 'The call to ``%function_name%()`` misses some required arguments starting with ``$%parameter_name%``.', array('function_name' => $function->getName(), 'parameter_name' => reset($missingArgs)))); } else { $this->phpFile->addComment($node->getLine(), Comment::error('usage.missing_required_argument', 'The call to ``%function_name%()`` misses a required argument ``$%parameter_name%``.', array('function_name' => $function->getName(), 'parameter_name' => reset($missingArgs)))); } } if ('disabled' !== $this->getSetting('argument_type_checks')) { $argTypes = array(); foreach ($args as $arg) { $argTypes[] = $arg->getAttribute('type') ?: $this->typeRegistry->getNativeType('unknown'); } $mismatchedTypes = $this->argumentChecker->getMismatchedArgumentTypes($function, $argTypes, $clazz); foreach ($mismatchedTypes as $index => $type) { $this->phpFile->addComment($args[$index]->getLine(), Comment::warning('usage.argument_type_mismatch', '``%expr%`` of type ``%expr_type%`` is not a sub-type of ``%expected_type%``.', array('expr' => self::$prettyPrinter->prettyPrintExpr($args[$index]->value), 'expr_type' => (string) $argTypes[$index], 'expected_type' => (string) $type))); } } }
private function checkBitwiseOperation() { if (!NodeUtil::isEqualityExpression($this->stream->node->left)) { return; } /* * When this method is called, the token stream points to the bitwise operation already. * * 0 === foo() & 5; * ^ * -----------(1) * * (1) The position of the token stream when this method is called. * * We now check that the equality expression on the left side (in the case above, it is an Identical node) ends * with a parenthesis, and the ending parenthesis is started somewhere on the left side side of the equality * operator (===). */ /** @var AbstractToken $closingParenthesis */ $closingParenthesis = $this->stream->token->findPreviousToken('NO_WHITESPACE_OR_COMMENT')->get(); if ($closingParenthesis->matches(')')) { $startToken = $this->stream->node->left->left->getAttribute('start_token'); // The left operator of the equality sign is not assigned an explicit start token, so we do not know where // it started. We can just bail out here, and hope that the closing parenthesis is indeed started on the left // side. This needs to be improved in the SimultaneousTokenAndAstStream over time so that we ideally always // have a start token available. if (null === $startToken) { return; } // The closing parenthesis was started somewhere on the left side, good ! if ($closingParenthesis->isClosing($startToken->findPreviousToken('NO_WHITESPACE_OR_COMMENT')->get())) { return; } } $this->phpFile->addComment($this->stream->node->getLine(), Comment::warning('precedence.possibly_wrong_comparison_of_bitop_result', 'Consider adding parentheses for clarity. Current Interpretation: ``%current_compare%``, Probably Intended Meaning: ``%alternative_compare%``', array('current_compare' => '(' . self::$prettyPrinter->prettyPrintExpr($this->stream->node->left) . ') ' . NodeUtil::getBitOp($this->stream->node) . ' ' . self::$prettyPrinter->prettyPrintExpr($this->stream->node->right), 'alternative_compare' => self::$prettyPrinter->prettyPrintExpr($this->stream->node->left->left) . ' ' . NodeUtil::getEqualOp($this->stream->node->left) . ' (' . self::$prettyPrinter->prettyPrintExpr($this->stream->node->left->right) . ' ' . NodeUtil::getBitOp($this->stream->node) . ' ' . self::$prettyPrinter->prettyPrintExpr($this->stream->node->right) . ')'))); }
private function checkForMissingProperty(\PHPParser_Node_Expr_PropertyFetch $node) { if (!is_string($node->name)) { return; } if (!($objType = $node->var->getAttribute('type'))) { return; } $objType = $objType->restrictByNotNull()->toMaybeObjectType(); if (!$objType) { // TODO: Add support to check on union types. return; } if ($objType->isInterface()) { $this->phpFile->addComment($node->getLine(), Comment::warning('types.property_access_on_interface', 'Accessing "%property_name%" on the interface "%interface_name%" suggest that you code against a concrete implementation. How about adding an ``instanceof`` check?', array('property_name' => $node->name, 'interface_name' => $objType->getName()))); return; } if (!$objType->isNormalized() || $objType->hasProperty($node->name)) { return; } // Ignore all property accesses on ``stdClass`` objects. Currently, we have // no way to describe, or track RECORD types. As such, any messages related to // stdClass are likely wrong, but at least there are too many false-positives // for now. So, we just disable this. if ($objType->isSubtypeOf($this->typeRegistry->getClassOrCreate('stdClass'))) { return; } // Ignore all property reads on ``SimpleXMLElement`` objects. The reasoning // behind this is similar to the exception for ``stdClass`` objects above. // We simply have no reliable way to describe the structure of these objects // for now. So, we disable checks for them to avoid a flood of false-positives. if (!\Scrutinizer\PhpAnalyzer\PhpParser\NodeUtil::isAssignmentOp($node->getAttribute('parent')) && $objType->isSubtypeOf($this->typeRegistry->getClassOrCreate('SimpleXMLElement'))) { return; } // Property accesses inside isset() are safe, do not make any noise just yet. if ($node->getAttribute('parent') instanceof \PHPParser_Node_Expr_Isset) { return; } if (null !== ($bestName = $this->getMostSimilarName($node->name, $objType->getPropertyNames()))) { $this->phpFile->addComment($node->getLine(), Comment::error('typos.mispelled_property_name', 'The property "%offending_property_name%" does not exist. Did you mean "%closest_property_name%"?', array('offending_property_name' => $node->name, 'closest_property_name' => $bestName))); } else { // Ignore additional accesses to this property. $objType->addProperty(new Property($node->name)); if (\Scrutinizer\PhpAnalyzer\PhpParser\NodeUtil::isAssignmentOp($node->getAttribute('parent'))) { if ($objType->hasMethod('__set')) { $this->phpFile->addComment($node->getLine(), Comment::warning('strict.maybe_undocument_property_write_capability', 'The property ``%property_name%`` does not exist. Since you implemented ``__set``, maybe consider adding a [@property annotation](http://www.phpdoc.org/docs/latest/for-users/tags/property.html).', array('property_name' => $node->name))); return; } } else { if ($objType->hasMethod('__get')) { $this->phpFile->addComment($node->getLine(), Comment::warning('strict.maybe_undocument_property_read_capability', 'The property ``%property_name%`` does not exist. Since you implemented ``__get``, maybe consider adding a [@property annotation](http://www.phpdoc.org/docs/latest/for-users/tags/property.html).', array('property_name' => $node->name))); return; } } $thisType = $this->t->getScope()->getTypeOfThis(); if ($thisType && $thisType->equals($objType)) { $this->phpFile->addComment($node->getLine(), Comment::warning('strict.undeclared_property', 'The property "%property_name%" does not exist. Did you maybe forget to declare it?', array('property_name' => $node->name))); } else { $this->phpFile->addComment($node->getLine(), Comment::error('typos.non_existent_property', 'The property "%property_name%" does not exist.', array('property_name' => $node->name))); } } }
private function markOffense($stmtName) { $this->phpFile->addComment($this->stream->token->getLine(), Comment::warning('coding_style.blocks_should_have_braces', 'Please always use braces to surround the code block of %statement_name% statements.', array('statement_name' => $stmtName))); }
public function visit(NodeTraversal $t, \PHPParser_Node $node, \PHPParser_Node $parent = null) { // Consider only non-dynamic variables in this pass. if (!$node instanceof \PHPParser_Node_Expr_Variable || !is_string($node->name)) { return; } // We ignore PHP globals. if (NodeUtil::isSuperGlobal($node)) { return; } // Ignore variables which are defined here. if ($parent instanceof \PHPParser_Node_Expr_Assign && $parent->var === $node) { return; } if (!$t->getScope()->isDeclared($node->name)) { if ($bestName = CheckForTyposPass::getMostSimilarName($node->name, $t->getScope()->getVarNames())) { $this->phpFile->addComment($node->getLine(), Comment::error('typos.mispelled_variable_name', 'The variable ``%offending_variable_name%`` does not exist. Did you mean ``%closest_variable_name%``?', array('offending_variable_name' => '$' . $node->name, 'closest_variable_name' => '$' . $bestName))); } else { $this->phpFile->addComment($node->getLine(), Comment::warning('usage.undeclared_variable', 'The variable ``%variable_name%`` does not exist. Did you forget to declare it?', array('variable_name' => '$' . $node->name))); } return; } if ($parent instanceof \PHPParser_Node_Expr_ArrayDimFetch && $node->getAttribute('array_initializing_variable') && $parent->var === $node) { $this->phpFile->addComment($node->getLine(), Comment::warning('usage.non_initialized_array', '``%array_fetch_expr%`` was never initialized. Although not strictly required by PHP, it is generally a good practice to add ``%array_fetch_expr% = array();`` before regardless.', array('array_fetch_expr' => self::$prettyPrinter->prettyPrintExpr($node)))); } }
/** * @param integer $line * @param string $type */ private function checkNaming($line, $code, $name, $type) { if (!$this->getSetting('naming.enabled')) { return; } if (!preg_match('#' . str_replace('#', '\\#', $this->getSetting('naming.' . $type)) . '#', $name)) { $this->phpFile->addComment($line, Comment::warning('coding_style.naming', '``%code%`` does not seem to conform to the naming convention (``%regex%``).', array('code' => $code, 'regex' => $this->getSetting('naming.' . $type)))->varyIn(array('regex'))); } }
private function createGetNameComment(\PHPParser_Node_Expr_MethodCall $node) { $propertyAccess = new \PHPParser_Node_Expr_PropertyFetch($node->var, 'name'); return Comment::warning('reflection_usage.get_name', 'Consider using ``%property_access%``. There is [an issue](https://bugs.php.net/bug.php?id=61384) with ``getName()`` and APC-enabled PHP versions.', array('property_access' => self::$prettyPrinter->prettyPrintExpr($propertyAccess)))->varyIn(array()); }
private function optimizeAssertFalse(\PHPParser_Node $node) { if (!isset($node->args[0])) { return; } $arg = $node->args[0]->value; $betterExpr = null; switch (true) { case $arg instanceof \PHPParser_Node_Expr_Greater: $betterExpr = clone $node; $betterExpr->name = 'assertLessThanOrEqual'; $betterExpr->args = $this->createArgs($arg->right, $arg->left); break; case $arg instanceof \PHPParser_Node_Expr_GreaterOrEqual: $betterExpr = clone $node; $betterExpr->name = 'assertLessThan'; $betterExpr->args = $this->createArgs($arg->right, $arg->left); break; case $arg instanceof \PHPParser_Node_Expr_Smaller: $betterExpr = clone $node; $betterExpr->name = 'assertGreaterThanOrEqual'; $betterExpr->args = $this->createArgs($arg->left, $arg->right); break; case $arg instanceof \PHPParser_Node_Expr_SmallerOrEqual: $betterExpr = clone $node; $betterExpr->name = 'assertGreaterThan'; $betterExpr->args = $this->createArgs($arg->left, $arg->right); break; } if ($betterExpr) { if (isset($node->args[1])) { $this->phpFile->addComment($node->getLine(), Comment::warning('phpunit.more_specific_assertion_for_false_with_custom_message', 'Instead of ``assertFalse()`` consider using ``%expr%``.', array('expr' => self::$prettyPrinter->prettyPrintExpr($betterExpr)))); } else { $this->phpFile->addComment($node->getLine(), Comment::warning('phpunit.more_specific_assertion_for_false', 'Instead of ``assertFalse()`` use ``%expr%``. This will lead to a better error message when the test fails.', array('expr' => self::$prettyPrinter->prettyPrintExpr($betterExpr)))); } } }
private function tryRemoveAssignment(NodeTraversal $t, \PHPParser_Node $n, \PHPParser_Node $exprRoot = null, $inState, $outState) { $parent = $n->getAttribute('parent'); if (NodeUtil::isAssignmentOp($n)) { $lhs = $n->var; $rhs = $n->expr; if ($n instanceof \PHPParser_Node_Expr_AssignList) { $i = 0; foreach ($n->vars as $var) { if (null === $var) { $i += 1; continue; } $newNode = new \PHPParser_Node_Expr_Assign($var, new \PHPParser_Node_Expr_ArrayDimFetch($rhs, new \PHPParser_Node_Scalar_LNumber($i)), $n->getLine()); $newNode->setAttribute('is_list_assign', true); $this->tryRemoveAssignment($t, $newNode, $exprRoot, $inState, $outState); $i += 1; } return; } // Recurse first. Example: dead_x = dead_y = 1; We try to clean up dead_y first. if (null !== $rhs) { $this->tryRemoveAssignment($t, $rhs, $exprRoot, $inState, $outState); $rhs = $lhs->getAttribute('next'); } $scope = $t->getScope(); if (!$lhs instanceof \PHPParser_Node_Expr_Variable || !is_string($lhs->name)) { return; } if (!$scope->isDeclared($lhs->name)) { return; } if (in_array($lhs->name, NodeUtil::$superGlobalNames, true)) { return; } $var = $scope->getVar($lhs->name); $escaped = $this->liveness->getEscapedLocals(); if (isset($escaped[$var])) { return; // Local variable that might be escaped due to closures. } // If we have an identity assignment such as a=a, always remove it // regardless of what the liveness results because it does not // change the result afterward. if (null !== $rhs && $rhs instanceof \PHPParser_Node_Expr_Variable && $var->getName() === $rhs->name && $n instanceof \PHPParser_Node_Expr_Assign) { $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.assignment_to_itself', 'Why assign ``%variable%`` to itself?', array('variable' => '$' . $rhs->name))); return; } // At the moment we miss some dead assignments if a variable is killed, // and defined in the same node of the control flow graph. // Example: $a = 'foo'; return $a = 'bar'; if ($outState->isLive($var)) { return; // Variable is not dead. } // Assignments to references do not need to be used in the // current scope. if ($var->isReference()) { return; } // if ($inState->isLive($var) // /*&& $this->isVariableStillLiveWithinExpression($n, $exprRoot, $var->getName()) */) { // The variable is killed here but it is also live before it. // This is possible if we have say: // if ($X = $a && $a = $C) {..} ; .......; $a = $S; // In this case we are safe to remove "$a = $C" because it is dead. // However if we have: // if ($a = $C && $X = $a) {..} ; .......; $a = $S; // removing "a = C" is NOT correct, although the live set at the node // is exactly the same. // TODO: We need more fine grain CFA or we need to keep track // of GEN sets when we recurse here. Maybe add the comment anyway, and let the user decide? // return; // } if ($n instanceof \PHPParser_Node_Expr_Assign) { if ($n->getAttribute('is_list_assign', false)) { $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.unnecessary_list_assign', 'The assignment to ``$%variable%`` is dead. Consider omitting it like so ``list($first,,$third)``.', array('variable' => $var->getName()))); return; } $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.unnecessary_var_assign', 'The assignment to ``$%variable%`` is dead and can be removed.', array('variable' => $var->getName()))); return; } } }
private function checkIfElseStatements(\PHPParser_Node_Stmt_If $node, BlockNode $if, BlockNode $else) { $ifTrue = $this->returnsTrue($if); $elseTrue = $this->returnsTrue($else); if ($ifTrue === $elseTrue) { if (null === $node->else) { $this->phpFile->addComment($node->getLine(), Comment::error('suspicious_code.superfluous_if_return', 'This ``if`` statement and the following ``return`` statement are superfluous as you return always ``%return_value%``.', array('return_value' => $ifTrue ? 'true' : 'false'))); } else { $this->phpFile->addComment($node->getLine(), Comment::error('suspicious_code.superfluous_if_else', 'This ``if``-``else`` statement are superfluous as you return always ``%return_value%``.', array('return_value' => $ifTrue ? 'true' : 'false'))); } return; } $condType = $node->cond->getAttribute('type'); $possiblyNeedsCast = !$condType || !$condType->isBooleanType(); $cond = $node->cond; if (!$ifTrue) { if ($cond instanceof \PHPParser_Node_Expr_BooleanNot) { $cond = $cond->expr; $condType = $cond->getAttribute('type'); $possiblyNeedsCast = !$condType || !$condType->isBooleanType(); } else { $cond = new \PHPParser_Node_Expr_BooleanNot($cond); $possiblyNeedsCast = false; } } if ($possiblyNeedsCast) { $cond = new \PHPParser_Node_Expr_Cast_Bool($cond); } if (null !== $node->else) { $this->phpFile->addComment($node->getLine(), Comment::warning('coding_style.simplify_boolean_return_of_if_else', 'The ``if``-``else`` statement can be simplified to ``return %expr%;``.', array('expr' => self::$prettyPrinter->prettyPrintExpr($cond)))); } else { $this->phpFile->addComment($node->getLine(), Comment::warning('coding_style.simplify_boolean_return_of_if_return', 'This ``if`` statement, and the following ``return`` statement can be replaced with ``return %expr%;``.', array('expr' => self::$prettyPrinter->prettyPrintExpr($cond)))); } }
private function createInAccessiblePropertyError($propertyName, $className, $visibility) { return Comment::error('access_control.inaccessible_property', 'The property "%property_name%" cannot be accessed from this context as it is declared %visibility% in class "%class_name%"?', array('property_name' => $propertyName, 'class_name' => $className, 'visibility' => $visibility)); }
private function verifyReturnType(PhpType $type, \PHPParser_Node $node) { if (null === ($commentType = $this->parser->getTypeFromReturnAnnotation($node))) { if ($this->getSetting('ask_for_return_if_not_inferrable') && $type->isUnknownType()) { $this->phpFile->addComment($node->getLine(), Comment::warning('php_doc.return_type_not_inferrable', 'Please add a ``@return`` annotation.')); } return; } if (!$this->getSetting('return')) { return; } if (!$type->isSubtypeOf($commentType)) { if ($this->getSetting('suggest_more_specific_types') && $this->typeRegistry->getNativeType('array')->isSubtypeOf($type)) { $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_mismatch_with_generic_array', 'Should the return type not be ``%expected_type%``? Also, consider making the array more specific, something like ``array<String>``, or ``String[]``.', array('expected_type' => $type->getDocType($this->importedNamespaces)))); } else { $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_mismatch', 'Should the return type not be ``%expected_type%``?', array('expected_type' => $type->getDocType($this->importedNamespaces)))); } return; } if (!$this->getSetting('suggest_more_specific_types')) { return; } if (!$this->isMoreSpecificType($type, $commentType)) { if ($type->isAllType()) { $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_all_type_more_specific', 'Please define a more specific return type; maybe using a union like ``null|Object``, or ``string|array``.')); } else { if ($this->typeRegistry->getNativeType('array')->isSubtypeOf($type)) { $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_array_element_type', 'Please define the element type for the returned array (using ``array<SomeType>``, or ``SomeType[]``).')); } } return; } $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_more_specific', 'Consider making the return type a bit more specific; maybe use ``%actual_type%``.', array('actual_type' => $type->getDocType($this->importedNamespaces)))); }