/** * Visit a node with kind `\ast\AST_CLASS` * * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitClass(Node $node) : Context { if ($node->flags & \ast\flags\CLASS_ANONYMOUS) { $class_name = AST::unqualifiedNameForAnonymousClassNode($node, $this->context); } else { $class_name = $node->name; } // This happens now and then and I have no idea // why. if (empty($class_name)) { return $this->context; } assert(!empty($class_name), "Class must have name in {$this->context}"); $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $this->context); // Hunt for an available alternate ID if necessary $alternate_id = 0; while ($this->code_base->hasClassWithFQSEN($class_fqsen)) { $class_fqsen = $class_fqsen->withAlternateId(++$alternate_id); } // Build the class from what we know so far $clazz = new Clazz($this->context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? -1), $class_name, UnionType::fromStringInContext($class_name, $this->context), $node->flags ?? 0); // Override the FQSEN with the found alternate ID $clazz->setFQSEN($class_fqsen); // Add the class to the code base as a globally // accessible object $this->code_base->addClass($clazz); // Look to see if we have a parent class if (!empty($node->children['extends'])) { $parent_class_name = $node->children['extends']->children['name']; // Check to see if the name isn't fully qualified if ($node->children['extends']->flags & \ast\flags\NAME_NOT_FQ) { if ($this->context->hasNamespaceMapFor(T_CLASS, $parent_class_name)) { // Get a fully-qualified name $parent_class_name = (string) $this->context->getNamespaceMapFor(T_CLASS, $parent_class_name); } else { $parent_class_name = $this->context->getNamespace() . '\\' . $parent_class_name; } } // The name is fully qualified. Make sure it looks // like it is if (0 !== strpos($parent_class_name, '\\')) { $parent_class_name = '\\' . $parent_class_name; } $parent_fqsen = FullyQualifiedClassName::fromStringInContext($parent_class_name, $this->context); // Set the parent for the class $clazz->setParentClassFQSEN($parent_fqsen); } // Add any implemeneted interfaces if (!empty($node->children['implements'])) { $interface_list = AST::qualifiedNameList($this->context, $node->children['implements']); foreach ($interface_list as $name) { $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString($name)); } } // Update the context to signal that we're now // within a class context. $context = $clazz->getContext()->withClassFQSEN($class_fqsen); return $context; }