/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeDuplicateClass(CodeBase $code_base, Clazz $clazz) { // Determine if its a duplicate by looking to see if // the FQSEN is suffixed with an alternate ID. if (!$clazz->getFQSEN()->isAlternate()) { return; } $original_fqsen = $clazz->getFQSEN()->getCanonicalFQSEN(); if (!$code_base->hasClassWithFQSEN($original_fqsen)) { // If there's a missing class we'll catch that // elsewhere return; } // Get the original class $original_class = $code_base->getClassByFQSEN($original_fqsen); // Check to see if the original definition was from // an internal class if ($original_class->isInternal()) { Issue::emit(Issue::RedefineClassInternal, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $original_class); // Otherwise, print the coordinates of the original // definition } else { Issue::emit(Issue::RedefineClass, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $original_class, $original_class->getContext()->getFile(), $original_class->getContext()->getLineNumberStart()); } return; }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeDuplicateClass(CodeBase $code_base, Clazz $clazz) { // Determine if its a duplicate by looking to see if // the FQSEN is suffixed with an alternate ID. if (!$clazz->getFQSEN()->isAlternate()) { return; } $original_fqsen = $clazz->getFQSEN()->getCanonicalFQSEN(); if (!$code_base->hasClassWithFQSEN($original_fqsen)) { // If there's a missing class we'll catch that // elsewhere return; } // Get the original class $original_class = $code_base->getClassByFQSEN($original_fqsen); // Check to see if the original definition was from // an internal class if ($original_class->isInternal()) { Log::err(Log::EREDEF, "{$clazz} defined at " . "{$clazz->getContext()->getFile()}:{$clazz->getContext()->getLineNumberStart()} " . "was previously defined as {$original_class} internally", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart()); // Otherwise, print the coordinates of the original // definition } else { Log::err(Log::EREDEF, "{$clazz} defined at " . "{$clazz->getContext()->getFile()}:{$clazz->getContext()->getLineNumberStart()} " . "was previously defined as {$original_class} at " . "{$original_class->getContext()->getFile()}:{$original_class->getContext()->getLineNumberStart()}", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart()); } return; }
/** * @return array * Get a map from column name to row values for * this instance */ public function toRow() : array { $parent_class_fqsen = $this->clazz->hasParentClassFQSEN() ? (string) $this->clazz->getParentClassFQSEN() : null; $interface_fqsen_list_string = implode('|', array_map(function (FullyQualifiedClassName $fqsen) { return (string) $fqsen; }, $this->clazz->getInterfaceFQSENList())); $trait_fqsen_list_string = implode('|', array_map(function (FullyQualifiedClassName $fqsen) { return (string) $fqsen; }, $this->clazz->getInterfaceFQSENList())); return ['name' => (string) $this->clazz->getName(), 'type' => (string) $this->clazz->getUnionType(), 'flags' => $this->clazz->getFlags(), 'fqsen' => (string) $this->clazz->getFQSEN(), 'context' => base64_encode(serialize($this->clazz->getContext())), 'is_deprecated' => $this->clazz->isDeprecated(), 'parent_class_fqsen' => $parent_class_fqsen, 'interface_fqsen_list' => $interface_fqsen_list_string, 'trait_fqsen_list' => $trait_fqsen_list_string, 'is_parent_constructor_called' => $this->clazz->getIsParentConstructorCalled()]; }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeParentConstructorCalled(CodeBase $code_base, Clazz $clazz) { // Only look at classes configured to require a call // to its parent constructor if (!in_array($clazz->getName(), Config::get()->parent_constructor_required)) { return; } // Don't worry about internal classes if ($clazz->isInternal()) { return; } // Don't worry if there's no parent class if (!$clazz->hasParentClassFQSEN()) { return; } if (!$code_base->hasClassWithFQSEN($clazz->getParentClassFQSEN())) { // This is an error, but its caught elsewhere. We'll // just roll through looking for other errors return; } $parent_clazz = $code_base->getClassByFQSEN($clazz->getParentClassFQSEN()); if (!$parent_clazz->isAbstract() && !$clazz->getIsParentConstructorCalled()) { Issue::emit(Issue::TypeParentConstructorCalled, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz->getFQSEN(), (string) $parent_clazz->getFQSEN()); } }
/** * @param CodeBase $code_base * The code base in which the class exists * * @param Clazz $class * A class being analyzed * * @return void */ public function analyzeClass(CodeBase $code_base, Clazz $class) { // As an example, we test to see if the name of // the class is `Class`, and emit an issue explain that // the name is not allowed. if ($class->getName() == 'Class') { $this->emitIssue($code_base, $class->getContext(), 'DemoPluginClassName', "Class {$class->getFQSEN()} cannot be called `Class`"); } }
/** * @return Method * A default constructor for the given class */ public static function defaultConstructorForClassInContext(Clazz $clazz, Context $context, CodeBase $code_base) : Method { $method_fqsen = FullyQualifiedMethodName::make($clazz->getFQSEN(), '__construct'); $method = new Method($context, '__construct', $clazz->getUnionType(), 0, $method_fqsen); if ($clazz->hasMethodWithName($code_base, $clazz->getName())) { $old_style_constructor = $clazz->getMethodByName($code_base, $clazz->getName()); $method->setParameterList($old_style_constructor->getParameterList()); $method->setNumberOfRequiredParameters($old_style_constructor->getNumberOfRequiredParameters()); $method->setNumberOfOptionalParameters($old_style_constructor->getNumberOfOptionalParameters()); } return $method; }
/** * Add properties, constants and methods from the given * class to this. * * @param Clazz $superclazz * A class to import from * * @return null */ public function importAncestorClass(CodeBase $code_base, Clazz $superclazz) { $this->memoize((string) $superclazz->getFQSEN(), function () use($code_base, $superclazz) { // Copy properties foreach ($superclazz->getPropertyMap($code_base) as $property) { $this->addProperty($code_base, $property); } // Copy constants foreach ($superclazz->getConstantMap($code_base) as $constant) { $this->addConstant($code_base, $constant); } // Copy methods foreach ($superclazz->getMethodMap($code_base) as $method) { $this->addMethod($code_base, $method); } }); }
/** * @return Method * A default constructor for the given class */ public static function defaultConstructorForClassInContext(Clazz $clazz, Context $context) : Method { $method = new Method($context, '__construct', $clazz->getUnionType(), 0); $method->setFQSEN(FullyQualifiedMethodName::make($clazz->getFQSEN(), '__construct')); return $method; }
/** * Check to see if signatures match * * @return void */ public static function analyzeComposition(CodeBase $code_base, Clazz $class) { // Get the Class's FQSEN $fqsen = $class->getFQSEN(); // Get the list of all inherited classes. $inherited_class_list = $class->getInheritedClassList($code_base); // No chance of failed composition if we don't inherit from // lots of stuff. if (count($inherited_class_list) < 2) { return; } // For each property, find out every inherited class that defines it // and check to see if the types line up. foreach ($class->getPropertyList($code_base) as $property) { try { $property_union_type = $property->getUnionType(); } catch (IssueException $exception) { $property_union_type = new UnionType(); } // Check for that property on each inherited // class/trait/interface foreach ($inherited_class_list as $inherited_class) { // Skip any classes/traits/interfaces not defining that // property if (!$inherited_class->hasPropertyWithName($code_base, $property->getName())) { continue; } // We don't call `getProperty` because that will create // them in some circumstances. $inherited_property_map = $inherited_class->getPropertyMap($code_base); if (!isset($inherited_property_map[$property->getName()])) { continue; } // Get the inherited property $inherited_property = $inherited_property_map[$property->getName()]; // Figure out if this property type can cast to the // inherited definition's type. $can_cast = $property_union_type->canCastToExpandedUnionType($inherited_property->getUnionType(), $code_base); if ($can_cast) { continue; } // Don't emit an issue if the property suppresses the issue if ($property->hasSuppressIssue(Issue::IncompatibleCompositionProp)) { continue; } Issue::maybeEmit($code_base, $property->getContext(), Issue::IncompatibleCompositionProp, $property->getFileRef()->getLineNumberStart(), (string) $class->getFQSEN(), (string) $inherited_class->getFQSEN(), $property->getName(), (string) $class->getFQSEN(), $class->getFileRef()->getFile(), $class->getFileRef()->getLineNumberStart()); } } // TODO: This has too much overlap with PhanParamSignatureMismatch // and we should figure out how to merge it. /* $method_map = $code_base->getMethodMapByFullyQualifiedClassName($fqsen); // For each method, find out every inherited class that defines it // and check to see if the types line up. foreach ($method_map as $i => $method) { $method_union_type = $method->getUnionType(); // We don't need to analyze constructors for signature // compatibility if ($method->getName() == '__construct') { continue; } // Get the method parameter list // Check for that method on each inherited // class/trait/interface foreach ($inherited_class_list as $inherited_class) { // Skip anything that doesn't define this method if (!$inherited_class->hasMethodWithName($code_base, $method->getName())) { continue; } $inherited_method = $inherited_class->getMethodByName($code_base, $method->getName()); if ($method == $inherited_method) { continue; } // Figure out if this method return type can cast to the // inherited definition's return type. $is_compatible = $method_union_type->canCastToExpandedUnionType( $inherited_method->getUnionType(), $code_base ); $inherited_method_parameter_map = $inherited_method->getParameterList(); // Figure out if all of the parameter types line up foreach ($method->getParameterList() as $i => $parameter) { $is_compatible = ( $is_compatible && isset($inherited_method_parameter_map[$i]) && $parameter->getUnionType()->canCastToExpandedUnionType( ($inherited_method_parameter_map[$i])->getUnionType(), $code_base ) ); } if ($is_compatible) { continue; } // Don't emit an issue if the method suppresses the issue if ($method->hasSuppressIssue(Issue::IncompatibleCompositionMethod)) { continue; } Issue::maybeEmit( $code_base, $method->getContext(), Issue::IncompatibleCompositionMethod, $method->getFileRef()->getLineNumberStart(), (string)$method, (string)$inherited_method, $inherited_method->getFileRef()->getFile(), $inherited_method->getFileRef()->getLineNumberStart() ); } } */ }
/** * @param Clazz $class * A class to add. * * @return void */ public function addClass(Clazz $class) { // Map the FQSEN to the class $this->fqsen_class_map[$class->getFQSEN()] = $class; }
/** * Add properties, constants and methods from the given * class to this. * * @param CodeBase $code_base * A reference to the code base in which the ancestor exists * * @param Clazz $class * A class to import from * * @param Option<Type>|None $type_option * A possibly defined ancestor type used to define template * parameter types when importing ancestor properties and * methods * * @return void */ public function importAncestorClass(CodeBase $code_base, Clazz $class, $type_option) { if (!$this->isFirstExecution(__METHOD__ . ':' . (string) $class->getFQSEN())) { return; } $class->addReference($this->getContext()); // Make sure that the class imports its parents first $class->hydrate($code_base); // Copy properties foreach ($class->getPropertyMap($code_base) as $property) { $this->addProperty($code_base, $property, $type_option); } // Copy constants foreach ($class->getConstantMap($code_base) as $constant) { $this->addConstant($code_base, $constant); } // Copy methods foreach ($class->getMethodMap($code_base) as $method) { $this->addMethod($code_base, $method, $type_option); } }
/** * Add a class to the code base * * @return null */ public function addClass(Clazz $class) { $this->class_map[$class->getFQSEN()] = $class; // For classes that aren't internal PHP classes if (!$class->getContext()->isInternal()) { // Associate the class with the file it was found in $this->getFileByPath($class->getContext()->getFile())->addClassFQSEN($class->getFQSEN()); } }