Skip to content

Commit

Permalink
Add support for codegen xhp classes (#138)
Browse files Browse the repository at this point in the history
* Add support for codegen xhp classes
Adds xhp attribute codegen to traits and classes.
No error is thrown when attributes are added to non-xhp classes.
This syntax is valid, but not useful in the current class.
Inheritance maybe?

* Fix broken copy paste

* Compatibility with older hsl versions

* Whitespace lint

* Update xhp attribute doc

I copy pasted property and did not touch up the doc well enough.
  • Loading branch information
lexidor authored Mar 31, 2021
1 parent 05c98f4 commit 6d20695
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 5 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "facebook/hack-codegen",
"description": "Hack Codegen is a library for programatically generating Hack code",
"description": "Hack Codegen is a library for programmatically generating Hack code",
"keywords": ["code generation", "Hack"],
"require": {
"hhvm": "^4.80",
Expand Down
11 changes: 10 additions & 1 deletion src/CodegenClass.hack
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ final class CodegenClass extends CodegenClassish {
private string $declComment = '';
private bool $isFinal = false;
private bool $isAbstract = false;
private bool $isXHP = false;
private ?CodegenConstructor $constructor = null;

/** @selfdocumenting */
Expand All @@ -46,6 +47,12 @@ final class CodegenClass extends CodegenClassish {
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);
Expand Down Expand Up @@ -148,10 +155,11 @@ final class CodegenClass extends CodegenClassish {
$generics_dec = $this->buildGenericsDeclaration();

$builder->addWithSuggestedLineBreaksf(
'%s%s%s%s%s',
'%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
Expand All @@ -172,6 +180,7 @@ final class CodegenClass extends CodegenClassish {
protected function appendBodyToBuilder(HackBuilder $builder): void {
$this->buildTraits($builder);
$this->buildConsts($builder);
$this->buildXHPAttributes($builder);
$this->buildVars($builder);
$this->buildManualDeclarations($builder);
$this->buildConstructor($builder);
Expand Down
35 changes: 33 additions & 2 deletions src/CodegenClassish.hack
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace Facebook\HackCodegen;

use namespace HH\Lib\{C, Str, Vec};
use namespace Facebook\HackCodegen\_Private\C as CP;
use namespace Facebook\HackCodegen\_Private\{C as CP, Vec as VecP};

/**
* Abstract class to generate class-like definitions.
Expand All @@ -33,6 +33,7 @@ abstract class CodegenClassish implements ICodeBuilderRenderer {
protected vec<CodegenMethod> $methods = vec[];
private vec<CodegenUsesTrait> $traits = vec[];
protected vec<CodegenConstantish> $consts = vec[];
protected vec<CodegenXHPAttribute> $xhpAttributes = vec[];
protected vec<CodegenProperty> $vars = vec[];
private bool $isConsistentConstruct = false;
protected bool $hasManualFooter = false;
Expand Down Expand Up @@ -153,6 +154,20 @@ abstract class CodegenClassish implements ICodeBuilderRenderer {
return $this;
}

/** @selfdocumenting */
public function addXhpAttribute(CodegenXHPAttribute $attribute): this {
$this->xhpAttributes[] = $attribute;
return $this;
}

/** @selfdocumenting */
public function addXhpAttributes(Traversable<CodegenXHPAttribute> $attributes): this {
foreach($attributes as $attr) {
$this->addXhpAttribute($attr);
}
return $this;
}

/** @selfdocumenting */
public function setDocBlock(string $comment): this {
$this->docBlock = $comment;
Expand Down Expand Up @@ -315,6 +330,22 @@ abstract class CodegenClassish implements ICodeBuilderRenderer {
}
}

protected function buildXHPAttributes(HackBuilder $builder): void {
if (C\is_empty($this->xhpAttributes)) {
return;
}
$builder->ensureNewLine();
$builder->addLine('attribute')->indent();

$attributes = $this->xhpAttributes;
$last = VecP\pop_backx(inout $attributes);
foreach($attributes as $attr) {
$builder->addRenderer($attr);
$builder->addLine(',');
}
$builder->addRenderer($last)->addLine(';')->unindent();
}

protected function buildVars(HackBuilder $builder): void {
if (C\is_empty($this->vars)) {
return;
Expand Down Expand Up @@ -380,7 +411,7 @@ abstract class CodegenClassish implements ICodeBuilderRenderer {
$generated_from =
$this->generatedFrom ? $this->generatedFrom->render() : null;

$doc_block_parts = \array_filter(varray[$this->docBlock, $generated_from]);
$doc_block_parts = Vec\filter_nulls(vec[$this->docBlock, $generated_from]);

if ($doc_block_parts) {
$builder->addDocBlock(Str\join($doc_block_parts, "\n\n"));
Expand Down
11 changes: 11 additions & 0 deletions src/CodegenFactoryTrait.hack
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,15 @@ trait CodegenFactoryTrait implements ICodegenFactory {
final public function codegenNewtype(string $name): CodegenType {
return (new CodegenType($this->getConfig(), $name))->newType();
}

final public function codegenXHPAttribute(string $name): CodegenXHPAttribute {
return new CodegenXHPAttribute($this->getConfig(), $name);
}

final public function codegenXHPAttributef(
Str\SprintfFormatString $format,
mixed ...$args
): CodegenXHPAttribute {
return $this->codegenXHPAttribute(\vsprintf($format, $args));
}
}
1 change: 1 addition & 0 deletions src/CodegenTrait.hack
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ final class CodegenTrait extends CodegenClassish {
$this->buildRequires($builder);
$this->buildTraits($builder);
$this->buildConsts($builder);
$this->buildXHPAttributes($builder);
$this->buildVars($builder);
$this->buildManualDeclarations($builder);
$this->buildMethods($builder);
Expand Down
116 changes: 116 additions & 0 deletions src/CodegenXHPAttribute.hack
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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\Str;

/**
* Generate code for an xhp attribute. Please don't use this class directly;
* instead use the function ICodegenFactory->codegenAttribute. E.g.:
*
* ICodegenFactory->codegenAttribute('src')
* ->setType('string')
* ->setInlineComment('A script src must be a valid URI')
* ->render();
*/
final class CodegenXHPAttribute implements ICodeBuilderRenderer {

use HackBuilderRenderer;

private ?string $comment;
private ?string $type;
private ?string $value;
private ?XHPAttributeDecorator $decorator;

public function __construct(
protected IHackCodegenConfig $config,
private string $name,
) {}

public function getName(): string {
return $this->name;
}

public function getType(): ?string {
return $this->type;
}

public function getValue(): mixed {
return $this->value;
}

public function setDecorator(?XHPAttributeDecorator $decorator): this {
invariant(
$decorator is null || $this->value is null,
'XHP attributes with a default value can not have an %s decorator',
xhp_attribute_decorator_to_string($decorator),
);
$this->decorator = $decorator;
return $this;
}

public function setInlineComment(string $comment): this {
$this->comment = $comment;
return $this;
}

/**
* Set the type of the member var. In Hack, if it's nullable
* you should prepend the question mark, e.g. "?string".
* XHP enums should be avoided, but you can specify "enum { 'foo' }"
* as a literal string if you need it.
*/
public function setType(string $type): this {
$this->type = $type;
return $this;
}

public function setTypef(
Str\SprintfFormatString $format,
mixed ...$args
): this {
return $this->setType(\vsprintf($format, $args));
}

/**
* Set the initial value for the variable. You can pass numbers, strings,
* arrays, etc, and it will generate the code to render those values.
*/
public function setValue<T>(
T $value,
IHackBuilderValueRenderer<T> $renderer,
): this {
invariant(
$this->decorator is null,
'XHP attributes with an %s decorator can not have a default value',
xhp_attribute_decorator_to_string($this->decorator),
);
$this->value = $renderer->render($this->config, $value);
return $this;
}

public function appendToBuilder(HackBuilder $builder): HackBuilder {
$value = $this->value;

return $builder
->addDocBlock($this->comment)
->addIf($this->type is nonnull, $this->type.' ')
->add($this->name)
->addIf($this->value is nonnull, ' = '.$value)
->addIf(
$this->decorator is nonnull,
' '.
xhp_attribute_decorator_to_string(
$this->decorator ?? XHPAttributeDecorator::REQUIRED,
),
);
}

}
20 changes: 19 additions & 1 deletion src/ICodegenFactory.hack
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ interface ICodegenFactory {
public function codegenProperty(string $name): CodegenProperty;

/**
* Generate a class or trait property, using a %-placehodler format string
* Generate a class or trait property, using a %-placeholder format string
* for the property name.
*
* @see codegenProperty
Expand Down Expand Up @@ -307,4 +307,22 @@ interface ICodegenFactory {
* @see codegenType
*/
public function codegenNewtype(string $name): CodegenType;

/**
* Generate a class of trait xhp attribute.
*
* @see codegenXHPAttributef
*/
public function codegenXHPAttribute(string $name): CodegenXHPAttribute;

/**
* Generate a class or trait xhp attribute, using a %-placeholder format
* string for the attribute name.
*
* @see codegenXHPAttribute
*/
public function codegenXHPAttributef(
Str\SprintfFormatString $format,
mixed ...$args
): CodegenXHPAttribute;
}
26 changes: 26 additions & 0 deletions src/XHPAttributeDecorator.hack
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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;

enum XHPAttributeDecorator: int {
REQUIRED = 0;
LATE_INIT = 1;
}

function xhp_attribute_decorator_to_string(
XHPAttributeDecorator $decorator,
): string {
switch ($decorator) {
case XHPAttributeDecorator::REQUIRED:
return '@required';
case XHPAttributeDecorator::LATE_INIT:
return '@lateinit';
}
}
17 changes: 17 additions & 0 deletions tests/CodegenClassTest.codegen
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,20 @@ class JKRowling
IRonWeasley {
}

!@#$%codegentest:testXHPClassWithAttributes
xhp class a {

const string BETWEEN_CONSTS = '';
attribute
/**
* The web is a magical place where a string with a set structure can be
* the key to visiting some remote place on the internet where you can find
* content made by other people.
*/
string href @required,
string target = 'about:blank',
string hreflang @lateinit;

private null $andProps;
}

Loading

0 comments on commit 6d20695

Please sign in to comment.