Skip to content

Commit

Permalink
Add support for optional fields within shapes (#76) (#89)
Browse files Browse the repository at this point in the history
* Add support for optional shape fields & nested shapes

* Simplify CodegenShapeMember

* Don't use public class properties

* Add support for CodegenShape_FUTURE

* Remove old codegen

* Use correct codegen file

* Prefer vec

* Don't accept old shape
  • Loading branch information
mwildehahn authored and fredemmott committed May 18, 2018
1 parent 355d40b commit db6d9c5
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 2 deletions.
6 changes: 6 additions & 0 deletions src/CodegenFactoryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ final public function codegenShape(
return new CodegenShape($this->getConfig(), $attrs);
}

final public function codegenShape_FUTURE(
CodegenShapeMember ...$members
): CodegenShape_FUTURE {
return new CodegenShape_FUTURE($this->getConfig(), vec($members));
}

final public function codegenType(string $name): CodegenType {
return new CodegenType($this->getConfig(), $name);
}
Expand Down
49 changes: 49 additions & 0 deletions src/CodegenShapeMember.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?hh // strict
/*
* 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;

final class CodegenShapeMember {

private bool $isOptional = false;

public function __construct(
private string $name,
private mixed $type,
) {
invariant(
is_string($type) || $type instanceof CodegenShape_FUTURE,
"You must provide either a string or shape",
);
}

public function setIsOptional(bool $value = true): this {
$this->isOptional = $value;
return $this;
}

public function getIsOptional(): bool {
return $this->isOptional;
}

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

public function getType(): string {
if ($this->type instanceof ICodeBuilderRenderer) {
return $this->type->render();
}

invariant($this->type !== null, "Somehow type is null");
invariant(is_string($this->type), "Somehow type is not a string");
return $this->type;
}
}
66 changes: 66 additions & 0 deletions src/CodegenShape_FUTURE.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?hh // strict
/*
* 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;

/**
* Generate code for a shape. Please don't use this class directly; instead use
* the function codegenShape_FUTURE. E.g.:
*
* ```
* codegenShape_FUTURE(
* new CodegenShapeMember('x', 'int'),
* (new CodegenShapeMember('y', 'int'))->setIsOptional(),
* )
* ```
*
*/
final class CodegenShape_FUTURE implements ICodeBuilderRenderer {

use HackBuilderRenderer;

private ?string $manualAttrsID = null;

public function __construct(
protected IHackCodegenConfig $config,
private vec<CodegenShapeMember> $members,
) {
}

public function setManualAttrsID(?string $id = null): this {
$this->manualAttrsID = $id;
return $this;
}

public function appendToBuilder(HackBuilder $builder): HackBuilder {
$builder->addLine('shape(')->indent();

foreach ($this->members as $member) {
$prefix = $member->getIsOptional() ? '?' : '';
$builder->addLinef(
"%s'%s' => %s,",
$prefix,
$member->getName(),
$member->getType(),
);
}

$manual_id = $this->manualAttrsID;
if ($manual_id !== null) {
$builder
->ensureNewLine()
->startManualSection($manual_id)
->ensureEmptyLine()
->endManualSection();
}

return $builder->unindent()->add(')');
}
}
9 changes: 7 additions & 2 deletions src/CodegenType.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class CodegenType implements ICodeBuilderRenderer {
use HackBuilderRenderer;

private ?string $type;
private ?CodegenShape $codegenShape;
private ?ICodeBuilderRenderer $codegenShape;
private string $keyword = 'type';

public function __construct(
Expand All @@ -40,11 +40,16 @@ public function setType(string $type): this {
return $this;
}

public function setShape(CodegenShape $codegen_shape): this {
public function setShape(mixed $codegen_shape): this {
invariant(
$this->type === null,
"You can't set both the type and the shape.",
);
invariant(
$codegen_shape instanceof ICodeBuilderRenderer && ($codegen_shape instanceof CodegenShape_FUTURE || $codegen_shape instanceof CodegenShape),
"You must provide either CodegenShape_FUTURE or CodegenShape",
);

$this->codegenShape = $codegen_shape;
return $this;
}
Expand Down
40 changes: 40 additions & 0 deletions test/CodegenShapeFutureTest.codegen
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@generated
!@#$%codegentest:testMultipleNestedShapes
shape(
'test' => shape(
?'point' => shape(
'x' => int,
'y' => int,
),
'url' => string,
),
?'key' => string,
)
!@#$%codegentest:testNestedShape
shape(
'url' => string,
?'point' => shape(
'x' => int,
'y' => int,
),
)
!@#$%codegentest:testNestedShapeLegacy
shape(
'url' => string,
?'point' => shape(
'x' => int,
'y' => int,
),
)
!@#$%codegentest:testShape
shape(
'x' => int,
'y' => int,
'url' => string,
)
!@#$%codegentest:testShapeOptionalFields
shape(
?'x' => int,
?'y' => int,
'url' => string,
)
81 changes: 81 additions & 0 deletions test/CodegenShape_FUTURETest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?hh // strict
/*
* 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;

final class CodegenShapeFutureTest extends CodegenBaseTest {

public function testShape(): void {
$shape = $this
->getCodegenFactory()
->codegenShape_FUTURE(
new CodegenShapeMember('x', 'int'),
new CodegenShapeMember('y', 'int'),
new CodegenShapeMember('url', 'string'),
);

$this->assertUnchanged($shape->render());
}

public function testShapeOptionalFields(): void {
$shape = $this
->getCodegenFactory()
->codegenShape_FUTURE(
(new CodegenShapeMember('x', 'int'))->setIsOptional(),
(new CodegenShapeMember('y', 'int'))->setIsOptional(),
new CodegenShapeMember('url', 'string'),
);

$this->assertUnchanged($shape->render());
}

public function testNestedShape(): void {
$nested = $this
->getCodegenFactory()
->codegenShape_FUTURE(
new CodegenShapeMember('x', 'int'),
new CodegenShapeMember('y', 'int'),
);

$shape = $this
->getCodegenFactory()
->codegenShape_FUTURE(
new CodegenShapeMember('url', 'string'),
(new CodegenShapeMember('point', $nested))->setIsOptional(),
);

$this->assertUnchanged($shape->render());
}

public function testMultipleNestedShapes(): void {
$first = $this
->getCodegenFactory()
->codegenShape_FUTURE(
new CodegenShapeMember('x', 'int'),
new CodegenShapeMember('y', 'int'),
);

$second = $this
->getCodegenFactory()
->codegenShape_FUTURE(
(new CodegenShapeMember('point', $first))->setIsOptional(),
new CodegenShapeMember('url', 'string'),
);

$shape = $this
->getCodegenFactory()
->codegenShape_FUTURE(
new CodegenShapeMember('test', $second),
(new CodegenShapeMember('key', 'string'))->setIsOptional(),
);

$this->assertUnchanged($shape->render());
}
}
6 changes: 6 additions & 0 deletions test/CodegenTypeTest.codegen
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ type Point = shape(
'y' => int,
);

!@#$%codegentest:testShape_FUTURE
type Point = shape(
'x' => int,
'y' => int,
);

!@#$%codegentest:testType
type Point = (int, int);

12 changes: 12 additions & 0 deletions test/CodegenTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,16 @@ public function testShape(): void {

$this->assertUnchanged($type->render());
}

public function testShape_FUTURE(): void {
$cgf = $this->getCodegenFactory();
$type = $cgf
->codegenType('Point')
->setShape($cgf->codegenShape_FUTURE(
new CodegenShapeMember('x', 'int'),
new CodegenShapeMember('y', 'int'),
));

$this->assertUnchanged($type->render());
}
}

0 comments on commit db6d9c5

Please sign in to comment.