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)))); } } } } }
private function getThisType(\PHPParser_Node $node, Scope $parentScope = null) { $parent = $node; while (null !== $parent) { // As of PHP 5.4, closures inherit the this type of their parent scope. Since there is currently no way to // configure the PHP version that we build against, we will simply always infer the ThisType. Other passes, // can then perform checks whether ``$this`` may be accessed, allowing us to produce better error messages. if ($parent instanceof \PHPParser_Node_Expr_Closure) { if (null === $parentScope) { return null; } return $parentScope->getTypeOfThis(); } if (NodeUtil::isMethodContainer($parent)) { $name = implode("\\", $parent->namespacedName->parts); return $this->typeRegistry->getClassOrCreate($name); } $parent = $parent->getAttribute('parent'); } return null; }
/** * This method will stop traversal depending on the parent node. * * Principally, we are only interested in adding edges between nodes that change control flow. Notable ones are * loops (WHILE, FOR, etc.) and IF-ELSE statements; other statements typically transfer control to their next sibling. * * With regard to expression trees, we currently do not perform any sort of control flow within them, even if there * are short circuiting operators or conditionals. Instead, we synthesize lattices up when performing data flow * analysis by finding the meet at each expression node. * * @param NodeTraversal $t * @param \PHPParser_Node $node * @param \PHPParser_Node $parent * * @return boolean */ public function shouldTraverse(NodeTraversal $t, \PHPParser_Node $node, \PHPParser_Node $parent = null) { $node->setAttribute('position', $this->astPositionCounter++); if ($node instanceof \PHPParser_Node_Stmt_TryCatch) { $this->exceptionHandlers->push($node); return true; } if (null !== $parent) { // Do not traverse into classes, interfaces, or traits as control flow never gets passed in. if (NodeUtil::isMethodContainer($parent)) { return false; } // Generally, the above also applies to closures except when they are the scope's root. if (NodeUtil::isScopeCreator($parent) && $parent !== $this->root) { return false; } // Skip conditions. if ($parent instanceof \PHPParser_Node_Stmt_For || $parent instanceof \PHPParser_Node_Stmt_Foreach) { return $parent->stmts === $node; } // Skip conditions. if ($parent instanceof \PHPParser_Node_Stmt_If || $parent instanceof \PHPParser_Node_Stmt_ElseIf || $parent instanceof \PHPParser_Node_Stmt_While || $parent instanceof \PHPParser_Node_Stmt_Do || $parent instanceof \PHPParser_Node_Stmt_Switch || $parent instanceof \PHPParser_Node_Stmt_Case) { return $parent->cond !== $node; } // Skip exception type. if ($parent instanceof \PHPParser_Node_Stmt_Catch) { return $parent->stmts === $node; } // Skip expressions, see above. if ($parent instanceof \PHPParser_Node_Expr && !$parent instanceof \PHPParser_Node_Expr_Closure) { return false; } if ($parent instanceof \PHPParser_Node_Stmt_Continue || $parent instanceof \PHPParser_Node_Stmt_Break || $parent instanceof \PHPParser_Node_Stmt_Return || $parent instanceof \PHPParser_Node_Stmt_Echo || $parent instanceof \PHPParser_Node_Stmt_Use || $parent instanceof \PHPParser_Node_Stmt_Unset || $parent instanceof \PHPParser_Node_Stmt_Declare || $parent instanceof \PHPParser_Node_Stmt_Global || $parent instanceof \PHPParser_Node_Stmt_Static || $parent instanceof \PHPParser_Node_Stmt_StaticVar || $parent instanceof \PHPParser_Node_Stmt_Throw) { return false; } // Skip parameters. if (NodeUtil::isScopeCreator($parent) && $node !== $parent->stmts) { return false; } // If we are reaching the CATCH, or FINALLY node, they current exception handler cannot catch anymore // exceptions, and we therefore can remove it. // TODO: Add Support for FINALLY (PHP 5.5) if ($parent instanceof \PHPParser_Node_Stmt_TryCatch && $parent->catches[0] === $node && $parent === $this->exceptionHandlers->top()) { $this->exceptionHandlers->pop(); } } return true; }
public function visit(NodeTraversal $t, \PHPParser_Node $node, \PHPParser_Node $parent = null) { switch (true) { case $node instanceof \PHPParser_Node_Expr_Variable: if (NodeUtil::isSuperGlobal($node)) { return; } if (is_string($node->name)) { $this->checkNaming($node->getLine(), '$' . $node->name, $node->name, 'local_variable'); } break; case $node instanceof \PHPParser_Node_Stmt_PropertyProperty: $this->checkNaming($node->getLine(), '$' . $node->name, $node->name, 'property_name'); break; case $node instanceof \PHPParser_Node_Stmt_Function: case $node instanceof \PHPParser_Node_Stmt_ClassMethod: $function = $this->typeRegistry->getFunctionByNode($node); // If a class method is constrained by a contract of an interface, or an abstract // class, then there is no point in adding warnings for them. if ($function instanceof ClassMethod && $function->isConstrainedByContract()) { break; } $this->checkNaming($node->getLine(), 'function ' . $node->name . '()', $node->name, 'method_name'); if (null !== $function && null !== ($returnType = $function->getReturnType()) && $returnType->isBooleanType()) { $this->checkNaming($node->getLine(), 'function ' . $node->name . '()', $node->name, 'isser_method_name'); } $this->checkParameters($node->params); break; case $node instanceof \PHPParser_Node_Expr_Closure: $this->checkParameters($node->params); break; case NodeUtil::isMethodContainer($node): $class = $this->typeRegistry->getClassByNode($node)->toMaybeObjectType(); if ($class) { $this->checkNaming($node->getLine(), $class->getShortName(), $class->getShortName(), 'type_name'); if ($class->isInterface()) { $this->checkNaming($node->getLine(), $class->getShortName(), $class->getShortName(), 'interface_name'); } elseif ($class instanceof Clazz) { if ($class->isUtilityClass()) { $this->checkNaming($node->getLine(), $class->getShortName(), $class->getShortName(), 'utility_class_name'); } elseif ($class->isAbstract()) { $this->checkNaming($node->getLine(), $class->getShortName(), $class->getShortName(), 'abstract_class_name'); } // No Else-If here as there might be an abstract exception. // TODO: isSubtype needs to return a TernaryValue so that we // can decide here what to do in the unknown case. // if ($class->isSubTypeOf($this->typeRegistry->getClassOrCreate('Exception'))) { // $this->checkNaming($node->getLine(), $class->getShortName(), $class->getShortName(), 'exception_name'); // } } // The original check only allows final classes to have a private // constructor. We also allow the constructor to be private if it // is declared final, and the class is declared abstract. This is // for example a common practice for utility classes. $method = $class->getMethod('__construct'); if (null !== $method && $method->isPrivate() && !$class->isFinal() && (!$method->isFinal() || !$class->isAbstract())) { if (($method = $class->getMethod('__construct')) && $method->isPrivate() && !$class->isFinal() && (!$method->isFinal() || !$class->isAbstract())) { if ($method->isFinal()) { if ($class->isUtilityClass()) { $this->phpFile->addComment($node->getLine(), Comment::warning('coding_style.non_abstract_util_class', 'Since you have declared the constructor as final, and this seems like a utility class, maybe you should also declare the class as abstract.')); } else { if (null === $method->getAstNode()) { break; } $this->phpFile->addComment($method->getAstNode()->getLine(), Comment::warning('coding_style.non_util_class_with_final_private_constructor', 'Instead of declaring the constructor as final, maybe you should declare the entire class as final.')); } } elseif ($class->isAbstract()) { if ($class->isUtilityClass()) { $this->phpFile->addComment($method->getAstNode()->getLine(), Comment::warning('coding_style.util_class_with_non_final_constructor', 'Since you have declared this class abstract, and the constructor is private, maybe you should also declare the constructor as final.')); } else { $this->phpFile->addComment($method->getAstNode()->getLine(), Comment::warning('coding_style.abstract_non_util_class_with_private_constructor', 'Something seems to be off here. Are you sure you want to declare the constructor as private, and the class as abstract?')); } } else { $this->phpFile->addComment($node->getLine(), Comment::warning('coding_style.non_final_class_with_private_constructor', 'Since you have declared the constructor as private, maybe you should also declare the class as final.')); } } } } break; } }
private function enterNode(\PHPParser_Node $node) { if (NodeUtil::isMethodContainer($node)) { $this->commentParser->setCurrentClassName(implode("\\", $node->namespacedName->parts)); $this->commentParser->setImportedNamespaces($this->importedNamespaces); $this->classParser->setImportedNamespaces($this->importedNamespaces); $class = $this->classParser->parse($node); $this->classFiles[$class] = $this->phpFile; if ($this->typeRegistry->hasClass($class->getName(), TypeRegistry::LOOKUP_NO_CACHE)) { $this->analyzer->logger->warning(sprintf('The class "%s" has been defined more than once (maybe as a fixture for code generation). Ignoring the second definition.', $class->getName())); return; } $this->typeRegistry->registerClass($class); } else { if ($node instanceof \PHPParser_Node_Stmt_Function) { $this->functionParser->setImportedNamespaces($this->importedNamespaces); $function = $this->functionParser->parse($node); if ($this->typeRegistry->hasFunction($functionName = $function->getName(), false)) { $this->analyzer->logger->warning(sprintf('The function "%s" has been defined more than once (probably conditionally). Ignoring the second definition.', $functionName)); return; } $this->typeRegistry->registerFunction($function); } else { if (NodeUtil::isConstantDefinition($node)) { assert($node instanceof \PHPParser_Node_Expr_FuncCall); if (!$node->args[0]->value instanceof \PHPParser_Node_Scalar_String) { return; } $constant = new GlobalConstant($node->args[0]->value->value); $constant->setAstNode($node); $constant->setPhpType($this->typeRegistry->getNativeType('unknown')); if (null !== ($type = $node->args[1]->value->getAttribute('type'))) { $constant->setPhpType($type); } if ($this->typeRegistry->hasConstant($constant->getName(), false)) { $this->analyzer->logger->warning(sprintf('The constant "%s" was defined more than once. Ignoring all but first definition.', $constant->getName())); return; } $this->typeRegistry->registerConstant($constant); } } } }