diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..6fc7cd0 --- /dev/null +++ b/bin/console @@ -0,0 +1,22 @@ +#!/usr/bin/env php + + */ + +declare(strict_types=1); + +require __DIR__ . '/../vendor/autoload.php'; + +use Respect\StringFormatter\DevTools\Commands\LintMixinCommand; +use Symfony\Component\Console\Application; + +return (static function () { + $application = new Application('Respect/StringFormatter', '1.0'); + $application->addCommand(new LintMixinCommand()); + + return $application->run(); +})(); diff --git a/composer.json b/composer.json index 9d7a7a8..3c421d4 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "description": "A powerful and flexible way of formatting and transforming strings", "require": { "php": "^8.5", + "respect/fluent": "^1.0", "respect/stringifier": "^3.0", "symfony/polyfill-mbstring": "^1.33", "symfony/translation-contracts": "^3.6" @@ -17,7 +18,9 @@ "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^12.5", + "respect/fluentgen": "^1.0", "respect/coding-standard": "^5.0", + "symfony/console": "^6.0|^7.0", "symfony/translation": "^6.0|^7.0" }, "license": "ISC", @@ -30,6 +33,7 @@ "autoload-dev": { "psr-4": { "Respect\\StringFormatter\\": ["docs/contributing/templates/php/src"], + "Respect\\StringFormatter\\DevTools\\": "src-dev/", "Respect\\StringFormatter\\Test\\": ["tests/", "docs/contributing/templates/php/tests"] } }, diff --git a/composer.lock b/composer.lock index 527e69e..9d971f2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,61 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3a55ef562d9ef3da73ee3986e81501d1", + "content-hash": "1e091347f6296882da9496e57bb4682d", "packages": [ + { + "name": "respect/fluent", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Respect/Fluent.git", + "reference": "93b2056ebd36648a24a2cea4f1dd2f696f81e510" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Respect/Fluent/zipball/93b2056ebd36648a24a2cea4f1dd2f696f81e510", + "reference": "93b2056ebd36648a24a2cea4f1dd2f696f81e510", + "shasum": "" + }, + "require": { + "php": "^8.5" + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^12.5", + "respect/coding-standard": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Respect\\Fluent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Respect/Fluent Contributors", + "homepage": "https://github.com/Respect/Fluent/graphs/contributors" + } + ], + "description": "Namespace-aware fluent class resolution", + "keywords": [ + "builder", + "fluent", + "respect" + ], + "support": { + "issues": "https://github.com/Respect/Fluent/issues", + "source": "https://github.com/Respect/Fluent/tree/1.0.0" + }, + "time": "2026-03-23T20:43:17+00:00" + }, { "name": "respect/stringifier", "version": "3.0.0", @@ -444,6 +497,171 @@ ], "time": "2025-08-01T08:46:24+00:00" }, + { + "name": "nette/php-generator", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "0d7060926f5c3e8c488b9b9ced42d857f12a34b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/0d7060926f5c3e8c488b9b9ced42d857f12a34b5", + "reference": "0d7060926f5c3e8c488b9b9ced42d857f12a34b5", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0.6", + "php": "8.1 - 8.5" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "nikic/php-parser": "^5.0", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.40@stable", + "tracy/tracy": "^2.8" + }, + "suggest": { + "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.5 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "support": { + "issues": "https://github.com/nette/php-generator/issues", + "source": "https://github.com/nette/php-generator/tree/v4.2.2" + }, + "time": "2026-02-26T00:58:33+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.3", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.3" + }, + "time": "2026-02-13T03:05:33+00:00" + }, { "name": "nikic/php-parser", "version": "v5.7.0", @@ -717,11 +935,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.38", + "version": "2.1.42", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", - "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0", + "reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0", "shasum": "" }, "require": { @@ -766,25 +984,25 @@ "type": "github" } ], - "time": "2026-01-30T17:12:46+00:00" + "time": "2026-03-17T14:58:32+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "468e02c9176891cc901143da118f09dc9505fc2f" + "reference": "6b5571001a7f04fa0422254c30a0017ec2f2cacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f", - "reference": "468e02c9176891cc901143da118f09dc9505fc2f", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/6b5571001a7f04fa0422254c30a0017ec2f2cacc", + "reference": "6b5571001a7f04fa0422254c30a0017ec2f2cacc", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.15" + "phpstan/phpstan": "^2.1.39" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -809,24 +1027,27 @@ "MIT" ], "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "keywords": [ + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.4" }, - "time": "2025-05-14T10:56:57+00:00" + "time": "2026-02-09T13:21:14+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.12", + "version": "2.0.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4" + "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4", - "reference": "e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6ab598e1bc106e6827fd346ae4a12b4a5d634c32", + "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32", "shasum": "" }, "require": { @@ -862,11 +1083,14 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", + "keywords": [ + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.12" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.16" }, - "time": "2026-01-22T13:40:00+00:00" + "time": "2026-02-14T09:05:21+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1216,16 +1440,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.9", + "version": "12.5.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "83d4c158526c879b4c5cf7149d27958b6d912373" + "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d4c158526c879b4c5cf7149d27958b6d912373", - "reference": "83d4c158526c879b4c5cf7149d27958b6d912373", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47283cfd98d553edcb1353591f4e255dc1bb61f0", + "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0", "shasum": "" }, "require": { @@ -1239,7 +1463,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.2", + "phpunit/php-code-coverage": "^12.5.3", "phpunit/php-file-iterator": "^6.0.1", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", @@ -1294,7 +1518,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.9" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.14" }, "funding": [ { @@ -1318,7 +1542,60 @@ "type": "tidelift" } ], - "time": "2026-02-05T08:01:09+00:00" + "time": "2026-02-18T12:38:40+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" }, { "name": "respect/coding-standard", @@ -1363,6 +1640,64 @@ }, "time": "2026-01-19T10:34:07+00:00" }, + { + "name": "respect/fluentgen", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Respect/FluentGen.git", + "reference": "0e47b54b6748cb445ae34222d3bd0c1d661e6c86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Respect/FluentGen/zipball/0e47b54b6748cb445ae34222d3bd0c1d661e6c86", + "reference": "0e47b54b6748cb445ae34222d3bd0c1d661e6c86", + "shasum": "" + }, + "require": { + "nette/php-generator": "^4.1", + "php": "^8.5" + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^12.5", + "respect/coding-standard": "^5.0", + "respect/fluent": "^1.0" + }, + "suggest": { + "respect/fluent": "Enables #[Composable] prefix composition support" + }, + "type": "library", + "autoload": { + "psr-4": { + "Respect\\FluentGen\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Respect/FluentGen Contributors", + "homepage": "https://github.com/Respect/FluentGen/graphs/contributors" + } + ], + "description": "Code generation for fluent builder interfaces", + "keywords": [ + "fluent", + "fluentgen", + "mixin", + "respect" + ], + "support": { + "issues": "https://github.com/Respect/FluentGen/issues", + "source": "https://github.com/Respect/FluentGen/tree/1.0.1" + }, + "time": "2026-03-23T21:57:24+00:00" + }, { "name": "sebastian/cli-parser", "version": "4.2.0", @@ -1651,16 +1986,16 @@ }, { "name": "sebastian/environment", - "version": "8.0.3", + "version": "8.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" + "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", - "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/7b8842c2d8e85d0c3a5831236bf5869af6ab2a11", + "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11", "shasum": "" }, "require": { @@ -1703,7 +2038,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.4" }, "funding": [ { @@ -1723,7 +2058,7 @@ "type": "tidelift" } ], - "time": "2025-08-12T14:11:56+00:00" + "time": "2026-03-15T07:05:40+00:00" }, { "name": "sebastian/exporter", @@ -2262,32 +2597,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.27.1", + "version": "8.28.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769" + "reference": "66151cfbd25b50e8becd9f809fb704f01fd4d6f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/29bdaee8b65e7ed2b8e702b01852edba8bae1769", - "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/66151cfbd25b50e8becd9f809fb704f01fd4d6f2", + "reference": "66151cfbd25b50e8becd9f809fb704f01fd4d6f2", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.2.0", "php": "^7.4 || ^8.0", - "phpstan/phpdoc-parser": "^2.3.1", + "phpstan/phpdoc-parser": "^2.3.2", "squizlabs/php_codesniffer": "^4.0.1" }, "require-dev": { - "phing/phing": "3.0.1|3.1.1", + "phing/phing": "3.0.1|3.1.2", "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.37", - "phpstan/phpstan-deprecation-rules": "2.0.3", - "phpstan/phpstan-phpunit": "2.0.12", - "phpstan/phpstan-strict-rules": "2.0.7", - "phpunit/phpunit": "9.6.31|10.5.60|11.4.4|11.5.49|12.5.7" + "phpstan/phpstan": "2.1.42", + "phpstan/phpstan-deprecation-rules": "2.0.4", + "phpstan/phpstan-phpunit": "2.0.16", + "phpstan/phpstan-strict-rules": "2.0.10", + "phpunit/phpunit": "9.6.34|10.5.63|11.4.4|11.5.50|12.5.14" }, "type": "phpcodesniffer-standard", "extra": { @@ -2311,7 +2646,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.27.1" + "source": "https://github.com/slevomat/coding-standard/tree/8.28.1" }, "funding": [ { @@ -2323,7 +2658,7 @@ "type": "tidelift" } ], - "time": "2026-01-25T15:57:07+00:00" + "time": "2026-03-22T17:22:38+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -2457,35 +2792,56 @@ "time": "2024-10-20T05:08:20+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "name": "symfony/console", + "version": "v7.4.7", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "url": "https://github.com/symfony/console.git", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { - "files": [ - "function.php" + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2494,18 +2850,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/console/tree/v7.4.7" }, "funding": [ { @@ -2516,25 +2878,523 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-03-06T14:06:20+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-09T10:14:57+00:00" }, { "name": "symfony/translation", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bfde13711f53f549e73b06d27b35a55207528877" + "reference": "1888cf064399868af3784b9e043240f1d89d25ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877", - "reference": "bfde13711f53f549e73b06d27b35a55207528877", + "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", + "reference": "1888cf064399868af3784b9e043240f1d89d25ce", "shasum": "" }, "require": { @@ -2601,7 +3461,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.4" + "source": "https://github.com/symfony/translation/tree/v7.4.6" }, "funding": [ { @@ -2621,7 +3481,7 @@ "type": "tidelift" } ], - "time": "2026-01-13T10:40:19+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "theseer/tokenizer", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 457fe6b..32fc233 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -15,4 +15,10 @@ tests/ + + + + src/Mixins/ + tests/feature/ + diff --git a/src-dev/Commands/LintMixinCommand.php b/src-dev/Commands/LintMixinCommand.php new file mode 100644 index 0000000..ef65e8f --- /dev/null +++ b/src-dev/Commands/LintMixinCommand.php @@ -0,0 +1,120 @@ + + */ + +declare(strict_types=1); + +namespace Respect\StringFormatter\DevTools\Commands; + +use Respect\FluentGen\Config; +use Respect\FluentGen\Fluent\InterfaceConfig; +use Respect\FluentGen\Fluent\MethodBuilder; +use Respect\FluentGen\Fluent\MixinGenerator; +use Respect\FluentGen\NamespaceScanner; +use Respect\StringFormatter\Formatter; +use Respect\StringFormatter\FormatterBuilder; +use Respect\StringFormatter\Mixins\Chain; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +use function count; +use function dirname; +use function file_get_contents; +use function file_put_contents; +use function is_file; +use function is_readable; +use function sprintf; + +#[AsCommand( + name: 'lint:mixin', + description: 'Apply linters to the generated mixin interfaces', +)] +final class LintMixinCommand extends Command +{ + protected function configure(): void + { + $this->addOption( + 'fix', + null, + null, + 'Automatically fix files with issues.', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $srcDir = dirname(__DIR__, 2) . '/src'; + + $config = new Config( + sourceDir: $srcDir, + sourceNamespace: 'Respect\\StringFormatter', + outputDir: $srcDir . '/Mixins', + outputNamespace: 'Respect\\StringFormatter\\Mixins', + ); + + $scanner = new NamespaceScanner( + nodeType: Formatter::class, + excludedClassNames: ['FormatterBuilder'], + ); + + $generator = new MixinGenerator( + config: $config, + scanner: $scanner, + methodBuilder: new MethodBuilder(classSuffix: 'Formatter'), + interfaces: [ + new InterfaceConfig( + suffix: 'Builder', + returnType: Chain::class, + static: true, + rootComment: '@mixin FormatterBuilder', + rootUses: [FormatterBuilder::class], + ), + new InterfaceConfig( + suffix: 'Chain', + returnType: Chain::class, + rootExtends: [Formatter::class], + ), + ], + ); + + $files = $generator->generate(); + + $updatableFiles = []; + foreach ($files as $filename => $content) { + $existingContent = ''; + if (is_file($filename) && is_readable($filename)) { + $existingContent = file_get_contents($filename) ?: ''; + } + + if ($content === $existingContent) { + continue; + } + + $updatableFiles[$filename] = $content; + $output->writeln(sprintf('--- a/%s', $filename)); + $output->writeln(sprintf('+++ b/%s', $filename)); + } + + if ($updatableFiles === []) { + $output->writeln('No changes needed.'); + } else { + $output->writeln(sprintf('Changes needed in %d files.', count($updatableFiles))); + } + + if ($updatableFiles !== [] && !$input->getOption('fix')) { + return Command::FAILURE; + } + + foreach ($updatableFiles as $filename => $content) { + file_put_contents($filename, $content); + } + + return Command::SUCCESS; + } +} diff --git a/src/FormatterBuilder.php b/src/FormatterBuilder.php index 6fa1f2a..c0be9e2 100644 --- a/src/FormatterBuilder.php +++ b/src/FormatterBuilder.php @@ -4,60 +4,43 @@ * SPDX-FileCopyrightText: (c) Respect Project Contributors * SPDX-License-Identifier: ISC * SPDX-FileContributor: Henrique Moody + * SPDX-FileContributor: Alexandre Gomes Gaigalas */ declare(strict_types=1); namespace Respect\StringFormatter; -use ReflectionClass; -use Respect\StringFormatter\Mixin\Builder; +use Respect\Fluent\Attributes\FluentNamespace; +use Respect\Fluent\Builders\Append; +use Respect\Fluent\Factories\NamespaceLookup; +use Respect\Fluent\Resolvers\Suffix; +use Respect\StringFormatter\Mixins\Builder; use function array_reduce; -use function ucfirst; /** @mixin Builder */ -final readonly class FormatterBuilder implements Formatter +#[FluentNamespace(new NamespaceLookup(new Suffix('', 'Formatter'), Formatter::class, 'Respect\\StringFormatter'))] +final readonly class FormatterBuilder extends Append implements Formatter { - /** @var array */ - private array $formatters; - public function __construct(Formatter ...$formatters) { - $this->formatters = $formatters; - } - - public static function create(Formatter ...$formatters): self - { - return new self(...$formatters); + parent::__construct(static::factoryFromAttribute(), ...$formatters); } public function format(string $input): string { - if ($this->formatters === []) { + /** @var array $formatters */ + $formatters = $this->getNodes(); + + if ($formatters === []) { throw new InvalidFormatterException('No formatters have been added to the builder'); } return array_reduce( - $this->formatters, + $formatters, static fn(string $carry, Formatter $formatter) => $formatter->format($carry), $input, ); } - - /** @param array $arguments */ - public function __call(string $name, array $arguments): self - { - /** @var class-string $class */ - $class = __NAMESPACE__ . '\\' . ucfirst($name) . 'Formatter'; - $reflection = new ReflectionClass($class); - - return clone($this, ['formatters' => [...$this->formatters, $reflection->newInstanceArgs($arguments)]]); - } - - /** @param array $arguments */ - public static function __callStatic(string $name, array $arguments): self - { - return self::create()->__call($name, $arguments); - } } diff --git a/src/Internal/CompiledPattern.php b/src/Internal/CompiledPattern.php index 3328097..d395bea 100644 --- a/src/Internal/CompiledPattern.php +++ b/src/Internal/CompiledPattern.php @@ -197,7 +197,7 @@ private static function compileQualifier(string $token, int $offset): string } preg_match('/^\{(\d*)(?:,(\d*))?\}$/', $token, $m); - $max = $m[2] ?? $m[1]; + $max = $m[2] ?? $m[1] ?? ''; return self::$compiledQualifiers[$token] = $max === '' ? '*' : sprintf('{0,%s}', $max); } diff --git a/src/Mixin/Builder.php b/src/Mixins/Builder.php similarity index 76% rename from src/Mixin/Builder.php rename to src/Mixins/Builder.php index abe9ce7..b9f8ffd 100644 --- a/src/Mixin/Builder.php +++ b/src/Mixins/Builder.php @@ -9,9 +9,10 @@ declare(strict_types=1); -namespace Respect\StringFormatter\Mixin; +namespace Respect\StringFormatter\Mixins; use Respect\StringFormatter\FormatterBuilder; +use Respect\StringFormatter\Modifier; /** @mixin FormatterBuilder */ interface Builder @@ -36,23 +37,19 @@ public static function mass(string $unit): Chain; public static function metric(string $unit): Chain; - public static function number( - int $decimals = 0, - string $decimalSeparator = '.', - string $thousandsSeparator = ',', - ): Chain; + public static function number(int $decimals = 0, string $decimalSeparator = '.', string $thousandsSeparator = ','): Chain; public static function pattern(string $pattern): Chain; /** @param array $parameters */ - public static function placeholder(array $parameters): Chain; + public static function placeholder(array $parameters, Modifier|null $modifier = null): Chain; public static function secureCreditCard(string $maskChar = '*'): Chain; public static function time(string $unit): Chain; /** @param 'both'|'left'|'right' $side */ - public static function trim(string $side, string|null $characters): Chain; + public static function trim(string $side = 'both', string|null $characters = null): Chain; public static function uppercase(): Chain; } diff --git a/src/Mixin/Chain.php b/src/Mixins/Chain.php similarity index 76% rename from src/Mixin/Chain.php rename to src/Mixins/Chain.php index 6614f5a..208a622 100644 --- a/src/Mixin/Chain.php +++ b/src/Mixins/Chain.php @@ -9,9 +9,10 @@ declare(strict_types=1); -namespace Respect\StringFormatter\Mixin; +namespace Respect\StringFormatter\Mixins; use Respect\StringFormatter\Formatter; +use Respect\StringFormatter\Modifier; interface Chain extends Formatter { @@ -35,23 +36,19 @@ public function mass(string $unit): Chain; public function metric(string $unit): Chain; - public function number( - int $decimals = 0, - string $decimalSeparator = '.', - string $thousandsSeparator = ',', - ): Chain; + public function number(int $decimals = 0, string $decimalSeparator = '.', string $thousandsSeparator = ','): Chain; public function pattern(string $pattern): Chain; /** @param array $parameters */ - public function placeholder(array $parameters): Chain; + public function placeholder(array $parameters, Modifier|null $modifier = null): Chain; public function secureCreditCard(string $maskChar = '*'): Chain; public function time(string $unit): Chain; /** @param 'both'|'left'|'right' $side */ - public function trim(string $side, string|null $characters): Chain; + public function trim(string $side = 'both', string|null $characters = null): Chain; public function uppercase(): Chain; } diff --git a/tests/Integration/FormatterBuilderTest.php b/tests/Integration/FormatterBuilderTest.php index 3e02480..a15ed8a 100644 --- a/tests/Integration/FormatterBuilderTest.php +++ b/tests/Integration/FormatterBuilderTest.php @@ -4,6 +4,7 @@ * SPDX-FileCopyrightText: (c) Respect Project Contributors * SPDX-License-Identifier: ISC * SPDX-FileContributor: Henrique Moody + * SPDX-FileContributor: Alexandre Gomes Gaigalas */ declare(strict_types=1); @@ -11,11 +12,10 @@ namespace Respect\StringFormatter\Test\Integration; use ArgumentCountError; -use Error; +use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -use ReflectionException; use Respect\StringFormatter\FormatterBuilder; use Respect\StringFormatter\InvalidFormatterException; use Respect\StringFormatter\MaskFormatter; @@ -163,8 +163,7 @@ public function itShouldThrowExceptionWhenFormatterIsNotInstantiable(): void { $builder = new FormatterBuilder(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Cannot instantiate interface Respect\StringFormatter\Formatter'); + $this->expectException(InvalidArgumentException::class); $builder->__call('', []); } @@ -189,8 +188,7 @@ public function itShouldThrowExceptionWhenFormatterDoesNotExist(): void { $builder = new FormatterBuilder(); - $this->expectException(ReflectionException::class); - $this->expectExceptionMessage('Class "Respect\StringFormatter\NonexistentFormatter" does not exist'); + $this->expectException(InvalidArgumentException::class); /** @phpstan-ignore method.notFound */ $builder->nonexistent();