public function testScopeChanges() { $function = new \PHPParser_Node_Stmt_Function('foo', array('stmts' => $functionBlock = new BlockNode(array(new \PHPParser_Node_Expr_Assign(new \PHPParser_Node_Expr_Variable('foo'), $closure = new \PHPParser_Node_Expr_Closure(array('stmts' => $closureBlock = new BlockNode(array())))))))); $callback = $this->getMock('Scrutinizer\\PhpAnalyzer\\PhpParser\\Traversal\\CallbackInterface'); $functionScope = $closureScope = null; $t = new NodeTraversal($callback); $callback->expects($this->atLeastOnce())->method('shouldTraverse')->will($this->returnValue(true)); $self = $this; $callback->expects($this->atLeastOnce())->method('visit')->will($this->returnCallback(function (NodeTraversal $t, \PHPParser_Node $node, \PHPParser_Node $parent = null) use(&$functionScope, &$closureScope, $function, $closure, $self) { if (null === $parent) { $functionScope = $t->getScope(); $self->assertSame(1, $t->getScopeDepth()); $self->assertSame($function, $t->getScopeRoot()); } if ($parent instanceof \PHPParser_Node_Expr_Closure) { $closureScope = $t->getScope(); $self->assertSame(2, $t->getScopeDepth()); $self->assertSame($closure, $t->getScopeRoot()); } })); $t->traverse($function); $this->assertInstanceOf('Scrutinizer\\PhpAnalyzer\\PhpParser\\Scope\\Scope', $functionScope); $this->assertInstanceOf('Scrutinizer\\PhpAnalyzer\\PhpParser\\Scope\\Scope', $closureScope); $this->assertNotSame($functionScope, $closureScope); }
public function enterScope(NodeTraversal $traversal) { // This has the side-effect of attaching type information to parameters. // TODO: This is more a hack atm, we should look into refactoring this. // Maybe a simple rename to buildScope(), or getOrBuildScope. $traversal->getScope(); }
public function enterScope(NodeTraversal $t) { $node = $t->getScopeRoot(); $function = null; if ($node instanceof \PHPParser_Node_Stmt_Function) { $function = $this->typeRegistry->getFunction($node->name); if (null !== $function) { $this->parser->setCurrentClassName(null); $this->parser->setImportedNamespaces($this->importedNamespaces = $function->getImportedNamespaces()); } } else { if ($node instanceof \PHPParser_Node_Stmt_ClassMethod) { $objType = $t->getScope()->getTypeOfThis()->toMaybeObjectType(); if (null !== $objType) { /** @var $objType MethodContainer */ $this->parser->setCurrentClassName($objType->getName()); $this->parser->setImportedNamespaces($this->importedNamespaces = $objType->getImportedNamespaces()); $function = $objType->getMethod($node->name); } } } if (null !== $function) { if (null !== ($returnType = $function->getReturnType())) { $this->verifyReturnType($returnType, $node); } foreach ($function->getParameters() as $param) { /** @var $param Parameter */ if (null !== ($paramType = $param->getPhpType())) { $this->verifyParamType($paramType, $node, $param->getName()); } } } return true; }
public function enterScope(NodeTraversal $t) { $scope = $t->getScope(); $cfg = $t->getControlFlowGraph(); $this->liveness = new LiveVariablesAnalysis($cfg, $scope); $this->liveness->analyze(); $this->tryRemoveDeadAssignments($t, $cfg); }
public function enterScope(NodeTraversal $t) { $cfg = $t->getControlFlowGraph(); $scope = $t->getScope(); $mayBeAnalysis = new MayBeReachingUseAnalysis($cfg, $scope); $mayBeAnalysis->analyze(); $mustBeAnalysis = new MustBeReachingDefAnalysis($cfg, $scope); $mustBeAnalysis->setLogger($this->logger); $mustBeAnalysis->analyze(); }
public function enterScope(NodeTraversal $traversal) { $typeInference = new TypeInference($traversal->getControlFlowGraph(), $this->reverseInterpreter, $this->functionInterpreter, $this->methodInterpreter, $this->commentParser, $traversal->getScope(), $this->registry, $this->logger); try { $typeInference->analyze(); } catch (MaxIterationsExceededException $ex) { $scopeRoot = $traversal->getScopeRoot(); $this->logger->warning($ex->getMessage() . ' - Scope-Root: ' . get_class($scopeRoot) . ' on line ' . $scopeRoot->getLine() . ' in ' . $this->file->getName()); } }
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 enterScope(NodeTraversal $t) { $node = $t->getScopeRoot(); $function = null; if ($node instanceof \PHPParser_Node_Stmt_ClassMethod) { $function = $t->getScope()->getTypeOfThis()->getMethod($node->name)->getMethod(); } else { if ($node instanceof \PHPParser_Node_Stmt_Function) { $function = $this->typeRegistry->getFunctionByNode($node); } } if (null !== $function) { $this->inferTypesForFunction($function); } }
private function handleAssign(NodeTraversal $t, \PHPParser_Node_Expr_Assign $node) { if (!$node->var instanceof \PHPParser_Node_Expr_Variable || !is_string($node->var->name)) { return; } $scope = $t->getScope(); if (null === ($var = $scope->getVar($node->var->name))) { return; } if ($var->isReference()) { return; } $nameNode = $var->getNameNode(); if ($nameNode instanceof \PHPParser_Node_Param && $this->getSetting('overriding_parameter')) { $this->phpFile->addComment($node->getLine(), Comment::warning('suspicious_code.assignment_to_parameter', 'Consider using a different name than the parameter ``$%param_name%``. This often makes code more readable.', array('param_name' => $node->var->name))); } else { if ($nameNode instanceof \PHPParser_Node_Expr_ClosureUse && $this->getSetting('overriding_closure_use')) { $this->phpFile->addComment($node->getLine(), Comment::warning('suspicious_code.assignment_to_closure_import', 'Consider using a different name than the imported variable ``$%variable_name%``, or did you forget to import by reference?', array('variable_name' => $node->var->name))); } } }
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)))); } }
private function checkCall(NodeTraversal $t, \PHPParser_Node $node) { if (!$node instanceof \PHPParser_Node_Expr_StaticCall && !$node instanceof \PHPParser_Node_Expr_MethodCall) { return; } $method = $this->typeRegistry->getCalledFunctionByNode($node); if (!$method instanceof ClassMethod) { return; } $type = $method->getClass(); if ($method->isPublic()) { return; } $thisType = $t->getScope()->getTypeOfThis(); if ($method->isPrivate()) { $type = $method->getDeclaringClassType(); if (null === $thisType || !$thisType->equals($type)) { $this->phpFile->addComment($node->getLine(), $this->createInAccessibleMethodError($method->getMethod()->getName(), $type->getName(), 'private')); return; } // Check that ``static`` is not used with private methods. if ($node instanceof \PHPParser_Node_Expr_StaticCall && $node->class instanceof \PHPParser_Node_Name && 1 === count($node->class->parts) && 'static' === strtolower($node->class->parts[0])) { $this->phpFile->addComment($node->getLine(), Comment::error('access_control.static_with_private_method', 'Since ``%method_name%()`` is declared private, calling it with ``static`` will lead to errors in possible sub-classes. You can either use ``self``, or increase the visibility of ``%method_name%()`` to at least protected.', array('method_name' => $method->getMethod()->getName()))); } return; } if ($method->isProtected()) { if (null === $thisType || !$thisType->isSubTypeOf($type)) { $this->phpFile->addComment($node->getLine(), $this->createInAccessibleMethodError($method->getMethod()->getName(), $type->getName(), 'protected')); } return; } throw new \LogicException('Invalid state.'); }
/** * Returns the originating function, or method. * * @param NodeTraversal $t * * @return AbstractFunction|null */ private function getSource(NodeTraversal $t) { $root = $t->getScopeRoot(); if ($root instanceof \PHPParser_Node_Stmt_Function) { return $this->typeRegistry->getFunctionByNode($root); } else { if ($root instanceof \PHPParser_Node_Stmt_ClassMethod) { // If the originating object was not part of this packages' dependencies, or we // have not scanned it for some other reason, we have to bail out here. if (null === ($thisObject = $t->getScope()->getTypeOfThis()->toMaybeObjectType())) { return null; } // This could be the case if the same class has been defined more than once. // We had such cases for example when people add fixtures for code generation to their // packages, and do not ensure that class names are unique. if (null === ($classMethod = $thisObject->getMethod($root->name))) { return null; } return $classMethod->getMethod(); } } return null; }
public function enterScope(NodeTraversal $t) { $scope = $t->getScope(); $root = $scope->getRootNode(); if (!$root instanceof \PHPParser_Node_Stmt_Function && !$root instanceof \PHPParser_Node_Stmt_ClassMethod) { return; } // Bail out on abstract methods. if ($root instanceof \PHPParser_Node_Stmt_ClassMethod && ($root->type & \PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT) !== 0) { return; } // Bail out on methods defined on interfaces. if ($root instanceof \PHPParser_Node_Stmt_ClassMethod && $root->getAttribute('parent')->getAttribute('parent') instanceof \PHPParser_Node_Stmt_Interface) { return; } // Bail out on built-in functions marked by the @jms-builtin annotation. // For these, we will solely infer types from doc comments. if (false !== strpos($root->getDocComment(), '@jms-builtin')) { return; } // Same as above, but for methods of classes marked with @jms-builtin. if ($root instanceof \PHPParser_Node_Stmt_ClassMethod) { $maybeClass = $root->getAttribute('parent')->getAttribute('parent'); if ($maybeClass instanceof \PHPParser_Node_Stmt_Class && false !== strpos($maybeClass->getDocComment(), '@jms-builtin')) { return; } } $cfg = $t->getControlFlowGraph(); $builder = new UnionTypeBuilder($this->typeRegistry); foreach ($cfg->getNode(null)->getInEdges() as $edge) { $sourceNode = $edge->getSource()->getAstNode(); if (!$sourceNode instanceof \PHPParser_Node_Stmt_Return) { $builder->addAlternate($this->typeRegistry->getNativeType('null')); continue; } // If there is no type information available, we cannot make any // assumptions for this function/method. if (!($type = $sourceNode->getAttribute('type'))) { return; } $builder->addAlternate($type); } $type = $builder->build(); if ($type->isUnknownType()) { return; } $function = $this->typeRegistry->getFunctionByNode($root); if ($function instanceof GlobalFunction) { if ($this->hasTypeChanged($type, $function->getReturnType())) { $this->repeatedPass->repeat(); } $function->setReturnType($type); } else { if ($function instanceof ContainerMethodInterface) { $method = $function->getMethod(); if ($this->hasTypeChanged($type, $method->getReturnType())) { $this->repeatedPass->repeat(); } $method->setReturnType($type); } } }