diff --git a/README.md b/README.md index 3be3a05..875e13c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # hack2php [![Build Status](https://travis-ci.org/codeneric/hack2php.svg?branch=master)](https://travis-ci.org/codeneric/hack2php) -hack2php is a project which aims to implement a compiler to translate Hack files to PHP 5.4 files. +hack2php is a project which aims to implement a compiler to translate Hack files to PHP 7 files. This ability becomes useful when you have no control over the environment in which your code is supposed to run, but you still want to write your code in Hack. -An example might be the development of WordPress plugins or themes. +An example might be the development of WordPress plugins or themes. E.g. this Hack code: + ```php `). + +Compile: `vendor/bin/hack2php > ` -# Getting started -Clone this repository and run `hhvm composer.phar install` +To validate the php code you can run `php -l ` -Compile a Hack file to PHP: `./bin/hack2php ` +## Tests -# Tests -There is only one test (HackToPhpTest). It reads each Hack file from the example-files directory, compiles it to PHP and checks the PHP syntax for errors. If no error were found, the test succeeds. Otherwise it fails. -To add a test, simply create a new Hack file in example-files. -Run the test: ` hhvm vendor/bin/phpunit` +There is only one test (HackToPhpTest). It reads each Hack file from the example-files directory, compiles it to PHP and checks the PHP syntax for errors. If no error were found, the test succeeds. Otherwise it fails. +To add a test, simply create a new Hack file in example-files. +Run the test: `hhvm vendor/bin//hacktest tests/` -# Contributing -If you find an issue you can help to fix it. Please add a Hack file which is not compiled correctly in the example-files folder and create a PR. -Or you can fix the issue directly :) -But please add an example Hack file to the example-files folder nonetheless. +## Contributing +If you find an issue you can help to fix it. Please add a Hack file which is not compiled correctly in the example-files folder and create a PR. +Or you can fix the issue directly :) +But please add an example Hack file to the example-files folder nonetheless. diff --git a/bin/hack2php b/bin/hack2php index ee91b42..d18c717 100755 --- a/bin/hack2php +++ b/bin/hack2php @@ -1,5 +1,5 @@ #!/usr/bin/env hhvm -> +async function transpileAsync(): Awaitable { + + $root = realpath(__DIR__.'/..'); + $found_autoloader = false; + while (true) { + $autoloader = $root.'/vendor/hh_autoload.php'; + if (file_exists($autoloader)) { + $found_autoloader = true; + require_once($autoloader); + break; + } + if ($root === '') { + break; + } + $parts = explode('/', $root); + array_pop(&$parts); + $root = implode('/', $parts); } - if ($root === '') { - break; + + if (!$found_autoloader) { + fprintf(STDERR, "Failed to find autoloader.\n"); + + exit(1); } - $parts = explode('/', $root); - array_pop(&$parts); - $root = implode('/', $parts); -} -if (!$found_autoloader) { - fprintf(STDERR, "Failed to find autoloader.\n"); - exit(1); -} -LinterCLI::main(); + $code = await TranspilerCLI::runAsync(); + exit($code); +} diff --git a/composer.json b/composer.json index c1cbaf5..63d3f2b 100644 --- a/composer.json +++ b/composer.json @@ -6,11 +6,10 @@ "hhvm/hhast": "^3.27" }, "require-dev": { - "phpunit/phpunit": "^5.7", - "91carriage/phpunit-hhi": "^5.7", "facebook/fbexpect": "^0.4.0|^1.0.0", "facebook/hack-codegen": "~3.0.3", - "hhvm/hhvm-autoload": "^1.5" + "hhvm/hhvm-autoload": "^1.5", + "hhvm/hacktest": "^1.3" }, "license": "MIT", "authors": [ diff --git a/composer.lock b/composer.lock index 1b140e5..01a10d0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,140 +4,146 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6a251c36badb3066e48292007114b903", + "content-hash": "6d78726c01dc42a1309f5e5d52dc3bad", "packages": [ { - "name": "facebook/hh-clilib", - "version": "v1.1.2", + "name": "facebook/difflib", + "version": "v1.0", "source": { "type": "git", - "url": "https://github.com/hhvm/hh-clilib.git", - "reference": "2772c82a8f5fc715d5a2fbe801563f36fdda9943" + "url": "https://github.com/hhvm/difflib.git", + "reference": "09505c7d66224fa7e5a8f775825bd70f05e57b01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hhvm/hh-clilib/zipball/2772c82a8f5fc715d5a2fbe801563f36fdda9943", - "reference": "2772c82a8f5fc715d5a2fbe801563f36fdda9943", + "url": "https://api.github.com/repos/hhvm/difflib/zipball/09505c7d66224fa7e5a8f775825bd70f05e57b01", + "reference": "09505c7d66224fa7e5a8f775825bd70f05e57b01", "shasum": "" }, "require": { - "hhvm/hsl": "^3.26", - "hhvm/type-assert": "^3.2" + "hhvm/hsl": "^3.28" }, "require-dev": { - "91carriage/phpunit-hhi": "^5.7", - "facebook/fbexpect": "^1.1.0", - "hhvm/hhast": "^3.27.0", - "phpunit/phpunit": "^5.7" + "facebook/fbexpect": "^2.1.2", + "hhvm/hacktest": "^1.0", + "hhvm/hhast": "^3.28" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.2.x-dev" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "time": "2018-06-20T03:25:12+00:00" + "time": "2018-09-24T22:01:02+00:00" }, { - "name": "fredemmott/hack-error-suppressor", - "version": "v1.0.1", + "name": "facebook/hh-clilib", + "version": "v2.0.1", "source": { "type": "git", - "url": "https://github.com/fredemmott/hack-error-suppressor.git", - "reference": "cb145c771b50f4f4eeec8c9cac4740df3d486a12" + "url": "https://github.com/hhvm/hh-clilib.git", + "reference": "ef39116c527ac2496c985657f93af0253c9d8227" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fredemmott/hack-error-suppressor/zipball/cb145c771b50f4f4eeec8c9cac4740df3d486a12", - "reference": "cb145c771b50f4f4eeec8c9cac4740df3d486a12", + "url": "https://api.github.com/repos/hhvm/hh-clilib/zipball/ef39116c527ac2496c985657f93af0253c9d8227", + "reference": "ef39116c527ac2496c985657f93af0253c9d8227", "shasum": "" }, + "require": { + "hhvm/hsl": "^3.26", + "hhvm/hsl-experimental": "^3.29.5", + "hhvm/type-assert": "^3.2" + }, "require-dev": { - "91carriage/phpunit-hhi": "^5.6", - "phpunit/phpunit": "^5.7" + "facebook/fbexpect": "^2.1.0", + "hhvm/hacktest": "^1.0.0", + "hhvm/hhast": "^3.29.1" }, "type": "library", - "autoload": { - "classmap": [ - "src/" - ] + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "ISC" - ], - "authors": [ - { - "name": "Fred Emmott", - "email": "fred@fredemmott.co.uk" - } + "MIT" ], - "description": "Suppress HHVM's automatic conversion of typechecker errors to fatals.", - "time": "2017-05-02T03:07:37+00:00" + "time": "2018-12-05T19:10:19+00:00" }, { "name": "hhvm/hhast", - "version": "v3.27.1", + "version": "v3.30.0", "source": { "type": "git", "url": "https://github.com/hhvm/hhast.git", - "reference": "8cd58881ccecaf81a0c330dd204f4e0089ef9104" + "reference": "525177bd5328483af2c2bc2a4d23be0599a8e418" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hhvm/hhast/zipball/8cd58881ccecaf81a0c330dd204f4e0089ef9104", - "reference": "8cd58881ccecaf81a0c330dd204f4e0089ef9104", + "url": "https://api.github.com/repos/hhvm/hhast/zipball/525177bd5328483af2c2bc2a4d23be0599a8e418", + "reference": "525177bd5328483af2c2bc2a4d23be0599a8e418", "shasum": "" }, "require": { - "facebook/hh-clilib": "^1.1.1", - "hhvm": "^3.26.0", - "hhvm/hsl": "^1.0.0|^3.26.0", + "facebook/difflib": "^1.0.0", + "facebook/hh-clilib": "^2.0.0", + "hhvm": "3.30.*", + "hhvm/hsl": "^3.30.0", "hhvm/type-assert": "^3.1" }, "require-dev": { - "91carriage/phpunit-hhi": "^5.7", - "facebook/fbexpect": "^1.1.0", - "facebook/hack-codegen": "~3.0.3", - "hhvm/hhvm-autoload": "^1.5", - "phpunit/phpunit": "^5.7" + "facebook/fbexpect": "^2.1.1", + "facebook/hack-codegen": "^4.0", + "hhvm/hacktest": "^1.3", + "hhvm/hhvm-autoload": "^1.5" }, "bin": [ "bin/hhast-lint", - "bin/hhast-inspect" + "bin/hhast-inspect", + "bin/hhast-migrate" ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "time": "2018-06-28T15:01:11+00:00" + "time": "2018-12-14T22:20:05+00:00" }, { "name": "hhvm/hhvm-autoload", - "version": "v1.6.6", + "version": "v1.8", "source": { "type": "git", "url": "https://github.com/hhvm/hhvm-autoload.git", - "reference": "2808fe06fa850532270c54155c0bdab1bf0f67da" + "reference": "5d3fdbf7c203b05e13edab7b4f9981b19877f368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hhvm/hhvm-autoload/zipball/2808fe06fa850532270c54155c0bdab1bf0f67da", - "reference": "2808fe06fa850532270c54155c0bdab1bf0f67da", + "url": "https://api.github.com/repos/hhvm/hhvm-autoload/zipball/5d3fdbf7c203b05e13edab7b4f9981b19877f368", + "reference": "5d3fdbf7c203b05e13edab7b4f9981b19877f368", "shasum": "" }, "require": { "composer-plugin-api": "^1.0", - "fredemmott/hack-error-suppressor": "^1.0", - "hhvm": "^3.23" + "hhvm": "^3.28" }, "replace": { "facebook/hhvm-autoload": "1.*" }, "require-dev": { - "91carriage/phpunit-hhi": "^5.5", - "facebook/fbexpect": "^1.1", - "phpunit/phpunit": "^5.5" + "facebook/fbexpect": "^2.1", + "hhvm/hacktest": "^1.0" }, "bin": [ "bin/hh-autoload" @@ -146,7 +152,10 @@ "extra": { "class": [ "Facebook\\AutoloadMap\\ComposerPlugin" - ] + ], + "branch-alias": { + "dev-master": "1.x-dev" + } }, "autoload": { "classmap": [ @@ -160,64 +169,110 @@ ] }, "notification-url": "https://packagist.org/downloads/", - "time": "2018-06-12T20:21:07+00:00" + "time": "2019-01-22T22:32:09+00:00" }, { "name": "hhvm/hsl", - "version": "v3.27.0", + "version": "v3.30.0", "source": { "type": "git", "url": "https://github.com/hhvm/hsl.git", - "reference": "f43f027681402f0a6807325400a761106fa10274" + "reference": "9a5f26ad5e7c078281f66dd74117af3a3b785e50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hhvm/hsl/zipball/f43f027681402f0a6807325400a761106fa10274", - "reference": "f43f027681402f0a6807325400a761106fa10274", + "url": "https://api.github.com/repos/hhvm/hsl/zipball/9a5f26ad5e7c078281f66dd74117af3a3b785e50", + "reference": "9a5f26ad5e7c078281f66dd74117af3a3b785e50", "shasum": "" }, "require": { - "hhvm": "^3.27.0", - "hhvm/hhvm-autoload": "^1.4" + "hhvm": "^3.30.0", + "hhvm/hhvm-autoload": "^1.7" }, "require-dev": { - "91carriage/phpunit-hhi": "^5.7", - "facebook/fbexpect": "^1.0.0", - "phpunit/phpunit": "^5.7" + "facebook/fbexpect": "^2.0.0", + "hhvm/hacktest": "^1.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "The Hack Standard Library", - "time": "2018-06-07T01:25:22+00:00" + "time": "2018-12-14T21:18:17+00:00" + }, + { + "name": "hhvm/hsl-experimental", + "version": "v3.30.3", + "source": { + "type": "git", + "url": "https://github.com/hhvm/hsl-experimental.git", + "reference": "989c59471877e1e7dae78f758213bcc0bb30f461" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hhvm/hsl-experimental/zipball/989c59471877e1e7dae78f758213bcc0bb30f461", + "reference": "989c59471877e1e7dae78f758213bcc0bb30f461", + "shasum": "" + }, + "require": { + "hhvm": "^3.30.0", + "hhvm/hhvm-autoload": "^1.7", + "hhvm/hsl": "^3.30.0" + }, + "require-dev": { + "facebook/fbexpect": "^2.0 <2.4.0", + "hhvm/hacktest": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.30.x-dev", + "dev-3.30.x": "3.30.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Hack Standard Library - Experimental Additions", + "time": "2019-02-20T21:35:23+00:00" }, { "name": "hhvm/type-assert", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/hhvm/type-assert.git", - "reference": "9bbe7cac2ff831142d74203479e72046cbc932f8" + "reference": "ab4d907d5fd4894a6833b5926cafc9fb9c894ff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hhvm/type-assert/zipball/9bbe7cac2ff831142d74203479e72046cbc932f8", - "reference": "9bbe7cac2ff831142d74203479e72046cbc932f8", + "url": "https://api.github.com/repos/hhvm/type-assert/zipball/ab4d907d5fd4894a6833b5926cafc9fb9c894ff9", + "reference": "ab4d907d5fd4894a6833b5926cafc9fb9c894ff9", "shasum": "" }, "require": { - "hhvm": "^3.23.0", + "hhvm": "^3.29.0", "hhvm/hhvm-autoload": "^1.4", - "hhvm/hsl": "^1.0|^3.26.0" + "hhvm/hsl": "^3.29.0" }, "require-dev": { - "91carriage/phpunit-hhi": "~5.1", - "facebook/fbexpect": "^0.4.0|^1.0.0", - "phpunit/phpunit": "~5.1" + "facebook/fbexpect": "^2.0.0", + "hhvm/hacktest": "^1.0", + "hhvm/hhast": "^3.27" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -227,17 +282,17 @@ "TypeAssert", "hack" ], - "time": "2018-05-09T18:23:07+00:00" + "time": "2018-12-14T23:24:44+00:00" } ], "packages-dev": [ { "name": "91carriage/phpunit-hhi", - "version": "5.7.4", + "version": "5.7.5", "source": { "type": "git", "url": "https://git.simon.geek.nz/91-carriage/phpunit-hhi.git", - "reference": "29e50b1130dc6d72266460f1f303ba1f24937a40" + "reference": "0526087ebf783199e4b90bf5ecb997b94a6f130f" }, "require": { "hhvm": ">=3.24.3" @@ -278,7 +333,8 @@ "phpunit", "testing" ], - "time": "2018-03-02T23:37:33+00:00" + "abandoned": "hhvm/hacktest", + "time": "2018-08-15T10:19:54+00:00" }, { "name": "doctrine/instantiator", @@ -430,6 +486,47 @@ ], "time": "2018-05-18T15:49:36+00:00" }, + { + "name": "hhvm/hacktest", + "version": "v1.3", + "source": { + "type": "git", + "url": "https://github.com/hhvm/hacktest.git", + "reference": "ed8eaf18c1a4443e0a735707ac3d0c2749273f08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hhvm/hacktest/zipball/ed8eaf18c1a4443e0a735707ac3d0c2749273f08", + "reference": "ed8eaf18c1a4443e0a735707ac3d0c2749273f08", + "shasum": "" + }, + "require": { + "facebook/hh-clilib": "^2.0.0", + "hhvm": "^3.28.0", + "hhvm/hhvm-autoload": "^1.6", + "hhvm/hsl": "^3.26", + "hhvm/type-assert": "^3.2" + }, + "require-dev": { + "facebook/fbexpect": "^2.3.0", + "hhvm/hhast": "^3.27" + }, + "bin": [ + "bin/hacktest" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Hack Test Library", + "time": "2018-11-30T17:17:20+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.8.1", @@ -632,16 +729,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -653,12 +750,12 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -691,7 +788,7 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1081,6 +1178,7 @@ "mock", "xunit" ], + "abandoned": true, "time": "2017-06-30T09:13:00+00:00" }, { @@ -1598,25 +1696,28 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.8.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-ctype": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -1649,20 +1750,20 @@ "polyfill", "portable" ], - "time": "2018-04-30T19:57:29+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/yaml", - "version": "v4.1.1", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e" + "reference": "d461670ee145092b7e2a56c1da7118f19cadadb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/80e4bfa9685fc4a09acc4a857ec16974a9cd944e", - "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d461670ee145092b7e2a56c1da7118f19cadadb0", + "reference": "d461670ee145092b7e2a56c1da7118f19cadadb0", "shasum": "" }, "require": { @@ -1681,7 +1782,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -1708,24 +1809,25 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "webmozart/assert", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -1758,7 +1860,7 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2018-12-25T11:19:39+00:00" } ], "aliases": [], diff --git a/composer.phar b/composer.phar index 7f6d896..b8ce13f 100644 Binary files a/composer.phar and b/composer.phar differ diff --git a/example-files/function.php b/example-files/function.php index c7937fd..89288e3 100644 --- a/example-files/function.php +++ b/example-files/function.php @@ -1,4 +1,4 @@ - $dependencies, string $version, ): void {}; +*/ diff --git a/hh_autoload.json b/hh_autoload.json index cca5e72..b6fe352 100644 --- a/hh_autoload.json +++ b/hh_autoload.json @@ -1,5 +1,5 @@ { - "roots": [], + "roots": ["bin", "src"], "devRoots": [], "devFailureHandler": "Facebook\\AutoloadMap\\HHClientFallbackHandler" } diff --git a/hhast-lint.json b/hhast-lint.json index c1703db..097f636 100644 --- a/hhast-lint.json +++ b/hhast-lint.json @@ -1,20 +1,5 @@ { "roots": [ - "example-files/" - ], - "builtinLinters": "none", - "namespaceAliases": { - "HHAST": "Facebook\\HHAST\\Linters" - }, - "extraLinters": [ - "HHAST\\HackToPHPLinter" - ], - "overrides": [ - { - "patterns": [ - "codegen/*" - ], - "disableAllAutoFixes": true - } + "src/", "bin/" ] } \ No newline at end of file diff --git a/out/common.php b/out/common.php deleted file mode 100644 index d9b66e6..0000000 --- a/out/common.php +++ /dev/null @@ -1,314 +0,0 @@ -get_charset_collate(); - $table_name = "codeneric_phmm_comments"; - /* - if(UAM_Config::$ENV === 'development'){ - $wpdb->query( " DROP TABLE $table_name" ); - }*/ - - - /*UNSAFE_EXPR*/ - $sql = "CREATE TABLE $table_name ( - id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, - time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - content text DEFAULT '' NOT NULL, - project_id bigint(20) UNSIGNED NOT NULL, - attachment_id bigint(20) UNSIGNED NOT NULL, - wp_user_id bigint(20) UNSIGNED NOT NULL, - client_id bigint(20) UNSIGNED NOT NULL, - UNIQUE KEY id (id) - ) $charset_collate;"; - - - dbDelta( /*UNSAFE_EXPR*/ $sql ); - - } - - private - $write_comment = array( - 'attachment_id' => 0, - 'wp_user_id' => 0, - 'project_id' => 0, - 'content' => "test", - 'client_id' => 0, - ); - - public function setUp() { - parent::setUp(); - - // // probably better to use wpdb query or insert here to avoid inconsistency - // // i.e. at this point we to do not know if save_comment works - // $state = CommentService::save_comment($this->write_comment); - - // $this->assertSame( - // $state, - // true, - // 'Saving the test comment during setup failed!', - // ); - } - - public function tearDown() { - parent::tearDown(); - - /*UNSAFE_EXPR*/ - $wpdb = $GLOBALS['wpdb']; - // get comment count with image id - /*UNSAFE_EXPR*/ - $wpdb->delete( - 'codeneric_phmm_comments', - array( - 'attachment_id' => 0, - 'wp_user_id' => 0, - 'project_id' => 0, - 'content' => "test", - 'client_id' => 0, - ) ); - } - - private function prepareComment() { - $state = CommentService::save_comment($this->write_comment); - $this->assertSame( - $state, - true, - 'Saving the test comment during setup failed!' ); - } - - public function testGetCommentsCount() { - $this->prepareComment(); - // need to setup array with image ids to match get_comments_count function parameters - $image_id = array(0); - // call comment api - $count = CommentService::get_comments_counts($image_id); - // count is an array and is expected to be size 1 - $this->assertSame(is_array($count), true, 'Count is not an array!'); - $this->assertSame( - count($count), - 1, - 'Size of count array is expected to be 1.' ); - // image id is 0, so we check field 0 for the count of comments for image 0 - // expected to be 1 according to setup. - $this->assertSame( - $count[0], - 1, - 'Comment count for image_id 0 is expected to be 1.' ); - } - - public function testGetComentsCountForProject() { - $this->prepareComment(); - // call comment api - $count = CommentService::get_comments_counts_for_project(0); - - // testing method is equal to get_comments_count since get_comments_counts_for_project - // is just a wrapper to use project id for requesting comment counts - // count is an array and is expected to be size 1 - $this->assertSame(is_array($count), true, 'Count is not an array!'); - $this->assertSame( - count($count), - 1, - 'Size of count array is expected to be 1.' ); - // image id is 0, so we check field 0 for the count of comments for image 0 - // expected to be 1 according to setup. - $this->assertSame( - $count[0], - 1, - 'Comment count for image_id 0 is expected to be 1.' ); - } - - public function testSaveComment() { - $this->prepareComment(); - // prepare query_result var - $query_result = array(); - // query api and try to save comment - $bool = CommentService::save_comment($this->write_comment); - // check if save_comment returns true to signalise successful query with db - $this->assertSame($bool, true, 'Something went wrong saving a comment.'); - // manually get comment and check content - // fields that will not be checked are id, time - // prepare query to get test comment - $query = - "SELECT * FROM codeneric_phmm_comments WHERE attachment_id = 0 AND content='test' AND project_id = 0 AND wp_user_id = 0 AND client_id = 0"; - /*UNSAFE_EXPR*/ - $wpdb = $GLOBALS['wpdb']; - // query db for test entry - /*UNSAFE_EXPR*/ - $query_result = $wpdb->get_results($query, ARRAY_A); - // check if query result is an array - $this->assertSame( - is_array($query_result), - true, - 'Query result is not an array.' ); - // expected size here is 2, since we have the setup entry and the entry we are saving to check - // function save_comment - $this->assertSame( - count($query_result), - 2, - 'Query result array size is expected to be 2.' ); - // get entry of query result array - $entry = $query_result[0]; - // check for values in entry - $this->assertSame( - intval($entry['attachment_id']), - 0, - 'Attachment id='.$entry['attachment_id'].' does not match.' ); - $this->assertSame( - intval($entry['wp_user_id']), - 0, - 'WP user id='.$entry['wp_user_id'].' does not match.' ); - $this->assertSame( - intval($entry['project_id']), - 0, - 'Project id='.$entry['project_id'].' does not match.' ); - $this->assertSame( - $entry['content'], - "test", - 'Content='.$entry['content'].' does not match.' ); - $this->assertSame( - intval($entry['client_id']), - 0, - 'Client id='.$entry['client_id'].' does not match.' ); - } - - public function testGetCommentsForImage() { - $this->prepareComment(); - // prepare query_result var - $query_result = array(); - // query api and try to get comments for image id 0 - $query_result = CommentService::get_comments_for_image(0,0); - // check if query result is an array and size is 1 - $this->assertSame( - is_array($query_result), - true, - 'Query result is not an array.' ); - $this->assertSame( - count($query_result), - 1, - 'Query result array size is expected to be 1.' ); - // get entry of query result array - $entry = $query_result[0]; - // check for values in entry - $this->assertSame( - intval($entry['attachment_id']), - 0, - 'Attachment id='.$entry['attachment_id'].' does not match.' ); - $this->assertSame( - intval($entry['wp_user_id']), - 0, - 'WP user id='.$entry['wp_user_id'].' does not match.' ); - $this->assertSame( - intval($entry['project_id']), - 0, - 'Project id='.$entry['project_id'].' does not match.' ); - $this->assertSame( - $entry['content'], - "test", - 'Content='.$entry['content'].' does not match.' ); - $this->assertSame( - intval($entry['client_id']), - 0, - 'Client id='.$entry['client_id'].' does not match.' ); - } - - public function testFuzzyGetCommentsCount() { - $this->forAll(Generator\seq(Generator\int()))->then( - function($ids) { - $count = CommentService::get_comments_counts($ids); - - foreach ($ids as $id) { - $this->assertEquals(0, $count[$id]); - } - // $this->assertEquals(count($array), count(array_reverse($array))); - } ); - } - - public function testFuzzyGetCommentsCountForProject() { - $this->forAll(Generator\int())->then( - function($id) { - $count = CommentService::get_comments_counts_for_project($id); - $this->assertEquals([], $count); - } ); - } - public function testFuzzySaveComment() { - $this->forAll( - Generator\associative( - [ - 'attachment_id' => Generator\nat(), - 'wp_user_id' => Generator\nat(), - 'project_id' => Generator\nat(), - 'content' => Generator\string(), - 'client_id' => Generator\nat(), - ] ) ) - ->then( - function($comment) { - $bool = CommentService::save_comment($comment); - // check if save_comment returns true to signalise successful query with db - $this->assertSame( - $bool, - true, - 'Something went wrong saving a comment.' ); - - $query_result = []; - - $wpdb = /*UNSAFE_EXPR*/ $GLOBALS['wpdb']; - - $s = esc_sql($comment['content']); - $query = - "SELECT * FROM codeneric_phmm_comments WHERE attachment_id = $comment['attachment_id'] AND content='$s' AND project_id = $comment['project_id'] AND wp_user_id = $comment['wp_user_id'] AND client_id = $comment['client_id']"; - - // query db for test entry - /*UNSAFE_EXPR*/ - $query_result = $wpdb->get_results($query, ARRAY_A); - // check if query result is an array - $this->assertSame( - is_array($query_result), - true, - 'Query result is not an array.' ); - - // get entry of query result array - $entry = $query_result[0]; - // check for values in entry - $this->assertSame( - intval($entry['attachment_id']), - $comment['attachment_id'], - 'Attachment id='.$entry['attachment_id'].' does not match.' ); - $this->assertSame( - intval($entry['wp_user_id']), - $comment['wp_user_id'], - 'WP user id='.$entry['wp_user_id'].' does not match.' ); - $this->assertSame( - intval($entry['project_id']), - $comment['project_id'], - 'Project id='.$entry['project_id'].' does not match.' ); - $this->assertSame( - $entry['content'], - $comment['content'], - 'Content='.$entry['content'].' does not match.' ); - $this->assertSame( - intval($entry['client_id']), - $comment['client_id'], - 'Client id='.$entry['client_id'].' does not match.' ); - } ); - } - - public function testFuzzyGetCommentsForImage() { - $this->forAll(Generator\int())->then( - function($imageID, $projectID) { - $count = CommentService::get_comments_for_image($imageID, $projectID); - $this->assertEquals([], $count); - - } ); - } - -} diff --git a/src/HackToPHPLinter.php b/src/HackToPHPLinter.php index 1bc4ba7..c7c98b0 100644 --- a/src/HackToPHPLinter.php +++ b/src/HackToPHPLinter.php @@ -1,4 +1,4 @@ - { @@ -542,37 +539,31 @@ private function transpile( } if ($child instanceof EnumDeclaration) { - // $enum_keyword = $node->getKeyword()->getCode(); $enum_name = $child->getName()->getCode(); $enumerators = $child->getEnumerators()?->getChildren(); - $enumerators = !\is_null($enumerators) ? $enumerators : []; - $code = "final class $enum_name { private function __construct() {} \n"; - - $code .= "private static \$hacklib_values = array(\n"; - foreach ($enumerators as $i => $e) { - invariant( - $e instanceof Enumerator, - 'Children of EnumDeclaration has to be Enumerator', - ); - $e_name = $e->getName()->getText(); - $e_value = $e->getValue()->getCode(); - $sep = $i >= \count($enumerators) - 1 ? '' : ','; - $code .= "\"$e_name\" => $e_value $sep\n"; + $enum_array = []; + // = Dict\pull($enumerators, $e ==> $e->getName()->getText(), $e ==> $e->getValue()->getCode()); + invariant() + if ($enumerators !== null) { + foreach ($enumerators as $e) { + invariant( + $e instanceof Enumerator, + 'Children of EnumDeclaration has to be Enumerator', + ); + $enum_array[$e->getName()->getText()] = $e->getValue()->getCode(); + } } - $code .= ");\n"; + $code = "final class $enum_name { private function __construct() {} \n"; + $code .= "private static \$hacklib_values = \n"; + $code .= \var_export($enum_array, true); + $code .= ";\n"; $code .= "use \HH\HACKLIB_ENUM_LIKE;\n"; - foreach ($enumerators as $e) { - invariant( - $e instanceof Enumerator, - 'Children of EnumDeclaration has to be Enumerator', - ); - $e_name = $e->getName()->getText(); - $e_value = $e->getValue()->getCode(); + foreach ($enum_array as $e_name => $e_value) { $code .= "const $e_name = $e_value;\n"; } $code .= " }\n"; + $sub_ast = $this->ast_from_code($code); $node = $node->replace($child, $sub_ast); } @@ -624,7 +615,7 @@ private function transpile( if ($node instanceof NamespaceDeclaration) { $code = $node ->getName() - ->getCode(); + ?->getCode(); $php = $this->sprinft($php, "namespace $code$P"); $parents[] = $node; @@ -927,7 +918,7 @@ private function transpile( // return $php; // } - if ($node->isToken()) { //abstraction + if ($node->isToken()) { //abstraction $token = $node->getCode(); $php = $this->sprinft($php, "$token$P"); diff --git a/src/TranspilerCLI.hh b/src/TranspilerCLI.hh new file mode 100644 index 0000000..c1a208a --- /dev/null +++ b/src/TranspilerCLI.hh @@ -0,0 +1,181 @@ +> + public static function getHelpTextForOptionalArguments(): string { + return 'PATH'; + } + + <<__Override>> + protected function getSupportedOptions(): vec { + return vec[ + CLIOptions\flag( + () ==> { + $this->xhprof = true; + }, + 'Enable XHProf profiling', + '--xhprof', + ), + CLIOptions\with_required_enum( + AST_Prv\LinterCLIMode::class, + $m ==> { + $this->mode = $m; + }, + 'Set the output mode; supported values are '. + Str\join(AST_Prv\LinterCLIMode::getValues(), ' | '), + '--mode', + '-m', + ), + CLIOptions\with_required_string( + $_ ==> {}, + 'Name of the caller; intended for use with `--mode json` or `--mode lsp`', + '--from', + ), + $this->getVerbosityOption(), + ]; + } + + <<__Override>> + public async function mainAsync(): Awaitable { + if ($this->xhprof) { + AST_Prv\XHProf::enable(); + } + + $result = await $this->mainImplAsync(); + + if ($this->xhprof) { + AST_Prv\XHProf::disableAndDump(\STDERR); + } + + return $result; + } + + private async function mainImplAsync(): Awaitable { + $terminal = $this->getTerminal(); + if ($this->mode === AST_Prv\LinterCLIMode::LSP) { + return await (new AST_Prv\LSPImpl\Server($terminal))->mainAsync(); + } + + $err = $this->getStderr(); + $roots = $this->getArguments(); + + + /* if (C\is_empty($roots)) { + $config = AST_Prv\LintRunConfig::getForPath(\getcwd()); + $roots = $config->getRoots(); + if (C\is_empty($roots)) { + await $err->writeAsync( + "You must either specify PATH arguments, or provide a configuration". + "file.\n", + ); + return 1; + } + } else { + foreach ($roots as $root) { + $path = \realpath($root); + if (\is_dir($path)) { + $config_file = $path.'/hhast-lint.json'; + if (\file_exists($config_file)) { + /* HHAST_IGNORE_ERROR[DontAwaitInALoop] * / + await $err->writeAsync( + "Warning: PATH arguments contain a hhast-lint.json, ". + "which modifies the linters used and customizes behavior. ". + "Consider 'cd ". + $root. + "; vendor/bin/hhast-lint'\n\n", + ); + } + } + } + $config = null; + } + */ + + $config = AST_Prv\LintRunConfig::getForPath(__DIR__ . '/lint-config'); + + switch ($this->mode) { + case AST_Prv\LinterCLIMode::PLAIN: + $error_handler = new AST_Prv\LintRunCLIEventHandler($terminal); + break; + case AST_Prv\LinterCLIMode::JSON: + $error_handler = new AST_Prv\LintRunJSONEventHandler($terminal); + break; + case AST_Prv\LinterCLIMode::LSP: + invariant_violation('should have returned earlier'); + } + + try { + $result = await (new AST_Prv\LintRun($config, $error_handler, $roots)) + ->runAsync(); + } catch (Linters\LinterException $e) { + $orig = $e->getPrevious() ?? $e; + $err = $terminal->getStderr(); + $pos = $e->getPosition(); + await $err->writeAsync(Str\format( + "A linter threw an exception:\n Linter: %s\n File: %s%s\n", + $e->getLinterClass(), + \realpath($e->getFileBeingLinted()), + $pos === null ? '' : Str\format(':%d:%d', $pos[0], $pos[1] + 1), + )); + if ($pos !== null && \is_readable($e->getFileBeingLinted())) { + list($line, $column) = $pos; + $content = \file_get_contents($e->getFileBeingLinted()); + await \file_get_contents($e->getFileBeingLinted()) + |> Str\split($$, "\n") + |> Vec\take($$, $line) + |> Vec\slice($$, Math\maxva($line - 3, 0)) + |> Vec\map($$, $line ==> ' > '.$line) + |> Str\join($$, "\n") + |> Str\format("%s\n %s^ HERE\n", $$, Str\repeat(' ', $column)) + |> $err->writeAsync($$); + } + await $err->writeAsync(Str\format( + " Exception: %s\n"." Message: %s\n", + \get_class($orig), + $orig->getMessage(), + )); + await $err->writeAsync( + $orig->getTraceAsString() + |> Str\split($$, "\n") + |> Vec\map($$, $line ==> ' '.$line) + |> Str\join($$, "\n") + |> " Trace:\n".$$."\n\n", + ); + return 2; + } + + switch ($result) { + case AST_Prv\LintRunResult::NO_ERRORS: + case AST_Prv\LintRunResult::HAD_AUTOFIXED_ERRORS: + return 0; + case AST_Prv\LintRunResult::HAVE_UNFIXED_ERRORS: + return 1; + } + } +} diff --git a/src/Util.php b/src/Util.php index 0d995be..e9c54af 100644 --- a/src/Util.php +++ b/src/Util.php @@ -2,7 +2,7 @@ namespace codeneric\util; -use type Facebook\HHAST\{LambdaExpression, EditableNode, VariableExpression}; +use type Facebook\HHAST\LambdaExpression; type error_message = shape( "descr" => string, diff --git a/src/lint-config/hhast-lint.json b/src/lint-config/hhast-lint.json new file mode 100644 index 0000000..cd64d9c --- /dev/null +++ b/src/lint-config/hhast-lint.json @@ -0,0 +1,19 @@ +{ + "roots": [ + ], + "builtinLinters": "none", + "namespaceAliases": { + "HHAST": "Facebook\\HHAST\\Linters" + }, + "extraLinters": [ + "HHAST\\HackToPHPLinter" + ], + "overrides": [ + { + "patterns": [ + "codegen/*" + ], + "disableAllAutoFixes": true + } + ] +} \ No newline at end of file diff --git a/tests/HackToPhpTest.php b/tests/HackToPhpTest.php index a00f867..35eb758 100644 --- a/tests/HackToPhpTest.php +++ b/tests/HackToPhpTest.php @@ -14,7 +14,8 @@ use function Facebook\FBExpect\expect; use namespace HH\Lib\{C, Str, Vec}; -final class HackToPHPTest extends \PHPUnit\Framework\TestCase { +final class HackToPhpTest extends \Facebook\HackTest\HackTest { + const string TEST_CODE_DIR = '/example-files'; private function rglob(string $pattern, int $flags = 0): array { $files = \glob($pattern, $flags); @@ -28,28 +29,32 @@ private function rglob(string $pattern, int $flags = 0): array { } return $files; } + public function testPHPOnlyFeature(): void { $d = \dirname(\dirname(__FILE__)); - $t = "$d/example-files/temp"; - if (!\file_exists($t)) - \mkdir($t); - $files = $this->rglob("example-files/*.php"); + $files = $this->rglob($d.self::TEST_CODE_DIR."/*.php"); // $files = $this->rglob("example-files/phmm/vendor/giorgiosironi/*.php"); - $i = 0; - echo \count($files)." hack files to compile..."; + echo C\count($files)." hack files to compile...\n"; + + $log = \tempnam(\sys_get_temp_dir(), 'hack2php_test_'); + \file_put_contents($log, \date('Y-m-d_h:i:sP')."\n"); foreach ($files as $filename) { - // echo "Testing $filename...\n"; - $tf = "temp_".\basename($filename); - $res = \exec("$d/bin/hack2php $filename | php -l "); + echo "Testing $filename...\n"; + $res = \exec("$d/bin/hack2php $filename 2>> $log | php -l "); expect($res)->toBeSame( - "No syntax errors detected in -", - "Syntax error in file $filename:\n$res", + "No syntax errors detected in Standard input code", + "PHP Syntax error in file $filename:\n$res", ); } + expect(\filesize($log))->toBeSame( + 26, + "Error while compiling.\nLog: %s ", + $log, + ); } }