/*
* 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;
final class HackBuilderTest extends CodegenBaseTest {
private function getHackBuilder(): HackBuilder {
return $this->getCodegenFactory()->codegenHackBuilder();
}
public function testIfBlock(): void {
$body = $this
->getHackBuilder()
->startIfBlockf('$value callSomeThingReallyLongName".
'ReallyReallyLongName',
Vector {
'$someSmallParameter',
"\$foobarbaz_alphabetagama +".
$del.
"\$foobarbaz_alphabetagamaa +".
$del.
"\$foobarbaz_alphabetagamatheta_foobarbaz",
},
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testLiteralMap(): void {
$body = $this
->getHackBuilder()
->addValue(
Map {
'MY_ENUM::A' => 'ANOTHER_ENUM::A',
'MY_ENUM::B' => 'ANOTHER_ENUM::B',
},
HackBuilderValues::map(
HackBuilderKeys::literal(),
HackBuilderValues::literal(),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testAnotherConfig(): void {
$body = (new HackBuilder(new TestAnotherCodegenConfig()))
->addInlineComment(
'Here we wrap at 40 chars because we use a different configuration.',
)
->startIfBlock('$do_that')
->add('return ')
->addValue(
vec[1, 2, 3],
HackBuilderValues::valueArray(HackBuilderValues::export()),
)
->closeStatement()
->endIfBlock();
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testSwitchBodyWithReturnsInCaseAndDefault(): void {
// Gosh, I have no idea what names of football shots are!
$players = Vector {
dict['name' => 'Ronaldo', 'favorite_shot' => 'freeKick'],
dict['name' => 'Messi', 'favorite_shot' => 'slideKick'],
dict['name' => 'Maradona', 'favorite_shot' => 'handOfGod'],
};
$body = $this
->getHackBuilder()
->startSwitch('$soccer_player')
->addCaseBlocks(
$players,
($player, $body) ==> {
$body
->addCase($player['name'], HackBuilderValues::export())
->addLinef('$shot = new Shot(\'%s\');', $player['favorite_shot'])
->returnCasef('$shot->execute()');
},
)
->addDefault()
->addLine('invariant_violation(\'ball deflated!\');')
->endDefault()
->endSwitch();
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testSwitchBodyWithBreaksInCaseAndDefault(): void {
// Gosh, I have no idea what names of football shots are!
$players = Vector {
dict['name' => 'Ronaldo', 'favorite_shot' => 'freeKick'],
dict['name' => 'Messi', 'favorite_shot' => 'slideKick'],
dict['name' => 'Maradona', 'favorite_shot' => 'handOfGod'],
};
$body = $this
->getHackBuilder()
->startSwitch('$soccer_player')
->addCaseBlocks(
$players,
($player, $body) ==> {
$body
->addCase($player['name'], HackBuilderValues::export())
->addLinef('$shot = new Shot(\'%s\');', $player['favorite_shot'])
->breakCase();
},
)
->addDefault()
->addLine('invariant_violation(\'ball deflated!\');')
->endDefault()
->endSwitch();
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testSwitchBodyWithMultipleCasesWithoutBreaks(): void {
// Gosh, I have no idea what names of football shots are!
$players = Vector {
dict['name' => 'Ronaldo', 'favorite_shot' => 'freeKick'],
dict['name' => 'Messi', 'favorite_shot' => 'slideKick'],
dict['name' => 'Maradona', 'favorite_shot' => 'handOfGod'],
};
$body = $this
->getHackBuilder()
->startSwitch('$soccer_player')
->addCaseBlocks(
$players,
($player, $body) ==> {
$body
->addCase($player['name'], HackBuilderValues::export())
->addLinef('$shot = new Shot(\'%s\');', $player['favorite_shot'])
->unindent();
},
)
->addDefault()
->addLine('invariant_violation(\'ball deflated!\');')
->endDefault()
->endSwitch();
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testExportedVectorDoesNotHaveHHPrefix(): void {
$body = $this
->getHackBuilder()
->add('$foo = ')
->addValue(
Vector {1, 2, 3},
HackBuilderValues::vector(HackBuilderValues::export()),
)
->getCode();
expect($body)->toContainSubstring('Vector');
expect($body)->toNotContainSubstring('HH');
expect_with_context(static::class, $body)->toBeUnchanged();
}
public function testVectorOfExportedVectors(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
Vector {Vector {'$foo', '$bar'}, Vector {'$herp', '$derp'}},
HackBuilderValues::vector(
HackBuilderValues::vector(HackBuilderValues::export()),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testVectorOfLiteralVectors(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
Vector {Vector {'$foo', '$bar'}, Vector {'$herp', '$derp'}},
HackBuilderValues::vector(
HackBuilderValues::vector(HackBuilderValues::literal()),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testVectorOfMaps(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
Vector {Map {'foo' => 'bar'}, Map {'herp' => 'derp'}},
HackBuilderValues::vector(
HackBuilderValues::map(
HackBuilderKeys::export(),
HackBuilderValues::export(),
),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testImmVectorOfImmVectors(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
ImmVector {ImmVector {'abc', 'def'}, ImmVector {'ghi', 'jkl'}},
HackBuilderValues::immVector(
HackBuilderValues::immVector(HackBuilderValues::export()),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testImmMapOfImmMaps(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
ImmMap {
'foo' => ImmMap {'a' => 12, 'b' => 34},
'bar' => ImmMap {'c' => 45},
},
HackBuilderValues::immMap(
HackBuilderKeys::export(),
HackBuilderValues::immMap(
HackBuilderKeys::export(),
HackBuilderValues::export(),
),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testImmSet(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
ImmSet {'abc', 'def'},
HackBuilderValues::immSet(HackBuilderValues::export()),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testVec(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
vec['foo', 'bar'],
HackBuilderValues::vec(HackBuilderValues::export()),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testKeyset(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
keyset['foo', 'bar'],
HackBuilderValues::keyset(HackBuilderValues::export()),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testDict(): void {
$body = $this
->getHackBuilder()
->addAssignment(
'$foo',
dict['foo' => 1, 'bar' => 2],
HackBuilderValues::dict(
HackBuilderKeys::export(),
HackBuilderValues::export(),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testClassnameMap(): void {
$body = $this
->getHackBuilder()
->addValue(
Map {self::class => \stdClass::class},
HackBuilderValues::map(
HackBuilderKeys::classname(),
HackBuilderValues::classname(),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
public function testLambdaMap(): void {
$body = $this
->getHackBuilder()
->addValue(
Map {'foo' => 'bar'},
HackBuilderValues::map(
HackBuilderKeys::lambda(($_config, $v) ==> "'key:".$v."'"),
HackBuilderValues::lambda(($_config, $v) ==> "'value:".$v."'"),
),
);
expect_with_context(static::class, $body->getCode())->toBeUnchanged();
}
}
final class TestAnotherCodegenConfig implements IHackCodegenConfig {
public function getFileHeader(): ?Vector {
return Vector {'Codegen Tests'};
}
public function getSpacesPerIndentation(): int {
return 4;
}
public function getMaxLineLength(): int {
return 40;
}
public function shouldUseTabs(): bool {
return false;
}
public function getRootDir(): string {
return '/';
}
public function getFormatter(): ?ICodegenFormatter {
return null;
}
}