/** * Report coding standard violation if class/interface/trait/function is too big * * @param PHPParser_Node $node Current node * * {@internal 'maximum' is padded to compensate for opening/closing curly braces }} */ private function checkSize($node) { $keyword = $this->metaData[$node->getType()]['keyword']; $maximum = $this->metaData[$node->getType()]['maximum']; $size = $node->getAttribute('endLine') - $node->getAttribute('startLine'); if ($size <= $maximum + 2) { return; } $this->phpcsFile->addWarning(sprintf('Keep your %s small (no more than %d lines)', $keyword, $maximum), $this->findStackPointer($node)); }
/** * @depends testConstruct */ public function testGetDocComment(PHPParser_Node $node) { $this->assertEquals('/** doc comment */', $node->getDocComment()); array_pop($node->getAttribute('comments')); // remove doc comment $this->assertNull($node->getDocComment()); array_pop($node->getAttribute('comments')); // remove comment $this->assertNull($node->getDocComment()); }
public function getNodeCode(\PHPParser_Node $node) { $startPos = $node->getAttribute('startOffset'); $endPos = $node->getAttribute('endOffset'); if ($startPos === null || $endPos === null) { return ''; // just to be sure } $startOffset = $this->tokenToStartOffset[$startPos]; $endOffset = $this->tokenToEndOffset[$endPos]; return substr($this->code, $startOffset, $endOffset - $startOffset); }
public function enterNode(\PHPParser_Node $node) { // Determine info about the closure's location if (!$this->closureNode) { if ($node instanceof \PHPParser_Node_Stmt_Namespace) { $this->location->namespace = is_array($node->name->parts) ? implode('\\', $node->name->parts) : null; } if ($node instanceof \PHPParser_Node_Stmt_Trait) { $this->location->trait = $this->location->namespace . '\\' . $node->name; $this->location->class = null; } elseif ($node instanceof \PHPParser_Node_Stmt_Class) { $this->location->class = $this->location->namespace . '\\' . $node->name; $this->location->trait = null; } } // Locate the node of the closure if ($node instanceof \PHPParser_Node_Expr_Closure) { if ($node->getAttribute('startLine') == $this->reflection->getStartLine()) { if ($this->closureNode) { throw new \RuntimeException('Two closures were declared on the same line of code. Cannot determine ' . 'which closure was the intended target.'); } else { $this->closureNode = $node; } } } }
/** * {@inheritdoc} */ public function enterNode(PHPParser_Node $node) { if ($node->getAttribute('visited')) { return; } return; switch ($node->getType()) { case 'Stmt_ClassMethod': case 'Expr_Closure': case 'Stmt_Function': case 'Stmt_If': case 'Stmt_Do': case 'Stmt_While': case 'Stmt_For': case 'Stmt_Foreach': break; } }
public function leaveNode(\PHPParser_Node $node) { switch ($node->getType()) { case 'Scalar_MagicConst_Line': return new NumberNode($node->getAttribute('startLine')); case 'Scalar_MagicConst_File': return new StringNode($this->location->file); case 'Scalar_MagicConst_Dir': return new StringNode($this->location->directory); case 'Scalar_MagicConst_Function': return new StringNode($this->location->function); case 'Scalar_MagicConst_Namespace': return new StringNode($this->location->namespace); case 'Scalar_MagicConst_Class': return new StringNode($this->location->class); case 'Scalar_MagicConst_Method': return new StringNode($this->location->method); case 'Scalar_MagicConst_Trait': return new StringNode($this->location->trait); } }
private function attachLiteralTypes(\PHPParser_Node $node, \PHPParser_Node $parent = null) { switch (true) { case $node instanceof \PHPParser_Node_Name_FullyQualified: $node->setAttribute('type', $this->typeRegistry->getClassOrCreate(implode("\\", $node->parts))); break; case $node instanceof \PHPParser_Node_Name: if ($parent instanceof \PHPParser_Node_Expr_New || $parent instanceof \PHPParser_Node_Expr_StaticPropertyFetch || $parent instanceof \PHPParser_Node_Expr_StaticCall || $parent instanceof \PHPParser_Node_Expr_Instanceof || $parent instanceof \PHPParser_Node_Stmt_Catch) { $name = implode("\\", $node->parts); $lowerName = strtolower($name); if ('static' === $name) { $node->setAttribute('type', $this->typeRegistry->getThisType($this->scope->getTypeOfThis())); } else { if ('self' === $name) { $node->setAttribute('type', $this->scope->getTypeOfThis()); } else { if ('parent' === $name) { $thisType = $this->scope->getTypeOfThis()->toMaybeObjectType(); if (null === $thisType || !$thisType->isClass() || null === $thisType->getSuperClassType()) { $node->setAttribute('type', $this->typeRegistry->getNativeType('unknown')); } else { $node->setAttribute('type', $thisType->getSuperClassType()); } } else { $node->setAttribute('type', $this->typeRegistry->getClassOrCreate($name)); } } } } break; case $node instanceof \PHPParser_Node_Expr_Array: case $node instanceof \PHPParser_Node_Expr_Cast_Array: // We only do attach the generic array type on the first build. // For subsequent builds, other passes likely have already made // the array type more specific. if (null === $node->getAttribute('type')) { $node->setAttribute('type', $this->typeRegistry->getNativeType('array')); } break; case $node instanceof \PHPParser_Node_Expr_UnaryMinus: case $node instanceof \PHPParser_Node_Expr_UnaryPlus: case $node instanceof \PHPParser_Node_Scalar_LNumber: case $node instanceof \PHPParser_Node_Scalar_LineConst: $node->setAttribute('type', $this->typeRegistry->getNativeType('integer')); break; case $node instanceof \PHPParser_Node_Scalar_DNumber: $node->setAttribute('type', $this->typeRegistry->getNativeType('double')); break; case $node instanceof \PHPParser_Node_Scalar_String: case $node instanceof \PHPParser_Node_Scalar_FileConst: case $node instanceof \PHPParser_Node_Scalar_DirConst: $node->setAttribute('type', $this->typeRegistry->getNativeType('string')); break; case $node instanceof \PHPParser_Node_Expr_ClassConstFetch: if ($node->class instanceof \PHPParser_Node_Name && in_array($node->class->parts[0], array('self', 'static')) && null !== ($thisType = $this->scope->getTypeOfThis()) && null !== ($objType = $thisType->toMaybeObjectType()) && ($objType->isClass() || $objType->isInterface()) && $objType->hasConstant($node->name)) { $node->setAttribute('type', $objType->getConstant($node->name)->getPhpType()); } break; case $node instanceof \PHPParser_Node_Expr_ConstFetch: $nameParts = $node->name->parts; if (1 === count($nameParts)) { $name = strtolower($nameParts[0]); if ('true' === $name) { $node->setAttribute('type', $this->typeRegistry->getNativeType('boolean')); } else { if ('false' === $name) { $node->setAttribute('type', $this->typeRegistry->getNativeType('false')); } else { if ('null' === $name) { $node->setAttribute('type', $this->typeRegistry->getNativeType('null')); } } } } break; } }
/** * Returns the class a method was called on. * * @param \PHPParser_Node $node * * @return null|PhpType */ public function getCalledClassByNode(\PHPParser_Node $node) { switch (true) { case $node instanceof \PHPParser_Node_Expr_MethodCall: if (null === ($objType = $node->var->getAttribute('type'))) { return null; } return $objType->restrictByNotNull()->toMaybeObjectType(); case $node instanceof \PHPParser_Node_Expr_StaticCall: if (null === ($objType = $node->class->getAttribute('type'))) { return null; } return $objType->restrictByNotNull()->toMaybeObjectType(); case $node instanceof \PHPParser_Node_Expr_New: if (null === ($objType = $node->getAttribute('type'))) { return null; } return $objType->restrictByNotNull()->toMaybeObjectType(); default: throw new \InvalidArgumentException('The node class "' . get_class($node) . '" is not resolvable to a function/method.'); } }
/** * Returns the type of the given node in the flow scope. * * If the node cannot be matched to a slot, or simply is not capable of being * refined, then this method returns null. * * @return PhpType|null */ public function getTypeIfRefinable(\PHPParser_Node $node, LinkedFlowScope $scope) { switch (true) { case $node instanceof \PHPParser_Node_Expr_ArrayDimFetch: $varType = $this->getTypeIfRefinable($node->var, $scope); if ($varType && $varType->isArrayType()) { $dim = \Scrutinizer\PhpAnalyzer\PhpParser\NodeUtil::getValue($node->dim); if ($dim->isEmpty()) { return null; } return $dim->flatMap([$varType, 'getItemType'])->getOrCall([$varType, 'getElementType']); } return null; // Handle the common pattern of assigning the result of an expression // inside of a condition, e.g. ``if (null !== $obj = $this->findObj())``. // Handle the common pattern of assigning the result of an expression // inside of a condition, e.g. ``if (null !== $obj = $this->findObj())``. case $node instanceof \PHPParser_Node_Expr_Assign: case $node instanceof \PHPParser_Node_Expr_AssignRef: if (TypeInference::hasQualifiedName($node->var) && null !== ($qualifiedName = TypeInference::getQualifiedName($node->var)) && null !== ($nameVar = $scope->getSlot($node->var->name))) { if (null !== ($nameType = $nameVar->getType())) { return $nameType; } return $node->var->getAttribute('type'); } return null; case $node instanceof \PHPParser_Node_Expr_Variable: if (is_string($node->name)) { $nameVar = $scope->getSlot($node->name); if (null !== $nameVar) { if (null !== $nameVar->getType()) { return $nameVar->getType(); } return $node->getAttribute('type'); } } return null; case $node instanceof \PHPParser_Node_Expr_StaticPropertyFetch: case $node instanceof \PHPParser_Node_Expr_PropertyFetch: if (null === ($qualifiedName = TypeInference::getQualifiedName($node))) { return null; } $propVar = $scope->getSlot($qualifiedName); if (null !== $propVar && null !== $propVar->getType()) { return $propVar->getType(); } if (null !== ($type = $node->getAttribute('type'))) { return $type; } return null; } return null; }
private function handleCall(\PHPParser_Node $node) { if (!$this->getSetting('assignment_of_null_return')) { return; } if (!($returnType = $node->getAttribute('type'))) { return; } if (!$returnType->isNullType()) { return; } if (!$node->getAttribute('parent') instanceof \PHPParser_Node_Expr_Assign) { return; } $this->phpFile->addComment($node->getAttribute('parent')->getLine(), Comment::warning('suspicious_code.assignment_of_null_return', 'The assignment to ``%assignment_expr%`` looks wrong as ``%call_expr%`` always returns null.', array('assignment_expr' => self::$prettyPrinter->prettyPrintExpr($node->getAttribute('parent')->var), 'call_expr' => self::$prettyPrinter->prettyPrintExpr($node)))); }
private function checkForTypeFunctionCall(\PHPParser_Node $node, LinkedFlowScope $blindScope, $outcome) { if ($node instanceof \PHPParser_Node_Stmt_Case) { $left = $node->getAttribute('parent')->cond; $right = $node->cond; } else { $left = $node->left; $right = $node->right; } // Handle gettype() function calls. $gettypeNode = $stringNode = null; if (NodeUtil::isGetTypeFunctionCall($left) && $right instanceof \PHPParser_Node_Scalar_String) { $gettypeNode = $left; $stringNode = $right; } else { if (NodeUtil::isGetTypeFunctionCall($right) && $left instanceof \PHPParser_Node_Scalar_String) { $gettypeNode = $right; $stringNode = $left; } } if (null !== $gettypeNode && null !== $stringNode && isset($gettypeNode->args[0])) { $operandNode = $gettypeNode->args[0]->value; $operandType = $this->getTypeIfRefinable($operandNode, $blindScope); if (null !== $operandType) { $resultEqualsValue = $node instanceof \PHPParser_Node_Expr_Equal || $node instanceof \PHPParser_Node_Expr_Identical || $node instanceof \PHPParser_Node_Stmt_Case; if (!$outcome) { $resultEqualsValue = !$resultEqualsValue; } return $this->caseGettypeFunctionCall($operandNode, $operandType, $stringNode->value, $resultEqualsValue, $blindScope); } } // Handle get_class() function calls. $getClassNode = $stringNode = null; if ('get_class' === NodeUtil::getFunctionName($left) && $right instanceof \PHPParser_Node_Scalar_String) { $getClassNode = $left; $stringNode = $right; } else { if ('get_class' === NodeUtil::getFunctionName($right) && $left instanceof \PHPParser_Node_Scalar_String) { $getClassNode = $right; $stringNode = $left; } } if (null !== $getClassNode && null !== $stringNode && isset($getClassNode->args[0])) { $operandNode = $getClassNode->args[0]->value; $operandType = $this->getTypeIfRefinable($operandNode, $blindScope); if (null !== $operandType) { $resultEqualsValue = $node instanceof \PHPParser_Node_Expr_Equal || $node instanceof \PHPParser_Node_Expr_Identical || $node instanceof \PHPParser_Node_Stmt_Case; if (!$outcome) { $resultEqualsValue = !$resultEqualsValue; } return $this->caseGetClassFunctionCall($operandNode, $operandType, $stringNode->value, $resultEqualsValue, $blindScope); } } // Handle is_??? and assert function calls as well as instanceof checks. $typeFunctionNode = $booleanNode = null; if ((NodeUtil::isTypeFunctionCall($left) || NodeUtil::isMaybeFunctionCall($left, 'assert') || $left instanceof \PHPParser_Node_Expr_Instanceof) && NodeUtil::isBoolean($right)) { $typeFunctionNode = $left; $booleanNode = $right; } else { if ((NodeUtil::isTypeFunctionCall($right) || NodeUtil::isMaybeFunctionCall($right, 'assert') || $right instanceof \PHPParser_Node_Expr_Instanceof) && NodeUtil::isBoolean($left)) { $typeFunctionNode = $right; $booleanNode = $left; } } if (null !== $booleanNode && null !== $typeFunctionNode) { $expectedOutcome = NodeUtil::getBooleanValue($booleanNode) ? $outcome : !$outcome; if ($typeFunctionNode instanceof \PHPParser_Node_Expr_Instanceof) { return $this->firstPreciserScopeKnowingConditionOutcome($typeFunctionNode, $blindScope, $expectedOutcome); } if (isset($typeFunctionNode->args[0])) { if (NodeUtil::isMaybeFunctionCall($typeFunctionNode, 'assert')) { return $this->firstPreciserScopeKnowingConditionOutcome($typeFunctionNode->args[0]->value, $blindScope, $expectedOutcome); } return $this->caseTypeFunctionCall($typeFunctionNode->name, $typeFunctionNode->args[0], $blindScope, $expectedOutcome); } } return null; }
private function findContinueOrBreakTarget(\PHPParser_Node $node) { if (null !== $node->num && !$node->num instanceof \PHPParser_Node_Scalar_LNumber) { return null; } // Continuously look up the ancestor tree for the BREAK target, and connect to it. $curNb = 0; $num = null === $node->num ? 1 : $node->num->value; if (0 === $num) { $num = 1; } $parent = $node->getAttribute('parent'); while (null !== $parent) { if ($parent instanceof \PHPParser_Node_Stmt_For || $parent instanceof \PHPParser_Node_Stmt_Foreach || $parent instanceof \PHPParser_Node_Stmt_While || $parent instanceof \PHPParser_Node_Stmt_Do || $parent instanceof \PHPParser_Node_Stmt_Switch) { $curNb += 1; if ($curNb >= $num) { break; } } $parent = $parent->getAttribute('parent'); } return $parent; }
/** * This method gets the PhpType from the Node argument and verifies that it is * present. If not type is present, it will be logged, and unknown is returned. * * @return PhpType */ private function getType(\PHPParser_Node $node) { $type = $node->getAttribute('type'); if (null === $type) { // Theoretically, we should never enter this branch because all // interesting nodes should have received a type by the scope builder, // or the type inference engine. It is not worth throwing an exception, // but we should at least log it, and fix it over time. $this->logger->debug(sprintf('Encountered untyped node "%s"; assuming unknown type.', get_class($node))); return $this->typeRegistry->getNativeType('unknown'); } return $type; }
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; } } }
/** * Returns the parent node of the given class, or null if not found. * * @param \PHPParser_Node $node * @param string $parentClass * * @return \PHPParser_Node|null */ public static function findParent(\PHPParser_Node $node, $parentClass) { $parent = $node->getAttribute('parent'); while (null !== $parent) { if ($parent instanceof $parentClass) { return $parent; } $parent = $parent->getAttribute('parent'); } return null; }
private function scanVars(Node $node, Node $parent) { switch (true) { case $node instanceof \PHPParser_Node_Stmt_StaticVar: $this->declareVar($node->name, null, true, $node); return; case $node instanceof \PHPParser_Node_Expr_Variable: if (($parent instanceof \PHPParser_Node_Expr_Assign || $parent instanceof \PHPParser_Node_Expr_AssignRef) && is_string($node->name) && $parent->var === $node) { $this->declareVar($node->name, null, true, $node); } // PHP automatically creates a variable (without issuing a warning) if an item is added // to a variable without the variable being declared as array previously. // ``function() { $a[] = 'foo'; }`` is perfectly permissible, and will create ``$a``. if ($parent instanceof \PHPParser_Node_Expr_ArrayDimFetch && $parent->var === $node) { $parentsParent = $parent->getAttribute('parent'); if (($parentsParent instanceof \PHPParser_Node_Expr_Assign || $parentsParent instanceof \PHPParser_Node_Expr_AssignRef) && is_string($node->name) && $parentsParent->var === $parent) { // TODO: We should better track which variables have been initialized // and which have not. This can be done by adding an undefined // type for those that have not. if (!$this->scope->isDeclared($node->name)) { $node->setAttribute('array_initializing_variable', true); } $this->declareVar($node->name, null, true, $node); } } if ($parent instanceof \PHPParser_Node_Arg && $parent->getAttribute('param_expects_ref', false) && is_string($node->name)) { $this->declareVar($node->name, null, true, $node); } return; case $node instanceof \PHPParser_Node_Expr_AssignList: foreach ($node->vars as $var) { if ($var instanceof \PHPParser_Node_Expr_Variable && is_string($var->name)) { $this->declareVar($var->name, null, true, $var); } } $this->scanVars($node->expr, $node); return; case $node instanceof \PHPParser_Node_Stmt_Trait: case $node instanceof \PHPParser_Node_Stmt_Interface: case $node instanceof \PHPParser_Node_Stmt_Class: case NodeUtil::isScopeCreator($node): return; // do not examine their children as they belong to a different scope // do not examine their children as they belong to a different scope case $node instanceof \PHPParser_Node_Stmt_Catch: $this->declareVar($node->var, null, true, $node); $this->scanVars($node->stmts, $node); return; case $node instanceof \PHPParser_Node_Stmt_Foreach: if ($node->keyVar !== null && $node->keyVar instanceof \PHPParser_Node_Expr_Variable && is_string($node->keyVar->name)) { $this->declareVar($node->keyVar->name, null, true, $node->keyVar); } if ($node->valueVar instanceof \PHPParser_Node_Expr_Variable && is_string($node->valueVar->name)) { $this->declareVar($node->valueVar->name, null, true, $node->valueVar); } $this->scanVars($node->stmts, $node); return; default: foreach ($node as $subNode) { if (is_array($subNode)) { foreach ($subNode as $aSubNode) { if (!$aSubNode instanceof \PHPParser_Node) { continue; } $this->scanVars($aSubNode, $node); } } else { if ($subNode instanceof \PHPParser_Node) { $this->scanVars($subNode, $node); } } } } }