src/CodegenClass.hack (107 lines of code) (raw):
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
namespace Facebook\HackCodegen;
use namespace HH\Lib\{Regex, Str};
/**
* Generate code for a class.
*
* To construct an instance, use `ICodegenFactory::codegenClass()`.
*
* ```
* $factory->codegenClass('Foo')
* ->setExtends('bar')
* ->setIsFinal()
* ->addMethod(codegen_method('foobar'))
* ->render();
* ```
*/
final class CodegenClass extends CodegenClassish {
use CodegenClassWithInterfaces;
private ?string $extendsClass;
private string $declComment = '';
private bool $isFinal = false;
private bool $isAbstract = false;
private bool $isXHP = false;
private ?CodegenConstructor $constructor = null;
/** @selfdocumenting */
public function setIsFinal(bool $value = true): this {
$this->isFinal = $value;
return $this;
}
/** @selfdocumenting */
public function setIsAbstract(bool $value = true): this {
$this->isAbstract = $value;
return $this;
}
/** @selfdocumenting */
public function setIsXHP(bool $value = true): this {
$this->isXHP = $value;
return $this;
}
/** Set the parent class of the generated class. */
public function setExtends(string $name): this {
return $this->setExtendsf('%s', $name);
}
/**
* Set the parent class of the generated class, using a %-placeholder format
* string.
*/
public function setExtendsf(Str\SprintfFormatString $name, mixed ...$args): this {
$this->extendsClass = \vsprintf($name, $args);
return $this;
}
/**
* Get the name of the parent class, or `null` if there is none.
*/
public function getExtends(): ?string {
return $this->extendsClass;
}
/** @selfdocumenting */
public function setConstructor(CodegenConstructor $constructor): this {
$this->constructor = $constructor;
return $this;
}
/**
* Add a comment before the class.
*
* For example:
*
* ```
* $class->addDeclComment('\/* HH_FIXME[4040] *\/');
* ```
*
* @param $comment the full comment, including delimiters
*/
public function addDeclComment(string $comment): this {
$this->declComment .= $comment."\n";
return $this;
}
/**
* Add a factory function to wrap instantiations of to the class.
*
* For example, if `MyClass` accepts a single `string` parameter, it would
* generate:
*
* ```
* function MyClass(string $s): MyClass {
* return new MyClass($s);
* }
* ```
*
* @param $params the parameters to generate, including types. If `null`,
* it will be inferred from the constructor if set.
*/
public function addConstructorWrapperFunc(
?Container<string> $params = null,
): this {
// Check if parameters are specified explicitly
$param_full = null;
if ($params) {
$param_full = $params;
} else if ($this->constructor) {
$param_full = $this->constructor->getParameters();
}
if ($param_full !== null) {
// Extract variable names from parameters
$param_names = vec[];
$re = re"/\\\$[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/";
foreach ($param_full as $str) {
foreach (Regex\every_match($str, $re) as $match) {
$param_names[] = $match[0];
}
}
$params_str = Str\join( $param_names, ', ');
$body = 'return new '.$this->getName().'('.$params_str.');';
$this->wrapperFunc = (
new CodegenFunction($this->config, $this->getName())
)
->addParameters($param_full)
->setReturnType($this->getName())
->setBody($body);
} else {
$this->wrapperFunc = (
new CodegenFunction($this->config, $this->getName())
)
->setReturnType($this->getName())
->setBody('return new '.$this->getName().'();');
}
return $this;
}
<<__Override>>
protected function buildDeclaration(HackBuilder $builder): void {
$generics_dec = $this->buildGenericsDeclaration();
$builder->addWithSuggestedLineBreaksf(
'%s%s%s%s%s%s',
$this->declComment,
$this->isAbstract ? 'abstract ' : '',
$this->isFinal ? 'final ' : '',
$this->isXHP ? 'xhp ' : '',
'class '.$this->name.$generics_dec,
$this->extendsClass !== null
? HackBuilder::DELIMITER.'extends '.$this->extendsClass
: '',
);
$this->renderInterfaceList($builder, 'implements');
}
private function buildConstructor(HackBuilder $builder): void {
$constructor = $this->constructor;
if ($constructor) {
$builder->ensureEmptyLine()->addRenderer($constructor);
}
}
<<__Override>>
protected function appendBodyToBuilder(HackBuilder $builder): void {
$this->buildTraits($builder);
$this->buildConsts($builder);
$this->buildXHPAttributes($builder);
$this->buildVars($builder);
$this->buildManualDeclarations($builder);
$this->buildConstructor($builder);
$this->buildMethods($builder);
}
}