diff --git a/.github/workflows/phpqa.yml b/.github/workflows/phpqa.yml index f28a069..44a11eb 100644 --- a/.github/workflows/phpqa.yml +++ b/.github/workflows/phpqa.yml @@ -1,4 +1,4 @@ -name: Static code analysis +name: "Static code analysis" on: [pull_request] @@ -6,16 +6,19 @@ jobs: static-code-analysis: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: PHPStan - uses: docker://jakzal/phpqa:php8.4 + - uses: actions/checkout@v6 + + - name: "PHPStan" + uses: docker://jakzal/phpqa:php8.5 with: args: phpstan analyze src/ -l 1 - - name: PHP-CS-Fixer - uses: docker://jakzal/phpqa:php8.4 + + - name: "PHP-CS-Fixer" + uses: docker://jakzal/phpqa:php8.5 with: - args: php-cs-fixer --dry-run --allow-risky=yes --no-interaction --ansi fix - - name: Deptrac - uses: docker://jakzal/phpqa:php8.4 + args: php-cs-fixer fix --dry-run --allow-risky=yes --no-interaction --ansi + + - name: "Deptrac" + uses: docker://jakzal/phpqa:php8.5 with: - args: deptrac --no-interaction --ansi --formatter-graphviz-display=0 + args: deptrac --config-file=depfile.yaml --no-interaction --ansi diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..7353797 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,30 @@ +in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') + ->name('*.php') +; + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + 'array_syntax' => ['syntax' => 'short'], + 'cast_spaces' => ['space' => 'single'], + 'no_extra_blank_lines' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'single_quote' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], + 'trim_array_spaces' => true, + 'no_unused_imports' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => [ + 'statements' => ['return'], + ], + ]) + ->setFinder($finder) +; diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 64e37a8..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,45 +0,0 @@ -filter: - paths: [src/*] - excluded_paths: [tests/*] -checks: - php: - code_rating: true - duplication: true - remove_extra_empty_lines: true - remove_php_closing_tag: true - remove_trailing_whitespace: true - fix_use_statements: - remove_unused: true - preserve_multiple: false - preserve_blanklines: true - order_alphabetically: true - fix_php_opening_tag: true - fix_linefeed: true - fix_line_ending: true - fix_identation_4spaces: true - fix_doc_comments: true -tools: - external_code_coverage: - timeout: 600 - runs: 2 - php_code_coverage: true - php_code_sniffer: - config: - standard: PSR2 - filter: - paths: ['src'] - php_loc: - enabled: true - excluded_dirs: [tests] - php_cpd: - enabled: true - excluded_dirs: [tests] -build: - image: default-bionic - environment: - php: 8.4.0 - nodes: - analysis: - tests: - override: - - php-scrutinizer-run diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5161ca6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: php - -php: - - 8.4 - -script: - - composer install --dev - - php vendor/bin/phpunit --coverage-clover=coverage.clover - -after_script: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/composer.json b/composer.json index cf26d85..66e6394 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,9 @@ "require-dev": { "phpunit/phpunit": "^13.1", "mockery/mockery": "^1.6", - "squizlabs/php_codesniffer": "^4.0" + "squizlabs/php_codesniffer": "^4.0", + "friendsofphp/php-cs-fixer": "^3.95", + "qossmic/deptrac-shim": "^1.0" }, "scripts": { "test": "vendor/bin/phpunit" diff --git a/depfile.yaml b/depfile.yaml new file mode 100644 index 0000000..d3264f7 --- /dev/null +++ b/depfile.yaml @@ -0,0 +1,66 @@ +parameters: + paths: + - ./src + exclude_files: + - .*test.* + layers: + - name: AST + collectors: + - type: directory + value: src/AST/.* + - name: Grammar + collectors: + - type: directory + value: src/Grammar/.* + - name: Highlighter + collectors: + - type: directory + value: src/Highlighter/.* + - name: Lexer + collectors: + - type: directory + value: src/Lexer/.* + - name: Parser + collectors: + - type: directory + value: src/Parser/.* + - name: TokenStream + collectors: + - type: directory + value: src/TokenStream/.* + - name: Root + collectors: + - type: classNameRegex + value: '/.*Rule(Engine|EngineBuilder)?$/' + ruleset: + Root: + - Lexer + - Parser + - TokenStream + - Grammar + - AST + - Highlighter + Parser: + - Lexer + - AST + - TokenStream + AST: + - Grammar + - TokenStream + - Parser + TokenStream: + - Grammar + - Parser + Grammar: + - Lexer + - TokenStream + - Parser + Lexer: + - Grammar + - TokenStream + - Parser + Highlighter: + - Lexer + - Parser + - TokenStream + - Grammar diff --git a/phpstan.neon b/phpstan.neon index 833d7ae..3672b11 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,3 @@ parameters: - checkGenericClassInNonGenericObjectType: false - checkMissingIterableValueType: false - - excludes_analyse: + excludePaths: - vendor/ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 82d373f..9142cb0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ @@ -10,7 +10,7 @@ - src + src diff --git a/src/Grammar/JavaScript/Methods/Join.php b/src/Grammar/JavaScript/Methods/Join.php index 6bbfadf..e7ae6b0 100644 --- a/src/Grammar/JavaScript/Methods/Join.php +++ b/src/Grammar/JavaScript/Methods/Join.php @@ -10,10 +10,10 @@ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; +use nicoSWD\Rule\Grammar\CallableFunction; +use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\TokenStream\Token\GenericToken; use nicoSWD\Rule\TokenStream\Token\TokenKind; -use nicoSWD\Rule\Parser\Exception\ParserException; -use nicoSWD\Rule\Grammar\CallableFunction; final class Join extends CallableFunction { diff --git a/src/Grammar/JavaScript/Methods/Test.php b/src/Grammar/JavaScript/Methods/Test.php index 88d66ab..22137aa 100644 --- a/src/Grammar/JavaScript/Methods/Test.php +++ b/src/Grammar/JavaScript/Methods/Test.php @@ -10,11 +10,11 @@ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; +use nicoSWD\Rule\Grammar\CallableFunction; +use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenBool; use nicoSWD\Rule\TokenStream\Token\TokenKind; -use nicoSWD\Rule\Parser\Exception\ParserException; -use nicoSWD\Rule\Grammar\CallableFunction; final class Test extends CallableFunction { @@ -36,7 +36,6 @@ public function call(mixed ...$parameters): TokenBool $subject = current($subject); } - $bool = (bool) preg_match($pattern, (string) $subject); return TokenBool::fromBool($bool); diff --git a/src/Lexer/DefaultLexer.php b/src/Lexer/DefaultLexer.php index c1b9f48..a7a6a56 100644 --- a/src/Lexer/DefaultLexer.php +++ b/src/Lexer/DefaultLexer.php @@ -346,6 +346,7 @@ private function readIdentifier(LexerContext $ctx): BaseToken if ($ctx->startsWith('in')) { $ctx->pos += 2; // skip 'in' + return $this->tokenFactory->createFromToken(TokenKind::NOT_IN, [TokenKind::NOT_IN->value => 'not in'], $offset); } diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index 6f518ea..db50d91 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -32,10 +32,10 @@ use nicoSWD\Rule\AST\SubtractionNode; use nicoSWD\Rule\AST\UnaryMinusNode; use nicoSWD\Rule\AST\VariableNode; +use nicoSWD\Rule\Lexer\Lexer; use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenKind; use nicoSWD\Rule\TokenStream\TokenIterator; -use nicoSWD\Rule\Lexer\Lexer; /** * Recursive descent parser that builds an AST from the token stream. @@ -237,12 +237,14 @@ private function parsePrimary(TokenIterator $tokens): Node // Array literal (may have method calls chained) if ($token->isOfKind(TokenKind::OPENING_ARRAY)) { $node = $this->parseArrayLiteral($tokens); + return $this->parseMethodChain($node, $tokens); } // Function call if ($token->isOfKind(TokenKind::FUNCTION)) { $node = $this->parseFunctionCall($tokens); + return $this->parseMethodChain($node, $tokens); } @@ -368,6 +370,7 @@ private function parseCommaSeparatedList(TokenIterator $tokens, callable $isTerm // Closing token ends the list if ($isTerminator($token)) { $tokens->next(); // consume the closing token + return $items; } diff --git a/src/RuleEngineBuilder.php b/src/RuleEngineBuilder.php index 241acc6..4b72564 100644 --- a/src/RuleEngineBuilder.php +++ b/src/RuleEngineBuilder.php @@ -13,9 +13,9 @@ use nicoSWD\Rule\AST\AstEvaluator; use nicoSWD\Rule\Grammar\Grammar; use nicoSWD\Rule\Grammar\JavaScript\JavaScript; -use nicoSWD\Rule\Parser\Parser; use nicoSWD\Rule\Lexer\DefaultLexer; use nicoSWD\Rule\Lexer\Lexer; +use nicoSWD\Rule\Parser\Parser; use nicoSWD\Rule\TokenStream\FunctionRegistry; use nicoSWD\Rule\TokenStream\MethodRegistry; use nicoSWD\Rule\TokenStream\ObjectMethodCallerFactory; diff --git a/src/TokenStream/MethodRegistry.php b/src/TokenStream/MethodRegistry.php index b73bb23..9216036 100644 --- a/src/TokenStream/MethodRegistry.php +++ b/src/TokenStream/MethodRegistry.php @@ -53,7 +53,6 @@ public function get(string $methodName, BaseToken $token, mixed $rawValue = null return new $this->methods[$methodName]($rawValue); } - private function registerMethods(): void { foreach ($this->grammar->getInternalMethods() as $internalCallable) { diff --git a/src/TokenStream/TokenCollection.php b/src/TokenStream/TokenCollection.php index 7642b76..7220ce0 100644 --- a/src/TokenStream/TokenCollection.php +++ b/src/TokenStream/TokenCollection.php @@ -10,31 +10,13 @@ namespace nicoSWD\Rule\TokenStream; +use ArrayObject; use nicoSWD\Rule\TokenStream\Token\BaseToken; -final class TokenCollection extends \ArrayObject +final class TokenCollection extends ArrayObject { - public function current(): BaseToken - { - /** @var BaseToken $token */ - $token = $this->offsetGet($this->key()); - - return $token; - } - public function add(BaseToken $token): void { $this->append($token); } - - public function toArray(): array - { - $items = []; - - foreach ($this as $item) { - $items[] = $item->getValue(); - } - - return $items; - } } diff --git a/src/autoload.php b/src/autoload.php deleted file mode 100644 index 3accf9f..0000000 --- a/src/autoload.php +++ /dev/null @@ -1,13 +0,0 @@ -assertTrue($this->evaluate($rule, [ 'foo' => 'MA', 'bar' => 'EGP', - 'baz' => '50001' + 'baz' => '50001', ])); $rule = 'foo == "EG" && bar=="EGP" && baz>50000'; @@ -30,7 +30,7 @@ public function multipleAnds(): void $this->assertFalse($this->evaluate($rule, [ 'foo' => 'MA', 'bar' => 'EGP', - 'baz' => '50001' + 'baz' => '50001', ])); $rule = '((foo=="EG") && (bar=="EGP") && (baz>50000))'; @@ -38,7 +38,7 @@ public function multipleAnds(): void $this->assertFalse($this->evaluate($rule, [ 'foo' => 'MA', 'bar' => 'EGP', - 'baz' => '50001' + 'baz' => '50001', ])); } @@ -54,7 +54,7 @@ public function mixedOrsAndAnds(): void $this->assertTrue($this->evaluate($rule, [ 'bar' => 'MA', 'foo' => 'EGP', - 'baz' => '50001' + 'baz' => '50001', ])); } @@ -82,13 +82,13 @@ public function freakingLongRule(): void $this->assertTrue($this->evaluate($rule, [ 'bar' => 'SA', 'qux' => '0002950751', - 'BAR' => 1 + 'BAR' => 1, ])); $this->assertFalse($this->evaluate($rule, [ 'bar' => 'SA', 'qux' => '0002950751', - 'BAR' => '0' + 'BAR' => '0', ])); } @@ -108,7 +108,7 @@ public function negativeComparison(): void 'bar' => 'MA', 'foo' => 'MAD', 'qux' => '0002950751', - 'baz' => '999999' + 'baz' => '999999', ])); } diff --git a/tests/integration/RuleTest.php b/tests/integration/RuleTest.php index cfe2f2e..6dce5d3 100755 --- a/tests/integration/RuleTest.php +++ b/tests/integration/RuleTest.php @@ -11,8 +11,8 @@ namespace nicoSWD\Rule\tests\integration; use nicoSWD\Rule; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; final class RuleTest extends TestCase { @@ -34,7 +34,7 @@ public function basicRuleWithCommentsEvaluatesCorrectly(): void $vars = [ 'foo' => 5, - 'bar' => 7 + 'bar' => 7, ]; $rule = new Rule\Rule($string, $vars); @@ -88,7 +88,7 @@ public function basicInRule(): void #[Test] public function basicNotInRule(): void { - $ruleStr = '5 not + $ruleStr = '5 not in [4, 6, 7]'; $rule = new Rule\Rule($ruleStr); diff --git a/tests/integration/SyntaxErrorTest.php b/tests/integration/SyntaxErrorTest.php index fc379a7..28c1dd5 100755 --- a/tests/integration/SyntaxErrorTest.php +++ b/tests/integration/SyntaxErrorTest.php @@ -22,7 +22,7 @@ final class SyntaxErrorTest extends AbstractTestBase public function emptyParenthesisThrowException(): void { $rule = new Rule('(totalamount != 3) ()', [ - 'totalamount' => '-1' + 'totalamount' => '-1', ]); $this->assertFalse($rule->isValid()); diff --git a/tests/integration/arrays/ArraysTest.php b/tests/integration/arrays/ArraysTest.php index c42267f..e65e40d 100755 --- a/tests/integration/arrays/ArraysTest.php +++ b/tests/integration/arrays/ArraysTest.php @@ -26,7 +26,7 @@ public function arraysEqualUserSuppliedArrays(): void $this->assertTrue($this->evaluate('[123, 12] === foo && bar === [23]', [ 'foo' => [123, 12], - 'bar' => [23] + 'bar' => [23], ])); } diff --git a/tests/integration/methods/CombinedTest.php b/tests/integration/methods/CombinedTest.php index 46ec13c..c1705a5 100755 --- a/tests/integration/methods/CombinedTest.php +++ b/tests/integration/methods/CombinedTest.php @@ -29,7 +29,6 @@ public function mixedMethodCalls(): void $this->assertTrue($this->evaluate('"HeLLo World".charAt(3) === "l".toUpperCase()')); - $this->assertTrue($this->evaluate( '// Something true 1 === 1 && diff --git a/tests/integration/methods/SplitTest.php b/tests/integration/methods/SplitTest.php index 2c59099..af7020c 100755 --- a/tests/integration/methods/SplitTest.php +++ b/tests/integration/methods/SplitTest.php @@ -55,7 +55,7 @@ public function splitDelimiterAsVariableWithMethodCall(): void 'foo.split(delimiter.toUpperCase()) === ["bbb", "bbb", "bbb"]', [ 'foo' => 'bbbAbbbAbbb', - 'delimiter' => 'a' + 'delimiter' => 'a', ] )); } diff --git a/tests/integration/scalars/ScalarTest.php b/tests/integration/scalars/ScalarTest.php index aeaff08..ee5f8f5 100755 --- a/tests/integration/scalars/ScalarTest.php +++ b/tests/integration/scalars/ScalarTest.php @@ -75,7 +75,7 @@ public function stringEscapeSequences(): void $this->assertFalse($this->evaluate('foo == "\t"', ['foo' => '\t'])); // Backslash escape - $this->assertTrue($this->evaluate('foo == "\\\\"', ['foo' => "\\"])); + $this->assertTrue($this->evaluate('foo == "\\\\"', ['foo' => '\\'])); // Carriage return escape $this->assertTrue($this->evaluate('foo == "\r"', ['foo' => "\r"])); diff --git a/tests/unit/Grammar/GrammarTest.php b/tests/unit/Grammar/GrammarTest.php index de88365..909705a 100755 --- a/tests/unit/Grammar/GrammarTest.php +++ b/tests/unit/Grammar/GrammarTest.php @@ -17,7 +17,7 @@ final class GrammarTest extends TestCase { public function testDefaultValues() { - $grammar = new class extends Grammar { + $grammar = new class () extends Grammar { public function getInternalFunctions(): array { return []; diff --git a/tests/unit/Lexer/LexerTest.php b/tests/unit/Lexer/LexerTest.php index ba1dfcc..837d7a8 100644 --- a/tests/unit/Lexer/LexerTest.php +++ b/tests/unit/Lexer/LexerTest.php @@ -63,7 +63,7 @@ public function itProducesSameTokensForNotInOperator(): void public function itProducesSameTokensForNotInWithNewlines(): void { // The lexer produces a cleaner "not in" value even with newlines between "not" and "in". - $rule = '5 not + $rule = '5 not in [4, 6, 7]'; $lexer = new DefaultLexer(new JavaScript(), new TokenFactory()); @@ -428,7 +428,7 @@ public function itUnescapesBackslashInDoubleQuotedString(): void $this->assertCount(1, $tokens); $this->assertSame(TokenKind::ENCAPSED_STRING, $tokens[0]->getKind()); - $this->assertSame("back\\slash", $tokens[0]->getValue()); + $this->assertSame('back\\slash', $tokens[0]->getValue()); } #[Test] @@ -486,7 +486,7 @@ public function itUnescapesNullByteInDoubleQuotedString(): void #[Test] public function itUnescapesMultipleEscapeSequencesInString(): void { - $rule = "\"line1\\nline2\\tindented\\\\end\""; + $rule = '"line1\\nline2\\tindented\\\\end"'; $lexer = new DefaultLexer(new JavaScript(), new TokenFactory()); $tokens = iterator_to_array($lexer->tokenize($rule)); diff --git a/tests/unit/Parser/ParserTest.php b/tests/unit/Parser/ParserTest.php index 34616a0..903f41a 100755 --- a/tests/unit/Parser/ParserTest.php +++ b/tests/unit/Parser/ParserTest.php @@ -11,20 +11,20 @@ namespace nicoSWD\Rule\tests\unit\Parser; use ArrayIterator; -use Mockery as m; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; +use Mockery as m; use nicoSWD\Rule\AST\ComparisonNode; use nicoSWD\Rule\AST\ComparisonOperator; use nicoSWD\Rule\AST\IntegerNode; use nicoSWD\Rule\AST\LogicalNode; use nicoSWD\Rule\AST\LogicalOperator; use nicoSWD\Rule\AST\StringNode; +use nicoSWD\Rule\Lexer\Lexer; use nicoSWD\Rule\Parser\Parser; use nicoSWD\Rule\TokenStream\Token\GenericToken; use nicoSWD\Rule\TokenStream\Token\TokenKind; -use nicoSWD\Rule\Lexer\Lexer; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; final class ParserTest extends TestCase { @@ -53,7 +53,7 @@ public function givenARuleStringWhenValidItShouldReturnTheCompiledRule(): void new GenericToken(TokenKind::GREATER, '>'), new GenericToken(TokenKind::INTEGER, 1), new GenericToken(TokenKind::SPACE, ' '), - new GenericToken(TokenKind::COMMENT, '// true dat!') + new GenericToken(TokenKind::COMMENT, '// true dat!'), ]; $arrayIterator = new ArrayIterator($tokens); diff --git a/tests/unit/Token/TokenFactoryTest.php b/tests/unit/Token/TokenFactoryTest.php index bb78064..5cc7648 100755 --- a/tests/unit/Token/TokenFactoryTest.php +++ b/tests/unit/Token/TokenFactoryTest.php @@ -13,8 +13,8 @@ use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\TokenStream\Token\TokenFactory; use nicoSWD\Rule\TokenStream\Token\TokenKind; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; final class TokenFactoryTest extends TestCase { diff --git a/tests/unit/TokenStream/ObjectMethodCallerTest.php b/tests/unit/TokenStream/ObjectMethodCallerTest.php index ba67237..4ef2ff6 100755 --- a/tests/unit/TokenStream/ObjectMethodCallerTest.php +++ b/tests/unit/TokenStream/ObjectMethodCallerTest.php @@ -15,8 +15,8 @@ use nicoSWD\Rule\TokenStream\Token\GenericToken; use nicoSWD\Rule\TokenStream\Token\TokenFactory; use nicoSWD\Rule\TokenStream\Token\TokenKind; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; use stdClass; final class ObjectMethodCallerTest extends TestCase @@ -33,7 +33,7 @@ public function givenAnObjectWithAPublicPropertyItShouldBeAccessible(): void #[Test] public function givenAnObjectWithAPublicWhenMethodMatchingItShouldBeUsed(): void { - $object = new class { + $object = new class () { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps public function my_test() { @@ -47,7 +47,7 @@ public function my_test() #[Test] public function givenAnObjectWithAPublicWhenMethodNameWithIsPrefixMatchesItShouldBeUsed(): void { - $object = new class { + $object = new class () { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps public function is_my_test() { @@ -67,7 +67,7 @@ public function isMyTest() #[Test] public function givenAnObjectWithAPublicWhenMethodNameWithGetPrefixMatchesItShouldBeUsed(): void { - $object = new class { + $object = new class () { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps public function get_my_test() { diff --git a/tests/unit/TokenStream/TokenIteratorTest.php b/tests/unit/TokenStream/TokenIteratorTest.php index 4532df8..b9dfff0 100755 --- a/tests/unit/TokenStream/TokenIteratorTest.php +++ b/tests/unit/TokenStream/TokenIteratorTest.php @@ -17,8 +17,8 @@ use nicoSWD\Rule\TokenStream\Token\TokenMethod; use nicoSWD\Rule\TokenStream\Token\TokenString; use nicoSWD\Rule\TokenStream\TokenIterator; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; final class TokenIteratorTest extends TestCase {