In order to understand internal classes, interfaces,
traits and functions, a CodeBase needs to be
initialized with the list of those elements begotten
before any classes are loaded.
# Example
Grab these before we define our own classes
$internal_class_name_list = get_declared_classes();
$internal_interface_name_list = get_declared_interfaces();
$internal_trait_name_list = get_declared_traits();
$internal_function_name_list = get_defined_functions()['internal'];
Load any required code ...
$code_base = new CodeBase(
$internal_class_name_list,
$internal_interface_name_list,
$internal_trait_name_list,
$internal_function_name_list
);
Do stuff ...
/** * 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; }
/** * 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 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()); } }
/** * @return bool * True if the FQSEN exists. If not, a log line is emitted */ private static function fqsenExistsForClass(FQSEN $fqsen, CodeBase $code_base, Clazz $clazz) : bool { if (!$code_base->hasClassWithFQSEN($fqsen)) { Log::err(Log::EUNDEF, "Trying to inherit from unknown class {$fqsen}", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart()); return false; } return true; }
/** * @return bool * True if the FQSEN exists. If not, a log line is emitted */ private static function fqsenExistsForClass(FQSEN $fqsen, CodeBase $code_base, Clazz $clazz, string $message_template) : bool { if (!$code_base->hasClassWithFQSEN($fqsen)) { Log::err(Log::EUNDEF, sprintf($message_template, $fqsen), $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart()); return false; } return true; }
/** * @return bool * True if the FQSEN exists. If not, a log line is emitted */ private static function fqsenExistsForClass(FQSEN $fqsen, CodeBase $code_base, Clazz $clazz, string $issue_type) : bool { if (!$code_base->hasClassWithFQSEN($fqsen)) { Issue::maybeEmit($code_base, $clazz->getContext(), $issue_type, $clazz->getFileRef()->getLineNumberStart(), (string) $fqsen); return false; } return true; }
/** * @return Clazz * The class that defined this element * * @throws CodeBaseException * An exception may be thrown if we can't find the * class */ public function getDefiningClass(CodeBase $code_base) : Clazz { $class_fqsen = $this->getDefiningClassFQSEN(); if (!$code_base->hasClassWithFQSEN($class_fqsen)) { throw new CodeBaseException($class_fqsen, "Defining class {$class_fqsen} for {$this->getFQSEN()} not found"); } return $code_base->getClassByFQSEN($class_fqsen); }
public function testMethodInCodeBase() { $context = $this->contextForCode("\n namespace A;\n Class B {\n public function c() {\n return 42;\n }\n }\n "); $class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\A\\b'); self::assertTrue($this->code_base->hasClassWithFQSEN($class_fqsen), "Class with FQSEN {$class_fqsen} not found"); $clazz = $this->code_base->getClassByFQSEN($class_fqsen); self::assertTrue($clazz->hasMethodWithName($this->code_base, 'c'), "Method with FQSEN not found"); }
/** * @param Node $node * A node of the type indicated by the method name that we'd * like to figure out the type that it produces. * * @return string * The class name represented by the given call */ public function visitProp(Node $node) : string { if (!($node->children['expr']->kind == \ast\AST_VAR && !$node->children['expr']->children['name'] instanceof Node)) { return ''; } // $var->prop->method() $var = $node->children['expr']; $class = null; if ($var->children['name'] == 'this') { // If we're not in a class scope, 'this' won't work if (!$this->context->isInClassScope()) { Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno); return ''; } // $this->$node->method() if ($node->children['prop'] instanceof Node) { // Too hard. Giving up. return ''; } $class = $this->context->getClassInScope($this->code_base); } else { // Get the list of viable class types for the // variable $union_type = AST::varUnionType($this->context, $var)->nonNativeTypes()->nonGenericArrayTypes(); if ($union_type->isEmpty()) { return ''; } $class_fqsen = $union_type->head()->asFQSEN(); if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) { return ''; } $class = $this->code_base->getClassByFQSEN($class_fqsen); } $property_name = $node->children['prop']; if (!$class->hasPropertyWithName($this->code_base, $property_name)) { // If we can't find the property, there's // no type. Thie issue should be caught // elsewhere. return ''; } try { $property = $class->getPropertyByNameInContext($this->code_base, $property_name, $this->context); } catch (AccessException $exception) { Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno); return ''; } $union_type = $property->getUnionType()->nonNativeTypes(); if ($union_type->isEmpty()) { // If we don't have a type on the property we // can't figure out the class type. return ''; } else { // Return the first type on the property // that could be a reference to a class return (string) $union_type->head()->asFQSEN(); } // No such property was found, or none were classes // that could be found return ''; }
/** * Visit a node with kind `\ast\AST_METHOD_CALL` * * @param Node $node * A node of the type indicated by the method name that we'd * like to figure out the type that it produces. * * @return UnionType * The set of types that are possibly produced by the * given node */ public function visitMethodCall(Node $node) : UnionType { $class_name = AST::classNameFromNode($this->context, $this->code_base, $node); if (empty($class_name)) { return new UnionType(); } $class_fqsen = FullyQualifiedClassName::fromstringInContext($class_name, $this->context); assert($this->code_base->hasClassWithFQSEN($class_fqsen), "Class {$class_fqsen} must exist"); $clazz = $this->code_base->getClassByFQSEN($class_fqsen); $method_name = $node->children['method']; // Give up on any complicated nonsense where the // method name is a variable such as in // `$variable->$function_name()`. if ($method_name instanceof Node) { return new UnionType(); } // Method names can some times turn up being // other method calls. assert(is_string($method_name), "Method name must be a string. Something else given."); if (!$clazz->hasMethodWithName($this->code_base, $method_name)) { Log::err(Log::EUNDEF, "call to undeclared method {$class_fqsen}->{$method_name}()", $this->context->getFile(), $node->lineno); return new UnionType(); } $method = $clazz->getMethodByNameInContext($this->code_base, $method_name, $this->context); return $method->getUnionType(); }
/** * @param Node $node * A node of the type indicated by the method name that we'd * like to figure out the type that it produces. * * @return string * The class name represented by the given call */ public function visitVar(Node $node) : string { // $$var->method() if ($node->children['name'] instanceof Node) { return ''; } // $this->method() if ($node->children['name'] == 'this') { if (!$this->context->isInClassScope()) { Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno); return ''; } return (string) $this->context->getClassFQSEN(); } $variable_name = $node->children['name']; if (!$this->context->getScope()->hasVariableWithName($variable_name)) { // Got lost, couldn't find the variable in the current scope // If it really isn't defined, it will be caught by the // undefined var error return ''; } $variable = $this->context->getScope()->getVariableWithName($variable_name); // Hack - loop through the possible types of the var and assume // first found class is correct foreach ($variable->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) { $child_class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context); if ($this->code_base->hasClassWithFQSEN($child_class_fqsen)) { return (string) FullyQualifiedClassName::fromStringInContext((string) $type, $this->context); } } // Could not find name return ''; }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitMethodCall(Node $node) : Context { $method_name = $node->children['method']; if (!is_string($method_name)) { return $this->context; } try { $method = (new ContextNode($this->code_base, $this->context, $node))->getMethod($method_name, false); } catch (IssueException $exception) { $exception->getIssueInstance()(); return $this->context; } catch (NodeException $exception) { // If we can't figure out the class for this method // call, cry YOLO and mark every method with that // name with a reference. if (Config::get()->dead_code_detection) { foreach ($this->code_base->getMethodListByName($method_name) as $method) { $method->addReference($this->context); } } // Swallow it return $this->context; } // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); return $this->context; }
/** * @return Method */ public function getClosure() : Func { $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context); if (!$this->code_base->hasFunctionWithFQSEN($closure_fqsen)) { throw new CodeBaseException($closure_fqsen, "Could not find closure {$closure_fqsen}"); } return $this->code_base->getFunctionByFQSEN($closure_fqsen); }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeDuplicateFunction(CodeBase $code_base, FunctionInterface $method) { $fqsen = $method->getFQSEN(); if (!$fqsen->isAlternate()) { return; } $original_fqsen = $fqsen->getCanonicalFQSEN(); if (!$code_base->hasMethod($original_fqsen)) { return; } $original_method = $code_base->getMethod($original_fqsen); $method_name = $method->getName(); if ($original_method->isInternal()) { Issue::emit(Issue::RedefineFunctionInternal, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart()); } else { Issue::emit(Issue::RedefineFunction, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $original_method->getFileRef()->getFile(), $original_method->getFileRef()->getLineNumberStart()); } }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeParameterTypes(CodeBase $code_base, FunctionInterface $method) { // Look at each method parameter foreach ($method->getParameterList() as $parameter) { $union_type = $parameter->getUnionType(); // Look at each type in the parameter's Union Type foreach ($union_type->getTypeSet() as $type) { // If its a native type or a reference to // self, its OK if ($type->isNativeType() || $type->isSelfType()) { continue; } // Otherwise, make sure the class exists $type_fqsen = $type->asFQSEN(); if (!$code_base->hasClassWithFQSEN($type_fqsen)) { Issue::emit(Issue::UndeclaredTypeParameter, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), (string) $type_fqsen); } } } }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeParameterTypes(CodeBase $code_base, Method $method) { // Look at each method parameter foreach ($method->getParameterList() as $parameter) { $union_type = $parameter->getUnionType(); // Look at each type in the parameter's Union Type foreach ($union_type->getTypeList() as $type) { // If its a native type or a reference to // self, its OK if ($type->isNativeType() || $type->isSelfType()) { continue; } // Otherwise, make sure the class exists $type_fqsen = $type->asFQSEN(); if (!$code_base->hasClassWithFQSEN($type_fqsen)) { Log::err(Log::EUNDEF, "parameter of undeclared type {$type_fqsen}", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart()); } } } }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeDuplicateFunction(CodeBase $code_base, FunctionInterface $method) { $fqsen = $method->getFQSEN(); if (!$fqsen->isAlternate()) { return; } $original_fqsen = $fqsen->getCanonicalFQSEN(); if ($original_fqsen instanceof FullyQualifiedFunctionName) { if (!$code_base->hasFunctionWithFQSEN($original_fqsen)) { return; } $original_method = $code_base->getFunctionByFQSEN($original_fqsen); } else { if (!$code_base->hasMethodWithFQSEN($original_fqsen)) { return; } $original_method = $code_base->getMethodByFQSEN($original_fqsen); } $method_name = $method->getName(); if (!$method->hasSuppressIssue(Issue::RedefineFunction)) { if ($original_method->isInternal()) { Issue::maybeEmit($code_base, $method->getContext(), Issue::RedefineFunctionInternal, $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart()); } else { Issue::maybeEmit($code_base, $method->getContext(), Issue::RedefineFunction, $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $original_method->getFileRef()->getFile(), $original_method->getFileRef()->getLineNumberStart()); } } }
/** * @return Constant * Get the (non-class) constant associated with this node * in this context * * @throws NodeException * An exception is thrown if we can't understand the node * * @throws CodeBaseExtension * An exception is thrown if we can't find the given * class */ public function getConst() : Constant { assert($this->node->kind === \ast\AST_CONST, "Node must be of type \\ast\\AST_CONST"); if ($this->node->children['name']->kind !== \ast\AST_NAME) { throw new NodeException($this->node, "Can't determine constant name"); } // Get an FQSEN for the root namespace $fqsen = null; $constant_name = $this->node->children['name']->children['name']; if (!$this->code_base->hasConstant($fqsen, $constant_name)) { throw new CodeBaseException($fqsen, "Cannot find constant with name {$constant_name}"); } return $this->code_base->getConstant($fqsen, $constant_name); }
/** * Take a look at all globally accessible elements and see if * we can find any dead code that is never referenced * * @return void */ public static function analyzeReferenceCounts(CodeBase $code_base) { // Check to see if dead code detection is enabled. Keep // in mind that the results here are just a guess and // we can't tell with certainty that anything is // definitely unreferenced. if (!Config::get()->dead_code_detection) { return; } // Get the count of all known elements $total_count = $code_base->totalElementCount(); $i = 0; // Functions self::analyzeElementListReferenceCounts($code_base, $code_base->getFunctionMap(), Issue::UnreferencedMethod, $total_count, $i); // Constants self::analyzeElementListReferenceCounts($code_base, $code_base->getGlobalConstantMap(), Issue::UnreferencedConstant, $total_count, $i); // Classes self::analyzeElementListReferenceCounts($code_base, $code_base->getClassMap(), Issue::UnreferencedClass, $total_count, $i); // Class Maps foreach ($code_base->getClassMapMap() as $class_map) { self::analyzeClassMapReferenceCounts($code_base, $class_map, $total_count, $i); } }
/** * Check to see if the given Clazz is a duplicate * * @return null */ public static function analyzeDuplicateFunction(CodeBase $code_base, Method $method) { $fqsen = $method->getFQSEN(); if (!$fqsen->isAlternate()) { return; } $original_fqsen = $fqsen->getCanonicalFQSEN(); if (!$code_base->hasMethod($original_fqsen)) { return; } $original_method = $code_base->getMethod($original_fqsen); $method_name = $method->getName(); if ('internal' === $original_method->getContext()->getFile()) { // If its in an conditional and the original is an // internal method, presume its all OK. if ($method->getContext()->getIsConditional()) { return; } Log::err(Log::EREDEF, "Function {$method_name} defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()} was previously defined internally", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart()); } else { Log::err(Log::EREDEF, "Function {$method_name} defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()} was previously defined at {$original_method->getContext()->getFile()}:{$original_method->getContext()->getLineNumberStart()}", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart()); } }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitCall(Node $node) : Context { $expression = $node->children['expr']; if (Config::get()->backward_compatibility_checks) { AST::backwardCompatibilityCheck($this->context, $node); foreach ($node->children['args']->children as $arg_node) { if ($arg_node instanceof Node) { AST::backwardCompatibilityCheck($this->context, $arg_node); } } } if ($expression->kind == \ast\AST_NAME) { try { $method = AST::functionFromNameInContext($expression->children['name'], $this->context, $this->code_base); } catch (CodeBaseException $exception) { Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno); return $this->context; } // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } else { if ($expression->kind == \ast\AST_VAR) { $variable_name = AST::variableName($expression); if (empty($variable_name)) { return $this->context; } // $var() - hopefully a closure, otherwise we don't know if ($this->context->getScope()->hasVariableWithName($variable_name)) { $variable = $this->context->getScope()->getVariableWithName($variable_name); $union_type = $variable->getUnionType(); if ($union_type->isEmpty()) { return $this->context; } $type = $union_type->head(); if (!$type instanceof CallableType) { return $this->context; } $closure_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString((string) $type->asFQSEN()); if ($this->code_base->hasMethod($closure_fqsen)) { // Get the closure $method = $this->code_base->getMethod($closure_fqsen); // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } } } } return $this->context; }
/** * @return Clazz[] * A list of classes associated with the given node * * @throws CodeBaseException * An exception is thrown if we can't find a class for * the given type */ private function classListFromNode(Node $node) { // Get the types associated with the node $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node); // Iterate over each viable class type to see if any // have the constant we're looking for foreach ($union_type->nonNativeTypes()->getTypeList() as $class_type) { // Get the class FQSEN $class_fqsen = $class_type->asFQSEN(); // See if the class exists if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) { throw new CodeBaseException($class_fqsen, "reference to undeclared class {$class_fqsen}"); } (yield $this->code_base->getClassByFQSEN($class_fqsen)); } }
/** * @return Clazz[] * A list of classes associated with the given node * * @throws IssueException * An exception is thrown if we can't find a class for * the given type */ private function classListFromNode(Node $node) { // Get the types associated with the node $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node); // Iterate over each viable class type to see if any // have the constant we're looking for foreach ($union_type->nonNativeTypes()->getTypeSet() as $class_type) { // Get the class FQSEN $class_fqsen = $class_type->asFQSEN(); // See if the class exists if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) { throw new IssueException(Issue::fromType(Issue::UndeclaredClassReference)($this->context->getFile(), $node->lineno ?? 0, [(string) $class_fqsen])); } (yield $this->code_base->getClassByFQSEN($class_fqsen)); } }
/** * Take a look at all globally accessible elements and see if * we can find any dead code that is never referenced * * @return void */ public static function analyzeReferenceCounts(CodeBase $code_base) { // Check to see if dead code detection is enabled. Keep // in mind that the results here are just a guess and // we can't tell with certainty that anything is // definitely unreferenced. if (!Config::get()->dead_code_detection) { return; } // Get the count of all known elements $total_count = count($code_base->getMethodMap(), COUNT_RECURSIVE) + count($code_base->getPropertyMap(), COUNT_RECURSIVE) + count($code_base->getConstantMap(), COUNT_RECURSIVE) + count($code_base->getClassMap(), COUNT_RECURSIVE); $i = 0; $analyze_list = function ($list) use($code_base, &$i, $total_count) { foreach ($list as $name => $element) { CLI::progress('dead code', ++$i / $total_count); self::analyzeElementReferenceCounts($code_base, $element); } }; $analyze_map = function ($map) use($code_base, &$i, $total_count) { foreach ($map as $fqsen_string => $list) { foreach ($list as $name => $element) { CLI::progress('dead code', ++$i / $total_count); // Don't worry about internal elements if ($element->getContext()->isInternal()) { continue; } $element_fqsen = $element->getFQSEN(); if ($element_fqsen instanceof FullyQualifiedClassElement) { $class_fqsen = $element->getDefiningClassFQSEN(); // Don't analyze elements defined in a parent // class if ((string) $class_fqsen !== $fqsen_string) { continue; } $defining_class = $element->getDefiningClass($code_base); // Don't analyze elements on interfaces or on // abstract classes, as they're uncallable. if ($defining_class->isInterface() || $defining_class->isAbstract() || $defining_class->isTrait()) { continue; } // Ignore magic methods if ($element instanceof Method && $element->getIsMagic()) { continue; } } self::analyzeElementReferenceCounts($code_base, $element); } } }; $analyze_map($code_base->getMethodMap()); $analyze_map($code_base->getPropertyMap()); $analyze_map($code_base->getConstantMap()); $analyze_list($code_base->getClassMap()); }
public function isSubclassOf(CodeBase $code_base, Type $parent) { $fqsen = $this->asFQSEN(); assert($fqsen instanceof FullyQualifiedClassName); $this_clazz = $code_base->getClassByFQSEN($fqsen); $parent_fqsen = $parent->asFQSEN(); assert($parent_fqsen instanceof FullyQualifiedClassName); $parent_clazz = $code_base->getClassByFQSEN($parent_fqsen); return $this_clazz->isSubclassOf($code_base, $parent_clazz); }
/** * @return Method * Get the closure in this scope or fail real hard */ public function getClosureInScope(CodeBase $code_base) : Method { assert($this->isClosureScope(), "Must be in closure scope to get closure. Actually in {$this}"); return $code_base->getMethod($this->getClosureFQSEN()); }
/** * Take a pass over all functions verifying various * states. * * @return null */ public static function analyzeFunctions(CodeBase $code_base) { $function_count = count($code_base->getFunctionAndMethodSet()); $i = 0; foreach ($code_base->getFunctionAndMethodSet() as $function_or_method) { CLI::progress('method', ++$i / $function_count); if ($function_or_method->isInternal()) { continue; } DuplicateFunctionAnalyzer::analyzeDuplicateFunction($code_base, $function_or_method); ParameterTypesAnalyzer::analyzeParameterTypes($code_base, $function_or_method); // Let any plugins analyze the methods or functions if ($function_or_method instanceof Func) { ConfigPluginSet::instance()->analyzeFunction($code_base, $function_or_method); } else { if ($function_or_method instanceof Method) { ConfigPluginSet::instance()->analyzeMethod($code_base, $function_or_method); } } } }
/** * @return Method[]|\Generator * The set of all alternates to this method */ public function alternateGenerator(CodeBase $code_base) : \Generator { $alternate_id = 0; $fqsen = $this->getFQSEN(); while ($code_base->hasMethodWithFQSEN($fqsen)) { (yield $code_base->getMethodByFQSEN($fqsen)); $fqsen = $fqsen->withAlternateId(++$alternate_id); } }
/** * @param CodeBase * The code base to use in order to find super classes, etc. * * @param $recursion_depth * This thing has a tendency to run-away on me. This tracks * how bad I messed up by seeing how far the expanded types * go * * @return UnionType * Expands class types to all inherited classes returning * a superset of this type. */ public function asExpandedTypes(CodeBase $code_base, int $recursion_depth = 0) : UnionType { return $this->memoize(__METHOD__, function () use($code_base, $recursion_depth) : UnionType { // We're going to assume that if the type hierarchy // is taller than some value we probably messed up // and should bail out. assert($recursion_depth < 20, "Recursion has gotten out of hand for type {$this}"); if ($this->isNativeType()) { return $this->asUnionType(); } $union_type = $this->asUnionType(); $class_fqsen = $this->isGenericArray() ? $this->genericArrayElementType()->asFQSEN() : $this->asFQSEN(); if (!$code_base->hasClassWithFQSEN($class_fqsen)) { return $union_type; } $clazz = $code_base->getClassByFQSEN($class_fqsen); $union_type->addUnionType($this->isGenericArray() ? $clazz->getUnionType()->asGenericArrayTypes() : $clazz->getUnionType()); // Resurse up the tree to include all types $recursive_union_type = new UnionType(); foreach ($union_type->getTypeSet() as $clazz_type) { if ((string) $clazz_type != (string) $this) { $recursive_union_type->addUnionType($clazz_type->asExpandedTypes($code_base, $recursion_depth + 1)); } else { $recursive_union_type->addType($clazz_type); } } return $recursive_union_type; }); }
/** * Take a pass over all functions verifying various * states. * * @return null */ public static function analyzeFunctions(CodeBase $code_base) { $function_count = count($code_base->getMethodMap(), COUNT_RECURSIVE); $i = 0; foreach ($code_base->getMethodMap() as $fqsen_string => $method_map) { foreach ($method_map as $name => $method) { CLI::progress('method', ++$i / $function_count); if ($method->getContext()->isInternal()) { continue; } DuplicateFunctionAnalyzer::analyzeDuplicateFunction($code_base, $method); ParameterTypesAnalyzer::analyzeParameterTypes($code_base, $method); } } }