diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6ff3d8b..005bffc 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -11,12 +11,12 @@ jobs: # Run tests on all OS's and HHVM versions, even if one fails fail-fast: false matrix: - os: [ ubuntu ] + os: [ ubuntu-20.04 ] hhvm: - '4.128' - - latest - - nightly - runs-on: ${{matrix.os}}-latest + - '4.153' + - '4.168' + runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v2 - uses: hhvm/actions/hack-lint-test@master diff --git a/README.md b/README.md index 9da0edf..d6a13ed 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ use \Facebook\HackRouter\Codegen; final class UpdateCodegen { public function main(): void { - Codegen::forTree( + $codegen = await Codegen::forTree( __DIR__.'/../src/', shape( 'controllerBase' => WebController::class, @@ -32,7 +32,8 @@ final class UpdateCodegen { 'class' => 'Router', ), ), - )->build; + ); + $codegen->build(); } } ``` diff --git a/hhast-lint.json b/hhast-lint.json index 3082049..38a0ce2 100644 --- a/hhast-lint.json +++ b/hhast-lint.json @@ -9,12 +9,6 @@ { "patterns": [ "tests/examples/codegen/*" ], "disableAllAutoFixes": true - }, - { - "patterns": [ "tests/examples/codegen/lookup-path.php" ], - "disabledLinters": [ - "Facebook\\HHAST\\Linters\\StrictModeOnlyLinter" - ] } ] } diff --git a/src/Codegen.hack b/src/Codegen.hack index f0da82a..abbd840 100644 --- a/src/Codegen.hack +++ b/src/Codegen.hack @@ -91,14 +91,23 @@ final class Codegen { ?'discardChanges' => bool, ); + /** + * @deprecated, use forTreeAsync() instead. + */ public static function forTree( string $source_root, self::TCodegenConfig $config, ): Codegen { // leaving for now as it's a public API - /* HHAST_IGNORE_ERROR[DontUseAsioJoin] fix before final release */ - return - new self(\HH\Asio\join(TreeParser::fromPathAsync($source_root)), $config); + /* HHAST_IGNORE_ERROR[DontUseAsioJoin] Kept for backward compatibility. */ + return \HH\Asio\join(static::forTreeAsync($source_root, $config)); + } + + public static async function forTreeAsync( + string $source_root, + self::TCodegenConfig $config, + ): Awaitable { + return new self(await TreeParser::fromPathAsync($source_root), $config); } <<__Memoize>> diff --git a/src/RouterCLILookupCodegenBuilder.hack b/src/RouterCLILookupCodegenBuilder.hack index 7badf35..efcf1b6 100644 --- a/src/RouterCLILookupCodegenBuilder.hack +++ b/src/RouterCLILookupCodegenBuilder.hack @@ -25,6 +25,34 @@ final class RouterCLILookupCodegenBuilder { private CodegenGeneratedFrom $generatedFrom; private HackCodegenFactory $cg; + const string CLI_LOOKUP_UTILITY_DOC_TEXT = <<<'TEXT' +A quick way to validate that a path or url routes to the controller you expect. + +Usage: path/to/this/utility <%path or url%>. +The output will look something like this: + +``` +$ tests/examples/codegen/lookup-path.php /a-string/123/derp +HEAD: Facebook\HackRouter\CodeGen\Tests\GetRequestExampleController +GET: Facebook\HackRouter\CodeGen\Tests\GetRequestExampleController +``` + +You may also copy paste the whole url: +``` +$ tests/examples/codegen/lookup-path.php http://localhost:8080/a-string/123/derp?query=abcd#fragment +HEAD: Facebook\HackRouter\CodeGen\Tests\GetRequestExampleController +GET: Facebook\HackRouter\CodeGen\Tests\GetRequestExampleController +``` + +If the given path does not match any controllers, you'll get the following result: +``` +$ tests/examples/codegen/lookup-path.php /a/b/c +No controller found for '/a/b/c'. +``` + +You can edit the manual sections to change the router and formatter used. +TEXT; + public function __construct( private IHackCodegenConfig $codegenConfig, ) { @@ -75,12 +103,13 @@ final class RouterCLILookupCodegenBuilder { ->addClass($this->getCodegenClass($router_classname, $utility_classname)) ->addFunction( $this->cg->codegenFunction('hack_router_cli_lookup_generated_main') + ->setDocBlock(static::CLI_LOOKUP_UTILITY_DOC_TEXT) ->addEmptyUserAttribute('__EntryPoint') ->setReturnType('void') ->setBodyf( "%s\n". '$argv = '. - '\\Facebook\\TypeAssert\\matches>('. + '\\Facebook\\TypeAssert\\matches>('. "\\HH\\global_get('argv'));\n". "(new %s())->main(\$argv);\n", $this->getInitCode(), @@ -216,7 +245,7 @@ final class RouterCLILookupCodegenBuilder { private function getMainMethod(): CodegenMethod { return $this->cg->codegenMethod('main') - ->addParameter('KeyedContainer $argv') + ->addParameter('vec $argv') ->setReturnType('void') ->setBody( $this->cg->codegenHackBuilder() @@ -229,6 +258,8 @@ final class RouterCLILookupCodegenBuilder { ->addLine('\\fprintf(\\STDERR, "Usage: %s PATH\n", $argv[0]);') ->addLine('exit(1);') ->endIfBlock() + ->addInlineComment('The parser is very lenient, `?: $path` is almost never needed.') + ->addLine('$path = \parse_url($path, \PHP_URL_PATH) ?: $path;') ->addAssignment( '$controllers', '$this->getControllersForPath($path)', diff --git a/tests/CodegenTestCase.hack b/tests/CodegenTestCase.hack index 8b2a629..db1b708 100644 --- a/tests/CodegenTestCase.hack +++ b/tests/CodegenTestCase.hack @@ -11,9 +11,9 @@ namespace Facebook\HackRouter; use function Facebook\FBExpect\expect; final class CodegenTestCase extends \Facebook\HackTest\HackTest { - public function testCanCreateForTree(): void { + public async function testCanCreateForTreeAsync(): Awaitable { // Just test it parses and we can create an instance - $codegen = Codegen::forTree(__DIR__.'/examples/', shape()); + $codegen = await Codegen::forTreeAsync(__DIR__.'/examples/', shape()); expect($codegen)->toBeInstanceOf(Codegen::class); } } diff --git a/tests/examples/codegen/lookup-path.php b/tests/examples/codegen/lookup-path.php index dc17654..6aab771 100644 --- a/tests/examples/codegen/lookup-path.php +++ b/tests/examples/codegen/lookup-path.php @@ -7,20 +7,50 @@ * To re-generate this file run vendor/hhvm/hacktest/bin/hacktest * * - * @partially-generated SignedSource<> + * @partially-generated SignedSource<<61e1a0bcff805162c34d005bb526d587>> */ namespace Facebook\HackRouter\CodeGen\Tests\Generated; +/** + * A quick way to validate that a path or url routes to the controller you + * expect. + * + * Usage: path/to/this/utility <%path or url%>. + * The output will look something like this: + * + * ``` + * $ tests/examples/codegen/lookup-path.php /a-string/123/derp + * HEAD: Facebook\HackRouter\CodeGen\Tests\GetRequestExampleController + * GET: Facebook\HackRouter\CodeGen\Tests\GetRequestExampleController + * ``` + * + * You may also copy paste the whole url: + * ``` + * $ tests/examples/codegen/lookup-path.php + * http://localhost:8080/a-string/123/derp?query=abcd#fragment + * HEAD: Facebook\HackRouter\CodeGen\Tests\GetRequestExampleController + * GET: Facebook\HackRouter\CodeGen\Tests\GetRequestExampleController + * ``` + * + * If the given path does not match any controllers, you'll get the following + * result: + * ``` + * $ tests/examples/codegen/lookup-path.php /a/b/c + * No controller found for '/a/b/c'. + * ``` + * + * You can edit the manual sections to change the router and formatter used. + */ <<__EntryPoint>> function hack_router_cli_lookup_generated_main(): void { /* BEGIN MANUAL SECTION init */ $autoloader = null; - $autoloader_candidates = ImmSet { + $autoloader_candidates = vec[ __DIR__.'/vendor/autoload.hack', __DIR__.'/../vendor/autoload.hack', __DIR__.'/../../vendor/autoload.hack', __DIR__.'/../../../vendor/autoload.hack', - }; + ]; foreach ($autoloader_candidates as $candidate) { if (\file_exists($candidate)) { $autoloader = $candidate; @@ -35,7 +65,7 @@ function hack_router_cli_lookup_generated_main(): void { \Facebook\AutoloadMap\initialize(); /* END MANUAL SECTION */ - $argv = \Facebook\TypeAssert\matches>(\HH\global_get('argv')); + $argv = \Facebook\TypeAssert\matches>(\HH\global_get('argv')); (new MySiteRouterCLILookup())->main($argv); } @@ -75,12 +105,14 @@ private function getControllersForPath( } } - public function main(KeyedContainer $argv): void { + public function main(vec $argv): void { $path = $argv[1] ?? null; if ($path === null) { \fprintf(\STDERR, "Usage: %s PATH\n", $argv[0]); exit(1); } + // The parser is very lenient, `?: $path` is almost never needed. + $path = \parse_url($path, \PHP_URL_PATH) ?: $path; $controllers = $this->getControllersForPath($path); if ($controllers->isEmpty()) { \printf("No controller found for '%s'.\n", $path);