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