Example #1
0
 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");
 }
Example #2
0
 public function testFullyQualifiedClassName()
 {
     $this->assertFQSENEqual(FullyQualifiedClassName::make('Name\\Space', 'A'), '\\Name\\Space\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::make('', 'A'), '\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromFullyQualifiedString('A'), '\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromFullyQualifiedString('\\Name\\Space\\A'), '\\Name\\Space\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromFullyQualifiedString('\\Namespace\\A,1'), '\\Namespace\\a,1');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromStringInContext('\\Namespace\\A', $this->context), '\\Namespace\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromStringInContext('A', $this->context), '\\a');
 }
Example #3
0
 public function testSimple()
 {
     $context = new Context();
     $context_namespace = $context->withNamespace('\\A');
     $context_class = $context_namespace->withClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\A\\B'));
     $context_method = $context_namespace->withMethodFQSEN(FullyQualifiedMethodName::fromFullyQualifiedString('\\A\\b::c'));
     $this->assertTrue(!empty($context));
     $this->assertTrue(!empty($context_namespace));
     $this->assertTrue(!empty($context_class));
     $this->assertTrue(!empty($context_method));
 }
Example #4
0
 public function testSimple()
 {
     $context = new Context();
     $context_namespace = $context->withNamespace('\\A');
     $context_class = $context_namespace->withScope(new ClassScope($context_namespace->getScope(), FullyQualifiedClassName::fromFullyQualifiedString('\\A\\B')));
     $context_method = $context_namespace->withScope(new FunctionLikeScope($context_namespace->getScope(), FullyQualifiedMethodName::fromFullyQualifiedString('\\A\\b::c')));
     $this->assertTrue(!empty($context));
     $this->assertTrue(!empty($context_namespace));
     $this->assertTrue(!empty($context_class));
     $this->assertTrue(!empty($context_method));
 }
 /**
  * @param $fully_qualified_string
  * An FQSEN string like '\Namespace\Class::methodName'
  */
 public static function fromFullyQualifiedString(string $fully_qualified_string)
 {
     assert(false !== strpos($fully_qualified_string, '::'), "Fully qualified class element lacks '::' delimiter in {$fully_qualified_string}.");
     list($fully_qualified_class_name_string, $name_string) = explode('::', $fully_qualified_string);
     $fully_qualified_class_name = FullyQualifiedClassName::fromFullyQualifiedString($fully_qualified_class_name_string);
     // Split off the alternate ID
     $parts = explode(',', $name_string);
     $name = $parts[0];
     $alternate_id = (int) ($parts[1] ?? 0);
     assert(is_int($alternate_id), "Alternate must be an integer in {$fully_qualified_string}");
     return static::make($fully_qualified_class_name, $name, $alternate_id);
 }
Example #6
0
 /**
  * @param array
  * A map from column name to value
  *
  * @return Model
  * An instance of the model derived from row data
  */
 public static function fromRow(array $row) : Clazz
 {
     $parent_fqsen = $row['parent_class_fqsen'] ? FullyQualifiedClassName::fromFullyQualifiedString($row['parent_class_fqsen']) : null;
     $interface_fqsen_list = array_map(function (string $fqsen_string) {
         return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
     }, array_filter(explode('|', $row['interface_fqsen_list'])));
     $trait_fqsen_list = array_map(function (string $fqsen_string) {
         return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
     }, array_filter(explode('|', $row['trait_fqsen_list'])));
     $clazz = new ClazzElement(unserialize(base64_decode($row['context'])), $row['name'], UnionType::fromFullyQualifiedString($row['type']), (int) $row['flags'], $parent_fqsen, $interface_fqsen_list, $trait_fqsen_list);
     return new Clazz($clazz);
 }
Example #7
0
 public static function createSchema() : Schema
 {
     $schema = new Schema('File', [new Column('file_path', Column::TYPE_STRING, true), new Column('modification_time', Column::TYPE_INT)]);
     $schema->addAssociation(new ListAssociation('FileClassFQSEN', Column::TYPE_STRING, function (File $file, array $class_fqsen_string_list) {
         $file->getFile()->setClassFQSENList(array_map(function (string $fqsen_string) {
             return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
         }, $class_fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FullyQualifiedClassName $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getClassFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FileMethodFQSEN', Column::TYPE_STRING, function (File $file, array $method_fqsen_string_list) {
         $file->getFile()->setMethodFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedMethodName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $method_fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getMethodFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FilePropertyFQSEN', Column::TYPE_STRING, function (File $file, array $fqsen_string_list) {
         $file->getFile()->setPropertyFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedPropertyName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getPropertyFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FileConstantFQSEN', Column::TYPE_STRING, function (File $file, array $fqsen_string_list) {
         $file->getFile()->setConstantFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedConstantName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getConstantFQSENList());
     }));
     return $schema;
 }
 /**
  * @param $fully_qualified_string
  * An FQSEN string like '\Namespace\Class::methodName'
  */
 public static function fromFullyQualifiedString(string $fully_qualified_string)
 {
     assert(false !== strpos($fully_qualified_string, '::'), "Fully qualified class element lacks '::' delimiter");
     list($fully_qualified_class_name_string, $name_string) = explode('::', $fully_qualified_string);
     $fully_qualified_class_name = FullyQualifiedClassName::fromFullyQualifiedString($fully_qualified_class_name_string);
     // Make sure that we're actually getting a class
     // name reference back
     assert($fully_qualified_class_name instanceof FullyQualifiedClassName, "FQSEN must be an instanceof FullyQualifiedClassName");
     // Split off the alternate ID
     $parts = explode(',', $name_string);
     $name = $parts[0];
     $alternate_id = (int) ($parts[1] ?? 0);
     assert(is_int($alternate_id), "Alternate must be an integer");
     return static::make($fully_qualified_class_name, $name, $alternate_id);
 }
Example #9
0
 /**
  * @return array
  * A map from alias to target
  */
 private function aliasTargetMapFromUseNode(Node $node, string $prefix = '') : array
 {
     assert($node->kind == \ast\AST_USE, 'Method takes AST_USE nodes');
     $map = [];
     foreach ($node->children ?? [] as $child_node) {
         $target = $child_node->children['name'];
         if (empty($child_node->children['alias'])) {
             if (($pos = strrpos($target, '\\')) !== false) {
                 $alias = substr($target, $pos + 1);
             } else {
                 $alias = $target;
             }
         } else {
             $alias = $child_node->children['alias'];
         }
         if ($node->flags == T_FUNCTION) {
             $parts = explode('\\', $target);
             $function_name = array_pop($parts);
             $target = FullyQualifiedFunctionName::make(implode('\\', $parts), $function_name);
         } else {
             $target = FullyQualifiedClassName::fromFullyQualifiedString($prefix . '\\' . $target);
         }
         $map[$alias] = [$child_node->flags, $target];
     }
     return $map;
 }
Example #10
0
 /**
  * @param CodeBase $code_base
  * A reference to the entire code base in which this
  * context exists
  *
  * @param ReflectionClass $class
  * A reflection class representing a builtin class.
  *
  * @return Clazz
  * A Class structural element representing the given named
  * builtin.
  */
 public static function fromReflectionClass(CodeBase $code_base, \ReflectionClass $class) : Clazz
 {
     // Build a set of flags based on the constitution
     // of the built-in class
     $flags = 0;
     if ($class->isFinal()) {
         $flags = \ast\flags\CLASS_FINAL;
     } else {
         if ($class->isInterface()) {
             $flags = \ast\flags\CLASS_INTERFACE;
         } else {
             if ($class->isTrait()) {
                 $flags = \ast\flags\CLASS_TRAIT;
             }
         }
     }
     if ($class->isAbstract()) {
         $flags |= \ast\flags\CLASS_ABSTRACT;
     }
     $context = new Context();
     // Build a base class element
     $clazz = new Clazz($context, $class->getName(), UnionType::fromStringInContext($class->getName(), $context), $flags);
     // If this class has a parent class, add it to the
     // class info
     if ($parent_class = $class->getParentClass()) {
         $parent_class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\' . $parent_class->getName());
         $clazz->setParentClassFQSEN($parent_class_fqsen);
     }
     foreach ($class->getDefaultProperties() as $name => $value) {
         // TODO: whats going on here?
         $reflection_property = new \ReflectionProperty($class->getName(), $name);
         $property = new Property($context->withClassFQSEN($clazz->getFQSEN()), $name, Type::fromObject($value)->asUnionType(), 0);
         $clazz->addProperty($code_base, $property);
     }
     foreach ($class->getInterfaceNames() as $name) {
         $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getTraitNames() as $name) {
         $clazz->addTraitFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getConstants() as $name => $value) {
         $clazz->addConstant($code_base, new Constant($context, $name, Type::fromObject($value)->asUnionType(), 0));
     }
     foreach ($class->getMethods() as $reflection_method) {
         $method_list = Method::methodListFromReflectionClassAndMethod($context->withClassFQSEN($clazz->getFQSEN()), $code_base, $class, $reflection_method);
         foreach ($method_list as $method) {
             $clazz->addMethod($code_base, $method);
         }
     }
     return $clazz;
 }
Example #11
0
 /**
  * @return array
  * A map from alias to target
  */
 private function aliasTargetMapFromUseNode(Node $node, string $prefix = '') : array
 {
     assert($node->kind == \ast\AST_USE, 'Method takes AST_USE nodes');
     $map = [];
     foreach ($node->children ?? [] as $child_node) {
         $target = $child_node->children['name'];
         if (empty($child_node->children['alias'])) {
             if (($pos = strrpos($target, '\\')) !== false) {
                 $alias = substr($target, $pos + 1);
             } else {
                 $alias = $target;
             }
         } else {
             $alias = $child_node->children['alias'];
         }
         // if AST_USE does not have any flags set, then its AST_USE_ELEM
         // children will (this will be for AST_GROUP_USE)
         if ($node->flags !== 0) {
             $target_node = $node;
         } else {
             $target_node = $child_node;
         }
         if ($target_node->flags == T_FUNCTION) {
             $parts = explode('\\', $target);
             $function_name = array_pop($parts);
             $target = FullyQualifiedFunctionName::make($prefix . '\\' . implode('\\', $parts), $function_name);
         } else {
             if ($target_node->flags == T_CONST) {
                 $parts = explode('\\', $target);
                 $name = array_pop($parts);
                 $target = FullyQualifiedGlobalConstantName::make($prefix . '\\' . implode('\\', $parts), $name);
             } else {
                 $target = FullyQualifiedClassName::fromFullyQualifiedString($prefix . '\\' . $target);
             }
         }
         $map[$alias] = [$target_node->flags, $target];
     }
     return $map;
 }
Example #12
0
 /**
  * @return FullyQualifiedGlobalStructuralElement
  * The namespace mapped name for the given flags and name
  */
 public function getNamespaceMapFor(int $flags, string $name) : FullyQualifiedGlobalStructuralElement
 {
     $name = strtolower($name);
     // Look for the mapping on the part before a
     // slash
     $name_parts = explode('\\', $name, 2);
     $suffix = '';
     if (count($name_parts) > 1) {
         $name = $name_parts[0];
         $suffix = $name_parts[1];
     }
     assert(!empty($this->namespace_map[$flags][$name]), "No namespace defined for name");
     assert($this->namespace_map[$flags][$name] instanceof FQSEN, "Namespace map was not an FQSEN");
     $fqsen = $this->namespace_map[$flags][$name];
     if (!$suffix) {
         return $fqsen;
     }
     switch ($flags) {
         case \ast\flags\USE_NORMAL:
             return FullyQualifiedClassName::fromFullyQualifiedString((string) $fqsen . '\\' . $suffix);
         case \ast\flags\USE_FUNCTION:
             return FullyQualifiedFunctionName::fromFullyQualifiedString((string) $fqsen . '\\' . $suffix);
     }
     assert(false, "Unknown flag {$flags}");
     return $fqsen;
 }
Example #13
0
 /**
  * @return void
  */
 public function unserialize($serialized)
 {
     list($file_ref, $serialized) = explode('^', $serialized);
     parent::unserialize($file_ref);
     list($namespace, $class_fqsen, $method_fqsen, $closure_fqsen) = explode('|', $serialized);
     $this->namespace = $namespace;
     $this->class_fqsen = $class_fqsen ? FullyQualifiedClassName::fromFullyQualifiedString($class_fqsen) : null;
     // Determine if we have a method or a function
     if (false === strpos($method_fqsen, '::')) {
         $this->method_fqsen = $method_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($method_fqsen) : null;
     } else {
         $this->method_fqsen = $method_fqsen ? FullyQualifiedMethodName::fromFullyQualifiedString($method_fqsen) : null;
     }
     $this->closure_fqsen = $closure_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($closure_fqsen) : null;
 }
Example #14
0
File: Clazz.php Project: etsy/phan
 /**
  * @param CodeBase $code_base
  * A reference to the entire code base in which this
  * context exists
  *
  * @param ReflectionClass $class
  * A reflection class representing a builtin class.
  *
  * @return Clazz
  * A Class structural element representing the given named
  * builtin.
  */
 public static function fromReflectionClass(CodeBase $code_base, \ReflectionClass $class) : Clazz
 {
     // Build a set of flags based on the constitution
     // of the built-in class
     $flags = 0;
     if ($class->isFinal()) {
         $flags = \ast\flags\CLASS_FINAL;
     } elseif ($class->isInterface()) {
         $flags = \ast\flags\CLASS_INTERFACE;
     } elseif ($class->isTrait()) {
         $flags = \ast\flags\CLASS_TRAIT;
     }
     if ($class->isAbstract()) {
         $flags |= \ast\flags\CLASS_ABSTRACT;
     }
     $context = new Context();
     $class_fqsen = FullyQualifiedClassName::fromStringInContext($class->getName(), $context);
     // Build a base class element
     $clazz = new Clazz($context, $class->getName(), UnionType::fromStringInContext($class->getName(), $context), $flags, $class_fqsen);
     // If this class has a parent class, add it to the
     // class info
     if ($parent_class = $class->getParentClass()) {
         $parent_class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\' . $parent_class->getName());
         $parent_type = $parent_class_fqsen->asType();
         $clazz->setParentType($parent_type);
     }
     // n.b.: public properties on internal classes don't get
     //       listed via reflection until they're set unless
     //       they have a default value. Therefore, we don't
     //       bother iterating over `$class->getProperties()`
     //       `$class->getStaticProperties()`.
     foreach ($class->getDefaultProperties() as $name => $value) {
         $property_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $property_fqsen = FullyQualifiedPropertyName::make($clazz->getFQSEN(), $name);
         $property = new Property($property_context, $name, Type::fromObject($value)->asUnionType(), 0, $property_fqsen);
         $clazz->addProperty($code_base, $property, new None());
     }
     foreach (UnionType::internalPropertyMapForClassName($clazz->getName()) as $property_name => $property_type_string) {
         $property_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $property_type = UnionType::fromStringInContext($property_type_string, new Context());
         $property_fqsen = FullyQualifiedPropertyName::make($clazz->getFQSEN(), $property_name);
         $property = new Property($property_context, $property_name, $property_type, 0, $property_fqsen);
         $clazz->addProperty($code_base, $property, new None());
     }
     foreach ($class->getInterfaceNames() as $name) {
         $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getTraitNames() as $name) {
         $clazz->addTraitFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getConstants() as $name => $value) {
         $constant_fqsen = FullyQualifiedClassConstantName::make($clazz->getFQSEN(), $name);
         $constant = new ClassConstant($context, $name, Type::fromObject($value)->asUnionType(), 0, $constant_fqsen);
         $clazz->addConstant($code_base, $constant);
     }
     foreach ($class->getMethods() as $reflection_method) {
         $method_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $method_list = FunctionFactory::methodListFromReflectionClassAndMethod($method_context, $code_base, $class, $reflection_method);
         foreach ($method_list as $method) {
             $clazz->addMethod($code_base, $method, new None());
         }
     }
     return $clazz;
 }
Example #15
0
 /**
  * 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(Decl $node) : Context
 {
     if ($node->flags & \ast\flags\CLASS_ANONYMOUS) {
         $class_name = (new ContextNode($this->code_base, $this->context, $node))->getUnqualifiedNameForAnonymousClass();
     } else {
         $class_name = (string) $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
     $class_context = $this->context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? -1);
     $clazz = new Clazz($class_context, $class_name, UnionType::fromStringInContext($class_name, $this->context), $node->flags ?? 0);
     // Override the FQSEN with the found alternate ID
     $clazz->setFQSEN($class_fqsen);
     // Get a comment on the class declaration
     $comment = Comment::fromStringInContext($node->docComment ?? '', $this->context);
     $clazz->setIsDeprecated($comment->isDeprecated());
     $clazz->setSuppressIssueList($comment->getSuppressIssueList());
     // 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 = (new ContextNode($this->code_base, $this->context, $node->children['implements']))->getQualifiedNameList();
         foreach ($interface_list as $name) {
             $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString($name));
         }
     }
     // Update the context to signal that we're now
     // within a class context.
     $context = $class_context->withClassFQSEN($class_fqsen);
     return $context;
 }
Example #16
0
 public function unserialize($serialized)
 {
     list($file_ref, $serialized) = explode('^', $serialized);
     parent::unserialize($file_ref);
     list($namespace, $is_conditional, $class_fqsen, $method_fqsen, $closure_fqsen) = explode('|', $serialized);
     $this->namespace = $namespace;
     $this->is_conditional = (bool) $is_conditional;
     $this->class_fqsen = $class_fqsen ? FullyQualifiedClassName::fromFullyQualifiedString($class_fqsen) : null;
     $this->method_fqsen = $method_fqsen ? FullyQualifiedMethodName::fromFullyQualifiedString($method_fqsen) : null;
     $this->closure_fqsen = $closure_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($closure_fqsen) : null;
 }
Example #17
0
 /**
  * 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(Decl $node) : Context
 {
     if ($node->flags & \ast\flags\CLASS_ANONYMOUS) {
         $class_name = (new ContextNode($this->code_base, $this->context, $node))->getUnqualifiedNameForAnonymousClass();
     } else {
         $class_name = (string) $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);
     assert($class_fqsen instanceof FullyQualifiedClassName, "The class FQSEN must be a FullyQualifiedClassName");
     // 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
     $class_context = $this->context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? -1);
     $class = new Clazz($class_context, $class_name, $class_fqsen->asUnionType(), $node->flags ?? 0, $class_fqsen);
     // Set the scope of the class's context to be the
     // internal scope of the class
     $class_context = $class_context->withScope($class->getInternalScope());
     // Get a comment on the class declaration
     $comment = Comment::fromStringInContext($node->docComment ?? '', $this->context);
     // Add any template types parameterizing a generic class
     foreach ($comment->getTemplateTypeList() as $template_type) {
         $class->getInternalScope()->addTemplateType($template_type);
     }
     $class->setIsDeprecated($comment->isDeprecated());
     $class->setSuppressIssueList($comment->getSuppressIssueList());
     // Add the class to the code base as a globally
     // accessible object
     $this->code_base->addClass($class);
     // 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
         $class->setParentType($parent_fqsen->asType());
     }
     // If the class explicitly sets its overriding extension type,
     // set that on the class
     $inherited_type_option = $comment->getInheritedTypeOption();
     if ($inherited_type_option->isDefined()) {
         $class->setParentType($inherited_type_option->get());
     }
     // Add any implemeneted interfaces
     if (!empty($node->children['implements'])) {
         $interface_list = (new ContextNode($this->code_base, $this->context, $node->children['implements']))->getQualifiedNameList();
         foreach ($interface_list as $name) {
             $class->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString($name));
         }
     }
     return $class_context;
 }