type holyshared = Engineer<mixed>

PHP、Hack、Ruby、OCaml、Rust、Javascript周りの技術ブログ

Hackのコード自動生成できるようにした

毎回エディタからファイル作成したり、名前空間指定したりするのが面倒なので、コードを生成できるようにした。
コードの生成というか、ファイル生成に近いです。

github.com

使い方

名前空間ディレクトリのペアに対応させるジェネレータをマッピングする設定ファイルを書いて、 オートロードで読み込めるようにするだけで、あとはコマンド実行するだけでクラスファイルを出力してくれる。

設定ファイルを用意する

<?hh //strict

namespace MyPackage\Generators;

use HHPack\Codegen\Cli\{ GeneratorProvider };
use HHPack\Codegen\HackUnit\{ TestClassGenerator };
use HHPack\Codegen\Project\{ PackageClassGenerator };
use function HHPack\Codegen\Cli\{ namespace_of, library, library_test };

final class Generators implements GeneratorProvider {

  // Your package namespace
  const string PACKAGE_NAMESPACE = 'MyPackage';
  const string PACKAGE_TEST_NAMESPACE = 'MyPackage\Test';

  public function generators(): Iterator<Pair<GenerateType, ClassFileGenerator>> {
    // Link package namespace to generator
    yield library(
      namespace_of(static::PACKAGE_NAMESPACE, 'src')
        ->map(PackageClassGenerator::class));

    // Link package test namespace to generator
    yield library_test(
      namespace_of(static::PACKAGE_TEST_NAMESPACE, 'test')
        ->map(TestClassGenerator::class));
  }
}

hh_autoload.jsonに設定を追加

次にhh_autoload.jsonのdevRootsにパスを設定します。
下記の設定だと、configディレクトリが読みこみ対象になります。

{
  "roots": "src",
  "devRoots": "config"
}

hhvm_autoloadが必要なので、インストールしてください。

github.com

これだけでコードが生成できるようになります。

コードの生成

パッケージのクラスを生成する

生成するタイプとクラス名を指定すると、設定ファイルで指定したディレクトリにファイルを生成できます。
ファイルにはあらかじめ名前空間が設定されるので、指定する手間が省けます。

クラスファイル

libを指定すると、クラスファイルを生成します。

vendor/bin/codegen lib Foo\LibClass

テストクラスファイル

testを指定すると、テスト用のクラスファイルを生成します。

vendor/bin/codegen test Foo\LibClassTest

独自ジェネレータ

ClassFileGeneratableインターフェースを実装すれば独自のジェネレータを定義できます。
下記は、プレーンなクラスファイルを生成するジェネレータです。

どういうファイルが生成可能かはhack-codegenを参考にしてください。
shapeを定義したり、trait定義できたり、結構いろんなものが生成できます。

github.com

<?hh //strict

namespace MyPackage\Generators;

use HHPack\Codegen\{GenerateClass, ClassFileGeneratable};
use Facebook\HackCodegen\{ICodegenFactory, CodegenFile, CodegenClass};

final class CustomGenerator implements ClassFileGeneratable {

  public function __construct(private ICodegenFactory $cg) {}

  public static function from(ICodegenFactory $factory): this {
    return new self($factory);
  }

  public function generate(GenerateClass $class): CodegenFile {
    // ファイルを生成するコードを書く
    return $this->cg
      ->codegenFile($class->fileName())
      ->setIsStrict(true)
      ->setNamespace($class->belongsNamespace());
  }

  private function classOf(string $className): CodegenClass {
    return $this->cg->codegenClass($className)->setIsFinal(true);
  }
}