diff --git a/UPGRADE.md b/UPGRADE.md index 7265963be..f1a9d689a 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,43 @@ +# From 3.1.3 to 3.2.0 + +- Changes to the `Php` compactor: + - Invalid annotations are no longer recognised as annotations: + ```php + /** + * @Annotation () + * @Namespaced\ Annotation + */ + ``` + + Will be transformed into: + + ```php + /** + * @Annotation + * @Namespaced + */ + ``` +- The removal of common annotations is enabled by default +- The setting `annotation#ignore` no longer accepts a `string` value, only `string[]` and `null` are allowed +- Upon some annotation parsing failures, the error is thrown to the user in order to identify and fix those cases + instead of always silently ignore the error. +- Annotations can no longer be escaped like so: + + ```php + /** + * \@NotEscaped + */ + ``` + + Indeed it will be compacted to: + + ```php + /** + @NotEscaped + */ + ``` + + # From 2.7 to 3.0 The change from 2.x to 3.x is quite significant but should be really smooth for the user. The main changes are: diff --git a/box.json.dist b/box.json.dist index 461943561..a66c7b42e 100644 --- a/box.json.dist +++ b/box.json.dist @@ -9,7 +9,10 @@ "with this source code in the file LICENSE." ], - "files": ["res/schema.json"], + "files": [ + "res/annotation-grammar.pp", + "res/schema.json" + ], "directories-bin": [".requirement-checker"], "compression": "GZ", diff --git a/composer.json b/composer.json index f654264b6..3003e2759 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "beberlei/assert": "^3.0", "composer/composer": "^1.6", "composer/xdebug-handler": "^1.1.0", - "herrera-io/annotations": "~1.0", + "hoa/compiler": "^3.17", "humbug/php-scoper": "^0.11", "justinrainbow/json-schema": "^5.2", "nikic/iter": "^1.6", diff --git a/composer.lock b/composer.lock index 8e9266d6c..1269dfb7f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f095c058d463c32b31586e70a6b9a2e8", + "content-hash": "f0fe2bb10b3ca6eb3fe4062f40b2a9c7", "packages": [ { "name": "amphp/amp", @@ -772,180 +772,787 @@ "time": "2018-08-31T19:07:57+00:00" }, { - "name": "doctrine/annotations", - "version": "v1.6.0", + "name": "hoa/compiler", + "version": "3.17.08.08", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + "url": "https://github.com/hoaproject/Compiler.git", + "reference": "aa09caf0bf28adae6654ca6ee415ee2f522672de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "url": "https://api.github.com/repos/hoaproject/Compiler/zipball/aa09caf0bf28adae6654ca6ee415ee2f522672de", + "reference": "aa09caf0bf28adae6654ca6ee415ee2f522672de", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "php": "^7.1" + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0", + "hoa/file": "~1.0", + "hoa/iterator": "~2.0", + "hoa/math": "~1.0", + "hoa/protocol": "~1.0", + "hoa/regex": "~1.0", + "hoa/visitor": "~2.0" }, "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^6.4" + "hoa/json": "~2.0", + "hoa/test": "~2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Hoa\\Compiler\\": "." } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" }, { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Compiler library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "algebraic", + "ast", + "compiler", + "context-free", + "coverage", + "exhaustive", + "grammar", + "isotropic", + "language", + "lexer", + "library", + "ll1", + "llk", + "parser", + "pp", + "random", + "regular", + "rule", + "sampler", + "syntax", + "token", + "trace", + "uniform" + ], + "time": "2017-08-08T07:44:07+00:00" + }, + { + "name": "hoa/consistency", + "version": "1.17.05.02", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Consistency.git", + "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f", + "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f", + "shasum": "" + }, + "require": { + "hoa/exception": "~1.0", + "php": ">=5.5.0" + }, + "require-dev": { + "hoa/stream": "~1.0", + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Consistency\\": "." }, + "files": [ + "Prelude.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Consistency library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "autoloader", + "callable", + "consistency", + "entity", + "flex", + "keyword", + "library" + ], + "time": "2017-05-02T12:18:12+00:00" + }, + { + "name": "hoa/event", + "version": "1.17.01.13", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Event.git", + "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54", + "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Event\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Hoa community", + "homepage": "https://hoa-project.net/" } ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", + "description": "The Hoa\\Event library.", + "homepage": "https://hoa-project.net/", "keywords": [ - "annotations", - "docblock", - "parser" + "event", + "library", + "listener", + "observer" ], - "time": "2017-12-06T07:11:42+00:00" + "time": "2017-01-13T15:30:50+00:00" }, { - "name": "doctrine/lexer", - "version": "v1.0.1", + "name": "hoa/exception", + "version": "1.17.01.16", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "url": "https://github.com/hoaproject/Exception.git", + "reference": "091727d46420a3d7468ef0595651488bfc3a458f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f", + "reference": "091727d46420a3d7468ef0595651488bfc3a458f", "shasum": "" }, "require": { - "php": ">=5.3.2" + "hoa/consistency": "~1.0", + "hoa/event": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "psr-4": { + "Hoa\\Exception\\": "." } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" }, { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Exception library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "exception", + "library" + ], + "time": "2017-01-16T07:53:27+00:00" + }, + { + "name": "hoa/file", + "version": "1.17.07.11", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/File.git", + "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/File/zipball/35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", + "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/iterator": "~2.0", + "hoa/stream": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\File\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Hoa community", + "homepage": "https://hoa-project.net/" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", + "description": "The Hoa\\File library.", + "homepage": "https://hoa-project.net/", "keywords": [ - "lexer", - "parser" + "Socket", + "directory", + "file", + "finder", + "library", + "link", + "temporary" + ], + "time": "2017-07-11T07:42:15+00:00" + }, + { + "name": "hoa/iterator", + "version": "2.17.01.10", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Iterator.git", + "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Iterator/zipball/d1120ba09cb4ccd049c86d10058ab94af245f0cc", + "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Iterator\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } ], - "time": "2014-09-09T13:34:57+00:00" + "description": "The Hoa\\Iterator library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "iterator", + "library" + ], + "time": "2017-01-10T10:34:47+00:00" }, { - "name": "herrera-io/annotations", - "version": "1.0.1", + "name": "hoa/math", + "version": "1.17.05.16", "source": { "type": "git", - "url": "https://github.com/kherge-abandoned/php-annotations.git", - "reference": "7d8b9a536da7f12aad8de7f28b2cb5266bde8da1" + "url": "https://github.com/hoaproject/Math.git", + "reference": "7150785d30f5d565704912116a462e9f5bc83a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kherge-abandoned/php-annotations/zipball/7d8b9a536da7f12aad8de7f28b2cb5266bde8da1", - "reference": "7d8b9a536da7f12aad8de7f28b2cb5266bde8da1", + "url": "https://api.github.com/repos/hoaproject/Math/zipball/7150785d30f5d565704912116a462e9f5bc83a0c", + "reference": "7150785d30f5d565704912116a462e9f5bc83a0c", "shasum": "" }, "require": { - "doctrine/annotations": "~1.0", - "php": ">=5.3.3" + "hoa/compiler": "~3.0", + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0", + "hoa/iterator": "~2.0", + "hoa/protocol": "~1.0", + "hoa/zformat": "~1.0" }, "require-dev": { - "herrera-io/phpunit-test-case": "1.*", - "phpunit/phpunit": "3.7.*" + "hoa/test": "~2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.x-dev" } }, "autoload": { - "psr-0": { - "Herrera\\Annotations": "src/lib" + "psr-4": { + "Hoa\\Math\\": "." } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Kevin Herrera", - "email": "kevin@herrera.io", - "homepage": "http://kevin.herrera.io" + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" } ], - "description": "A tokenizer for Doctrine annotations.", - "homepage": "https://github.com/herrera-io/php-annotations", + "description": "The Hoa\\Math library.", + "homepage": "https://hoa-project.net/", "keywords": [ - "annotations", - "doctrine", - "tokenizer" + "arrangement", + "combination", + "combinatorics", + "counting", + "library", + "math", + "permutation", + "sampler", + "set" + ], + "time": "2017-05-16T08:02:17+00:00" + }, + { + "name": "hoa/protocol", + "version": "1.17.01.14", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Protocol.git", + "reference": "5c2cf972151c45f373230da170ea015deecf19e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Protocol/zipball/5c2cf972151c45f373230da170ea015deecf19e2", + "reference": "5c2cf972151c45f373230da170ea015deecf19e2", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Protocol\\": "." + }, + "files": [ + "Wrapper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Protocol library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "protocol", + "resource", + "stream", + "wrapper" + ], + "time": "2017-01-14T12:26:10+00:00" + }, + { + "name": "hoa/regex", + "version": "1.17.01.13", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Regex.git", + "reference": "7e263a61b6fb45c1d03d8e5ef77668518abd5bec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Regex/zipball/7e263a61b6fb45c1d03d8e5ef77668518abd5bec", + "reference": "7e263a61b6fb45c1d03d8e5ef77668518abd5bec", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0", + "hoa/math": "~1.0", + "hoa/protocol": "~1.0", + "hoa/ustring": "~4.0", + "hoa/visitor": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Regex\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Regex library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "compiler", + "library", + "regex" + ], + "time": "2017-01-13T16:10:24+00:00" + }, + { + "name": "hoa/stream", + "version": "1.17.02.21", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Stream.git", + "reference": "3293cfffca2de10525df51436adf88a559151d82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Stream/zipball/3293cfffca2de10525df51436adf88a559151d82", + "reference": "3293cfffca2de10525df51436adf88a559151d82", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/protocol": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Stream\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Stream library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "Context", + "bucket", + "composite", + "filter", + "in", + "library", + "out", + "protocol", + "stream", + "wrapper" + ], + "time": "2017-02-21T16:01:06+00:00" + }, + { + "name": "hoa/ustring", + "version": "4.17.01.16", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Ustring.git", + "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Ustring/zipball/e6326e2739178799b1fe3fdd92029f9517fa17a0", + "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "suggest": { + "ext-iconv": "ext/iconv must be present (or a third implementation) to use Hoa\\Ustring::transcode().", + "ext-intl": "To get a better Hoa\\Ustring::toAscii() and Hoa\\Ustring::compareTo()." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Ustring\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Ustring library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "search", + "string", + "unicode" + ], + "time": "2017-01-16T07:08:25+00:00" + }, + { + "name": "hoa/visitor", + "version": "2.17.01.16", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Visitor.git", + "reference": "c18fe1cbac98ae449e0d56e87469103ba08f224a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Visitor/zipball/c18fe1cbac98ae449e0d56e87469103ba08f224a", + "reference": "c18fe1cbac98ae449e0d56e87469103ba08f224a", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Visitor\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Visitor library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "structure", + "visit", + "visitor" + ], + "time": "2017-01-16T07:02:03+00:00" + }, + { + "name": "hoa/zformat", + "version": "1.17.01.10", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Zformat.git", + "reference": "522c381a2a075d4b9dbb42eb4592dd09520e4ac2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Zformat/zipball/522c381a2a075d4b9dbb42eb4592dd09520e4ac2", + "reference": "522c381a2a075d4b9dbb42eb4592dd09520e4ac2", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Zformat\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Zformat library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "parameter", + "zformat" ], - "abandoned": true, - "time": "2014-02-03T17:34:08+00:00" + "time": "2017-01-10T10:39:54+00:00" }, { "name": "humbug/php-scoper", diff --git a/doc/configuration.md b/doc/configuration.md index b9a92f678..23b5fed0d 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -579,11 +579,78 @@ command ✨. ### Annotations (`annotations`) -// TODO: review this setting + doc, default value...] +The annotations (`boolean`|`object`|`null` default `true`) setting is used to enable compacting annotations in PHP source +code. -The annotations (`boolean`|`object`|`null` default `{}`) setting is used to enable compacting annotations in PHP source -code. By setting it to `true`, all Doctrine-style annotations are compacted in PHP files. You may also specify a list of -annotations to ignore, which will be stripped while protecting the remaining annotations: +This setting is only taken into consideration if the [`KevinGH\Box\Compactor\Php` compactor][compactors] is enabled. + +By default, it removes all non real-like annotations from the PHP code. See the following example: + +
+Original code + +```php + $y; +} +``` +
+ +
+Compacted code + +```php + $y; +} +``` +
+ + +Note that the empty line returns are on purpose: it is to keep the same line number for the files between your source +code and the code bundled in the PHAR. + +If you wish to keep all annotations, you can disable the annotations like so: + +```json +{ + "annotations": false +} +``` + +For a more granular list, you can manually configure the list of annotations you wish to ignore: ```json { @@ -598,11 +665,6 @@ annotations to ignore, which will be stripped while protecting the remaining ann } ``` -You may want to see this website for a list of annotations which are commonly ignored on -[herrera-io/php-annotations][herrera-io/php-annotations]: - -Note that this setting is used only if the compactor `KevinGH\Box\Compactor\Php` is registered. - ### PHP-Scoper (`php-scoper`) diff --git a/res/annotation-grammar.pp b/res/annotation-grammar.pp new file mode 100644 index 000000000..5e2447599 --- /dev/null +++ b/res/annotation-grammar.pp @@ -0,0 +1,86 @@ +%pragma lexer.unicode 1 + +%skip space [\x20\x09\x0a\x0d]+ +%token doc_ /\*\* -> docblock + +%skip docblock:space [\x20\x09\x0a\x0d]+ +%skip docblock:star \*(?!/) +%token docblock:_doc \*/ -> default +%token docblock:at @(?!\s) -> annot +%token docblock:text [^*@]+|@(?=\s)|\*(?!/) + +%token annot:valued_identifier \\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*(?=\() +%token annot:simple_identifier \\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)* -> __shift__ +%token annot:parenthesis_ \( -> value + +%skip value:star [*](?!/) +%skip value:space [\x20\x09\x0a\x0d]+ +%token value:_parenthesis \) -> __shift__ * 2 +%token value:at @(?!\s) -> annot +%token value:comma , +%token value:brace_ { +%token value:_brace } +%token value:double_colon :: +%token value:colon : +%token value:equals = +%token value:quote_ " -> string +%token value:null \b(?:null|NULL)\b +%token value:boolean \b(?:true|TRUE|false|FALSE)\b +%token value:float -?(0|[1-9]\d*)(?=[eE\.])(\.\d+)?([eE][+-]?\d+)? +%token value:integer -?(0|[1-9]\d*) +%token value:identifier_ns \\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+ +%token value:identifier [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* + +%token string:string (?:[^"\\]+|(\\\\)*\\"|(\\\\)+|\\?[^"\\]+)+ +%token string:_quote " -> __shift__ + +#annotations: + ::doc_:: + (::text:: | annotation())* + ::_doc:: + +#annotation: + ::at:: + ( + + | ( ::parenthesis_:: ( parameters() )? ::_parenthesis:: ) + ) + +#list: + ::brace_:: ( (value() ( ::comma:: value() )*) ::comma::? )? ::_brace:: + +#map: + ::brace_:: pairs() ::comma::? ::_brace:: + +pairs: + (pair_equal() | pair_colon()) ( ::comma:: (pair_equal() | pair_colon()) )* + +#pair_equal: + ( | | string() | | | constant()) ::equals:: value() + +#pair_colon: + ( | | string() | | | constant()) ::colon:: value() + +#value: + | | string() | | | map() | list() | annotation() | constant() + +#parameters: + ( parameter() ( ::comma:: parameter())* ::comma::? )? + +parameter: + named_parameter() | unnamed_parameter() + +#named_parameter: + ::equals:: value() + +#unnamed_parameter: + value() + +#constant: + reference() ::double_colon:: + +#string: + ::quote_:: ? ::_quote:: + +#reference: + | diff --git a/res/schema.json b/res/schema.json index 087d2ce9b..3f851e1cd 100644 --- a/res/schema.json +++ b/res/schema.json @@ -20,7 +20,7 @@ "items": { "type": "string" }, - "type": ["array", "string"] + "type": ["array", "null"] } } }, diff --git a/scoper.inc.php b/scoper.inc.php index 5dd34748a..0cbecfcba 100644 --- a/scoper.inc.php +++ b/scoper.inc.php @@ -16,11 +16,13 @@ return [ 'patchers' => [ + // TODO: to check if still necessary function (string $filePath, string $prefix, string $contents): string { $finderClass = sprintf('\%s\%s', $prefix, Finder::class); return str_replace($finderClass, '\\'.Finder::class, $contents); }, + // Box compactors: not required to work but avoid any confusion for the users function (string $filePath, string $prefix, string $contents): string { $files = [ 'src/functions.php', @@ -63,6 +65,7 @@ function (string $filePath, string $prefix, string $contents): string { $contents ); }, + // Paragonie custom autoloader which relies on some regexes function (string $filePath, string $prefix, string $contents): string { if ('vendor/paragonie/sodium_compat/autoload.php' !== $filePath) { return $contents; @@ -85,6 +88,7 @@ function (string $filePath, string $prefix, string $contents): string { ) ); }, + // Paragonie dynamic constants declarations function (string $filePath, string $prefix, string $contents): string { if ('vendor/paragonie/sodium_compat/lib/php72compat.php' !== $filePath) { return $contents; @@ -99,6 +103,47 @@ function (string $filePath, string $prefix, string $contents): string { $contents ); }, + // Hoa patches + function (string $filePath, string $prefix, string $contents): string { + if ('vendor/hoa/stream/Stream.php' !== $filePath) { + return $contents; + } + + return preg_replace( + '/Hoa\\\\Consistency::registerShutdownFunction\(xcallable\(\'(.*)\'\)\)/', + sprintf( + 'Hoa\\Consistency::registerShutdownFunction(xcallable(\'%s$1\'))', + $prefix.'\\\\\\\\' + ), + $contents + ); + }, + function (string $filePath, string $prefix, string $contents): string { + if ('vendor/hoa/consistency/Autoloader.php' !== $filePath) { + return $contents; + } + + $contents = preg_replace( + '/(\$entityPrefix = \$entity;)/', + sprintf( + '$entity = substr($entity, %d);$1', + strlen($prefix) + 1 + ), + $contents + ); + + $contents = preg_replace( + '/return \$this->runAutoloaderStack\((.*)\);/', + sprintf( + 'return $this->runAutoloaderStack(\'%s\'.\'%s\'.$1);', + $prefix, + '\\\\\\' + ), + $contents + ); + + return $contents; + }, ], 'files-whitelist' => [ __DIR__.'/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php', @@ -111,6 +156,37 @@ function (string $filePath, string $prefix, string $contents): string { \Herrera\Box\Compactor\Php::class, \KevinGH\Box\Compactor\Php::class, \KevinGH\Box\Compactor\PhpScoper::class, + + // Hoa symbols + 'SUCCEED', + 'FAILED', + '…', + 'DS', + 'PS', + 'ROOT_SEPARATOR', + 'RS', + 'CRLF', + 'OS_WIN', + 'S_64_BITS', + 'S_32_BITS', + 'PHP_INT_MIN', + 'PHP_FLOAT_MIN', + 'PHP_FLOAT_MAX', + 'PHP_WINDOWS_VERSION_PLATFORM', + 'π', + 'nil', + '_public', + '_protected', + '_private', + '_static', + '_abstract', + '_pure', + '_final', + '_dynamic', + '_concrete', + '_overridable', + 'WITH_COMPOSER', + 'xcallable', ], 'whitelist-global-constants' => false, 'whitelist-global-classes' => false, diff --git a/src/Annotation/AnnotationDumper.php b/src/Annotation/AnnotationDumper.php new file mode 100644 index 000000000..fb5e0d7f5 --- /dev/null +++ b/src/Annotation/AnnotationDumper.php @@ -0,0 +1,194 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Annotation; + +use Assert\Assertion; +use Hoa\Compiler\Llk\TreeNode; +use function array_filter; +use function array_map; +use function array_shift; +use function array_values; +use function implode; +use function in_array; +use function sprintf; +use function strtolower; + +/** + * @private + */ +final class AnnotationDumper +{ + /** + * Dumps the list of annotations from the given tree. + * + * @param string[] $ignored List of annotations to ignore + * + * @throws InvalidToken + * + * @return string[] + */ + public function dump(TreeNode $node, array $ignored): array + { + Assertion::allString($ignored); + + $ignored = array_map('strtolower', $ignored); + + if ('#annotations' !== $node->getId()) { + return []; + } + + return array_values( + array_filter( + $this->transformNodesToString( + $node->getChildren(), + $ignored + ) + ) + ); + } + + /** + * @param TreeNode $nodes + * @param string[] $ignored + * + * @return (string|null)[] + */ + private function transformNodesToString(array $nodes, array $ignored): array + { + return array_map( + function (TreeNode $node) use ($ignored): ?string { + return $this->transformNodeToString($node, $ignored); + }, + $nodes + ); + } + + /** + * @param string[] $ignored + */ + private function transformNodeToString(TreeNode $node, array $ignored): ?string + { + switch ($node->getId()) { + case '#annotation': + Assertion::greaterOrEqualThan($node->getChildrenNumber(), 1); + + $children = $node->getChildren(); + + /** @var TreeNode $token */ + $token = array_shift($children); + $parameters = array_values($children); + + if ('simple_identifier' === $token->getValueToken()) { + Assertion::count($parameters, 0); + + $tokenValue = $token->getValueValue(); + + return in_array(strtolower($tokenValue), $ignored, true) ? null : '@'.$tokenValue; + } + + if ('valued_identifier' === $token->getValueToken()) { + $transformedChildren = $this->transformNodesToString( + $parameters, + $ignored + ); + + return sprintf( + '@%s(%s)', + $token->getValueValue(), + implode( + '', + $transformedChildren + ) + ); + } + + throw InvalidToken::createForUnknownType($token); + case 'token': + if (in_array($node->getValueToken(), ['identifier', 'simple_identifier', 'integer', 'float', 'boolean', 'identifier_ns', 'null'], true)) { + return $node->getValueValue(); + } + + if ('string' === $node->getValueToken()) { + return sprintf('"%s"', $node->getValueValue()); + } + + if ('valued_identifier' === $node->getValueToken()) { + return sprintf('%s()', $node->getValueValue()); + } + + throw InvalidToken::createForUnknownType($node); + case '#parameters': + $transformedChildren = $this->transformNodesToString( + $node->getChildren(), + $ignored + ); + + return implode(',', $transformedChildren); + case '#named_parameter': + case '#pair_equal': + case '#pair_colon': + Assertion::same($node->getChildrenNumber(), 2); + + $name = $node->getChild(0); + $parameter = $node->getChild(1); + + return sprintf( + '%s%s%s', + $this->transformNodeToString($name, $ignored), + '#pair_colon' === $node->getId() ? ':' : '=', + $this->transformNodeToString($parameter, $ignored) + ); + + case '#value': + Assertion::same($node->getChildrenNumber(), 1); + + return $this->transformNodeToString($node->getChild(0), $ignored); + case '#string': + Assertion::lessOrEqualThan($node->getChildrenNumber(), 1); + + return 1 === $node->getChildrenNumber() ? $this->transformNodeToString($node->getChild(0), $ignored) : '""'; + case '#list': + case '#map': + $transformedChildren = $this->transformNodesToString( + $node->getChildren(), + $ignored + ); + + return sprintf( + '{%s}', + implode( + ',', + $transformedChildren + ) + ); + + case '#unnamed_parameter': + case '#reference': + Assertion::same($node->getChildrenNumber(), 1); + + return $this->transformNodeToString($node->getChild(0), $ignored); + case '#constant': + Assertion::same($node->getChildrenNumber(), 2); + + return sprintf( + '%s::%s', + $this->transformNodeToString($node->getChild(0), $ignored), + $this->transformNodeToString($node->getChild(1), $ignored) + ); + } + + throw InvalidToken::createForUnknownId($node); + } +} diff --git a/src/Annotation/DocblockAnnotationParser.php b/src/Annotation/DocblockAnnotationParser.php new file mode 100644 index 000000000..b372657a6 --- /dev/null +++ b/src/Annotation/DocblockAnnotationParser.php @@ -0,0 +1,49 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Annotation; + +/** + * @private + */ +final class DocblockAnnotationParser +{ + private $docblockParser; + private $annotationDumper; + private $ignored; + + /** + * @param string[] $ignored + */ + public function __construct(DocblockParser $docblockParser, AnnotationDumper $annotationDumper, array $ignored) + { + $this->docblockParser = $docblockParser; + $this->annotationDumper = $annotationDumper; + $this->ignored = $ignored; + } + + /** + * @throws InvalidDocblock + * @throws InvalidToken + * + * @return string[] Parsed compacted annotations parsed from the docblock + */ + public function parse(string $docblock): array + { + return $this->annotationDumper->dump( + $this->docblockParser->parse($docblock), + $this->ignored + ); + } +} diff --git a/src/Annotation/DocblockParser.php b/src/Annotation/DocblockParser.php new file mode 100644 index 000000000..2f2689899 --- /dev/null +++ b/src/Annotation/DocblockParser.php @@ -0,0 +1,58 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Annotation; + +use Hoa\Compiler\Exception\UnrecognizedToken; +use Hoa\Compiler\Llk\Llk; +use Hoa\Compiler\Llk\TreeNode; +use Hoa\File\Read; +use function preg_replace; +use function strpos; +use function substr; +use function trim; + +/** + * @private + */ +final class DocblockParser +{ + /** + * Parses the docblock and returns its AST. + * + * @throws InvalidDocblock + */ + public function parse(string $docblock): TreeNode + { + $docblock = trim($docblock); + + if (0 !== strpos($docblock, '/**') || '*/' !== substr($docblock, -2)) { + return new TreeNode('#null'); + } + + $docblock = preg_replace( + '/(\/\*\*[\s\S]*?\@author.+?)(\<.+?\@.+?\>)([\s\S]*?\*\/)/', + '$1$3', + $docblock + ); + + $compiler = Llk::load(new Read(__DIR__.'/../../res/annotation-grammar.pp')); + + try { + return $compiler->parse($docblock); + } catch (UnrecognizedToken $exception) { + throw InvalidDocblock::createFromHoaUnrecognizedToken($docblock, $exception); + } + } +} diff --git a/src/Annotation/InvalidDocblock.php b/src/Annotation/InvalidDocblock.php new file mode 100644 index 000000000..81119b1d8 --- /dev/null +++ b/src/Annotation/InvalidDocblock.php @@ -0,0 +1,38 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Annotation; + +use Hoa\Compiler\Exception\UnrecognizedToken; +use UnexpectedValueException; +use function sprintf; + +/** + * @private + */ +final class InvalidDocblock extends UnexpectedValueException +{ + public static function createFromHoaUnrecognizedToken(string $docblock, UnrecognizedToken $exception): self + { + return new self( + sprintf( + 'Could not parse the following docblock: "%s". Cause: "%s"', + $docblock, + $exception->getMessage() + ), + 0, + $exception + ); + } +} diff --git a/src/Annotation/InvalidToken.php b/src/Annotation/InvalidToken.php new file mode 100644 index 000000000..6dc7b1a3c --- /dev/null +++ b/src/Annotation/InvalidToken.php @@ -0,0 +1,45 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Annotation; + +use Hoa\Compiler\Llk\TreeNode; +use UnexpectedValueException; +use function sprintf; + +/** + * @private + */ +final class InvalidToken extends UnexpectedValueException +{ + public static function createForUnknownType(TreeNode $node): self + { + return new self( + sprintf( + 'Unknown token type "%s"', + $node->getValueToken() + ) + ); + } + + public static function createForUnknownId(TreeNode $node): self + { + return new self( + sprintf( + 'Unknown token ID "%s"', + $node->getId() + ) + ); + } +} diff --git a/src/Compactor/Php.php b/src/Compactor/Php.php index b6b26d609..7e83f3477 100644 --- a/src/Compactor/Php.php +++ b/src/Compactor/Php.php @@ -14,11 +14,9 @@ namespace KevinGH\Box\Compactor; -use Doctrine\Common\Annotations\DocLexer; -use Exception; -use Herrera\Annotations\Convert\ToString; -use Herrera\Annotations\Tokenizer; -use Herrera\Annotations\Tokens; +use KevinGH\Box\Annotation\DocblockAnnotationParser; +use KevinGH\Box\Annotation\InvalidToken; +use RuntimeException; use const T_COMMENT; use const T_DOC_COMMENT; use const T_WHITESPACE; @@ -39,22 +37,21 @@ * @author Kevin Herrera * @author Fabien Potencier * @author Jordi Boggiano + * @author Théo Fidry * @private */ final class Php extends FileExtensionCompactor { - private $converter; - private $tokenizer; + private $annotationParser; /** * {@inheritdoc} */ - public function __construct(Tokenizer $tokenizer, array $extensions = ['php']) + public function __construct(DocblockAnnotationParser $annotationParser, array $extensions = ['php']) { parent::__construct($extensions); - $this->converter = new ToString(); - $this->tokenizer = $tokenizer; + $this->annotationParser = $annotationParser; } /** @@ -62,25 +59,22 @@ public function __construct(Tokenizer $tokenizer, array $extensions = ['php']) */ protected function compactContent(string $contents): string { - // TODO: refactor this piece of code - // - strip down blank spaces - // - remove useless spaces - // - strip down comments except Doctrine style annotations unless whitelisted -> BC break to document; - // Alternatively provide an easy way to strip down all "regular" annotations such as @package, @param - // & co. - // - completely remove comments & docblocks if empty - // TODO regarding the doc: it current has its own `annotations` entry. Maybe it would be best to - // include it as a sub element of `compactors` $output = ''; foreach (token_get_all($contents) as $token) { if (is_string($token)) { $output .= $token; } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) { - if ($this->tokenizer && false !== strpos($token[1], '@')) { + if (false !== strpos($token[1], '@')) { try { $output .= $this->compactAnnotations($token[1]); - } catch (Exception $exception) { + } catch (InvalidToken $exception) { + // This exception is due to the dumper to be out of sync with the current grammar and/or the + // grammar being incomplete. In both cases throwing here is better in order to identify and + // this those cases instead of silently failing. + + throw $exception; + } catch (RuntimeException $exception) { $output .= $token[1]; } } else { @@ -107,48 +101,30 @@ protected function compactContent(string $contents): string private function compactAnnotations(string $docblock): string { - $annotations = []; - $index = -1; - $inside = 0; - $tokens = $this->tokenizer->parse($docblock); - - if (empty($tokens)) { - return str_repeat("\n", substr_count($docblock, "\n")); - } + $breaksNbr = substr_count($docblock, "\n"); - foreach ($tokens as $token) { - if ((0 === $inside) && (DocLexer::T_AT === $token[0])) { - ++$index; - } elseif (DocLexer::T_OPEN_PARENTHESIS === $token[0]) { - ++$inside; - } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token[0]) { - --$inside; - } - - if (!isset($annotations[$index])) { - $annotations[$index] = []; - } + $annotations = $this->annotationParser->parse($docblock); - $annotations[$index][] = $token; + if ([] === $annotations) { + return str_repeat("\n", $breaksNbr); } - $breaks = substr_count($docblock, "\n"); - $docblock = '/**'; + $compactedDocblock = '/**'; foreach ($annotations as $annotation) { - $annotation = new Tokens($annotation); - $docblock .= "\n".$this->converter->convert($annotation); + $compactedDocblock .= "\n".$annotation; } - $breaks -= count($annotations); + $breaksNbr -= count($annotations); - if ($breaks > 0) { - $docblock .= str_repeat("\n", $breaks - 1); - $docblock .= "\n*/"; + if ($breaksNbr > 0) { + $compactedDocblock .= str_repeat("\n", $breaksNbr - 1); + $compactedDocblock .= "\n*/"; } else { - $docblock .= ' */'; + // A space is required here to avoid having /***/ + $compactedDocblock .= ' */'; } - return $docblock; + return $compactedDocblock; } } diff --git a/src/Configuration.php b/src/Configuration.php index 37cbca25e..34ec14943 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -18,13 +18,15 @@ use Closure; use DateTimeImmutable; use DateTimeZone; -use Herrera\Annotations\Tokenizer; use Herrera\Box\Compactor\Php as LegacyPhp; use Humbug\PhpScoper\Configuration as PhpScoperConfiguration; use Humbug\PhpScoper\Console\ApplicationFactory; use Humbug\PhpScoper\Scoper; use Humbug\PhpScoper\Scoper\FileWhitelistScoper; use InvalidArgumentException; +use KevinGH\Box\Annotation\AnnotationDumper; +use KevinGH\Box\Annotation\DocblockAnnotationParser; +use KevinGH\Box\Annotation\DocblockParser; use KevinGH\Box\Compactor\Php as PhpCompactor; use KevinGH\Box\Compactor\PhpScoper as PhpScoperCompactor; use KevinGH\Box\Composer\ComposerConfiguration; @@ -57,6 +59,7 @@ use function file_exists; use function getcwd; use function implode; +use function in_array; use function intval; use function is_array; use function is_bool; @@ -108,9 +111,67 @@ final class Configuration private const DEFAULT_SIGNING_ALGORITHM = Phar::SHA1; private const DEFAULT_ALIAS_PREFIX = 'box-auto-generated-alias-'; + private const DEFAULT_IGNORED_ANNOTATIONS = [ + 'abstract', + 'access', + 'annotation', + 'api', + 'attribute', + 'attributes', + 'author', + 'category', + 'code', + 'codecoverageignore', + 'codecoverageignoreend', + 'codecoverageignorestart', + 'copyright', + 'deprec', + 'deprecated', + 'endcode', + 'example', + 'exception', + 'filesource', + 'final', + 'fixme', + 'global', + 'ignore', + 'ingroup', + 'inheritdoc', + 'internal', + 'license', + 'link', + 'magic', + 'method', + 'name', + 'override', + 'package', + 'package_version', + 'param', + 'private', + 'property', + 'required', + 'return', + 'see', + 'since', + 'static', + 'staticvar', + 'subpackage', + 'suppresswarnings', + 'target', + 'throw', + 'throws', + 'todo', + 'tutorial', + 'usedby', + 'uses', + 'var', + 'version', + ]; + private const ALGORITHM_KEY = 'algorithm'; private const ALIAS_KEY = 'alias'; private const ANNOTATIONS_KEY = 'annotations'; + private const IGNORED_ANNOTATIONS_KEY = 'ignore'; private const AUTO_DISCOVERY_KEY = 'force-autodiscovery'; private const BANNER_KEY = 'banner'; private const BANNER_FILE_KEY = 'banner-file'; @@ -1544,19 +1605,21 @@ private static function retrieveCompactors(stdClass $raw, string $basePath, Conf { self::checkIfDefaultValue($logger, $raw, self::COMPACTORS_KEY, []); + $compactorClasses = array_unique((array) ($raw->{self::COMPACTORS_KEY} ?? [])); + + $ignoredAnnotations = self::retrievePhpCompactorIgnoredAnnotations($raw, $compactorClasses, $logger); + if (false === isset($raw->{self::COMPACTORS_KEY})) { return []; } - $compactorClasses = array_unique((array) $raw->{self::COMPACTORS_KEY}); - $compators = array_map( - static function (string $class) use ($raw, $basePath, $logger): Compactor { + static function (string $class) use ($raw, $basePath, $logger, $ignoredAnnotations): Compactor { Assertion::classExists($class, 'The compactor class "%s" does not exist.'); Assertion::implementsInterface($class, Compactor::class, 'The class "%s" is not a compactor class.'); if (PhpCompactor::class === $class || LegacyPhp::class === $class) { - return self::createPhpCompactor($raw); + return self::createPhpCompactor($ignoredAnnotations); } if (PhpScoperCompactor::class === $class) { @@ -2421,17 +2484,92 @@ private static function runGitCommand(string $command, string $file): string ); } - private static function createPhpCompactor(stdClass $raw): Compactor - { - $tokenizer = new Tokenizer(); + /** + * @param string[] $compactorClasses + * + * @return string[] + */ + private static function retrievePhpCompactorIgnoredAnnotations( + stdClass $raw, + array $compactorClasses, + ConfigurationLogger $logger + ): array { + $hasPhpCompactor = in_array(PhpCompactor::class, $compactorClasses, true) || in_array(LegacyPhp::class, $compactorClasses, true); + + self::checkIfDefaultValue($logger, $raw, self::ANNOTATIONS_KEY, true); + self::checkIfDefaultValue($logger, $raw, self::ANNOTATIONS_KEY, null); + + if (false === property_exists($raw, self::ANNOTATIONS_KEY)) { + return self::DEFAULT_IGNORED_ANNOTATIONS; + } + + if (false === $hasPhpCompactor) { + $logger->addWarning( + sprintf( + 'The "%s" setting has been set but is ignored since no PHP compactor has been configured', + self::ANNOTATIONS_KEY + ) + ); + } + + /** @var null|bool|stdClass $annotations */ + $annotations = $raw->{self::ANNOTATIONS_KEY}; + + if (true === $annotations || null === $annotations) { + return self::DEFAULT_IGNORED_ANNOTATIONS; + } - if (false === empty($raw->{self::ANNOTATIONS_KEY}) && isset($raw->{self::ANNOTATIONS_KEY}->ignore)) { - $tokenizer->ignore( - (array) $raw->{self::ANNOTATIONS_KEY}->ignore + if (false === $annotations) { + return []; + } + + if (false === property_exists($annotations, self::IGNORED_ANNOTATIONS_KEY)) { + $logger->addWarning( + sprintf( + 'The "%s" setting has been set but no "%s" setting has been found, hence "%s" is treated as' + .' if it is set to `false`', + self::ANNOTATIONS_KEY, + self::IGNORED_ANNOTATIONS_KEY, + self::ANNOTATIONS_KEY + ) ); + + return []; + } + + $ignored = []; + + if (property_exists($annotations, self::IGNORED_ANNOTATIONS_KEY) + && in_array($ignored = $annotations->{self::IGNORED_ANNOTATIONS_KEY}, [null, []], true) + ) { + self::addRecommendationForDefaultValue($logger, self::ANNOTATIONS_KEY.'#'.self::IGNORED_ANNOTATIONS_KEY); + + return (array) $ignored; } - return new PhpCompactor($tokenizer); + return $ignored; + } + + private static function createPhpCompactor(array $ignoredAnnotations): Compactor + { + $ignoredAnnotations = array_values( + array_filter( + array_map( + static function (string $annotation): ?string { + return strtolower(trim($annotation)); + }, + $ignoredAnnotations + ) + ) + ); + + return new PhpCompactor( + new DocblockAnnotationParser( + new DocblockParser(), + new AnnotationDumper(), + $ignoredAnnotations + ) + ); } private static function createPhpScoperCompactor(stdClass $raw, string $basePath, ConfigurationLogger $logger): Compactor diff --git a/tests/Annotation/AnnotationDumperTest.php b/tests/Annotation/AnnotationDumperTest.php new file mode 100644 index 000000000..5772ff8d9 --- /dev/null +++ b/tests/Annotation/AnnotationDumperTest.php @@ -0,0 +1,783 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Annotation; + +use Generator; +use PHPUnit\Framework\TestCase; + +/** + * @covers \KevinGH\Box\Annotation\AnnotationDumper + */ +class AnnotationDumperTest extends TestCase +{ + /** + * @var DocblockParser + */ + private $docblockParser; + + /** + * @var AnnotationDumper + */ + private $annotationDumper; + + /** + * {@inheritdoc} + */ + protected function setUp(): void + { + $this->docblockParser = new DocblockParser(); + $this->annotationDumper = new AnnotationDumper(); + } + + /** + * @dataProvider provideDocblocks + */ + public function test_it_can_parse_PHP_docblocks(string $docblock, array $expected, array $ignore = []): void + { + $actual = $this->annotationDumper->dump( + $this->docblockParser->parse($docblock), + $ignore + ); + + $this->assertSame($expected, $actual); + } + + public function provideDocblocks(): Generator + { + yield [ + '// @comment', + [], + ]; + + yield [ + <<<'DOCBLOCK' + /** + * Empty. + */ +DOCBLOCK + , + [], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation + */ +DOCBLOCK + , + ['@Annotation'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation() + */ +DOCBLOCK + , + ['@Annotation()'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation () + */ +DOCBLOCK + , + ['@Annotation'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @A + * @B + */ +DOCBLOCK + , + [ + '@A', + '@B', + ], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @A() + * @B() + */ +DOCBLOCK + , + [ + '@A()', + '@B()', + ], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Namespaced\Annotation + */ +DOCBLOCK + , + ['@Namespaced\Annotation'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Namespaced\ Annotation + */ +DOCBLOCK + , + ['@Namespaced'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Namespaced\Annotation() + */ +DOCBLOCK + , + ['@Namespaced\Annotation()'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation("string") + */ +DOCBLOCK + , + ['@Annotation("string")'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * "string" + * ) + */ +DOCBLOCK + , + ['@Annotation("string")'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(123, "string", 1.23, false, true, null) + */ +DOCBLOCK + , + ['@Annotation(123,"string",1.23,false,true,null)'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(a="b", c="d") + */ +DOCBLOCK + , + ['@Annotation(a="b",c="d")'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * a=123, + * b="string", + * c=1.23, + * e=false, + * f=true, + * g=null + * ) + */ +DOCBLOCK + , + ['@Annotation(a=123,b="string",c=1.23,e=false,f=true,g=null)'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(key={}) + */ +DOCBLOCK + , + ['@Annotation(key={})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({"string"}) + */ +DOCBLOCK + , + ['@Annotation({"string"})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * { + * "string" + * } + * ) + */ +DOCBLOCK + , + ['@Annotation({"string"})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({123, "string", 1.23, false, true, null}) + */ +DOCBLOCK + , + ['@Annotation({123,"string",1.23,false,true,null})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({"key"="value"}) + */ +DOCBLOCK + , + ['@Annotation({"key"="value"})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a="b", c="d"}) + */ +DOCBLOCK + , + ['@Annotation({a="b",c="d"})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a="b", "c"="d", 123="e"}) + */ +DOCBLOCK + , + ['@Annotation({a="b","c"="d",123="e"})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key={}}) + */ +DOCBLOCK + , + ['@Annotation({key={}})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(a={b={}}) + */ +DOCBLOCK + , + ['@Annotation(a={b={}})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key: {}}) + */ +DOCBLOCK + , + ['@Annotation({key:{}})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(a={b: {}}) + */ +DOCBLOCK + , + ['@Annotation(a={b:{}})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key: "value"}) + */ +DOCBLOCK + , + ['@Annotation({key:"value"})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a: "b", c: "d"}) + */ +DOCBLOCK + , + ['@Annotation({a:"b",c:"d"})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a: "b", "c": "d", 123: "e"}) + */ +DOCBLOCK + , + ['@Annotation({a:"b","c":"d",123:"e"})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * { + * "a", + * { + * { + * "c" + * }, + * "b" + * } + * } + * ) + */ +DOCBLOCK + , + ['@Annotation({"a",{{"c"},"b"}})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(@Nested) + */ +DOCBLOCK + , + ['@Annotation(@Nested)'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(@Nested()) + */ +DOCBLOCK + , + ['@Annotation(@Nested())'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(@Nested, @Nested) + */ +DOCBLOCK + , + ['@Annotation(@Nested,@Nested)'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * @Nested(), + * @Nested() + * ) + */ +DOCBLOCK + , + ['@Annotation(@Nested(),@Nested())'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(key=@Nested) + */ +DOCBLOCK + , + ['@Annotation(key=@Nested)'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(a=@Nested(),b=@Nested) + */ +DOCBLOCK + , + ['@Annotation(a=@Nested(),b=@Nested)'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key=@Nested}) + */ +DOCBLOCK + , + ['@Annotation({key=@Nested})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a=@Nested(),b=@Nested}) + */ +DOCBLOCK + , + ['@Annotation({a=@Nested(),b=@Nested})'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * @Nested( + * { + * "a", + * { + * { + * "c" + * }, + * "b" + * } + * } + * ), + * @Nested( + * { + * "d", + * { + * { + * "f", + * }, + * "e" + * } + * } + * ) + * ) + */ +DOCBLOCK + , + ['@Annotation(@Nested({"a",{{"c"},"b"}}),@Nested({"d",{{"f"},"e"}}))'], + ]; + + yield [ + << [ + <<<'DOCBLOCK' +/** @Annotation1 @Annotation2 @Annotation3 */ +DOCBLOCK + , + [ + '@Annotation1', + '@Annotation2', + '@Annotation3', + ], + ]; + + yield 'multiple with comments' => [ + <<<'DOCBLOCK' +/** + * Hello world + * @Annotation1 + * Hola mundo + * @Annotation2 + */ +DOCBLOCK + , + [ + '@Annotation1', + '@Annotation2', + ], + ]; + + yield 'fully qualified with parameter' => [ + <<<'DOCBLOCK' +/** +* @\Ns\Annotation("value") +*/ +DOCBLOCK + , + ['@\Ns\Annotation("value")'], + ]; + yield 'with array' => [ + <<<'DOCBLOCK' +/** +* @return array +*/ +DOCBLOCK + , + ['@return'], + ]; + + yield 'fully qualified, nested, multiple parameters' => [ + <<<'DOCBLOCK' +/** +* @\Ns\Name(int=1, annot=@Annot, float=1.2) +*/ +DOCBLOCK + , + ['@\Ns\Name(int=1,annot=@Annot,float=1.2)'], + ]; + + yield 'nested, with arrays' => [ + <<<'DOCBLOCK' +/** +* @Annot( +* v1={1,2,3}, +* v2={@one,@two,@three}, +* v3={one=1,two=2,three=3}, +* v4={one=@one(1),two=@two(2),three=@three(3)} +* ) +*/ +DOCBLOCK + , + ['@Annot(v1={1,2,3},v2={@one,@two,@three},v3={one=1,two=2,three=3},v4={one=@one(1),two=@two(2),three=@three(3)})'], + ]; + + yield 'ORM Id example' => [ + <<<'DOCBLOCK' +/** + * @ORM\Id @ORM\Column(type="integer") + * @ORM\GeneratedValue + */ +DOCBLOCK + , + [ + '@ORM\Id', + '@ORM\Column(type="integer")', + '@ORM\GeneratedValue', + ], + ]; + + yield 'unicode' => [ + <<<'DOCBLOCK' +/** + * @Fancy😊Annotation + */ +DOCBLOCK + , + ['@Fancy😊Annotation'], + ]; + + yield 'spaces after @' => [ + <<<'DOCBLOCK' +/** + * @ + * @ Hello world + */ +DOCBLOCK + , + [], + ]; + + yield 'numbers' => [ + <<<'DOCBLOCK' +/** + * @Annotation(1, 123, -123, 1.2, 123.456, -123.456, 1e2, 123e456, 1.2e-3, -123.456E-789) + */ +DOCBLOCK + , + ['@Annotation(1,123,-123,1.2,123.456,-123.456,1e2,123e456,1.2e-3,-123.456E-789)'], + ]; + + yield 'ORM Column example' => [ + <<<'DOCBLOCK' +/** @ORM\Column(type="string", length=50, nullable=true) */ +DOCBLOCK + , + ['@ORM\Column(type="string",length=50,nullable=true)'], + ]; + + yield 'complex ORM M:N' => [ + <<<'DOCBLOCK' +/** + * @ORM\ManyToMany(targetEntity=CmsGroup::class, inversedBy="users", cascade={"persist"}) + * @ORM\JoinTable(name="cms_users_groups", + * joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ +DOCBLOCK + , + [ + '@ORM\ManyToMany(targetEntity=CmsGroup::class,inversedBy="users",cascade={"persist"})', + '@ORM\JoinTable(name="cms_users_groups",joinColumns={@ORM\JoinColumn(name="user_id",referencedColumnName="id")},inverseJoinColumns={@ORM\JoinColumn(name="group_id",referencedColumnName="id")})', + ], + ]; + + yield 'Symfony route' => [ + <<<'DOCBLOCK' +/** + * @Route("/argument_with_route_param_and_default/{value}", defaults={"value": "value"}, name="argument_with_route_param_and_default") + */ +DOCBLOCK + , + ['@Route("/argument_with_route_param_and_default/{value}",defaults={"value":"value"},name="argument_with_route_param_and_default")'], + ]; + + yield 'SymfonyFrameworkExtraBundle annotations' => [ + <<<'DOCBLOCK' +/** + * @Route("/is_granted/resolved/conflict") + * @IsGranted("ISGRANTED_VOTER", subject="request") + * @Security("is_granted('ISGRANTED_VOTER', request)") + */ +DOCBLOCK + , + [ + '@Route("/is_granted/resolved/conflict")', + '@IsGranted("ISGRANTED_VOTER",subject="request")', + '@Security("is_granted(\'ISGRANTED_VOTER\', request)")', + ], + ]; + + yield 'JMS Serializer field' => [ + <<<'DOCBLOCK' +/** + * @Type("array") + * @SerializedName("addresses") + * @XmlElement(namespace="http://example.com/namespace2") + * @XmlMap(inline = false, entry = "address", keyAttribute = "id", namespace="http://example.com/namespace2") + */ +DOCBLOCK + , + [ + '@Type("array")', + '@SerializedName("addresses")', + '@XmlElement(namespace="http://example.com/namespace2")', + '@XmlMap(inline=false,entry="address",keyAttribute="id",namespace="http://example.com/namespace2")', + ], + ]; + + yield 'string escaping' => [ + <<<'DOCBLOCK' +/** + * @Annotation("", "foo", "b\"a\"r", "ba\\z", "bla\h", "\\\\hello\\\\") + */ +DOCBLOCK + , + ['@Annotation("","foo","b\"a\"r","ba\\\\z","bla\h","\\\\\\\\hello\\\\\\\\")'], + ]; + yield 'constants' => [ + <<<'DOCBLOCK' +/** + * @Annotation(Foo\Bar::BAZ, \Foo\Bar\Baz::BLAH) + */ +DOCBLOCK + , + ['@Annotation(Foo\Bar::BAZ,\Foo\Bar\Baz::BLAH)'], + ]; + yield [ + <<<'DOCBLOCK' +/** + * @TrailingComma( + * 123, + * @Foo(1, 2, 3,), + * @Bar, + * ) + */ +DOCBLOCK + , + ['@TrailingComma(123,@Foo(1,2,3),@Bar)'], + ]; + + yield 'inline annotation' => [ + <<<'DOCBLOCK' +/** + * Hello world from @Annotation + */ +DOCBLOCK + , + ['@Annotation'], + ]; + + yield 'one-line annotation' => [ + <<<'DOCBLOCK' +/** @var string */ +DOCBLOCK + , + ['@var'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Ignored + * @Kept + */ +DOCBLOCK + , + ['@Kept'], + ['Ignored'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @ignored + * @Kept + */ +DOCBLOCK + , + ['@Kept'], + ['Ignored'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Kept(@Ignored) + */ +DOCBLOCK + , + ['@Kept()'], + ['Ignored'], + ]; + } +} diff --git a/tests/Annotation/DocblockAnnotationParserTest.php b/tests/Annotation/DocblockAnnotationParserTest.php new file mode 100644 index 000000000..94dfcd233 --- /dev/null +++ b/tests/Annotation/DocblockAnnotationParserTest.php @@ -0,0 +1,80 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Annotation; + +use Generator; +use PHPUnit\Framework\TestCase; + +/** + * @covers \KevinGH\Box\Annotation\DocblockAnnotationParser + */ +class DocblockAnnotationParserTest extends TestCase +{ + /** + * @var DocblockAnnotationParser + */ + private $annotationParser; + + /** + * {@inheritdoc} + */ + protected function setUp(): void + { + $this->annotationParser = new DocblockAnnotationParser( + new DocblockParser(), + new AnnotationDumper(), + ['ignored'] + ); + } + + /** + * @dataProvider provideDocblocks + */ + public function test_it_can_parse_PHP_docblocks(string $docblock, array $expected): void + { + $actual = $this->annotationParser->parse($docblock); + + $this->assertSame($expected, $actual); + } + + public function provideDocblocks(): Generator + { + yield [ + '// @comment', + [], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation + */ +DOCBLOCK + , + ['@Annotation'], + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @ignored + * @Kept + */ +DOCBLOCK + , + ['@Kept'], + ]; + } +} diff --git a/tests/Annotation/DocblockParserTest.php b/tests/Annotation/DocblockParserTest.php new file mode 100644 index 000000000..b1686306f --- /dev/null +++ b/tests/Annotation/DocblockParserTest.php @@ -0,0 +1,1238 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Annotation; + +use Generator; +use Hoa\Compiler\Visitor\Dump; +use PHPUnit\Framework\TestCase; + +/** + * @covers \KevinGH\Box\Annotation\DocblockParser + */ +class DocblockParserTest extends TestCase +{ + /** + * @var DocblockParser + */ + private $docblockParser; + + /** + * {@inheritdoc} + */ + protected function setUp(): void + { + $this->docblockParser = new DocblockParser(); + } + + /** + * @dataProvider provideDocblocks + */ + public function test_it_can_parse_PHP_docblocks(string $docblock, string $expected): void + { + $actual = (new Dump())->visit( + $this->docblockParser->parse($docblock) + ); + + $this->assertSame($expected, $actual); + } + + /** + * @dataProvider provideInvalidDocblocks + */ + public function test_it_throws_an_error_if_the_annotation_is_invalid(string $docblock, string $expected): void + { + try { + $this->docblockParser->parse($docblock); + + $this->fail('Expected exception to be thrown.'); + } catch (InvalidDocblock $exception) { + $this->assertSame( + $expected, + $exception->getMessage() + ); + } + } + + public function provideDocblocks(): Generator + { + yield [ + '// @comment', + <<<'TRACE' +> #null + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' + /** + * Empty. + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:simple_identifier, Annotation) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation() + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation () + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:simple_identifier, Annotation) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @A + * @B + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:simple_identifier, A) +> > #annotation +> > > token(annot:simple_identifier, B) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @A() + * @B() + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, A) +> > #annotation +> > > token(annot:valued_identifier, B) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Namespaced\Annotation + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:simple_identifier, Namespaced\Annotation) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Namespaced\ Annotation + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:simple_identifier, Namespaced) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Namespaced\Annotation() + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Namespaced\Annotation) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation("string") + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #string +> > > > > > > token(string:string, string) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * "string" + * ) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #string +> > > > > > > token(string:string, string) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(123, "string", 1.23, false, true, null) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > token(value:integer, 123) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #string +> > > > > > > token(string:string, string) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > token(value:float, 1.23) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > token(value:boolean, false) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > token(value:boolean, true) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > token(value:null, null) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(FALSE, TRUE, NULL) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > token(value:boolean, FALSE) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > token(value:boolean, TRUE) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > token(value:null, NULL) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(key="value") + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #named_parameter +> > > > > token(value:identifier, key) +> > > > > #value +> > > > > > #string +> > > > > > > token(string:string, value) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(a="b", c="d") + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #named_parameter +> > > > > token(value:identifier, a) +> > > > > #value +> > > > > > #string +> > > > > > > token(string:string, b) +> > > > #named_parameter +> > > > > token(value:identifier, c) +> > > > > #value +> > > > > > #string +> > > > > > > token(string:string, d) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * a=123, + * b="string", + * c=1.23, + * e=false, + * f=true, + * g=null + * ) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #named_parameter +> > > > > token(value:identifier, a) +> > > > > #value +> > > > > > token(value:integer, 123) +> > > > #named_parameter +> > > > > token(value:identifier, b) +> > > > > #value +> > > > > > #string +> > > > > > > token(string:string, string) +> > > > #named_parameter +> > > > > token(value:identifier, c) +> > > > > #value +> > > > > > token(value:float, 1.23) +> > > > #named_parameter +> > > > > token(value:identifier, e) +> > > > > #value +> > > > > > token(value:boolean, false) +> > > > #named_parameter +> > > > > token(value:identifier, f) +> > > > > #value +> > > > > > token(value:boolean, true) +> > > > #named_parameter +> > > > > token(value:identifier, g) +> > > > > #value +> > > > > > token(value:null, null) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #list + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(key={}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #named_parameter +> > > > > token(value:identifier, key) +> > > > > #value +> > > > > > #list + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({"string"}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #list +> > > > > > > #value +> > > > > > > > #string +> > > > > > > > > token(string:string, string) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * { + * "string" + * } + * ) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #list +> > > > > > > #value +> > > > > > > > #string +> > > > > > > > > token(string:string, string) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' + /** + * @Annotation({123, "string", 1.23, false, true, null}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #list +> > > > > > > #value +> > > > > > > > token(value:integer, 123) +> > > > > > > #value +> > > > > > > > #string +> > > > > > > > > token(string:string, string) +> > > > > > > #value +> > > > > > > > token(value:float, 1.23) +> > > > > > > #value +> > > > > > > > token(value:boolean, false) +> > > > > > > #value +> > > > > > > > token(value:boolean, true) +> > > > > > > #value +> > > > > > > > token(value:null, null) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key="value"}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, key) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, value) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({"key"="value"}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_equal +> > > > > > > > #string +> > > > > > > > > token(string:string, key) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, value) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a="b", c="d"}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, a) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, b) +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, c) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, d) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a="b", "c"="d", 123="e"}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, a) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, b) +> > > > > > > #pair_equal +> > > > > > > > #string +> > > > > > > > > token(string:string, c) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, d) +> > > > > > > #pair_equal +> > > > > > > > token(value:integer, 123) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, e) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key={}}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, key) +> > > > > > > > #value +> > > > > > > > > #list + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(a={b={}}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #named_parameter +> > > > > token(value:identifier, a) +> > > > > #value +> > > > > > #map +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, b) +> > > > > > > > #value +> > > > > > > > > #list + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key: {}}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_colon +> > > > > > > > token(value:identifier, key) +> > > > > > > > #value +> > > > > > > > > #list + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(a={b: {}}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #named_parameter +> > > > > token(value:identifier, a) +> > > > > #value +> > > > > > #map +> > > > > > > #pair_colon +> > > > > > > > token(value:identifier, b) +> > > > > > > > #value +> > > > > > > > > #list + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key: "value"}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_colon +> > > > > > > > token(value:identifier, key) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, value) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a: "b", c: "d"}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_colon +> > > > > > > > token(value:identifier, a) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, b) +> > > > > > > #pair_colon +> > > > > > > > token(value:identifier, c) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, d) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a: "b", "c": "d", 123: "e"}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_colon +> > > > > > > > token(value:identifier, a) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, b) +> > > > > > > #pair_colon +> > > > > > > > #string +> > > > > > > > > token(string:string, c) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, d) +> > > > > > > #pair_colon +> > > > > > > > token(value:integer, 123) +> > > > > > > > #value +> > > > > > > > > #string +> > > > > > > > > > token(string:string, e) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * { + * "a", + * { + * { + * "c" + * }, + * "b" + * } + * } + * ) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #list +> > > > > > > #value +> > > > > > > > #string +> > > > > > > > > token(string:string, a) +> > > > > > > #value +> > > > > > > > #list +> > > > > > > > > #value +> > > > > > > > > > #list +> > > > > > > > > > > #value +> > > > > > > > > > > > #string +> > > > > > > > > > > > > token(string:string, c) +> > > > > > > > > #value +> > > > > > > > > > #string +> > > > > > > > > > > token(string:string, b) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(@Nested) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:simple_identifier, Nested) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(@Nested()) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:valued_identifier, Nested) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(@Nested, @Nested) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:simple_identifier, Nested) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:simple_identifier, Nested) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * @Nested(), + * @Nested() + * ) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:valued_identifier, Nested) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:valued_identifier, Nested) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(key=@Nested) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #named_parameter +> > > > > token(value:identifier, key) +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:simple_identifier, Nested) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation(a=@Nested(),b=@Nested) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #named_parameter +> > > > > token(value:identifier, a) +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:valued_identifier, Nested) +> > > > #named_parameter +> > > > > token(value:identifier, b) +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:simple_identifier, Nested) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({key=@Nested}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, key) +> > > > > > > > #value +> > > > > > > > > #annotation +> > > > > > > > > > token(annot:simple_identifier, Nested) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation({a=@Nested(),b=@Nested}) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #map +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, a) +> > > > > > > > #value +> > > > > > > > > #annotation +> > > > > > > > > > token(annot:valued_identifier, Nested) +> > > > > > > #pair_equal +> > > > > > > > token(value:identifier, b) +> > > > > > > > #value +> > > > > > > > > #annotation +> > > > > > > > > > token(annot:simple_identifier, Nested) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @Annotation( + * @Nested( + * { + * "a", + * { + * { + * "c" + * }, + * "b" + * } + * } + * ), + * @Nested( + * { + * "d", + * { + * { + * "f", + * }, + * "e" + * } + * } + * ) + * ) + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:valued_identifier, Annotation) +> > > #parameters +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:valued_identifier, Nested) +> > > > > > > #parameters +> > > > > > > > #unnamed_parameter +> > > > > > > > > #value +> > > > > > > > > > #list +> > > > > > > > > > > #value +> > > > > > > > > > > > #string +> > > > > > > > > > > > > token(string:string, a) +> > > > > > > > > > > #value +> > > > > > > > > > > > #list +> > > > > > > > > > > > > #value +> > > > > > > > > > > > > > #list +> > > > > > > > > > > > > > > #value +> > > > > > > > > > > > > > > > #string +> > > > > > > > > > > > > > > > > token(string:string, c) +> > > > > > > > > > > > > #value +> > > > > > > > > > > > > > #string +> > > > > > > > > > > > > > > token(string:string, b) +> > > > #unnamed_parameter +> > > > > #value +> > > > > > #annotation +> > > > > > > token(annot:valued_identifier, Nested) +> > > > > > > #parameters +> > > > > > > > #unnamed_parameter +> > > > > > > > > #value +> > > > > > > > > > #list +> > > > > > > > > > > #value +> > > > > > > > > > > > #string +> > > > > > > > > > > > > token(string:string, d) +> > > > > > > > > > > #value +> > > > > > > > > > > > #list +> > > > > > > > > > > > > #value +> > > > > > > > > > > > > > #list +> > > > > > > > > > > > > > > #value +> > > > > > > > > > > > > > > > #string +> > > > > > > > > > > > > > > > > token(string:string, f) +> > > > > > > > > > > > > #value +> > > > > > > > > > > > > > #string +> > > > > > > > > > > > > > > token(string:string, e) + +TRACE + ]; + + yield [ + << #annotations +> > #annotation +> > > token(annot:simple_identifier, Escaped) + +TRACE + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @author Made Up + */ +DOCBLOCK + , + <<<'TRACE' +> #annotations +> > #annotation +> > > token(annot:simple_identifier, author) + +TRACE + ]; + } + + public function provideInvalidDocblocks(): Generator + { + yield [ + '/**@a(1,,)*/', + <<<'EOF' +Could not parse the following docblock: "/**@a(1,,)*/". Cause: "Unexpected token "," (comma) at line 1 and column 9: +/**@a(1,,)*/ + ↑" +EOF + ]; + + yield [ + '/**@\\*/', + <<<'EOF' +Could not parse the following docblock: "/**@\*/". Cause: "Unrecognized token "\" at line 1 and column 5: +/**@\*/ + ↑" +EOF + ]; + + yield [ + '/**@a(!)*/', + <<<'EOF' +Could not parse the following docblock: "/**@a(!)*/". Cause: "Unrecognized token "!" at line 1 and column 7: +/**@a(!)*/ + ↑" +EOF + ]; + + yield [ + '/**@a({x)*/', + <<<'EOF' +Could not parse the following docblock: "/**@a({x)*/". Cause: "Unexpected token ")" (_parenthesis) at line 1 and column 9: +/**@a({x)*/ + ↑" +EOF + ]; + + yield [ + '/**@a({@:1})*/', + <<<'EOF' +Could not parse the following docblock: "/**@a({@:1})*/". Cause: "Unrecognized token ":" at line 1 and column 9: +/**@a({@:1})*/ + ↑" +EOF + ]; + + yield [ + <<<'DOCBLOCK' +/** + * @!Skipped + */ +DOCBLOCK + , + <<<'EOF' +Could not parse the following docblock: "/** + * @!Skipped + */". Cause: "Unrecognized token "!" at line 1 and column 9: +/** + * @!Skipped + */ + ↑" +EOF + ]; + } +} diff --git a/tests/Compactor/PhpTest.php b/tests/Compactor/PhpTest.php index 30b451a49..2a1a30733 100644 --- a/tests/Compactor/PhpTest.php +++ b/tests/Compactor/PhpTest.php @@ -15,7 +15,9 @@ namespace KevinGH\Box; use Generator; -use Herrera\Annotations\Tokenizer; +use KevinGH\Box\Annotation\AnnotationDumper; +use KevinGH\Box\Annotation\DocblockAnnotationParser; +use KevinGH\Box\Annotation\DocblockParser; use KevinGH\Box\Compactor\Php; use PHPUnit\Framework\TestCase; @@ -29,7 +31,13 @@ class PhpTest extends TestCase */ public function test_it_supports_PHP_files(string $file, bool $supports): void { - $compactor = new Php(new Tokenizer()); + $compactor = new Php( + new DocblockAnnotationParser( + new DocblockParser(), + new AnnotationDumper(), + [] + ) + ); $contents = <<<'PHP' compact($file, $content); @@ -68,8 +76,14 @@ public function provideFiles(): Generator public function providePhpContent(): Generator { + $regularAnnotationParser = new DocblockAnnotationParser( + new DocblockParser(), + new AnnotationDumper(), + [] + ); + yield 'simple PHP file with comments' => [ - new Tokenizer(), + $regularAnnotationParser, <<<'PHP' [ - (static function (): Tokenizer { - $tokenizer = new Tokenizer(); - $tokenizer->ignore(['ignored']); - - return $tokenizer; - })(), + new DocblockAnnotationParser( + new DocblockParser(), + new AnnotationDumper(), + ['ignored'] + ), <<<'PHP' [ - (static function (): Tokenizer { - $tokenizer = new Tokenizer(); - $tokenizer->ignore(['author', 'inline']); - - return $tokenizer; - })(), + new DocblockAnnotationParser( + new DocblockParser(), + new AnnotationDumper(), + ['author', 'inline'] + ), <<<'PHP' [ - new Tokenizer(), + $regularAnnotationParser, ' [ - new Tokenizer(), + yield 'Invalid annotation with ignored param' => [ + $regularAnnotationParser, <<<'PHP' [ + $regularAnnotationParser, + <<<'PHP' + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box; + +use Generator; +use KevinGH\Box\Compactor\Php; +use stdClass; +use function current; + +/** + * @covers \KevinGH\Box\Configuration + * @group config + */ +class ConfigurationPhpCompactorTest extends ConfigurationTestCase +{ + public function test_the_PHP_compactor_can_be_registered(): void + { + $this->setConfig([ + 'compactors' => [ + Php::class, + ], + ]); + + $compactors = $this->config->getCompactors(); + + $this->assertCount(1, $compactors); + + /** @var Compactor $compactor */ + $compactor = current($compactors); + + $this->assertInstanceOf(Php::class, $compactor); + + $this->assertSame([], $this->config->getRecommendations()); + $this->assertSame([], $this->config->getWarnings()); + } + + public function test_the_PHP_compactor_ignored_annotations_can_be_configured(): void + { + $this->setConfig([ + 'annotations' => (object) [ + 'ignore' => [ + 'author', + 'license', + ], + ], + 'compactors' => [ + Php::class, + ], + ]); + + $compactors = $this->config->getCompactors(); + + $this->assertCount(1, $compactors); + + /** @var Compactor $compactor */ + $compactor = current($compactors); + + $this->assertInstanceOf(Php::class, $compactor); + + $this->assertSame([], $this->config->getRecommendations()); + $this->assertSame([], $this->config->getWarnings()); + } + + public function test_a_recommendation_is_given_if_the_PHP_compactor_annotations_are_configured_with_their_default_values(): void + { + foreach ([true, null] as $annotations) { + $this->setConfig([ + 'annotations' => $annotations, + 'compactors' => [ + Php::class, + ], + ]); + + $compactors = $this->config->getCompactors(); + + $this->assertCount(1, $compactors); + + $this->assertSame( + ['The "annotations" setting can be omitted since is set to its default value'], + $this->config->getRecommendations() + ); + $this->assertSame([], $this->config->getWarnings()); + } + } + + /** + * @dataProvider provideAnnotationConfigurationsWithoutPhpCompactorRegistered + * + * @param mixed $annotationValue + */ + public function test_a_warning_is_given_if_the_PHP_compactor_annotations_are_configured_but_no_PHP_compactor_is_registered( + $annotationValue, + array $expectedRecommendations, + array $expectedWarnings + ): void { + $this->setConfig([ + 'annotations' => $annotationValue, + ]); + + $compactors = $this->config->getCompactors(); + + $this->assertCount(0, $compactors); + + $this->assertSame($expectedRecommendations, $this->config->getRecommendations()); + $this->assertSame($expectedWarnings, $this->config->getWarnings()); + } + + public function test_a_recommendation_is_given_if_the_PHP_compactor_ignored_annotations_are_configured_with_their_default_values(): void + { + $this->setConfig([ + 'annotations' => (object) [ + 'ignore' => [], + ], + ]); + + $compactors = $this->config->getCompactors(); + + $this->assertCount(0, $compactors); + + $this->assertSame( + ['The "annotations#ignore" setting can be omitted since is set to its default value'], + $this->config->getRecommendations() + ); + $this->assertSame( + ['The "annotations" setting has been set but is ignored since no PHP compactor has been configured'], + $this->config->getWarnings() + ); + } + + /** + * @dataProvider providePhpContentsToCompact + */ + public function test_ignored_annotations_are_provided_to_the_PHP_compactor( + array $config, + string $contents, + string $expected + ): void { + $this->setConfig($config); + + $compactors = $this->config->getCompactors(); + + $this->assertCount(1, $compactors); + + /** @var Compactor $compactor */ + $compactor = current($compactors); + + $this->assertInstanceOf(Php::class, $compactor); + + $actual = $compactor->compact('path/to/file.php', $contents); + + $this->assertSame($expected, $actual); + } + + public function provideAnnotationConfigurationsWithoutPhpCompactorRegistered(): Generator + { + $defaultWarning = 'The "annotations" setting has been set but is ignored since no PHP compactor has been configured'; + + yield [ + (object) [ + 'ignore' => [ + 'author', + 'license', + ], + ], + [], + [$defaultWarning], + ]; + + yield [ + true, + ['The "annotations" setting can be omitted since is set to its default value'], + [$defaultWarning], + ]; + + yield [ + false, + [], + [$defaultWarning], + ]; + + yield [ + new stdClass(), + [], + [ + $defaultWarning, + 'The "annotations" setting has been set but no "ignore" setting has been found, hence "annotations" is treated as if it is set to `false`', + ], + ]; + } + + public function providePhpContentsToCompact(): Generator + { + yield [ + [ + 'annotations' => (object) [ + 'ignore' => [ + 'author', + ' license ', + '', + ], + ], + 'compactors' => [Php::class], + ], + <<<'PHP' + $y; +} +PHP + , + <<<'PHP' + $y; +} +PHP + ]; + + $falseAnnotationConfigs = [ + false, + new stdClass(), + (object) [ + 'ignore' => [], + ], + (object) [ + 'ignore' => null, + ], + ]; + + foreach ($falseAnnotationConfigs as $config) { + yield [ + [ + 'annotations' => $config, + 'compactors' => [Php::class], + ], + <<<'PHP' + $y; +} +PHP + , + <<<'PHP' + $y; +} +PHP + ]; + } + + $defaultAnnotationConfigs = [ + null, + true, + ]; + + foreach ($defaultAnnotationConfigs as $config) { + yield [ + [ + 'annotations' => $config, + 'compactors' => [Php::class], + ], + <<<'PHP' + $y; +} +PHP + , + <<<'PHP' + $y; +} +PHP + ]; + } + + yield [ + [ + 'compactors' => [Php::class], + ], + <<<'PHP' + $y; +} +PHP + , + <<<'PHP' + $y; +} +PHP + ]; + } +} diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index e4c336787..2e1abaf6f 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -14,10 +14,8 @@ namespace KevinGH\Box; -use Closure; use DateTimeImmutable; use Generator; -use Herrera\Annotations\Tokenizer; use InvalidArgumentException; use KevinGH\Box\Compactor\DummyCompactor; use KevinGH\Box\Compactor\InvalidCompactor; @@ -649,47 +647,6 @@ public function test_it_cannot_configure_an_invalid_compactor(): void } } - public function test_get_compactors_annotations(): void - { - $this->setConfig([ - 'files' => [self::DEFAULT_FILE], - 'annotations' => (object) [ - 'ignore' => [ - 'author', - ], - ], - 'compactors' => [ - Php::class, - ], - ]); - - $compactors = $this->config->getCompactors(); - - $tokenizer = ( - Closure::bind( - function (Php $phpCompactor): Tokenizer { - return $phpCompactor->tokenizer; - }, - null, - Php::class - ) - )($compactors[0]); - - $this->assertNotNull($tokenizer); - - $ignored = ( - Closure::bind( - function (Tokenizer $tokenizer): array { - return $tokenizer->ignored; - }, - null, - Tokenizer::class - ) - )($tokenizer); - - $this->assertSame(['author'], $ignored); - } - public function test_the_php_scoper_configuration_location_can_be_configured(): void { dump_file('custom.scoper.ini.php', " 'custom'];"); diff --git a/tests/Console/Command/CompileTest.php b/tests/Console/Command/CompileTest.php index f266b3be4..eb1c02058 100644 --- a/tests/Console/Command/CompileTest.php +++ b/tests/Console/Command/CompileTest.php @@ -1336,42 +1336,65 @@ public function test_it_can_build_a_PHAR_file_in_debug_mode(): void -excludeComposerFiles: true -compactors: array:1 [ 0 => KevinGH\Box\Compactor\Php {#140 - -converter: Herrera\Annotations\Convert\ToString {#140 - -break: "\\n" - -char: " " - -level: null - -space: false - -size: 0 - #result: null - #tokens: null - } - -tokenizer: Herrera\Annotations\Tokenizer {#140 - -aliases: [] - -ignored: [] - -lexer: Doctrine\Common\Annotations\DocLexer {#140 - #noCase: array:9 [ - "@" => 101 - "," => 104 - "(" => 109 - ")" => 103 - "{" => 108 - "}" => 102 - "=" => 105 - ":" => 112 - "\" => 107 - ] - #withCase: array:3 [ - "true" => 110 - "false" => 106 - "null" => 111 - ] - -input: null - -tokens: [] - -position: 0 - -peek: 0 - +lookahead: null - +token: null - } + -annotationParser: KevinGH\Box\Annotation\DocblockAnnotationParser {#140 + -docblockParser: KevinGH\Box\Annotation\DocblockParser {#140} + -annotationDumper: KevinGH\Box\Annotation\AnnotationDumper {#140} + -ignored: array:54 [ + 0 => "abstract" + 1 => "access" + 2 => "annotation" + 3 => "api" + 4 => "attribute" + 5 => "attributes" + 6 => "author" + 7 => "category" + 8 => "code" + 9 => "codecoverageignore" + 10 => "codecoverageignoreend" + 11 => "codecoverageignorestart" + 12 => "copyright" + 13 => "deprec" + 14 => "deprecated" + 15 => "endcode" + 16 => "example" + 17 => "exception" + 18 => "filesource" + 19 => "final" + 20 => "fixme" + 21 => "global" + 22 => "ignore" + 23 => "ingroup" + 24 => "inheritdoc" + 25 => "internal" + 26 => "license" + 27 => "link" + 28 => "magic" + 29 => "method" + 30 => "name" + 31 => "override" + 32 => "package" + 33 => "package_version" + 34 => "param" + 35 => "private" + 36 => "property" + 37 => "required" + 38 => "return" + 39 => "see" + 40 => "since" + 41 => "static" + 42 => "staticvar" + 43 => "subpackage" + 44 => "suppresswarnings" + 45 => "target" + 46 => "throw" + 47 => "throws" + 48 => "todo" + 49 => "tutorial" + 50 => "usedby" + 51 => "uses" + 52 => "var" + 53 => "version" + ] } -extensions: array:1 [ 0 => "php" diff --git a/tests/Throwable/Exception/NoConfigurationFoundTest.php b/tests/NoConfigurationFoundTest.php similarity index 100% rename from tests/Throwable/Exception/NoConfigurationFoundTest.php rename to tests/NoConfigurationFoundTest.php