[
  {
    "path": ".editorconfig",
    "content": "; top-most EditorConfig file\nroot = true\n\n; Unix-style newlines\n[*]\nend_of_line = LF\n\n[*.php]\nindent_style = space\nindent_size = 4\n\n[*.test]\nindent_style = space\nindent_size = 4\n\n[*.rst]\nindent_style = space\nindent_size = 4\n"
  },
  {
    "path": ".gitattributes",
    "content": "/bin/ export-ignore\n/doc/ export-ignore\n/extra/ export-ignore\n/tests/ export-ignore\n/.editorconfig export-ignore\n/.git* export-ignore\n/.php-cs-fixer.dist.php export-ignore\n/phpunit.xml.dist export-ignore\n/phpstan.neon.dist export-ignore\n/phpstan-baseline.neon export-ignore\n/splitsh.json export-ignore\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: \"CI\"\n\non:\n    pull_request:\n    push:\n\nenv:\n    SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE: 1\n\npermissions:\n  contents: read\n\njobs:\n    tests:\n        name: \"PHP ${{ matrix.php-version }}\"\n\n        runs-on: 'ubuntu-latest'\n\n        strategy:\n            matrix:\n                php-version:\n                    - '8.1'\n                    - '8.2'\n                    - '8.3'\n                    - '8.4'\n\n        steps:\n            - name: \"Checkout code\"\n              uses: actions/checkout@v4\n\n            - name: \"Install PHP with extensions\"\n              uses: shivammathur/setup-php@v2\n              with:\n                  coverage: \"none\"\n                  php-version: ${{ matrix.php-version }}\n                  ini-values: memory_limit=-1\n\n            - name: \"Add PHPUnit matcher\"\n              run: echo \"::add-matcher::${{ runner.tool_cache }}/phpunit.json\"\n\n            - run: composer install\n\n            - name: \"Switch use_yield to true on PHP ${{ matrix.php-version }}\"\n              if: \"matrix.php-version == '8.2'\"\n              run: |\n                  sed -i -e \"s/'use_yield' => false/'use_yield' => true/\" src/Environment.php\n\n            - name: \"Install PHPUnit\"\n              run: vendor/bin/simple-phpunit install\n\n            - name: \"PHPUnit version\"\n              run: vendor/bin/simple-phpunit --version\n\n            - name: \"Run tests\"\n              run: vendor/bin/simple-phpunit\n\n    extension-tests:\n        needs:\n            - 'tests'\n\n        name: \"${{ matrix.extension }} PHP ${{ matrix.php-version }}\"\n\n        runs-on: 'ubuntu-latest'\n\n        continue-on-error: true\n\n        strategy:\n            matrix:\n                php-version:\n                    - '8.1'\n                    - '8.2'\n                    - '8.3'\n                    - '8.4'\n                extension:\n                    - 'cache-extra'\n                    - 'cssinliner-extra'\n                    - 'html-extra'\n                    - 'inky-extra'\n                    - 'intl-extra'\n                    - 'markdown-extra'\n                    - 'string-extra'\n                    - 'twig-extra-bundle'\n\n        steps:\n            - name: \"Checkout code\"\n              uses: actions/checkout@v4\n\n            - name: \"Install PHP with extensions\"\n              uses: shivammathur/setup-php@v2\n              with:\n                  coverage: \"none\"\n                  php-version: ${{ matrix.php-version }}\n                  ini-values: memory_limit=-1\n\n            - name: \"Add PHPUnit matcher\"\n              run: echo \"::add-matcher::${{ runner.tool_cache }}/phpunit.json\"\n\n            - name: \"Composer install Twig\"\n              run: composer install\n\n            - name: \"Install PHPUnit\"\n              run: vendor/bin/simple-phpunit install\n\n            - name: \"PHPUnit version\"\n              run: vendor/bin/simple-phpunit --version\n\n            - name: \"Prevent installing symfony/translation-contracts 3.0\"\n              if: \"matrix.extension == 'twig-extra-bundle'\"\n              working-directory: extra/${{ matrix.extension }}\n              run: \"composer require --no-update 'symfony/translation-contracts:^1.1|^2.0'\"\n\n            - name: \"Composer install ${{ matrix.extension }}\"\n              working-directory: extra/${{ matrix.extension }}\n              run: composer install\n\n            - name: \"Switch use_yield to true\"\n              if: \"matrix.php-version == '8.2'\"\n              run: |\n                  sed -i -e \"s/'use_yield' => false/'use_yield' => true/\" extra/${{ matrix.extension }}/vendor/twig/twig/src/Environment.php\n\n            - name: \"Run tests for ${{ matrix.extension }}\"\n              working-directory: extra/${{ matrix.extension }}\n              run: ../../vendor/bin/simple-phpunit\n\n    integration-tests:\n        needs:\n            - 'tests'\n\n        name: \"Integration tests with PHP ${{ matrix.php-version }}\"\n\n        runs-on: 'ubuntu-latest'\n\n        continue-on-error: true\n\n        strategy:\n            matrix:\n                php-version:\n                    - '8.2'\n\n        steps:\n            - name: \"Checkout code\"\n              uses: actions/checkout@v4\n\n            - name: \"Install PHP with extensions\"\n              uses: shivammathur/setup-php@v2\n              with:\n                  coverage: \"none\"\n                  extensions: \"gd, pdo_sqlite, uuid\"\n                  php-version: ${{ matrix.php-version }}\n                  ini-values: memory_limit=-1\n                  tools: composer:v2\n\n            - run: bash ./tests/drupal_test.sh\n              shell: \"bash\"\n\n    phpstan:\n        name: \"PHPStan\"\n\n        runs-on: 'ubuntu-latest'\n\n        strategy:\n            matrix:\n                php-version:\n                    - '8.4'\n\n        steps:\n            - name: \"Checkout code\"\n              uses: actions/checkout@v4\n\n            - name: \"Install PHP with extensions\"\n              uses: shivammathur/setup-php@v2\n              with:\n                  coverage: \"none\"\n                  php-version: ${{ matrix.php-version }}\n                  ini-values: memory_limit=-1\n\n            - run: composer install\n\n            - name: \"Run tests\"\n              run: vendor/bin/phpstan\n"
  },
  {
    "path": ".github/workflows/documentation.yml",
    "content": "name: \"Documentation\"\n\non:\n    pull_request:\n    push:\n\npermissions:\n  contents: read\n\njobs:\n    build:\n        name: \"Build\"\n\n        runs-on: ubuntu-latest\n\n        steps:\n            -   name: \"Checkout code\"\n                uses: actions/checkout@v4\n\n            -   name: \"Set-up PHP\"\n                uses: shivammathur/setup-php@v2\n                with:\n                    php-version: 8.2\n                    coverage: none\n                    tools: \"composer:v2\"\n\n            -   name: Get composer cache directory\n                id: composercache\n                working-directory: doc/_build\n                run: echo \"dir=$(composer config cache-files-dir)\" >> $GITHUB_OUTPUT\n\n            -   name: Cache dependencies\n                uses: actions/cache@v4\n                with:\n                    path: ${{ steps.composercache.outputs.dir }}\n                    key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}\n                    restore-keys: ${{ runner.os }}-composer-\n\n            -   name: \"Install dependencies\"\n                working-directory: doc/_build\n                run: composer install --prefer-dist --no-progress\n\n            -   name: \"Build the docs\"\n                working-directory: doc/_build\n                run: php build.php --disable-cache\n\n    doctor-rst:\n        name: \"DOCtor-RST\"\n\n        runs-on: ubuntu-latest\n\n        steps:\n            - name: \"Checkout code\"\n              uses: actions/checkout@v4\n\n            - name: \"Run DOCtor-RST\"\n              uses: docker://oskarstark/doctor-rst\n              with:\n                  args: --short\n              env:\n                  DOCS_DIR: 'doc/'\n"
  },
  {
    "path": ".github/workflows/fabbot.yml",
    "content": "name: CS\n\non:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  call-fabbot:\n    name: Fabbot\n    uses: symfony-tools/fabbot/.github/workflows/fabbot.yml@main\n    with:\n      package: Twig\n"
  },
  {
    "path": ".gitignore",
    "content": "/doc/_build/vendor\n/doc/_build/output\n/composer.lock\n/phpunit.xml\n/vendor\n.phpunit.result.cache\n"
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "content": "<?php\n\nuse PhpCsFixer\\Config;\nuse PhpCsFixer\\Finder;\nuse PhpCsFixer\\Runner\\Parallel\\ParallelConfigFactory;\n\nreturn (new Config())\n    ->setRules([\n        '@Symfony' => true,\n        '@Symfony:risky' => true,\n        '@PHPUnit75Migration:risky' => true,\n        'php_unit_dedicate_assert' => ['target' => '5.6'],\n        'array_syntax' => ['syntax' => 'short'],\n        'php_unit_fqcn_annotation' => true,\n        'no_unreachable_default_argument_value' => false,\n        'braces' => ['allow_single_line_closure' => true],\n        'heredoc_to_nowdoc' => false,\n        'single_line_throw' => false,\n        'ordered_imports' => true,\n        'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],\n        'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],\n    ])\n    ->setRiskyAllowed(true)\n    ->setParallelConfig(ParallelConfigFactory::detect())\n    ->setFinder((new Finder())->in(__DIR__))\n;\n"
  },
  {
    "path": "CHANGELOG",
    "content": "# 3.24.1 (2026-XX-XX)\n\n * n/a\n\n# 3.24.0 (2026-03-17)\n\n * Deprecate not implementing the `getOperatorTokens()` method in `ExpressionParserInterface` implementations\n * Deprecate passing a non-`AbstractExpression` node to `Twig\\Node\\Expression\\Binary\\MatchesBinary` constructor\n * Deprecate passing a non-`AbstractExpression` node to `Parser::setParent()`\n * Add support for renaming variables in object destructuring (`{name: userName} = user`)\n * Add `html_attr_relaxed` escaping strategy that preserves :, @, [, and ] for front-end framework attribute names\n * Add support for short-circuiting in null-safe operator chains\n * Add the `html_attr` function and `html_attr_merge` as well as `html_attr_type` filters\n\n# 3.23.0 (2026-01-23)\n\n * Add `=` assignment operator (allows to set variables in expression or to replace the short-form of the set tag)\n * Add sequence, mapping, and object destructuring\n * Add `?.` null-safe operator\n * Add `===` and `!==` operators (equivalent to the `same as` and `not same as` tests)\n * Fix opcache preload warning for unlinked anonymous class\n * Fix spread operator behavior\n\n# 3.22.2 (2025-12-14)\n\n * Fix \"cycle\" with non-countable ArrayAccess + Traversable objects\n * Use \"getShareDir\" as an indicator of Symfony version in Symfony bundle\n * Fix escaper compatibility with PHP 8.5\n\n# 3.22.1 (2025-11-16)\n\n * Add support for Symfony 8\n\n# 3.22.0 (2025-10-29)\n\n * Add support for two words test in guard tag\n * Add `Environment::registerUndefinedTestCallback()`\n * Fix compatibility with Symfony 8\n * Fix accessing arrays with stringable objects as key\n * Avoid errors when failing to guess the template info for an error\n * Fix expression parser compatibility layer\n * Fix compiling 'index' with repr (not string) in EmbedNode\n * Update configuration keys + allow extra keys for CommonMark extensions\n * Allow usage of other Markdown converters than CommonMark in LeagueMarkdown\n\n# 3.21.1 (2025-05-03)\n\n * Fix ExtensionSet usage of BinaryOperatorExpressionParser\n\n# 3.21.0 (2025-05-02)\n\n * Fix wrong array index\n * Deprecate `Template::loadTemplate()`\n * Fix testing and expression when it evaluates to an instance of `Markup`\n * Add `ReturnPrimitiveTypeInterface` (and sub-interfaces for number, boolean, string, and array)\n * Add `SupportDefinedTestInterface` for expression nodes supporting the `defined` test\n * Deprecate using the `|` operator in an expression with `+` or `-` without using parentheses to clarify precedence\n * Deprecate operator precedence outside of the [0, 512] range\n * Introduce expression parser classes to describe operators and operands provided by extensions\n   instead of arrays (it comes with many deprecations that are documented in\n   the ``deprecated`` documentation chapter)\n * Deprecate the `Twig\\ExpressionParser`, and `Twig\\OperatorPrecedenceChange` classes\n * Add attributes `AsTwigFilter`, `AsTwigFunction`, and `AsTwigTest` to ease extension development\n\n# 3.20.0 (2025-02-13)\n\n * Fix support for ignoring syntax errors in an undefined handler in guard\n * Add configuration for Commonmark\n * Fix wrong array index\n * Bump minimum PHP version to 8.1\n * Add support for registering callbacks for undefined functions, filters or token parsers in the IntegrationTestCase\n * Use correct line number for `ForElseNode`\n * Fix timezone conversion on strings\n\n# 3.19.0 (2025-01-28)\n\n * Fix a security issue where escaping was missing when using `??`\n * Deprecate `Token::getType()`, use `Token::test()` instead\n * Add `Token::toEnglish()`\n * Add `ForElseNode`\n * Deprecate `Twig\\ExpressionParser::parseOnlyArguments()` and\n  `Twig\\ExpressionParser::parseArguments()` (use\n  `Twig\\ExpressionParser::parseNamedArguments()` instead)\n * Fix `constant()` behavior when used with `??`\n * Add the `invoke` filter\n * Make `{}` optional for the `types` tag\n * Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes\n * Ignore static properties when using the dot operator\n\n# 3.18.0 (2024-12-29)\n\n * Support for invoking closures\n * Fix unary operator precedence change\n * Ignore `SyntaxError` exceptions from undefined handlers when using the `guard` tag\n * Add a way to stream template rendering (`TemplateWrapper::stream()` and `TemplateWrapper::streamBlock()`)\n\n# 3.17.1 (2024-12-12)\n\n * Fix the null coalescing operator when the test returns null\n * Fix the Elvis operator when used as '? :' instead of '?:'\n\n# 3.17.0 (2024-12-10)\n\n * Fix ArrayAccess with objects as keys\n * Support underscores in number literals\n * Deprecate `ConditionalExpression` and `NullCoalesceExpression` (use `ConditionalTernary` and `NullCoalesceBinary` instead)\n\n# 3.16.0 (2024-11-29)\n\n * Deprecate `InlinePrint`\n * Fix having macro variables starting with an underscore\n * Deprecate not passing a `Source` instance to `TokenStream`\n * Deprecate returning `null` from `TwigFilter::getSafe()` and `TwigFunction::getSafe()`, return `[]` instead\n\n# 3.15.0 (2024-11-17)\n\n * [BC BREAK] Add support for accessing class constants with the dot operator;\n   this can be a BC break if you don't use UPPERCASE constant names\n * Add Spanish inflector support for the `plural` and `singular` filters in the String extension\n * Deprecate `TempNameExpression` in favor of `LocalVariable`\n * Deprecate `NameExpression` in favor of `ContextVariable`\n * Deprecate `AssignNameExpression` in favor of `AssignContextVariable`\n * Remove `MacroAutoImportNodeVisitor`\n * Deprecate `MethodCallExpression` in favor of `MacroReferenceExpression`\n * Fix support for the \"is defined\" test on `_self.xxx` (auto-imported) macros\n * Fix support for the \"is defined\" test on inherited macros\n * Add named arguments support for the dot operator arguments (`foo.bar(some: arg)`)\n * Add named arguments support for macros\n * Add a new `guard` tag that allows to test if some Twig callables are available at compilation time\n * Allow arrow functions everywhere\n * Deprecate passing a string or an array to Twig callable arguments accepting arrow functions (pass a `\\Closure`)\n * Add support for triggering deprecations for future operator precedence changes\n * Deprecate using the `not` unary operator in an expression with ``*``, ``/``, ``//``, or ``%`` without using explicit parentheses to clarify precedence\n * Deprecate using the `??` binary operator without explicit parentheses\n * Deprecate using the `~` binary operator in an expression with `+` or `-` without using parentheses to clarify precedence\n * Deprecate not passing `AbstractExpression` args to most constructor arguments for classes extending `AbstractExpression`\n * Fix `power` expressions with a negative number in parenthesis (`(-1) ** 2`)\n * Deprecate instantiating `Node` directly. Use `EmptyNode` or `Nodes` instead.\n * Add support for inline comments\n * Add `Profile::getStartTime()` and `Profile::getEndTime()`\n * Fix \"ignore missing\" when used on an \"embed\" tag\n * Fix the possibility to override an aliased block (via use)\n * Add template cache hot reload\n * Allow Twig callable argument names to be free-form (snake-case or camelCase) independently of the PHP callable signature\n   They were automatically converted to snake-cased before\n * Deprecate the `attribute` function; use the `.` notation and wrap the name with parenthesis instead\n * Add support for argument unpackaging\n * Add JSON support for the file extension escaping strategy\n * Support Markup instances (and any other \\Stringable) as dynamic mapping keys\n * Deprecate the `sandbox` tag\n * Improve the way one can deprecate a Twig callable (use `deprecation_info` instead of the other callable options)\n * Add the `enum` function\n * Add support for logical `xor` operator\n\n# 3.14.2 (2024-11-07)\n\n * Fix an infinite recursion in the sandbox code\n\n# 3.14.1 (2024-11-06)\n\n * [BC BREAK] Fix a security issue in the sandbox mode allowing an attacker to call attributes on Array-like objects\n   They are now checked via the property policy\n * Fix a security issue in the sandbox mode allowing an attacker to be able to call `toString()`\n   under some circumstances on an object even if the `__toString()` method is not allowed by the security policy\n\n# 3.14.0 (2024-09-09)\n\n * Fix a security issue when an included sandboxed template has been loaded before without the sandbox context\n * Add the possibility to reset globals via `Environment::resetGlobals()`\n * Deprecate `Environment::mergeGlobals()`\n\n# 3.13.0 (2024-09-07)\n\n * Add the `types` tag (experimental)\n * Deprecate the `Twig\\Test\\NodeTestCase::getTests()` data provider, override `provideTests()` instead.\n * Mark `Twig\\Test\\NodeTestCase::getEnvironment()` as final, override `createEnvironment()` instead.\n * Deprecate `Twig\\Test\\NodeTestCase::getVariableGetter()`, call `createVariableGetter()` instead.\n * Deprecate `Twig\\Test\\NodeTestCase::getAttributeGetter()`, call `createAttributeGetter()` instead.\n * Deprecate not overriding `Twig\\Test\\IntegrationTestCase::getFixturesDirectory()`, this method will be abstract in 4.0\n * Marked `Twig\\Test\\IntegrationTestCase::getTests()` and `getLegacyTests()` as final\n\n# 3.12.0 (2024-08-29)\n\n * Deprecate the fact that the `extends` and `use` tags are always allowed in a sandboxed template.\n   This behavior will change in 4.0 where these tags will need to be explicitly allowed like any other tag.\n * Deprecate the \"tag\" constructor argument of the \"Twig\\Node\\Node\" class as the tag is now automatically set by the Parser when needed\n * Fix precedence of two-word tests when the first word is a valid test\n * Deprecate the `spaceless` filter\n * Deprecate some internal methods from `Parser`: `getBlockStack()`, `hasBlock()`, `getBlock()`, `hasMacro()`, `hasTraits()`, `getParent()`\n * Deprecate passing `null` to `Twig\\Parser::setParent()`\n * Update `Node::__toString()` to include the node tag if set\n * Add support for integers in methods of `Twig\\Node\\Node` that take a Node name\n * Deprecate not passing a `BodyNode` instance as the body of a `ModuleNode` or `MacroNode` constructor\n * Deprecate returning \"null\" from \"TokenParserInterface::parse()\".\n * Deprecate `OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES`\n * Fix performance regression when `use_yield` is `false` (which is the default)\n * Improve compatibility when `use_yield` is `false` (as extensions still using `echo` will work as is)\n * Accept colons (`:`) in addition to equals (`=`) to separate argument names and values in named arguments\n * Add the `html_cva` function (in the HTML extra package)\n * Add support for named arguments to the `block` and `attribute` functions\n * Throw a SyntaxError exception at compile time when a Twig callable has not the minimum number of required arguments\n * Add a `CallableArgumentsExtractor` class\n * Deprecate passing a name to `FunctionExpression`, `FilterExpression`, and `TestExpression`;\n   pass a `TwigFunction`, `TwigFilter`, or `TestFilter` instead\n * Deprecate all Twig callable attributes on `FunctionExpression`, `FilterExpression`, and `TestExpression`\n * Deprecate the `filter` node of `FilterExpression`\n * Add the notion of Twig callables (functions, filters, and tests)\n * Bump minimum PHP version to 8.0\n * Fix integration tests when a test has more than one data/expect section and deprecations\n * Add the `enum_cases` function\n\n# 3.11.2 (2024-11-06)\n\n * [BC BREAK] Fix a security issue in the sandbox mode allowing an attacker to call attributes on Array-like objects\n   They are now checked via the property policy\n * Fix a security issue in the sandbox mode allowing an attacker to be able to call `toString()`\n   under some circumstances on an object even if the `__toString()` method is not allowed by the security policy\n\n# 3.11.1 (2024-09-10)\n\n * Fix a security issue when an included sandboxed template has been loaded before without the sandbox context\n\n# 3.11.0 (2024-08-08)\n\n * Deprecate `OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER`\n * Add `Twig\\Cache\\ChainCache` and `Twig\\Cache\\ReadOnlyFilesystemCache`\n * Add the possibility to deprecate attributes and nodes on `Node`\n * Add the possibility to add a package and a version to the `deprecated` tag\n * Add the possibility to add a package for filter/function/test deprecations\n * Mark `ConstantExpression` as being `@final`\n * Add the `find` filter\n * Fix optimizer mode validation in `OptimizerNodeVisitor`\n * Add the possibility to yield from a generator in `PrintNode`\n * Add the `shuffle` filter\n * Add the `singular` and `plural` filters in `StringExtension`\n * Deprecate the second argument of `Twig\\Node\\Expression\\CallExpression::compileArguments()`\n * Deprecate `Twig\\ExpressionParser\\parseHashExpression()` in favor of\n   `Twig\\ExpressionParser::parseMappingExpression()`\n * Deprecate `Twig\\ExpressionParser\\parseArrayExpression()` in favor of\n   `Twig\\ExpressionParser::parseSequenceExpression()`\n * Add `sequence` and `mapping` tests\n * Deprecate `Twig\\Node\\Expression\\NameExpression::isSimple()` and\n    `Twig\\Node\\Expression\\NameExpression::isSpecial()`\n\n# 3.10.3 (2024-05-16)\n\n * Fix missing ; in generated code\n\n# 3.10.2 (2024-05-14)\n\n * Fix support for the deprecated escaper signature\n\n# 3.10.1 (2024-05-12)\n\n * Fix BC break on escaper extension\n * Fix constant return type\n\n# 3.10.0 (2024-05-11)\n\n * Make `CoreExtension::formatDate`, `CoreExtension::convertDate`, and\n   `CoreExtension::formatNumber` part of the public API\n * Add `needs_charset` option for filters and functions\n * Extract the escaping logic from the `EscaperExtension` class to a new\n   `EscaperRuntime` class.\n\n   The following methods from ``Twig\\\\Extension\\\\EscaperExtension`` are\n   deprecated: ``setEscaper()``, ``getEscapers()``, ``setSafeClasses``,\n   ``addSafeClasses()``. Use the same methods on the\n   ``Twig\\\\Runtime\\\\EscaperRuntime`` class instead.\n  * Fix capturing output from extensions that still use echo\n  * Fix a PHP warning in the Lexer on malformed templates\n  * Fix blocks not available under some circumstances\n  * Synchronize source context in templates when setting a Node on a Node\n\n# 3.9.3 (2024-04-18)\n\n * Add missing `twig_escape_filter_is_safe` deprecated function\n * Fix yield usage with CaptureNode\n * Add missing unwrap call when using a TemplateWrapper instance internally\n * Ensure Lexer is initialized early on\n\n# 3.9.2 (2024-04-17)\n\n * Fix usage of display_end hook\n\n# 3.9.1 (2024-04-17)\n\n * Fix missing `$blocks` variable in `CaptureNode`\n\n# 3.9.0 (2024-04-16)\n\n * Add support for PHP 8.4\n * Deprecate AbstractNodeVisitor\n * Deprecate passing Template to Environment::resolveTemplate(), Environment::load(), and Template::loadTemplate()\n * Add a new \"yield\" mode for output generation;\n   Node implementations that use \"echo\" or \"print\" should use \"yield\" instead;\n   all Node implementations should be flagged with `#[YieldReady]` once they've been made ready for \"yield\";\n   the \"use_yield\" Environment option can be turned on when all nodes have been made `#[YieldReady]`;\n   \"yield\" will be the only strategy supported in the next major version\n * Add return type for Symfony 7 compatibility\n * Fix premature loop exit in Security Policy lookup of allowed methods/properties\n * Deprecate all internal extension functions in favor of methods on the extension classes\n * Mark all extension functions as @internal\n * Add SourcePolicyInterface to selectively enable the Sandbox based on a template's Source\n * Throw a proper Twig exception when using cycle on an empty array\n\n# 3.8.0 (2023-11-21)\n\n * Catch errors thrown during template rendering\n * Fix IntlExtension::formatDateTime use of date formatter prototype\n * Fix premature loop exit in Security Policy lookup of allowed methods/properties\n * Remove NumberFormatter::TYPE_CURRENCY (deprecated in PHP 8.3)\n * Restore return type annotations\n * Allow Symfony 7 packages to be installed\n * Deprecate `twig_test_iterable` function. Use the native `is_iterable` instead.\n\n# 3.7.1 (2023-08-28)\n\n * Fix some phpdocs\n\n# 3.7.0 (2023-07-26)\n\n * Add support for the ...spread operator on arrays and hashes\n\n# 3.6.1 (2023-06-08)\n\n * Suppress some native return type deprecation messages\n\n# 3.6.0 (2023-05-03)\n\n * Allow psr/container 2.0\n * Add the new PHP 8.0 IntlDateFormatter::RELATIVE_* constants for date formatting\n * Make the Lexer initialize itself lazily\n\n# 3.5.1 (2023-02-08)\n\n * Arrow functions passed to the \"reduce\" filter now accept the current key as a third argument\n * Restores the leniency of the matches twig comparison\n * Fix error messages in sandboxed mode for \"has some\" and \"has every\"\n\n# 3.5.0 (2022-12-27)\n\n * Make Twig\\ExpressionParser non-internal\n * Add \"has some\" and \"has every\" operators\n * Add Compile::reset()\n * Throw a better runtime error when the \"matches\" regexp is not valid\n * Add \"twig *_names\" intl functions\n * Fix optimizing closures callbacks\n * Add a better exception when getting an undefined constant via `constant`\n * Fix `if` nodes when outside of a block and with an empty body\n\n# 3.4.3 (2022-09-28)\n\n * Fix a security issue on filesystem loader (possibility to load a template outside a configured directory)\n\n# 3.4.2 (2022-08-12)\n\n * Allow inherited magic method to still run with calling class\n * Fix CallExpression::reflectCallable() throwing TypeError\n * Fix typo in naming (currency_code)\n\n# 3.4.1 (2022-05-17)\n\n* Fix optimizing non-public named closures\n\n# 3.4.0 (2022-05-22)\n\n * Add support for named closures\n\n# 3.3.10 (2022-04-06)\n\n * Enable bytecode invalidation when auto_reload is enabled\n\n# 3.3.9 (2022-03-25)\n\n * Fix custom escapers when using multiple Twig environments\n * Add support for \"constant('class', object)\"\n * Do not reuse internally generated variable names during parsing\n\n# 3.3.8 (2022-02-04)\n\n * Fix a security issue when in a sandbox: the `sort` filter must require a Closure for the `arrow` parameter\n * Fix deprecation notice on `round`\n * Fix call to deprecated `convertToHtml` method\n\n# 3.3.7 (2022-01-03)\n\n* Allow more null support when Twig expects a string (for better 8.1 support)\n* Only use Commonmark extensions if markdown enabled\n\n# 3.3.6 (2022-01-03)\n\n* Only use Commonmark extensions if markdown enabled\n\n# 3.3.5 (2022-01-03)\n\n* Allow CommonMark extensions to easily be added\n* Allow null when Twig expects a string (for better 8.1 support)\n* Make some performance optimizations\n* Allow Symfony translation contract v3+\n\n# 3.3.4 (2021-11-25)\n\n * Bump minimum supported Symfony component versions\n * Fix a deprecated message\n\n# 3.3.3 (2021-09-17)\n\n * Allow Symfony 6\n * Improve compatibility with PHP 8.1\n * Explicitly specify the encoding for mb_ord in JS escaper\n\n# 3.3.2 (2021-05-16)\n\n * Revert \"Throw a proper exception when a template name is an absolute path (as it has never been supported)\"\n\n# 3.3.1 (2021-05-12)\n\n * Fix PHP 8.1 compatibility\n * Throw a proper exception when a template name is an absolute path (as it has never been supported)\n\n# 3.3.0 (2021-02-08)\n\n * Fix macro calls in a \"cache\" tag\n * Add the slug filter\n * Allow extra bundle to be compatible with Twig 2\n\n# 3.2.1 (2021-01-05)\n\n * Fix extra bundle compat with older versions of Symfony\n\n# 3.2.0 (2021-01-05)\n\n * Add the Cache extension in the \"extra\" repositories: \"cache\" tag\n * Add \"registerUndefinedTokenParserCallback\"\n * Mark built-in node visitors as @internal\n * Fix \"odd\" not working for negative numbers\n\n# 3.1.1 (2020-10-27)\n\n * Fix \"include(template_from_string())\"\n\n# 3.1.0 (2020-10-21)\n\n * Fix sandbox support when using \"include(template_from_string())\"\n * Make round brackets optional for one argument tests like \"same as\" or \"divisible by\"\n * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a }\n\n# 3.0.5 (2020-08-05)\n\n * Fix twig_compare w.r.t. whitespace trimming\n * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag\n * Fix a regression when not using a space before an operator\n * Restrict callables to closures in filters\n * Allow trailing commas in argument lists (in calls as well as definitions)\n\n# 3.0.4 (2020-07-05)\n\n * Fix comparison operators\n * Fix options not taken into account when using \"Michelf\\MarkdownExtra\"\n * Fix \"Twig\\Extra\\Intl\\IntlExtension::getCountryName()\" to accept \"null\" as a first argument\n * Throw exception in case non-Traversable data is passed to \"filter\"\n * Fix context optimization on PHP 7.4\n * Fix PHP 8 compatibility\n * Fix ambiguous syntax parsing\n\n# 3.0.3 (2020-02-11)\n\n * Add a check to ensure that iconv() is defined\n\n# 3.0.2 (2020-02-11)\n\n * Avoid exceptions when an intl resource is not found\n * Fix implementation of case-insensitivity for method names\n\n# 3.0.1 (2019-12-28)\n\n * fixed Symfony 5.0 support for the HTML extra extension\n\n# 3.0.0 (2019-11-15)\n\n * fixed number formatter in Intl extra extension when using a formatter prototype\n\n# 3.0.0-BETA1 (2019-11-11)\n\n * removed the \"if\" condition support on the \"for\" tag\n * made the in, <, >, <=, >=, ==, and != operators more strict when comparing strings and integers/floats\n * removed the \"filter\" tag\n * added type hints everywhere\n * changed Environment::resolveTemplate() to always return a TemplateWrapper instance\n * removed Template::__toString()\n * removed Parser::isReservedMacroName()\n * removed SanboxedPrintNode\n * removed Node::setTemplateName()\n * made classes marked as \"@final\" final\n * removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface\n * removed the \"spaceless\" tag\n * removed Twig\\Environment::getBaseTemplateClass() and Twig\\Environment::setBaseTemplateClass()\n * removed the \"base_template_class\" option on Twig\\Environment\n * bumped minimum PHP version to 7.2\n * removed PSR-0 classes\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2009-present by the Twig Team.\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright notice,\n      this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright notice,\n      this list of conditions and the following disclaimer in the documentation\n      and/or other materials provided with the distribution.\n    * Neither the name of Twig nor the names of its contributors\n      may be used to endorse or promote products derived from this software\n      without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.rst",
    "content": "Twig, the flexible, fast, and secure template language for PHP\n==============================================================\n\nTwig is a template language for PHP.\n\nTwig uses a syntax similar to the Django and Jinja template languages which\ninspired the Twig runtime environment.\n\nSponsors\n--------\n\n.. raw:: html\n\n    <a href=\"https://docs.blackfire.io/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo\">\n        <img src=\"https://static.blackfire.io/assets/intemporals/logo/png/blackfire-io_secondary_horizontal_transparent.png?1\" width=\"255px\" alt=\"Blackfire.io\">\n    </a>\n\nMore Information\n----------------\n\nRead the `documentation`_ for more information.\n\n.. _documentation: https://twig.symfony.com/documentation\n"
  },
  {
    "path": "bin/generate_operators_precedence.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\ExpressionParserType;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\Loader\\ArrayLoader;\n\nrequire_once dirname(__DIR__).'/vendor/autoload.php';\n\n$output = fopen(dirname(__DIR__).'/doc/operators_precedence.rst', 'w');\n\n$twig = new Environment(new ArrayLoader([]));\n$descriptionLength = 11;\n$expressionParsers = [];\n$seen = new SplObjectStorage();\nforeach ($twig->getExpressionParsers() as $expressionParser) {\n    if (!$seen->offsetExists($expressionParser)) {\n        $expressionParsers[] = $expressionParser;\n        $seen->offsetSet($expressionParser, true);\n        $descriptionLength = max($descriptionLength, $expressionParser instanceof ExpressionParserDescriptionInterface ? strlen($expressionParser->getDescription()) : '');\n    }\n}\n\nfwrite($output, \"\\n+------------+------------------+---------+---------------+\".str_repeat('-', $descriptionLength + 2).\"+\\n\");\nfwrite($output, '| Precedence | Operator         | Type    | Associativity | Description'.str_repeat(' ', $descriptionLength - 11).\" |\\n\");\nfwrite($output, '+============+==================+=========+===============+'.str_repeat('=', $descriptionLength + 2).'+');\n\nusort($expressionParsers, static fn ($a, $b) => $b->getPrecedence() <=> $a->getPrecedence());\n\n$previous = null;\nforeach ($expressionParsers as $expressionParser) {\n    if (null !== $previous) {\n        fwrite($output, \"\\n+------------+------------------+---------+---------------+\".str_repeat('-', $descriptionLength + 2).'+');\n    }\n    $precedence = $expressionParser->getPrecedence();\n    $previousPrecedence = $previous ? $previous->getPrecedence() : \\PHP_INT_MAX;\n    $associativity = $expressionParser instanceof InfixExpressionParserInterface ? (InfixAssociativity::Left === $expressionParser->getAssociativity() ? 'Left' : 'Right') : 'n/a';\n    $previousAssociativity = $previous ? ($previous instanceof InfixExpressionParserInterface ? (InfixAssociativity::Left === $previous->getAssociativity() ? 'Left' : 'Right') : 'n/a') : 'n/a';\n    if ($previousPrecedence !== $precedence) {\n        $previous = null;\n    }\n    $operatorName = '``'.$expressionParser->getName().'``';\n    if ($expressionParser->getAliases()) {\n        $operatorName .= ', ``'.implode('``, ``', $expressionParser->getAliases()).'``';\n    }\n    fwrite($output, rtrim(sprintf(\"\\n| %-10s | %-16s | %-7s | %-13s | %-{$descriptionLength}s |\\n\",\n        (!$previous || $previousPrecedence !== $precedence ? $precedence : '').($expressionParser->getPrecedenceChange() ? ' => '.$expressionParser->getPrecedenceChange()->getNewPrecedence() : ''),\n        $operatorName,\n        !$previous || ExpressionParserType::getType($previous) !== ExpressionParserType::getType($expressionParser) ? ExpressionParserType::getType($expressionParser)->value : '',\n        !$previous || $previousAssociativity !== $associativity ? $associativity : '',\n        $expressionParser instanceof ExpressionParserDescriptionInterface ? $expressionParser->getDescription() : '',\n    )));\n    $previous = $expressionParser;\n}\nfwrite($output, \"\\n+------------+------------------+---------+---------------+\".str_repeat('-', $descriptionLength + 2).\"+\\n\");\nfwrite($output, \"\\nWhen a precedence will change in 4.0, the new precedence is indicated by the arrow ``=>``.\\n\");\n\nfwrite($output, \"\\nHere is the same table for Twig 4.0 with adjusted precedences:\\n\");\n\nfwrite($output, \"\\n+------------+------------------+---------+---------------+\".str_repeat('-', $descriptionLength + 2).\"+\\n\");\nfwrite($output, '| Precedence | Operator         | Type    | Associativity | Description'.str_repeat(' ', $descriptionLength - 11).\" |\\n\");\nfwrite($output, '+============+==================+=========+===============+'.str_repeat('=', $descriptionLength + 2).'+');\n\nusort($expressionParsers, static function ($a, $b) {\n    $aPrecedence = $a->getPrecedenceChange() ? $a->getPrecedenceChange()->getNewPrecedence() : $a->getPrecedence();\n    $bPrecedence = $b->getPrecedenceChange() ? $b->getPrecedenceChange()->getNewPrecedence() : $b->getPrecedence();\n\n    return $bPrecedence - $aPrecedence;\n});\n\n$previous = null;\nforeach ($expressionParsers as $expressionParser) {\n    if (null !== $previous) {\n        fwrite($output, \"\\n+------------+------------------+---------+---------------+\".str_repeat('-', $descriptionLength + 2).'+');\n    }\n    $precedence = $expressionParser->getPrecedenceChange() ? $expressionParser->getPrecedenceChange()->getNewPrecedence() : $expressionParser->getPrecedence();\n    $previousPrecedence = $previous ? ($previous->getPrecedenceChange() ? $previous->getPrecedenceChange()->getNewPrecedence() : $previous->getPrecedence()) : \\PHP_INT_MAX;\n    $associativity = $expressionParser instanceof InfixExpressionParserInterface ? (InfixAssociativity::Left === $expressionParser->getAssociativity() ? 'Left' : 'Right') : 'n/a';\n    $previousAssociativity = $previous ? ($previous instanceof InfixExpressionParserInterface ? (InfixAssociativity::Left === $previous->getAssociativity() ? 'Left' : 'Right') : 'n/a') : 'n/a';\n    if ($previousPrecedence !== $precedence) {\n        $previous = null;\n    }\n    $operatorName = '``'.$expressionParser->getName().'``';\n    if ($expressionParser->getAliases()) {\n        $operatorName .= ', ``'.implode('``, ``', $expressionParser->getAliases()).'``';\n    }\n    fwrite($output, rtrim(sprintf(\"\\n| %-10s | %-16s | %-7s | %-13s | %-{$descriptionLength}s |\\n\",\n        !$previous || $previousPrecedence !== $precedence ? $precedence : '',\n        $operatorName,\n        !$previous || ExpressionParserType::getType($previous) !== ExpressionParserType::getType($expressionParser) ? ExpressionParserType::getType($expressionParser)->value : '',\n        !$previous || $previousAssociativity !== $associativity ? $associativity : '',\n        $expressionParser instanceof ExpressionParserDescriptionInterface ? $expressionParser->getDescription() : '',\n    )));\n    $previous = $expressionParser;\n}\nfwrite($output, \"\\n+------------+------------------+---------+---------------+\".str_repeat('-', $descriptionLength + 2).\"+\\n\");\n\nfclose($output);\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"twig/twig\",\n    \"type\": \"library\",\n    \"description\": \"Twig, the flexible, fast, and secure template language for PHP\",\n    \"keywords\": [\"templating\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"BSD-3-Clause\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        },\n        {\n            \"name\": \"Twig Team\",\n            \"role\": \"Contributors\"\n        },\n        {\n            \"name\": \"Armin Ronacher\",\n            \"email\": \"armin.ronacher@active-4.com\",\n            \"role\": \"Project Founder\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"symfony/deprecation-contracts\": \"^2.5|^3\",\n        \"symfony/polyfill-mbstring\": \"^1.3\",\n        \"symfony/polyfill-ctype\": \"^1.8\"\n    },\n    \"require-dev\": {\n        \"symfony/phpunit-bridge\": \"^5.4.9|^6.4|^7.0\",\n        \"psr/container\": \"^1.0|^2.0\",\n        \"phpstan/phpstan\": \"^2.0@stable\",\n        \"php-cs-fixer/shim\": \"^3.0@stable\"\n    },\n    \"autoload\": {\n        \"files\": [\n            \"src/Resources/core.php\",\n            \"src/Resources/debug.php\",\n            \"src/Resources/escaper.php\",\n            \"src/Resources/string_loader.php\"\n        ],\n        \"psr-4\" : {\n            \"Twig\\\\\" : \"src/\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\" : {\n            \"Twig\\\\Tests\\\\\" : \"tests/\"\n        }\n    }\n}\n"
  },
  {
    "path": "doc/.doctor-rst.yaml",
    "content": "rules:\n    american_english: ~\n    avoid_repetetive_words: ~\n    blank_line_after_directive: ~\n    blank_line_before_directive: ~\n    composer_dev_option_not_at_the_end: ~\n    correct_code_block_directive_based_on_the_content: ~\n    deprecated_directive_should_have_version: ~\n    ensure_order_of_code_blocks_in_configuration_block: ~\n    extension_xlf_instead_of_xliff: ~\n    indention: ~\n    lowercase_as_in_use_statements: ~\n    max_blank_lines:\n        max: 2\n    no_blank_line_after_filepath_in_php_code_block: ~\n    no_blank_line_after_filepath_in_twig_code_block: ~\n    no_blank_line_after_filepath_in_xml_code_block: ~\n    no_blank_line_after_filepath_in_yaml_code_block: ~\n    no_composer_req: ~\n    no_explicit_use_of_code_block_php: ~\n    no_inheritdoc: ~\n    no_namespace_after_use_statements: ~\n    no_php_open_tag_in_code_block_php_directive: ~\n    no_space_before_self_xml_closing_tag: ~\n    ordered_use_statements: ~\n    php_prefix_before_bin_console: ~\n    replace_code_block_types: ~\n    replacement: ~\n    short_array_syntax: ~\n    typo: ~\n    unused_links: ~\n    use_deprecated_directive_instead_of_versionadded: ~\n    use_https_xsd_urls: ~\n    valid_inline_highlighted_namespaces: ~\n    valid_use_statements: ~\n    versionadded_directive_should_have_version: ~\n    yaml_instead_of_yml_suffix: ~\n    yarn_dev_option_at_the_end: ~\n\n    versionadded_directive_major_version:\n        major_version: 3\n\n    versionadded_directive_min_version:\n        min_version: '3.0'\n\n    deprecated_directive_major_version:\n        major_version: 3\n\n    deprecated_directive_min_version:\n        min_version: '3.0'\n\nwhitelist:\n    lines:\n        - 'I like Twig.<br />'\n"
  },
  {
    "path": "doc/_build/build.php",
    "content": "#!/usr/bin/env php\n<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nrequire __DIR__.'/vendor/autoload.php';\n\nuse Symfony\\Component\\Console\\Application;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\nuse SymfonyDocsBuilder\\BuildConfig;\nuse SymfonyDocsBuilder\\DocBuilder;\n\n(new Application('Twig docs Builder', '1.0'))\n    ->register('build-docs')\n    ->addOption('disable-cache', null, InputOption::VALUE_NONE, 'Use this option to force a full regeneration of all doc contents')\n    ->setCode(static function (InputInterface $input, OutputInterface $output) {\n        $io = new SymfonyStyle($input, $output);\n        $io->text('Building all Twig docs...');\n\n        $outputDir = __DIR__.'/output';\n        $buildConfig = (new BuildConfig())\n            ->setContentDir(__DIR__.'/..')\n            ->setOutputDir($outputDir)\n            ->setImagesDir(__DIR__.'/output/_images')\n            ->setImagesPublicPrefix('_images')\n            ->setTheme('rtd')\n        ;\n\n        $buildConfig->setExcludedPaths(['vendor/']);\n        $buildConfig->disableJsonFileGeneration();\n        $buildConfig->disableBuildCache();\n\n        $result = (new DocBuilder())->build($buildConfig);\n\n        if ($result->isSuccessful()) {\n            // fix assets URLs to make them absolute (otherwise, they don't work in subdirectories)\n            foreach (glob($outputDir.'/**/*.html') as $htmlFilePath) {\n                $htmlContents = file_get_contents($htmlFilePath);\n                file_put_contents($htmlFilePath, str_replace('href=\"assets/', 'href=\"/assets/', $htmlContents));\n            }\n\n            $io->success(sprintf('The Twig docs were successfully built at %s', realpath($outputDir)));\n        } else {\n            $io->error(sprintf(\"There were some errors while building the docs:\\n\\n%s\\n\", $result->getErrorTrace()));\n            $io->newLine();\n            $io->comment('Tip: you can add the -v, -vv or -vvv flags to this command to get debug information.');\n\n            return 1;\n        }\n\n        return 0;\n    })\n    ->getApplication()\n    ->setDefaultCommand('build-docs', true)\n    ->run();\n"
  },
  {
    "path": "doc/_build/composer.json",
    "content": "{\n    \"minimum-stability\": \"dev\",\n    \"prefer-stable\": true,\n    \"config\": {\n        \"platform\": {\n            \"php\": \"8.1.0\"\n        },\n        \"preferred-install\": {\n            \"*\": \"dist\"\n        },\n        \"sort-packages\": true\n    },\n    \"require\": {\n        \"php\": \">=8.1\",\n        \"symfony/console\": \"^5.4\",\n        \"symfony/process\": \"^5.4\",\n        \"symfony-tools/docs-builder\": \"^0.18\"\n    }\n}\n"
  },
  {
    "path": "doc/advanced.rst",
    "content": "Extending Twig\n==============\n\nTwig can be extended in many ways; you can add extra tags, filters, tests,\noperators, global variables, and functions. You can even extend the parser\nitself with node visitors.\n\n.. note::\n\n    The first section of this chapter describes how to extend Twig. If you want\n    to reuse your changes in different projects or if you want to share them\n    with others, you should then create an extension as described in the\n    following section.\n\n.. caution::\n\n    When extending Twig without creating an extension, Twig won't be able to\n    recompile your templates when the PHP code is updated. To see your changes\n    in real-time, either disable template caching or package your code into an\n    extension (see the next section of this chapter).\n\nBefore extending Twig, you must understand the differences between all the\ndifferent possible extension points and when to use them.\n\nFirst, remember that Twig has two main language constructs:\n\n* ``{{ }}``: used to print the result of an expression evaluation;\n\n* ``{% %}``: used to execute statements.\n\nTo understand why Twig exposes so many extension points, let's see how to\nimplement a *Lorem ipsum* generator (it needs to know the number of words to\ngenerate).\n\nYou can use a ``lipsum`` *tag*:\n\n.. code-block:: twig\n\n    {% lipsum 40 %}\n\nThat works, but using a tag for ``lipsum`` is not a good idea for at least\nthree main reasons:\n\n* ``lipsum`` is not a language construct;\n* The tag outputs something;\n* The tag is not flexible as you cannot use it in an expression:\n\n  .. code-block:: twig\n\n      {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}\n\nIn fact, you rarely need to create tags; and that's good news because tags are\nthe most complex extension point.\n\nNow, let's use a ``lipsum`` *filter*:\n\n.. code-block:: twig\n\n    {{ 40|lipsum }}\n\nAgain, it works. But a filter should transform the passed value to something\nelse. Here, we use the value to indicate the number of words to generate (so,\n``40`` is an argument of the filter, not the value we want to transform).\n\nNext, let's use a ``lipsum`` *function*:\n\n.. code-block:: twig\n\n    {{ lipsum(40) }}\n\nHere we go. For this specific example, the creation of a function is the\nextension point to use. And you can use it anywhere an expression is accepted:\n\n.. code-block:: twig\n\n    {{ 'some text' ~ lipsum(40) ~ 'some more text' }}\n\n    {% set lipsum = lipsum(40) %}\n\nLastly, you can also use a *global* object with a method able to generate lorem\nipsum text:\n\n.. code-block:: twig\n\n    {{ text.lipsum(40) }}\n\nAs a rule of thumb, use functions for frequently used features and global\nobjects for everything else.\n\nKeep in mind the following when you want to extend Twig:\n\n========== ========================== ========== =========================\nWhat?      Implementation difficulty? How often? When?\n========== ========================== ========== =========================\n*macro*    simple                     frequent   Content generation\n*global*   simple                     frequent   Helper object\n*function* simple                     frequent   Content generation\n*filter*   simple                     frequent   Value transformation\n*tag*      complex                    rare       DSL language construct\n*test*     simple                     rare       Boolean decision\n*operator* simple                     rare       Values transformation\n========== ========================== ========== =========================\n\nGlobals\n-------\n\nGlobal variables are available in all templates and macros. Use ``addGlobal()``\nto add a global variable to a Twig environment::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->addGlobal('text', new Text());\n\nYou can then use the ``text`` variable anywhere in a template:\n\n.. code-block:: twig\n\n    {{ text.lipsum(40) }}\n\nFilters\n-------\n\nCreating a filter consists of associating a name with a PHP callable::\n\n    // an anonymous function\n    $filter = new \\Twig\\TwigFilter('rot13', function ($string) {\n        return str_rot13($string);\n    });\n\n    // or a simple PHP function\n    $filter = new \\Twig\\TwigFilter('rot13', 'str_rot13');\n\n    // or a class static method\n    $filter = new \\Twig\\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);\n    $filter = new \\Twig\\TwigFilter('rot13', 'SomeClass::rot13Filter');\n\n    // or a class method\n    $filter = new \\Twig\\TwigFilter('rot13', [$this, 'rot13Filter']);\n    // the one below needs a runtime implementation (see below for more information)\n    $filter = new \\Twig\\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);\n\nThe first argument passed to the ``\\Twig\\TwigFilter`` constructor is the name of the\nfilter you will use in templates and the second one is the PHP callable to\nassociate with it.\n\nThen, add the filter to the Twig environment::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->addFilter($filter);\n\nAnd here is how to use it in a template:\n\n.. code-block:: twig\n\n    {{ 'Twig'|rot13 }}\n\n    {# will output Gjvt #}\n\nWhen called by Twig, the PHP callable receives the left side of the filter\n(before the pipe ``|``) as the first argument and the extra arguments passed\nto the filter (within parentheses ``()``) as extra arguments.\n\nFor instance, the following code:\n\n.. code-block:: twig\n\n    {{ 'TWIG'|lower }}\n    {{ now|date('d/m/Y') }}\n\nis compiled to something like the following::\n\n    <?php echo strtolower('TWIG') ?>\n    <?php echo twig_date_format_filter($now, 'd/m/Y') ?>\n\nThe ``\\Twig\\TwigFilter`` class takes an array of options as its last argument::\n\n    $filter = new \\Twig\\TwigFilter('rot13', 'str_rot13', $options);\n\nCharset-aware Filters\n~~~~~~~~~~~~~~~~~~~~~\n\nIf you want to access the default charset in your filter, set the\n``needs_charset`` option to ``true``; Twig will pass the default charset as the\nfirst argument to the filter call::\n\n    $filter = new \\Twig\\TwigFilter('rot13', function (string $charset, $string) {\n        return str_rot13($string);\n    }, ['needs_charset' => true]);\n\nEnvironment-aware Filters\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf you want to access the current environment instance in your filter, set the\n``needs_environment`` option to ``true``; Twig will pass the current\nenvironment as the first argument to the filter call::\n\n    $filter = new \\Twig\\TwigFilter('rot13', function (\\Twig\\Environment $env, $string) {\n        // get the current charset for instance\n        $charset = $env->getCharset();\n\n        return str_rot13($string);\n    }, ['needs_environment' => true]);\n\nContext-aware Filters\n~~~~~~~~~~~~~~~~~~~~~\n\nIf you want to access the current context in your filter, set the\n``needs_context`` option to ``true``; Twig will pass the current context as\nthe first argument to the filter call (or the second one if\n``needs_environment`` is also set to ``true``)::\n\n    $filter = new \\Twig\\TwigFilter('rot13', function ($context, $string) {\n        // ...\n    }, ['needs_context' => true]);\n\n    $filter = new \\Twig\\TwigFilter('rot13', function (\\Twig\\Environment $env, $context, $string) {\n        // ...\n    }, ['needs_context' => true, 'needs_environment' => true]);\n\nAutomatic Escaping\n~~~~~~~~~~~~~~~~~~\n\nIf automatic escaping is enabled, the output of the filter may be escaped\nbefore printing. If your filter acts as an escaper (or explicitly outputs HTML\nor JavaScript code), you will want the raw output to be printed. In such a\ncase, set the ``is_safe`` option::\n\n    $filter = new \\Twig\\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);\n\nSome filters may need to work on input that is already escaped or safe, for\nexample when adding (safe) HTML tags to originally unsafe output. In such a\ncase, set the ``pre_escape`` option to escape the input data before it is run\nthrough your filter::\n\n    $filter = new \\Twig\\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);\n\nVariadic Filters\n~~~~~~~~~~~~~~~~\n\nWhen a filter should accept an arbitrary number of arguments, set the\n``is_variadic`` option to ``true``; Twig will pass the extra arguments as the\nlast argument to the filter call as an array::\n\n    $filter = new \\Twig\\TwigFilter('thumbnail', function ($file, array $options = []) {\n        // ...\n    }, ['is_variadic' => true]);\n\nBe warned that :ref:`named arguments <named-arguments>` passed to a variadic\nfilter cannot be checked for validity as they will automatically end up in the\noption array.\n\nDynamic Filters\n~~~~~~~~~~~~~~~\n\nA filter name containing the special ``*`` character is a dynamic filter and\nthe ``*`` part will match any string::\n\n    $filter = new \\Twig\\TwigFilter('*_path', function ($name, $arguments) {\n        // ...\n    });\n\nThe following filters are matched by the above defined dynamic filter:\n\n* ``product_path``\n* ``category_path``\n\nA dynamic filter can define more than one dynamic parts::\n\n    $filter = new \\Twig\\TwigFilter('*_path_*', function ($name, $suffix, $arguments) {\n        // ...\n    });\n\nThe filter receives all dynamic part values before the normal filter arguments,\nbut after the environment and the context. For instance, a call to\n``'Paris'|a_path_b()`` will result in the following arguments to be passed to the\nfilter: ``('a', 'b', 'Paris')``.\n\nDeprecated Filters\n~~~~~~~~~~~~~~~~~~\n\n.. versionadded:: 3.15\n\n    The ``deprecation_info`` option was added in Twig 3.15.\n\nYou can mark a filter as being deprecated by setting the ``deprecation_info``\noption::\n\n    $filter = new \\Twig\\TwigFilter('obsolete', function () {\n        // ...\n    }, ['deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.11', 'new_one')]);\n\nThe ``DeprecatedCallableInfo`` constructor takes the following parameters:\n\n* The Composer package name that defines the filter;\n* The version when the filter was deprecated.\n\nOptionally, you can also provide the following parameters about an alternative:\n\n* The package name that contains the alternative filter;\n* The alternative filter name that replaces the deprecated one;\n* The package version that added the alternative filter.\n\nWhen a filter is deprecated, Twig emits a deprecation notice when compiling a\ntemplate using it. See :ref:`deprecation-notices` for more information.\n\n.. note::\n\n    Before Twig 3.15, you can mark a filter as being deprecated by setting the\n    ``deprecated`` option to ``true``. You can also give an alternative filter\n    that replaces the deprecated one when that makes sense::\n\n        $filter = new \\Twig\\TwigFilter('obsolete', function () {\n            // ...\n        }, ['deprecated' => true, 'alternative' => 'new_one']);\n\n    .. versionadded:: 3.11\n\n        The ``deprecating_package`` option was added in Twig 3.11.\n\n    You can also set the ``deprecating_package`` option to specify the package\n    that is deprecating the filter, and ``deprecated`` can be set to the\n    package version when the filter was deprecated::\n\n        $filter = new \\Twig\\TwigFilter('obsolete', function () {\n            // ...\n        }, ['deprecated' => '1.1', 'deprecating_package' => 'twig/some-package']);\n\nFunctions\n---------\n\nFunctions are defined in the exact same way as filters, but you need to create\nan instance of ``\\Twig\\TwigFunction``::\n\n    $twig = new \\Twig\\Environment($loader);\n    $function = new \\Twig\\TwigFunction('function_name', function () {\n        // ...\n    });\n    $twig->addFunction($function);\n\nFunctions support the same features as filters, except for the ``pre_escape``\nand ``preserves_safety`` options.\n\nTests\n-----\n\nTests are defined in the exact same way as filters and functions, but you need\nto create an instance of ``\\Twig\\TwigTest``::\n\n    $twig = new \\Twig\\Environment($loader);\n    $test = new \\Twig\\TwigTest('test_name', function () {\n        // ...\n    });\n    $twig->addTest($test);\n\nTests allow you to create custom application specific logic for evaluating\nboolean conditions. As a simple example, let's create a Twig test that checks if\nobjects are 'red'::\n\n    $twig = new \\Twig\\Environment($loader);\n    $test = new \\Twig\\TwigTest('red', function ($value) {\n        if (isset($value->color) && $value->color == 'red') {\n            return true;\n        }\n        if (isset($value->paint) && $value->paint == 'red') {\n            return true;\n        }\n        return false;\n    });\n    $twig->addTest($test);\n\nTest functions must always return ``true``/``false``.\n\nWhen creating tests you can use the ``node_class`` option to provide custom test\ncompilation. This is useful if your test can be compiled into PHP primitives.\nThis is used by many of the tests built into Twig::\n\n    namespace App;\n\n    use Twig\\Environment;\n    use Twig\\Node\\Expression\\TestExpression;\n    use Twig\\TwigTest;\n\n    $twig = new Environment($loader);\n    $test = new TwigTest(\n        'odd',\n        null,\n        ['node_class' => OddTestExpression::class]);\n    $twig->addTest($test);\n\n    class OddTestExpression extends TestExpression\n    {\n        public function compile(\\Twig\\Compiler $compiler)\n        {\n            $compiler\n                ->raw('(')\n                ->subcompile($this->getNode('node'))\n                ->raw(' % 2 != 0')\n                ->raw(')')\n            ;\n        }\n    }\n\nThe above example shows how you can create tests that use a node class. The node\nclass has access to one sub-node called ``node``. This sub-node contains the\nvalue that is being tested. When the ``odd`` filter is used in code such as:\n\n.. code-block:: twig\n\n    {% if my_value is odd %}\n\nThe ``node`` sub-node will contain an expression of ``my_value``. Node-based\ntests also have access to the ``arguments`` node. This node will contain the\nvarious other arguments that have been provided to your test.\n\nIf you want to pass a variable number of positional or named arguments to the\ntest, set the ``is_variadic`` option to ``true``. Tests support dynamic\nnames (see dynamic filters for the syntax).\n\nTags\n----\n\nOne of the most exciting features of a template engine like Twig is the\npossibility to define new **language constructs**. This is also the most complex\nfeature as you need to understand how Twig's internals work.\n\nMost of the time though, a tag is not needed:\n\n* If your tag generates some output, use a **function** instead.\n\n* If your tag modifies some content and returns it, use a **filter** instead.\n\n  For instance, if you want to create a tag that converts a Markdown formatted\n  text to HTML, create a ``markdown`` filter instead:\n\n  .. code-block:: twig\n\n      {{ '**markdown** text'|markdown }}\n\n  If you want use this filter on large amounts of text, wrap it with the\n  :doc:`apply <tags/apply>` tag:\n\n  .. code-block:: twig\n\n      {% apply markdown %}\n      Title\n      =====\n\n      Much better than creating a tag as you can **compose** filters.\n      {% endapply %}\n\n* If your tag does not output anything, but only exists because of a side\n  effect, create a **function** that returns nothing and call it via the\n  :doc:`do <tags/do>` tag.\n\n  For instance, if you want to create a tag that logs text, create a ``log``\n  function instead and call it via the :doc:`do <tags/do>` tag:\n\n  .. code-block:: twig\n\n      {% do log('Log some things') %}\n\nIf you still want to create a tag for a new language construct, great!\n\nLet's create a ``set`` tag that allows the definition of simple variables from\nwithin a template. The tag can be used like follows:\n\n.. code-block:: twig\n\n    {% set name = \"value\" %}\n\n    {{ name }}\n\n    {# should output value #}\n\n.. note::\n\n    The ``set`` tag is part of the Core extension and as such is always\n    available. The built-in version is slightly more powerful and supports\n    multiple assignments by default.\n\nThree steps are needed to define a new tag:\n\n* Defining a Token Parser class (responsible for parsing the template code);\n\n* Defining a Node class (responsible for converting the parsed code to PHP);\n\n* Registering the tag.\n\nRegistering a new tag\n~~~~~~~~~~~~~~~~~~~~~\n\nAdd a tag by calling the ``addTokenParser`` method on the ``\\Twig\\Environment``\ninstance::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->addTokenParser(new CustomSetTokenParser());\n\nDefining a Token Parser\n~~~~~~~~~~~~~~~~~~~~~~~\n\nNow, let's see the actual code of this class::\n\n    class CustomSetTokenParser extends \\Twig\\TokenParser\\AbstractTokenParser\n    {\n        public function parse(\\Twig\\Token $token)\n        {\n            $parser = $this->parser;\n            $lineno = $token->getLine();\n            $stream = $parser->getStream();\n\n            $name = $stream->expect(\\Twig\\Token::NAME_TYPE)->getValue();\n            $stream->expect(\\Twig\\Token::OPERATOR_TYPE, '=');\n            $value = $parser->getExpressionParser()->parseExpression();\n            $stream->expect(\\Twig\\Token::BLOCK_END_TYPE);\n\n            return new CustomSetNode($name, $value, $lineno);\n        }\n\n        public function getTag()\n        {\n            return 'set';\n        }\n    }\n\nThe ``getTag()`` method must return the tag we want to parse, here ``set``.\n\nThe ``parse()`` method is invoked whenever the parser encounters a ``set``\ntag. It should return a ``\\Twig\\Node\\Node`` instance that represents the node (the\n``CustomSetNode`` calls creating is explained in the next section).\n\nThe parsing process is simplified thanks to a bunch of methods you can call\nfrom the token stream (``$this->parser->getStream()``):\n\n* ``getCurrent()``: Gets the current token in the stream.\n\n* ``next()``: Moves to the next token in the stream, *but returns the old one*.\n\n* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether\n  the current token is of a particular type or value (or both). The value may be an\n  array of several possible values.\n\n* ``expect($type[, $value[, $message]])``: If the current token isn't of the given\n  type/value a syntax error is thrown. Otherwise, if the type and value are correct,\n  the token is returned and the stream moves to the next token.\n\n* ``look()``: Looks at the next token without consuming it.\n\nParsing expressions is done by calling the ``parseExpression()`` like we did for\nthe ``set`` tag.\n\nWhen encountering a syntax error during parsing, throw an exception::\n\n    throw new SyntaxError('Some error message.', $stream->getCurrent()->getLine(), $stream->getSourceContext());\n\nFor better error reporting to the user, follow these recommendations:\n\n * Use ``\\Twig\\Error\\SyntaxError``;\n\n * **Always** pass the line number of the node and the source context;\n\n * End the exception message with a dot.\n\n.. tip::\n\n    Reading the existing ``TokenParser`` classes is the best way to learn all\n    the nitty-gritty details of the parsing process.\n\nDefining a Node\n~~~~~~~~~~~~~~~\n\nThe ``CustomSetNode`` class itself is quite short::\n\n    class CustomSetNode extends \\Twig\\Node\\Node\n    {\n        public function __construct($name, \\Twig\\Node\\Expression\\AbstractExpression $value, $line)\n        {\n            parent::__construct(['value' => $value], ['name' => $name], $line);\n        }\n\n        public function compile(\\Twig\\Compiler $compiler)\n        {\n            $compiler\n                ->addDebugInfo($this)\n                ->write('$context[\\''.$this->getAttribute('name').'\\'] = ')\n                ->subcompile($this->getNode('value'))\n                ->raw(\";\\n\")\n            ;\n        }\n    }\n\nThe compiler implements a fluid interface and provides methods that help the\ndeveloper generate beautiful and readable PHP code:\n\n* ``subcompile()``: Compiles a node.\n\n* ``raw()``: Writes the given string as is.\n\n* ``write()``: Writes the given string by adding indentation at the beginning\n  of each line.\n\n* ``string()``: Writes a quoted string.\n\n* ``repr()``: Writes a PHP representation of a given value (see\n  ``\\Twig\\Node\\ForNode`` for a usage example).\n\n* ``addDebugInfo()``: Adds the line of the original template file related to\n  the current node as a comment. It's highly recommended to call this method\n  when implementing custom nodes.\n\n* ``indent()``: Indents the generated code (see ``\\Twig\\Node\\BlockNode`` for a\n  usage example).\n\n* ``outdent()``: Outdents the generated code (see ``\\Twig\\Node\\BlockNode`` for a\n  usage example).\n\nFor structural nodes, always call ``addDebugInfo()`` early on in the\ncompilation process to improve error reporting to the user in case the code\nwould throw an exception.\n\n.. _creating_extensions:\n\nCreating an Extension\n---------------------\n\nThe main motivation for writing an extension is to move often used code into a\nreusable class like adding support for internationalization. An extension can\ndefine tags, filters, tests, operators, functions, and node visitors.\n\nMost of the time, it is useful to create a single extension for your project,\nto host all the specific tags and filters you want to add to Twig.\n\n.. tip::\n\n    When packaging your code into an extension, Twig is smart enough to\n    recompile your templates whenever you make a change to it (when\n    ``auto_reload`` is enabled).\n\nAn extension is a class that implements the following interface::\n\n    interface \\Twig\\Extension\\ExtensionInterface\n    {\n        /**\n         * Returns the token parser instances to add to the existing list.\n         *\n         * @return \\Twig\\TokenParser\\TokenParserInterface[]\n         */\n        public function getTokenParsers();\n\n        /**\n         * Returns the node visitor instances to add to the existing list.\n         *\n         * @return \\Twig\\NodeVisitor\\NodeVisitorInterface[]\n         */\n        public function getNodeVisitors();\n\n        /**\n         * Returns a list of filters to add to the existing list.\n         *\n         * @return \\Twig\\TwigFilter[]\n         */\n        public function getFilters();\n\n        /**\n         * Returns a list of tests to add to the existing list.\n         *\n         * @return \\Twig\\TwigTest[]\n         */\n        public function getTests();\n\n        /**\n         * Returns a list of functions to add to the existing list.\n         *\n         * @return \\Twig\\TwigFunction[]\n         */\n        public function getFunctions();\n\n        /**\n         * Returns a list of expression parsers to add to the existing list.\n         *\n         * @return \\Twig\\ExpressionParser\\ExpressionParserInterface[]\n         */\n        public function getExpressionParsers();\n    }\n\nTo keep your extension class clean and lean, inherit from the built-in\n``\\Twig\\Extension\\AbstractExtension`` class instead of implementing the interface as it provides\nempty implementations for all methods::\n\n    class CustomTwigExtension extends \\Twig\\Extension\\AbstractExtension\n    {\n    }\n\nThis extension does nothing for now. We will customize it in the next sections.\n\nYou can save your extension anywhere on the filesystem, as all extensions must\nbe registered explicitly to be available in your templates.\n\nYou can register an extension by using the ``addExtension()`` method on your\nmain ``Environment`` object::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->addExtension(new CustomTwigExtension());\n\n.. tip::\n\n    The Twig core extensions are great examples of how extensions work.\n\nGlobals\n~~~~~~~\n\nGlobal variables can be registered in an extension via the ``getGlobals()``\nmethod::\n\n    class CustomTwigExtension extends \\Twig\\Extension\\AbstractExtension implements \\Twig\\Extension\\GlobalsInterface\n    {\n        public function getGlobals(): array\n        {\n            return [\n                'text' => new Text(),\n            ];\n        }\n\n        // ...\n    }\n\n.. caution::\n\n    Globals are fetched once from extensions and then cached for the lifetime\n    of the Twig environment. It means that globals should not be used to store\n    values that can change during the lifetime of the Twig environment. For\n    instance, if you're using an application server like RoadRunner or\n    FrankenPHP, you should not store values related to the current context (like\n    the HTTP request). If you do so, don't forget to reset the cache between\n    requests by calling ``Environment::resetGlobals()``.\n\nFunctions\n~~~~~~~~~\n\nFunctions can be registered in an extension via the ``getFunctions()``\nmethod::\n\n    class CustomTwigExtension extends \\Twig\\Extension\\AbstractExtension\n    {\n        public function getFunctions()\n        {\n            return [\n                new \\Twig\\TwigFunction('lipsum', 'generate_lipsum'),\n            ];\n        }\n\n        // ...\n    }\n\nFilters\n~~~~~~~\n\nTo add a filter to an extension, you need to override the ``getFilters()``\nmethod. This method must return an array of filters to add to the Twig\nenvironment::\n\n    class CustomTwigExtension extends \\Twig\\Extension\\AbstractExtension\n    {\n        public function getFilters()\n        {\n            return [\n                new \\Twig\\TwigFilter('rot13', 'str_rot13'),\n            ];\n        }\n\n        // ...\n    }\n\nTags\n~~~~\n\nAdding a tag in an extension can be done by overriding the\n``getTokenParsers()`` method. This method must return an array of tags to add\nto the Twig environment::\n\n    class CustomTwigExtension extends \\Twig\\Extension\\AbstractExtension\n    {\n        public function getTokenParsers()\n        {\n            return [new CustomSetTokenParser()];\n        }\n\n        // ...\n    }\n\nIn the above code, we have added a single new tag, defined by the\n``CustomSetTokenParser`` class. The ``CustomSetTokenParser`` class is\nresponsible for parsing the tag and compiling it to PHP.\n\nOperators\n~~~~~~~~~\n\n.. versionadded:: 3.21\n\n    The ``getExpressionParsers()`` method was added in Twig 3.21.\n\n.. deprecated:: 3.21\n\n    The ``getExpressionParsers()`` method replaces the now deprecated\n    ``getOperators()`` method. See the :doc:`deprecated <deprecated>` page for\n    details on how to upgrade from ``getOperators()`` to\n    ``getExpressionParsers()``.\n\nThe ``getExpressionParsers()`` method lets you add new operators. To implement\na new one, have a look at the default operators provided by\n``Twig\\Extension\\CoreExtension``.\n\nTests\n~~~~~\n\nThe ``getTests()`` method lets you add new test functions::\n\n    class CustomTwigExtension extends \\Twig\\Extension\\AbstractExtension\n    {\n        public function getTests()\n        {\n            return [\n                new \\Twig\\TwigTest('even', 'twig_test_even'),\n            ];\n        }\n\n        // ...\n    }\n\nUsing PHP Attributes to define Extensions\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. versionadded:: 3.21\n\n    The attribute classes were added in Twig 3.21.\n\nYou can add the ``#[AsTwigFilter]``, ``#[AsTwigFunction]``, and ``#[AsTwigTest]``\nattributes to public methods of any class to define filters, functions, and tests.\n\nCreate a class using these attributes::\n\n    use Twig\\Attribute\\AsTwigFilter;\n    use Twig\\Attribute\\AsTwigFunction;\n    use Twig\\Attribute\\AsTwigTest;\n\n    class ProjectExtension\n    {\n        #[AsTwigFilter('rot13')]\n        public static function rot13(string $string): string\n        {\n            // ...\n        }\n\n        #[AsTwigFunction('lipsum')]\n        public static function lipsum(int $count): string\n        {\n            // ...\n        }\n\n        #[AsTwigTest('even')]\n        public static function isEven(int $number): bool\n        {\n            // ...\n        }\n    }\n\nThen register the ``Twig\\Extension\\AttributeExtension`` with the class name::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->addExtension(new \\Twig\\Extension\\AttributeExtension(ProjectExtension::class));\n\nIf all the methods are static, you are done. The ``ProjectExtension`` class will\nnever be instantiated and the class attributes will be scanned only when a template\nis compiled.\n\nOtherwise, if some methods are not static, you need to register the class as\na runtime extension using one of the runtime loaders::\n\n    use Twig\\Attribute\\AsTwigFunction;\n\n    class ProjectExtension\n    {\n        // Inject hypothetical dependencies\n        public function __construct(private LipsumProvider $lipsumProvider) {}\n\n        #[AsTwigFunction('lipsum')]\n        public function lipsum(int $count): string\n        {\n            return $this->lipsumProvider->lipsum($count);\n        }\n    }\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->addExtension(new \\Twig\\Extension\\AttributeExtension(ProjectExtension::class);\n    $twig->addRuntimeLoader(new \\Twig\\RuntimeLoader\\FactoryLoader([\n        ProjectExtension::class => function () use ($lipsumProvider) {\n            return new ProjectExtension($lipsumProvider);\n        },\n    ]));\n\nIf you want to access the current environment instance in your filter or function,\nadd the ``Twig\\Environment`` type to the first argument of the method::\n\n    class ProjectExtension\n    {\n        #[AsTwigFunction('lipsum')]\n        public function lipsum(\\Twig\\Environment $env, int $count): string\n        {\n            // ...\n        }\n    }\n\n``#[AsTwigFilter]`` and ``#[AsTwigFunction]`` support variadic arguments\nautomatically when applied to variadic methods::\n\n    class ProjectExtension\n    {\n        #[AsTwigFilter('thumbnail')]\n        public function thumbnail(string $file, mixed ...$options): string\n        {\n            // ...\n        }\n    }\n\nThe attributes support other options used to configure the Twig Callables:\n\n * ``AsTwigFilter``: ``needsCharset``, ``needsEnvironment``, ``needsContext``, ``isSafe``, ``isSafeCallback``, ``preEscape``, ``preservesSafety``, ``deprecationInfo``\n * ``AsTwigFunction``: ``needsCharset``, ``needsEnvironment``, ``needsContext``, ``isSafe``, ``isSafeCallback``, ``deprecationInfo``\n * ``AsTwigTest``: ``needsCharset``, ``needsEnvironment``, ``needsContext``, ``deprecationInfo``\n\nDefinition vs Runtime\n~~~~~~~~~~~~~~~~~~~~~\n\nTwig filters, functions, and tests runtime implementations can be defined as\nany valid PHP callable:\n\n* **functions/static methods**: Simple to implement and fast (used by all Twig\n  core extensions); but it is hard for the runtime to depend on external\n  objects;\n\n* **closures**: Simple to implement;\n\n* **object methods**: More flexible and required if your runtime code depends\n  on external objects.\n\nThe simplest way to use methods is to define them on the extension itself::\n\n    class CustomTwigExtension extends \\Twig\\Extension\\AbstractExtension\n    {\n        private $rot13Provider;\n\n        public function __construct($rot13Provider)\n        {\n            $this->rot13Provider = $rot13Provider;\n        }\n\n        public function getFunctions()\n        {\n            return [\n                new \\Twig\\TwigFunction('rot13', [$this, 'rot13']),\n            ];\n        }\n\n        public function rot13($value)\n        {\n            return $this->rot13Provider->rot13($value);\n        }\n    }\n\nThis is very convenient but not recommended as it makes template compilation\ndepend on runtime dependencies even if they are not needed (think for instance\nas a dependency that connects to a database engine).\n\nYou can decouple the extension definitions from their runtime implementations by\nregistering a ``\\Twig\\RuntimeLoader\\RuntimeLoaderInterface`` instance on the\nenvironment that knows how to instantiate such runtime classes (runtime classes\nmust be autoload-able)::\n\n    class RuntimeLoader implements \\Twig\\RuntimeLoader\\RuntimeLoaderInterface\n    {\n        public function load($class)\n        {\n            // implement the logic to create an instance of $class\n            // and inject its dependencies\n            // most of the time, it means using your dependency injection container\n            if ('CustomTwigRuntime' === $class) {\n                return new $class(new Rot13Provider());\n            } else {\n                // ...\n            }\n        }\n    }\n\n    $twig->addRuntimeLoader(new RuntimeLoader());\n\n.. note::\n\n    Twig comes with a PSR-11 compatible runtime loader\n    (``\\Twig\\RuntimeLoader\\ContainerRuntimeLoader``).\n\nIt is now possible to move the runtime logic to a new\n``CustomTwigRuntime`` class and use it directly in the extension::\n\n    class CustomTwigRuntime\n    {\n        private $rot13Provider;\n\n        public function __construct($rot13Provider)\n        {\n            $this->rot13Provider = $rot13Provider;\n        }\n\n        public function rot13($value)\n        {\n            return $this->rot13Provider->rot13($value);\n        }\n    }\n\n    class CustomTwigExtension extends \\Twig\\Extension\\AbstractExtension\n    {\n        public function getFunctions()\n        {\n            return [\n                new \\Twig\\TwigFunction('rot13', ['CustomTwigRuntime', 'rot13']),\n                // or\n                new \\Twig\\TwigFunction('rot13', 'CustomTwigRuntime::rot13'),\n            ];\n        }\n    }\n\n.. note::\n\n    The extension class should implement the ``Twig\\Extension\\LastModifiedExtensionInterface``\n    interface to invalidate the template cache when the runtime class is modified.\n    The ``AbstractExtension`` class implements this interface and tracks the\n    runtime class if its name is the same as the extension class but ends with\n    ``Runtime`` instead of ``Extension``.\n\nTesting an Extension\n--------------------\n\nFunctional Tests\n~~~~~~~~~~~~~~~~\n\nYou can create functional tests for extensions by creating the following file\nstructure in your test directory::\n\n    Fixtures/\n        filters/\n            lower.test\n            upper.test\n        functions/\n            date.test\n            format.test\n        tags/\n            for.test\n            if.test\n    IntegrationTest.php\n\nThe ``IntegrationTest.php`` file should look like this::\n\n    namespace Project\\Tests;\n\n    use Twig\\Test\\IntegrationTestCase;\n\n    class IntegrationTest extends IntegrationTestCase\n    {\n        public function getExtensions()\n        {\n            return [\n                new CustomTwigExtension1(),\n                new CustomTwigExtension2(),\n            ];\n        }\n\n        public function getFixturesDir()\n        {\n            return __DIR__.'/Fixtures/';\n        }\n    }\n\nFixtures examples can be found within the Twig repository\n`tests/Twig/Fixtures`_ directory.\n\nNode Tests\n~~~~~~~~~~\n\nTesting the node visitors can be complex, so extend your test cases from\n``\\Twig\\Test\\NodeTestCase``. Examples can be found in the Twig repository\n`tests/Twig/Node`_ directory.\n\n.. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/3.x/tests/Fixtures\n.. _`tests/Twig/Node`:     https://github.com/twigphp/Twig/tree/3.x/tests/Node\n"
  },
  {
    "path": "doc/api.rst",
    "content": "Twig for Developers\n===================\n\nThis chapter describes the API to Twig and not the template language. It will\nbe most useful as reference to those implementing the template interface to\nthe application and not those who are creating Twig templates.\n\nBasics\n------\n\nTwig uses a central object called the **environment** (of class\n``\\Twig\\Environment``). Instances of this class are used to store the\nconfiguration and extensions, and are used to load templates.\n\nMost applications create one ``\\Twig\\Environment`` object on application\ninitialization and use that to load templates. In some cases, it might be useful\nto have multiple environments side by side, with different configurations.\n\nThe typical way to configure Twig to load templates for an application looks\nroughly like this::\n\n    require_once '/path/to/vendor/autoload.php';\n\n    $loader = new \\Twig\\Loader\\FilesystemLoader('/path/to/templates');\n    $twig = new \\Twig\\Environment($loader, [\n        'cache' => '/path/to/compilation_cache',\n    ]);\n\nThis creates a template environment with a default configuration and a loader\nthat looks up templates in the ``/path/to/templates/`` directory. Different\nloaders are available and you can also write your own if you want to load\ntemplates from a database or other resources.\n\n.. note::\n\n    Notice that the second argument of the environment is an array of options.\n    The ``cache`` option is a compilation cache directory, where Twig caches\n    the compiled templates to avoid the parsing phase for subsequent\n    requests. It is very different from the cache you might want to add for\n    the evaluated templates. For such a need, you can use any available PHP\n    cache library.\n\nLoading Templates\n-----------------\n\nTo load a template, call the ``load()`` method on a Twig environment which\nreturns a ``\\Twig\\TemplateWrapper`` instance::\n\n    $template = $twig->load('index.html.twig');\n\nRendering Templates\n-------------------\n\nTo render a template with some variables, call the ``render()`` method::\n\n    echo $template->render(['the' => 'variables', 'go' => 'here']);\n\n.. note::\n\n    The ``display()`` method is a shortcut to output the rendered template.\n\nYou can also load and render the template directly via the Environment::\n\n    echo $twig->render('index.html.twig', ['the' => 'variables', 'go' => 'here']);\n\nIf a template defines blocks, they can be rendered individually via the\n``renderBlock()`` call::\n\n    echo $template->renderBlock('block_name', ['the' => 'variables', 'go' => 'here']);\n\nStreaming Templates\n-------------------\n\n.. versionadded:: 3.18\n\nTo stream a template, call the ``stream()`` method::\n\n    $template->stream(['the' => 'variables', 'go' => 'here']);\n\nTo stream a specific template block, call the ``streamBlock()`` method::\n\n    $template->streamBlock('block_name', ['the' => 'variables', 'go' => 'here']);\n\n.. note::\n\n    The ``stream()`` and ``streamBlock()`` methods return an iterable.\n\n.. _environment_options:\n\nEnvironment Options\n-------------------\n\nWhen creating a new ``\\Twig\\Environment`` instance, you can pass an array of\noptions as the constructor second argument::\n\n    $twig = new \\Twig\\Environment($loader, ['debug' => true]);\n\nThe following options are available:\n\n* ``debug`` *boolean*\n\n  When set to ``true``, the generated templates have a\n  ``__toString()`` method that you can use to display the generated nodes\n  (default to ``false``).\n\n* ``charset`` *string* (defaults to ``utf-8``)\n\n  The charset used by the templates.\n\n* ``cache`` *string* or ``false``\n\n  An absolute path where to store the compiled templates, or\n  ``false`` to disable caching (which is the default).\n\n* ``auto_reload`` *boolean*\n\n  When developing with Twig, it's useful to recompile the\n  template whenever the source code changes. If you don't provide a value for\n  the ``auto_reload`` option, it will be determined automatically based on the\n  ``debug`` value.\n\n.. _environment_options_strict_variables:\n\n* ``strict_variables`` *boolean*\n\n  If set to ``false``, Twig will silently ignore invalid\n  variables (variables and or attributes/methods that do not exist) and\n  replace them with a ``null`` value. When set to ``true``, Twig throws an\n  exception instead (default to ``false``).\n\n* ``autoescape`` *string*\n\n  Sets the default auto-escaping strategy (``name``, ``html``, ``js``, ``css``, ``url``,\n  ``html_attr``, ``html_attr_relaxed``, or a PHP callback that takes the template \"filename\"\n  and returns the escaping strategy to use -- the callback cannot be a function\n  name to avoid collision with built-in escaping strategies); set it to\n  ``false`` to disable auto-escaping. The ``name`` escaping strategy determines\n  the escaping strategy to use for a template based on the template filename\n  extension (this strategy does not incur any overhead at runtime as\n  auto-escaping is done at compilation time.)\n\n* ``optimizations`` *integer*\n\n  A flag that indicates which optimizations to apply\n  (default to ``-1`` -- all optimizations are enabled; set it to ``0`` to\n  disable).\n\n* ``use_yield`` *boolean*\n\n  ``true``: forces templates to exclusively use ``yield`` instead of ``echo``\n  (all extensions must be yield ready)\n\n  ``false`` (default): allows templates to use a mix of ``yield`` and ``echo``\n  calls to allow for a progressive migration.\n  \n  Switch to ``true`` when possible as this will be the only supported mode in\n  Twig 4.0.\n\nLoaders\n-------\n\nLoaders are responsible for loading templates from a resource such as the file\nsystem.\n\nCompilation Cache\n~~~~~~~~~~~~~~~~~\n\nAll template loaders can cache the compiled templates on the filesystem for\nfuture reuse. It speeds up Twig a lot as templates are only compiled once.\n\nBuilt-in Loaders\n~~~~~~~~~~~~~~~~\n\nHere is a list of the built-in loaders:\n\n``\\Twig\\Loader\\FilesystemLoader``\n.................................\n\n``\\Twig\\Loader\\FilesystemLoader`` loads templates from the file system. This loader\ncan find templates in folders on the file system and is the preferred way to\nload them::\n\n    $loader = new \\Twig\\Loader\\FilesystemLoader($templateDir);\n\nIt can also look for templates in an array of directories::\n\n    $loader = new \\Twig\\Loader\\FilesystemLoader([$templateDir1, $templateDir2]);\n\nWith such a configuration, Twig will first look for templates in\n``$templateDir1`` and if they do not exist, it will fallback to look for them\nin the ``$templateDir2``.\n\nYou can add or prepend paths via the ``addPath()`` and ``prependPath()``\nmethods::\n\n    $loader->addPath($templateDir3);\n    $loader->prependPath($templateDir4);\n\nThe filesystem loader also supports namespaced templates. This allows you to group\nyour templates under different namespaces which have their own template paths.\n\nWhen using the ``setPaths()``, ``addPath()``, and ``prependPath()`` methods,\nspecify the namespace as the second argument (when not specified, these\nmethods act on the \"main\" namespace)::\n\n    $loader->addPath($templateDir, 'admin');\n\nNamespaced templates can be accessed via the special\n``@namespace_name/template_path`` notation::\n\n    $twig->render('@admin/index.html.twig', []);\n\n``\\Twig\\Loader\\FilesystemLoader`` supports absolute and relative paths. Using relative\npaths is preferred as it makes the cache keys independent of the project root\ndirectory (for instance, it allows warming the cache from a build server where\nthe directory might be different from the one used on production servers)::\n\n    $loader = new \\Twig\\Loader\\FilesystemLoader('templates', getcwd().'/..');\n\n.. note::\n\n    When not passing the root path as a second argument, Twig uses ``getcwd()``\n    for relative paths.\n\n``\\Twig\\Loader\\ArrayLoader``\n............................\n\n``\\Twig\\Loader\\ArrayLoader`` loads a template from a PHP array. It is passed an\narray of strings bound to template names::\n\n    $loader = new \\Twig\\Loader\\ArrayLoader([\n        'index.html.twig' => 'Hello {{ name }}!',\n    ]);\n    $twig = new \\Twig\\Environment($loader);\n\n    echo $twig->render('index.html.twig', ['name' => 'Fabien']);\n\nThis loader is very useful for unit testing. It can also be used for small\nprojects where storing all templates in a single PHP file might make sense.\n\n.. tip::\n\n    When using the ``Array`` loader with a cache mechanism, you should know that\n    a new cache key is generated each time a template content \"changes\" (the\n    cache key being the source code of the template). If you don't want to see\n    your cache grows out of control, you need to take care of clearing the old\n    cache file by yourself.\n\n``\\Twig\\Loader\\ChainLoader``\n............................\n\n``\\Twig\\Loader\\ChainLoader`` delegates the loading of templates to other loaders::\n\n    $loader1 = new \\Twig\\Loader\\ArrayLoader([\n        'base.html.twig' => '{% block content %}{% endblock %}',\n    ]);\n    $loader2 = new \\Twig\\Loader\\ArrayLoader([\n        'index.html.twig' => '{% extends \"base.html.twig\" %}{% block content %}Hello {{ name }}{% endblock %}',\n        'base.html.twig'  => 'Will never be loaded',\n    ]);\n\n    $loader = new \\Twig\\Loader\\ChainLoader([$loader1, $loader2]);\n\n    $twig = new \\Twig\\Environment($loader);\n\nWhen looking for a template, Twig tries each loader in turn and returns as soon\nas the template is found. When rendering the ``index.html.twig`` template from the\nabove example, Twig will load it with ``$loader2`` but the ``base.html.twig``\ntemplate will be loaded from ``$loader1``.\n\n.. note::\n\n    You can also add loaders via the ``addLoader()`` method.\n\nCreate your own Loader\n~~~~~~~~~~~~~~~~~~~~~~\n\nAll loaders implement the ``\\Twig\\Loader\\LoaderInterface``::\n\n    interface \\Twig\\Loader\\LoaderInterface\n    {\n        /**\n         * Returns the source context for a given template logical name.\n         *\n         * @param string $name The template logical name\n         *\n         * @return \\Twig\\Source\n         *\n         * @throws \\Twig\\Error\\LoaderError When $name is not found\n         */\n        public function getSourceContext($name);\n\n        /**\n         * Gets the cache key to use for the cache for a given template name.\n         *\n         * @param string $name The name of the template to load\n         *\n         * @return string The cache key\n         *\n         * @throws \\Twig\\Error\\LoaderError When $name is not found\n         */\n        public function getCacheKey($name);\n\n        /**\n         * Returns true if the template is still fresh.\n         *\n         * @param string    $name The template name\n         * @param timestamp $time The last modification time of the cached template\n         *\n         * @return bool    true if the template is fresh, false otherwise\n         *\n         * @throws \\Twig\\Error\\LoaderError When $name is not found\n         */\n        public function isFresh($name, $time);\n\n        /**\n         * Check if we have the source code of a template, given its name.\n         *\n         * @param string $name The name of the template to check if we can load\n         *\n         * @return bool    If the template source code is handled by this loader or not\n         */\n        public function exists($name);\n    }\n\nThe ``isFresh()`` method must return ``true`` if the current cached template\nis still fresh, given the last modification time, or ``false`` otherwise.\n\nThe ``getSourceContext()`` method must return an instance of ``\\Twig\\Source``.\n\nUsing Extensions\n----------------\n\nTwig extensions are packages that add new features to Twig. Register an\nextension via the ``addExtension()`` method::\n\n    $twig->addExtension(new \\Twig\\Extension\\SandboxExtension());\n\nTwig comes bundled with the following extensions:\n\n* ``\\Twig\\Extension\\CoreExtension``: Defines all the core features of Twig.\n\n* ``\\Twig\\Extension\\DebugExtension``: Defines the ``dump`` function to help debug\n  template variables.\n\n* ``\\Twig\\Extension\\EscaperExtension``: Adds automatic output-escaping and the\n  possibility to escape/unescape blocks of code.\n\n* ``\\Twig\\Extension\\SandboxExtension``: Adds a sandbox mode to the default Twig\n  environment, making it safe to evaluate untrusted code.\n\n* ``\\Twig\\Extension\\ProfilerExtension``: Enables the built-in Twig profiler.\n\n* ``\\Twig\\Extension\\OptimizerExtension``: Optimizes the node tree before\n  compilation.\n\n* ``\\Twig\\Extension\\StringLoaderExtension``: Defines the ``template_from_string``\n   function to allow loading templates from string in a template.\n\nThe Core, Escaper, and Optimizer extensions are registered by default.\n\nBuilt-in Extensions\n-------------------\n\nThis section describes the features added by the built-in extensions.\n\n.. tip::\n\n    Read the chapter about :doc:`extending Twig <advanced>` to learn how to\n    create your own extensions.\n\nCore Extension\n~~~~~~~~~~~~~~\n\nThe ``core`` extension defines all the core features of Twig:\n\n* :doc:`Tags <tags/index>`;\n* :doc:`Filters <filters/index>`;\n* :doc:`Functions <functions/index>`;\n* :doc:`Tests <tests/index>`.\n\nEscaper Extension\n~~~~~~~~~~~~~~~~~\n\nThe ``escaper`` extension adds automatic output escaping to Twig. It defines a\ntag, ``autoescape``, and a filter, ``raw``.\n\nWhen creating the escaper extension, you can switch on or off the global\noutput escaping strategy::\n\n    $escaper = new \\Twig\\Extension\\EscaperExtension('html');\n    $twig->addExtension($escaper);\n\nIf set to ``html``, all variables in templates are escaped (using the ``html``\nescaping strategy), except those using the ``raw`` filter:\n\n.. code-block:: twig\n\n    {{ article.to_html|raw }}\n\nYou can also change the escaping mode locally by using the ``autoescape`` tag:\n\n.. code-block:: twig\n\n    {% autoescape 'html' %}\n        {{ var }}\n        {{ var|raw }}      {# var won't be escaped #}\n        {{ var|escape }}   {# var won't be double-escaped #}\n    {% endautoescape %}\n\n.. warning::\n\n    The ``autoescape`` tag has no effect on included files.\n\nThe escaping rules are implemented as follows:\n\n* Literals (integers, booleans, arrays, ...) used in the template directly as\n  variables or filter arguments are never automatically escaped:\n\n  .. code-block:: html+twig\n\n        {{ \"Twig<br/>\" }} {# won't be escaped #}\n\n        {% set text = \"Twig<br/>\" %}\n        {{ text }} {# will be escaped #}\n\n* Expressions which the result is a literal or a variable marked safe\n  are never automatically escaped:\n\n  .. code-block:: html+twig\n\n        {{ any_value ? \"Twig<br/>\" : \"<br/>Twig\" }} {# won't be escaped #}\n\n        {% set text = \"Twig<br/>\" %}\n        {{ true ? text : \"<br/>Twig\" }} {# will be escaped #}\n        {{ false ? text : \"<br/>Twig\" }} {# won't be escaped #}\n\n        {% set text = \"Twig<br/>\" %}\n        {{ any_value ? text|raw : \"<br/>Twig\" }} {# won't be escaped #}\n\n* Objects with a ``__toString`` method are converted to strings and\n  escaped. You can mark some classes and/or interfaces as being safe for some\n  strategies via ``EscaperExtension::addSafeClass()``:\n\n  .. code-block:: twig\n\n        // mark objects of class \"HtmlGenerator\" as safe for the HTML strategy\n        $escaper->addSafeClass('HtmlGenerator', ['html']);\n\n        // mark objects of interface \"HtmlGeneratorInterface\" as safe for the HTML strategy\n        $escaper->addSafeClass('HtmlGeneratorInterface', ['html']);\n\n        // mark objects of class \"HtmlGenerator\" as safe for the HTML and JS strategies\n        $escaper->addSafeClass('HtmlGenerator', ['html', 'js']);\n\n        // mark objects of class \"HtmlGenerator\" as safe for all strategies\n        $escaper->addSafeClass('HtmlGenerator', ['all']);\n\n* Escaping is applied before printing, after any other filter is applied:\n\n  .. code-block:: twig\n\n        {{ var|upper }} {# is equivalent to {{ var|upper|escape }} #}\n\n* The ``raw`` filter should only be used at the end of the filter chain:\n\n  .. code-block:: twig\n\n        {{ var|raw|upper }} {# will be escaped #}\n\n        {{ var|upper|raw }} {# won't be escaped #}\n\n* Automatic escaping is not applied if the last filter in the chain is marked\n  safe for the current context (e.g. ``html`` or ``js``). ``escape`` and\n  ``escape('html')`` are marked safe for HTML, ``escape('js')`` is marked\n  safe for JavaScript, ``raw`` is marked safe for everything.\n\n  .. code-block:: twig\n\n        {% autoescape 'js' %}\n            {{ var|escape('html') }} {# will be escaped for HTML and JavaScript #}\n            {{ var }} {# will be escaped for JavaScript #}\n            {{ var|escape('js') }} {# won't be double-escaped #}\n        {% endautoescape %}\n\n.. note::\n\n    Note that autoescaping has some limitations as escaping is applied on\n    expressions after evaluation. For instance, when working with\n    concatenation, ``{{ value|raw ~ other }}`` won't give the expected result\n    as escaping is applied on the result of the concatenation, not on the\n    individual variables (so, the ``raw`` filter won't have any effect here).\n\nSandbox Extension\n~~~~~~~~~~~~~~~~~\n\nThe ``sandbox`` extension can be used to evaluate untrusted code. Read more\nabout it in the :doc:`sandbox` chapter.\n\nProfiler Extension\n~~~~~~~~~~~~~~~~~~\n\nThe ``profiler`` extension enables a profiler for Twig templates; it should\nonly be used on your development machines as it adds some overhead::\n\n    $profile = new \\Twig\\Profiler\\Profile();\n    $twig->addExtension(new \\Twig\\Extension\\ProfilerExtension($profile));\n\n    $dumper = new \\Twig\\Profiler\\Dumper\\TextDumper();\n    echo $dumper->dump($profile);\n\nA profile contains information about time and memory consumption for template,\nblock, and macro executions.\n\nYou can also dump the data in a `Blackfire.io <https://blackfire.io/>`_\ncompatible format::\n\n    $dumper = new \\Twig\\Profiler\\Dumper\\BlackfireDumper();\n    file_put_contents('/path/to/profile.prof', $dumper->dump($profile));\n\nUpload the profile to visualize it (create a `free account\n<https://blackfire.io/signup?utm_source=twig&utm_medium=doc&utm_campaign=profiler>`_\nfirst):\n\n.. code-block:: sh\n\n    blackfire --slot=7 upload /path/to/profile.prof\n\nOptimizer Extension\n~~~~~~~~~~~~~~~~~~~\n\nThe ``optimizer`` extension optimizes the node tree before compilation::\n\n    $twig->addExtension(new \\Twig\\Extension\\OptimizerExtension());\n\nBy default, all optimizations are turned on. You can select the ones you want\nto enable by passing them to the constructor::\n\n    $optimizer = new \\Twig\\Extension\\OptimizerExtension(\\Twig\\NodeVisitor\\OptimizerNodeVisitor::OPTIMIZE_FOR);\n\n    $twig->addExtension($optimizer);\n\nTwig supports the following optimizations:\n\n* ``\\Twig\\NodeVisitor\\OptimizerNodeVisitor::OPTIMIZE_ALL``, enables all optimizations\n  (this is the default value).\n\n* ``\\Twig\\NodeVisitor\\OptimizerNodeVisitor::OPTIMIZE_NONE``, disables all optimizations.\n  This reduces the compilation time, but it can increase the execution time\n  and the consumed memory.\n\n* ``\\Twig\\NodeVisitor\\OptimizerNodeVisitor::OPTIMIZE_FOR``, optimizes the ``for`` tag by\n  removing the ``loop`` variable creation whenever possible.\n\nExceptions\n----------\n\nTwig can throw exceptions:\n\n* ``\\Twig\\Error\\Error``: The base exception for all errors.\n\n* ``\\Twig\\Error\\SyntaxError``: Thrown to tell the user that there is a problem with\n  the template syntax.\n\n* ``\\Twig\\Error\\RuntimeError``: Thrown when an error occurs at runtime (when a filter\n  does not exist for instance).\n\n* ``\\Twig\\Error\\LoaderError``: Thrown when an error occurs during template loading.\n\n* ``\\Twig\\Sandbox\\SecurityError``: Thrown when an unallowed tag, filter, or\n  method is called in a sandboxed template.\n"
  },
  {
    "path": "doc/coding_standards.rst",
    "content": "Coding Standards\n================\n\n.. note::\n\n    The `Twig CS fixer tool <https://github.com/VincentLanglet/Twig-CS-Fixer>`_\n    uses the coding standards described in this document to automatically fix\n    your templates.\n\nWhen writing Twig templates, we recommend you to follow these official coding\nstandards:\n\n* Put exactly one space after the start of a delimiter (``{{``, ``{%``,\n  and ``{#``) and before the end of a delimiter (``}}``, ``%}``, and ``#}``)\n  if the content is non empty:\n\n  .. code-block:: twig\n\n    {{ user }}\n    {# comment #} {##}\n    {% if user %}{% endif %}\n\n  When using the whitespace control character, do not put any spaces between\n  it and the delimiter:\n\n  .. code-block:: twig\n\n    {{- user -}}\n    {#- comment -#} {#--#}\n    {%- if user -%}{%- endif -%}\n\n* Put exactly one space before and after the following operators:\n  comparison operators (``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``), math\n  operators (``+``, ``-``, ``/``, ``*``, ``%``, ``//``, ``**``), logic\n  operators (``not``, ``and``, ``or``), ``~``, ``is``, ``in``, and the ternary\n  operator (``?:``):\n\n  .. code-block:: twig\n\n    {{ 1 + 2 }}\n    {{ first_name ~ ' ' ~ last_name }}\n    {{ is_correct ? true : false }}\n\n* Put exactly one space after the ``:`` sign in mappings and ``,`` in sequences\n  and mappings:\n\n  .. code-block:: twig\n\n    [1, 2, 3]\n    {'name': 'Fabien'}\n\n* Do not put any spaces after an opening parenthesis and before a closing\n  parenthesis in expressions:\n\n  .. code-block:: twig\n\n    {{ 1 + (2 * 3) }}\n\n* Do not put any spaces before and after string delimiters:\n\n  .. code-block:: twig\n\n    {{ 'Twig' }}\n    {{ \"Twig\" }}\n\n* Do not put any spaces before and after the following operators: ``|``,\n  ``.``, ``..``, ``[]``:\n\n  .. code-block:: twig\n\n    {{ name|upper|lower }}\n    {{ user.name }}\n    {{ user[name] }}\n    {% for i in 1..12 %}{% endfor %}\n\n* Do not put any spaces before and after the parenthesis used for filter and\n  function calls:\n\n  .. code-block:: twig\n\n    {{ name|default('Fabien') }}\n    {{ range(1..10) }}\n\n* Do not put any spaces before and after the opening and the closing of\n  sequences and mappings:\n\n  .. code-block:: twig\n\n    [1, 2, 3]\n    {'name': 'Fabien'}\n\n* Put exactly one space before and after ``=`` in macro argument declarations:\n\n  .. code-block:: twig\n\n    {% macro html_input(class = \"input\") %}\n\n* Put exactly one space after the ``:`` sign when using named arguments:\n\n  .. code-block:: twig\n\n    {{ html_input(class: \"input\") }}\n\n* Use snake case for all variable names (provided by the application and\n  created in templates), function/filter/test names, argument names and named\n  arguments:\n\n  .. code-block:: twig\n\n    {% set name = 'Fabien' %}\n    {% set first_name = 'Fabien' %}\n\n    {{ 'Fabien Potencier'|to_lower_case }}\n    {{ generate_random_number() }}\n\n    {% macro html_input(class_name) %}\n\n    {{ html_input(class_name: 'pwd') }}\n\n* Indent your code inside tags (use the same indentation as the one used for\n  the target language of the rendered template):\n\n  .. code-block:: twig\n\n    {% block content %}\n        {% if true %}\n            true\n        {% endif %}\n    {% endblock %}\n\n* Use ``:`` instead of ``=`` to separate argument names and values:\n\n  .. code-block:: twig\n\n    {{ data|convert_encoding(from: 'iso-2022-jp', to: 'UTF-8') }}\n"
  },
  {
    "path": "doc/deprecated.rst",
    "content": "Deprecated Features\n===================\n\nThis document lists deprecated features in Twig 3.x. Deprecated features are\nkept for backward compatibility and removed in the next major release (a\nfeature that was deprecated in Twig 3.x is removed in Twig 4.0).\n\nFunctions\n---------\n\n* The ``twig_test_iterable`` function is deprecated; use the native PHP\n  ``is_iterable`` function instead.\n\n* The ``attribute`` function is deprecated as of Twig 3.15. Use the ``.``\n  operator instead and wrap the name with parenthesis:\n\n  .. code-block:: twig\n\n    {# before #}\n    {{ attribute(object, method) }}\n    {{ attribute(object, method, arguments) }}\n    {{ attribute(array, item) }}\n\n    {# after #}\n    {{ object.(method) }}\n    {{ object.(method)(arguments) }}\n    {{ array[item] }}\n\n  Note that it won't be removed in 4.0 to allow a smoother upgrade path.\n\nExtensions\n----------\n\n* All functions defined in Twig extensions are marked as internal as of Twig\n  3.9.0, and will be removed in Twig 4.0. They have been replaced by internal\n  methods on their respective extension classes.\n\n  If you were using the ``twig_escape_filter()`` function in your code, use\n  ``$env->getRuntime(EscaperRuntime::class)->escape()`` instead.\n\n* The following methods from ``Twig\\Extension\\EscaperExtension`` are\n  deprecated: ``setEscaper()``, ``getEscapers()``, ``setSafeClasses``,\n  ``addSafeClasses()``. Use the same methods on the\n  ``Twig\\Runtime\\EscaperRuntime`` class instead:\n  \n  Before:\n  ``$twig->getExtension(EscaperExtension::class)->METHOD();``\n  \n  After:\n  ``$twig->getRuntime(EscaperRuntime::class)->METHOD();``\n\nNodes\n-----\n\n* The \"tag\" constructor parameter of the ``Twig\\Node\\Node`` class is deprecated\n  as of Twig 3.12 as the tag is now automatically set by the Parser when\n  needed.\n\n* The following ``Twig\\Node\\Node`` methods will take a string or an integer\n  (instead of just a string) in Twig 4.0 for their \"name\" argument:\n  ``getNode()``, ``hasNode()``, ``setNode()``, ``removeNode()``, and\n  ``deprecateNode()``.\n\n* Not passing a ``BodyNode`` instance as the body of a ``ModuleNode`` or\n  ``MacroNode`` constructor is deprecated as of Twig 3.12.\n\n* Returning ``null`` from ``TokenParserInterface::parse()`` is deprecated as of\n  Twig 3.12 (as forbidden by the interface).\n\n* The second argument of the\n  ``Twig\\Node\\Expression\\CallExpression::compileArguments()`` method is\n  deprecated.\n\n* The ``Twig\\Node\\Expression\\NameExpression::isSimple()`` and\n  ``Twig\\Node\\Expression\\NameExpression::isSpecial()`` methods are deprecated as\n  of Twig 3.11 and will be removed in Twig 4.0.\n\n* The ``filter`` node of ``Twig\\Node\\Expression\\FilterExpression`` is\n  deprecated as of Twig 3.12 and will be removed in 4.0. Use the ``filter``\n  attribute instead to get the filter:\n\n  Before:\n  ``$node->getNode('filter')->getAttribute('value')``\n\n  After:\n  ``$node->getAttribute('twig_callable')->getName()``\n\n* Passing a name to ``Twig\\Node\\Expression\\FunctionExpression``,\n  ``Twig\\Node\\Expression\\FilterExpression``, and\n  ``Twig\\Node\\Expression\\TestExpression`` is deprecated as of Twig 3.12.\n  As of Twig 4.0, you need to pass a ``TwigFunction``, ``TwigFilter``, or\n  ``TestFilter`` instead.\n\n  Let's take a ``FunctionExpression`` as an example.\n\n  If you have a node that extends ``FunctionExpression`` and if you don't\n  override the constructor, you don't need to do anything. But if you override\n  the constructor, then you need to change the type hint of the name and mark\n  the constructor with the ``Twig\\Attribute\\FirstClassTwigCallableReady`` attribute.\n\n  Before::\n\n      class NotReadyFunctionExpression extends FunctionExpression\n      {\n          public function __construct(string $function, Node $arguments, int $lineno)\n          {\n              parent::__construct($function, $arguments, $lineno);\n          }\n      }\n\n      class NotReadyFilterExpression extends FilterExpression\n      {\n          public function __construct(Node $node, ConstantExpression $filter, Node $arguments, int $lineno)\n          {\n              parent::__construct($node, $filter, $arguments, $lineno);\n          }\n      }\n\n      class NotReadyTestExpression extends TestExpression\n      {\n          public function __construct(Node $node, string $test, ?Node $arguments, int $lineno)\n          {\n              parent::__construct($node, $test, $arguments, $lineno);\n          }\n      }\n\n  After::\n\n      class ReadyFunctionExpression extends FunctionExpression\n      {\n          #[FirstClassTwigCallableReady]\n          public function __construct(TwigFunction|string $function, Node $arguments, int $lineno)\n          {\n              parent::__construct($function, $arguments, $lineno);\n          }\n      }\n\n      class ReadyFilterExpression extends FilterExpression\n      {\n          #[FirstClassTwigCallableReady]\n          public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno)\n          {\n              parent::__construct($node, $filter, $arguments, $lineno);\n          }\n      }\n\n      class ReadyTestExpression extends TestExpression\n      {\n          #[FirstClassTwigCallableReady]\n          public function __construct(Node $node, TwigTest|string $test, ?Node $arguments, int $lineno)\n          {\n              parent::__construct($node, $test, $arguments, $lineno);\n          }\n      }\n\n* The following ``Twig\\Node\\Expression\\FunctionExpression`` attributes are\n  deprecated as of Twig 3.12: ``needs_charset``,  ``needs_environment``,\n  ``needs_context``,  ``arguments``,  ``callable``,  ``is_variadic``,\n  and ``dynamic_name``.\n\n* The following ``Twig\\Node\\Expression\\FilterExpression`` attributes are\n  deprecated as of Twig 3.12: ``needs_charset``,  ``needs_environment``,\n  ``needs_context``,  ``arguments``,  ``callable``,  ``is_variadic``,\n  and ``dynamic_name``.\n\n* The following ``Twig\\Node\\Expression\\TestExpression`` attributes are\n  deprecated as of Twig 3.12: ``arguments``,  ``callable``,  ``is_variadic``,\n  and ``dynamic_name``.\n\n* The ``MethodCallExpression`` class is deprecated as of Twig 3.15, use\n  ``MacroReferenceExpression`` instead.\n\n* The ``Twig\\Node\\Expression\\TempNameExpression`` class is deprecated as of\n  Twig 3.15; use ``Twig\\Node\\Expression\\Variable\\LocalVariable`` instead.\n\n* The ``Twig\\Node\\Expression\\NameExpression`` class is deprecated as of Twig\n  3.15; use ``Twig\\Node\\Expression\\Variable\\ContextVariable`` instead.\n\n* The ``Twig\\Node\\Expression\\AssignNameExpression`` class is deprecated as of\n  Twig 3.15; use ``Twig\\Node\\Expression\\Variable\\AssignContextVariable``\n  instead.\n\n* Node implementations that use ``echo`` or ``print`` should use ``yield``\n  instead; all Node implementations should use the\n  ``#[\\Twig\\Attribute\\YieldReady]`` attribute on their class once they've been\n  made ready for ``yield``; the ``use_yield`` Environment option can be turned\n  on when all nodes use the ``#[\\Twig\\Attribute\\YieldReady]`` attribute.\n\n * The ``Twig\\Node\\InlinePrint`` class is deprecated as of Twig 3.16 with no\n   replacement.\n\n * The ``Twig\\Node\\Expression\\NullCoalesceExpression`` class is deprecated as\n   of Twig 3.17, use ``Twig\\Node\\Expression\\Binary\\NullCoalesceBinary``\n   instead.\n\n * The ``Twig\\Node\\Expression\\ConditionalExpression`` class is deprecated as of\n   Twig 3.17, use ``Twig\\Node\\Expression\\Ternary\\ConditionalTernary`` instead.\n\n * The ``is_defined_test`` attribute is deprecated as of Twig 3.21, use\n   ``Twig\\Node\\Expression\\SupportDefinedTestInterface`` instead.\n\n* Instantiating ``Twig\\Node\\Node`` directly is deprecated as of Twig 3.15. Use\n  ``EmptyNode`` or ``Nodes`` instead depending on the use case. The\n  ``Twig\\Node\\Node`` class will be abstract in Twig 4.0.\n\n* Not passing ``AbstractExpression`` arguments to the following ``Node`` class\n  constructors is deprecated as of Twig 3.15:\n\n  * ``AbstractBinary``\n  * ``AbstractUnary``\n  * ``BlockReferenceExpression``\n  * ``TestExpression``\n  * ``DefinedTest``\n  * ``FilterExpression``\n  * ``RawFilter``\n  * ``DefaultFilter``\n  * ``InlinePrint``\n  * ``NullCoalesceExpression``\n\nNode Visitors\n-------------\n\n* The ``Twig\\NodeVisitor\\AbstractNodeVisitor`` class is deprecated, implement the\n  ``Twig\\NodeVisitor\\NodeVisitorInterface`` interface instead.\n\n* The ``Twig\\NodeVisitor\\OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER`` and the\n  ``Twig\\NodeVisitor\\OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES`` options are\n  deprecated as of Twig 3.12 and will be removed in Twig 4.0; they don't do\n  anything anymore.\n\nParser\n------\n\n* The following methods from ``Twig\\Parser`` are deprecated as of Twig 3.12:\n  ``getBlockStack()``, ``hasBlock()``, ``getBlock()``, ``hasMacro()``,\n  ``hasTraits()``, ``getParent()``.\n\n* Passing ``null`` to ``Twig\\Parser::setParent()`` is deprecated as of Twig\n  3.12.\n\n* Passing a non-``AbstractExpression`` node to ``Twig\\Parser::setParent()`` is\n  deprecated as of Twig 3.24; the method will require an ``AbstractExpression``\n  instance in Twig 4.0.\n\n* Passing non-``AbstractExpression`` nodes to\n  ``Twig\\Node\\Expression\\Binary\\MatchesBinary`` constructor is deprecated as of\n  Twig 3.24; the constructor will require an ``AbstractExpression`` instance in Twig\n  4.0.\n\n* The ``Twig\\Parser::getExpressionParser()`` method is deprecated as of Twig\n  3.21, use ``Twig\\Parser::parseExpression()`` instead.\n\n* The ``Twig\\ExpressionParser`` class is deprecated as of Twig 3.21:\n\n  * ``parseExpression()``, use ``Parser::parseExpression()``\n  * ``parsePrimaryExpression()``, use ``Parser::parseExpression()``\n  * ``parseStringExpression()``, use ``Parser::parseExpression()``\n  * ``parseHashExpression()``, use ``Parser::parseExpression()``\n  * ``parseMappingExpression()``, use ``Parser::parseExpression()``\n  * ``parseArrayExpression()``, use ``Parser::parseExpression()``\n  * ``parseSequenceExpression()``, use ``Parser::parseExpression()``\n  * ``parsePostfixExpression``\n  * ``parseSubscriptExpression``\n  * ``parseFilterExpression``\n  * ``parseFilterExpressionRaw``\n  * ``parseArguments()``, use ``Twig\\ExpressionParser\\Infix\\ArgumentsTrait::parseNamedArguments()``\n  * ``parseAssignmentExpression``, use ``AbstractTokenParser::parseAssignmentExpression``\n  * ``parseMultitargetExpression``\n  * ``parseOnlyArguments()``, use ``Twig\\ExpressionParser\\Infix\\ArgumentsTrait::parseNamedArguments()``\n\nToken\n-----\n\n* Not passing a ``Source`` instance to ``Twig\\TokenStream`` constructor is\n  deprecated as of Twig 3.16.\n\n* The ``Token::getType()`` method is deprecated as of Twig 3.19, use\n  ``Token::test()`` instead.\n\n* The ``Token::ARROW_TYPE`` constant is deprecated as of Twig 3.21, the arrow\n  ``=>`` is now an operator (``Token::OPERATOR_TYPE``).\n\n* The ``Token::PUNCTUATION_TYPE`` with values ``(``, ``[``, ``|``, ``.``,\n  ``?``, or ``?:`` are now of the ``Token::OPERATOR_TYPE`` type.\n\nTemplates\n---------\n\n* The method ``Template::loadTemplate()`` is deprecated.\n* Passing ``Twig\\Template`` instances to Twig public API is deprecated (like\n  in ``Environment::resolveTemplate()`` and ``Environment::load()``); pass\n  instances of ``Twig\\TemplateWrapper`` instead.\n\nFilters\n-------\n\n* The ``spaceless`` filter is deprecated as of Twig 3.12 and will be removed in\n  Twig 4.0.\n\nSandbox\n-------\n\n* Having the ``extends`` and ``use`` tags allowed by default in a sandbox is\n  deprecated as of Twig 3.12. You will need to explicitly allow them if needed\n  in 4.0.\n\n* Deprecate the ``sandbox`` tag, use the ``sandboxed`` option of the\n  ``include`` function instead:\n\n  Before::\n\n    {% sandbox %}\n      {% include 'user_defined.html.twig' %}\n    {% endsandbox %}\n\n  After::\n\n    {{ include('user_defined.html.twig', sandboxed: true) }}\n\nTesting Utilities\n-----------------\n\n* Implementing the data provider method ``Twig\\Test\\NodeTestCase::getTests()``\n  is deprecated as of Twig 3.13. Instead, implement the static data provider\n  ``provideTests()``.\n\n* In order to make their functionality available for static data providers, the\n  helper methods ``getVariableGetter()`` and ``getAttributeGetter()`` on\n  ``Twig\\Test\\NodeTestCase`` have been deprecated. Call the new methods\n  ``createVariableGetter()`` and ``createAttributeGetter()`` instead.\n\n* The method ``Twig\\Test\\NodeTestCase::getEnvironment()`` is considered final\n  as of Twig 3.13. If you want to override how the Twig environment is\n  constructed, override ``createEnvironment()`` instead.\n\n* The method ``getFixturesDir()`` on ``Twig\\Test\\IntegrationTestCase`` is\n  deprecated, implement the new static method ``getFixturesDirectory()``\n  instead, which will be abstract in 4.0.\n\n* The data providers ``getTests()`` and ``getLegacyTests()`` on\n  ``Twig\\Test\\IntegrationTestCase`` are considered final as of Twig 3.13.\n\nEnvironment\n-----------\n\n* The ``Twig\\Environment::mergeGlobals()`` method is deprecated as of Twig 3.14\n  and will be removed in Twig 4.0:\n\n  Before::\n\n      $context = $twig->mergeGlobals($context);\n\n  After::\n\n      $context += $twig->getGlobals();\n\nFunctions/Filters/Tests\n-----------------------\n\n* The ``deprecated``, ``deprecating_package``, ``alternative`` options on Twig\n  functions/filters/Tests are deprecated as of Twig 3.15, and will be removed\n  in Twig 4.0. Use the ``deprecation_info`` option instead:\n\n  Before::\n\n      $twig->addFunction(new TwigFunction('upper', 'upper', [\n          'deprecated' => '3.12', 'deprecating_package' => 'twig/twig',\n      ]));\n\n  After::\n\n      $twig->addFunction(new TwigFunction('upper', 'upper', [\n          'deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.12'),\n      ]));\n\n* For variadic arguments, use snake-case for the argument name to ease the\n  transition to 4.0.\n\n* Passing a ``string`` or an ``array`` to Twig callable arguments accepting\n  arrow functions is deprecated as of Twig 3.15; these arguments will have a\n  ``\\Closure`` type hint in 4.0.\n\n* Returning ``null`` from ``TwigFilter::getSafe()`` and\n  ``TwigFunction::getSafe()`` is deprecated as of Twig 3.16; return ``[]``\n  instead.\n\nOperators\n---------\n\n* An operator precedence must be part of the [0, 512] range as of Twig 3.21.\n\n* The ``.`` operator allows accessing class constants as of Twig 3.15.\n  This can be a BC break if you don't use UPPERCASE constant names.\n\n* Using ``~`` in an expression with the ``+`` or ``-`` operators without using\n  parentheses to clarify precedence triggers a deprecation as of Twig 3.15 (in\n  Twig 4.0, ``+`` / ``-`` will have a higher precedence than ``~``).\n\n  For example, the following expression will trigger a deprecation in Twig 3.15::\n\n    {{ '42' ~ 1 + 41 }}\n\n  To avoid the deprecation, wrap the concatenation in parentheses to clarify\n  the precedence::\n\n    {{ ('42' ~ 1) + 41 }} {# this is equivalent to what Twig 3.x does without the parentheses #}\n\n    {# or #}\n\n    {{ '42' ~ (1 + 41) }} {# this is equivalent to what Twig 4.x will do without the parentheses #}\n\n* Using ``??`` without explicit parentheses to clarify precedence triggers a\n  deprecation as of Twig 3.15 (in Twig 4.0, ``??`` will have the lowest\n  precedence).\n\n  For example, the following expression will trigger a deprecation in Twig 3.15::\n\n    {{ 'notnull' ?? 'foo' ~ '_bar' }}\n\n  To avoid the deprecation, wrap the ``??`` expression in parentheses to clarify\n  the precedence::\n\n    {{ ('notnull' ?? 'foo') ~ '_bar' }} {# this is equivalent to what Twig 3.x does without the parentheses #}\n\n    {# or #}\n\n    {{ 'notnull' ?? ('foo' ~ '_bar') }} {# this is equivalent to what Twig 4.x will do without the parentheses #}\n\n* Using the ``not`` unary operator in an expression with ``*``, ``/``, ``//``,\n  or ``%`` operators without explicit parentheses to clarify precedence\n  triggers a deprecation as of Twig 3.15 (in Twig 4.0, ``not`` will have a\n  higher precedence than ``*``, ``/``, ``//``, and ``%``).\n\n  For example, the following expression will trigger a deprecation in Twig 3.15::\n\n    {{ not 1 * 2 }}\n\n  To avoid the deprecation, wrap the concatenation in parentheses to clarify\n  the precedence::\n\n    {{ (not 1 * 2) }} {# this is equivalent to what Twig 3.x does without the parentheses #}\n\n    {# or #}\n\n    {{ (not 1) * 2 }} {# this is equivalent to what Twig 4.x will do without the parentheses #}\n\n* Using the ``|`` operator in an expression with ``+`` or ``-`` without explicit\n  parentheses to clarify precedence triggers a deprecation as of Twig 3.21 (in\n  Twig 4.0, ``|`` will have a higher precedence than ``+`` and ``-``).\n\n  For example, the following expression will trigger a deprecation in Twig 3.21::\n\n    {{ -1|abs }}\n\n  To avoid the deprecation, add parentheses to clarify the precedence::\n\n    {{ -(1|abs) }} {# this is equivalent to what Twig 3.x does without the parentheses #}\n\n    {# or #}\n\n    {{ (-1)|abs }} {# this is equivalent to what Twig 4.x will do without the parentheses #}\n\n* The ``Twig\\Extension\\ExtensionInterface::getOperators()`` method is deprecated\n  as of Twig 3.21, use ``Twig\\Extension\\ExtensionInterface::getExpressionParsers()``\n  instead:\n\n  Before::\n\n      public function getOperators(): array {\n          return [\n              'not' => [\n                  'precedence' => 10,\n                  'class' => NotUnary::class,\n              ],\n          ];\n      }\n\n  After::\n\n      public function getExpressionParsers(): array {\n          return [\n              new UnaryOperatorExpressionParser(NotUnary::class, 'not', 10),\n          ];\n      }\n\n* The ``Twig\\OperatorPrecedenceChange`` class is deprecated as of Twig 3.21,\n  use ``Twig\\ExpressionParser\\PrecedenceChange`` instead.\n\n* Not implementing the ``getOperatorTokens()`` method in\n  ``Twig\\ExpressionParser\\ExpressionParserInterface`` implementations is\n  deprecated as of Twig 3.24. This method will be added to the interface in\n  Twig 4.0. It returns the operator token strings that the expression parser\n  handles (used by the Lexer and the parser registry). If your custom\n  expression parser extends ``Twig\\ExpressionParser\\AbstractExpressionParser``,\n  the default implementation returns ``[$this->getName(), ...$this->getAliases()]``.\n  Override it if your parser doesn't handle operator tokens (return ``[]``) or if\n  the operator tokens differ from the parser name.\n"
  },
  {
    "path": "doc/filters/abs.rst",
    "content": "``abs``\n=======\n\nThe ``abs`` filter returns the absolute value.\n\n.. code-block:: twig\n\n    {# number = -5 #}\n\n    {{ number|abs }}\n\n    {# outputs 5 #}\n\n.. note::\n\n    Internally, Twig uses the PHP `abs`_ function.\n\n.. _`abs`: https://www.php.net/abs\n"
  },
  {
    "path": "doc/filters/batch.rst",
    "content": "``batch``\n=========\n\nThe ``batch`` filter \"batches\" items by returning a list of lists with the\ngiven number of items. A second parameter can be provided and used to fill in\nmissing items:\n\n.. code-block:: html+twig\n\n    {% set items = ['a', 'b', 'c', 'd'] %}\n\n    <table>\n        {% for row in items|batch(3, 'No item') %}\n            <tr>\n                {% for index, column in row %}\n                    <td>{{ index }} - {{ column }}</td>\n                {% endfor %}\n            </tr>\n        {% endfor %}\n    </table>\n\nThe above example will be rendered as:\n\n.. code-block:: html+twig\n\n    <table>\n        <tr>\n            <td>0 - a</td>\n            <td>1 - b</td>\n            <td>2 - c</td>\n        </tr>\n        <tr>\n            <td>3 - d</td>\n            <td>4 - No item</td>\n            <td>5 - No item</td>\n        </tr>\n    </table>\n\nIf you choose to set the third parameter ``preserve_keys`` to ``false``, the keys will be reset in each loop.\n\n.. code-block:: html+twig\n\n    {% set items = ['a', 'b', 'c', 'd'] %}\n\n    <table>\n        {% for row in items|batch(3, 'No item', false) %}\n            <tr>\n                {% for index, column in row %}\n                    <td>{{ index }} - {{ column }}</td>\n                {% endfor %}\n            </tr>\n        {% endfor %}\n    </table>\n\nThe above example will be rendered as:\n\n.. code-block:: html+twig\n\n    <table>\n        <tr>\n            <td>0 - a</td>\n            <td>1 - b</td>\n            <td>2 - c</td>\n        </tr>\n        <tr>\n            <td>0 - d</td>\n            <td>1 - No item</td>\n            <td>2 - No item</td>\n        </tr>\n    </table>\n\nArguments\n---------\n\n* ``size``: The size of the batch; fractional numbers will be rounded up\n* ``fill``: Used to fill in missing items\n* ``preserve_keys``: Whether to preserve keys or not (defaults to ``true``)\n"
  },
  {
    "path": "doc/filters/capitalize.rst",
    "content": "``capitalize``\n==============\n\nThe ``capitalize`` filter capitalizes a value. The first character will be\nuppercase, all others lowercase:\n\n.. code-block:: twig\n\n    {{ 'my first car'|capitalize }}\n\n    {# outputs 'My first car' #}\n"
  },
  {
    "path": "doc/filters/column.rst",
    "content": "``column``\n==========\n\nThe ``column`` filter returns the values from a single column in the input\narray.\n\n.. code-block:: twig\n\n    {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}\n\n    {% set fruits = items|column('fruit') %}\n\n    {# fruits now contains ['apple', 'orange'] #}\n\n.. note::\n\n    Internally, Twig uses the PHP `array_column`_ function.\n\nArguments\n---------\n\n* ``name``: The column name to extract\n\n.. _`array_column`: https://www.php.net/array_column\n"
  },
  {
    "path": "doc/filters/convert_encoding.rst",
    "content": "``convert_encoding``\n====================\n\nThe ``convert_encoding`` filter converts a string from one encoding to\nanother. The first argument is the expected output charset and the second one\nis the input charset:\n\n.. code-block:: twig\n\n    {{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}\n\n.. note::\n\n    This filter relies on the `iconv`_ extension.\n\nArguments\n---------\n\n* ``to``:   The output charset\n* ``from``: The input charset\n\n.. _`iconv`: https://www.php.net/iconv\n"
  },
  {
    "path": "doc/filters/country_name.rst",
    "content": "``country_name``\n================\n\nThe ``country_name`` filter returns the country name given its ISO-3166 code:\n\n.. code-block:: twig\n\n    {# France #}\n    {{ 'FR'|country_name }}\n\nBy default, the filter uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# États-Unis #}\n    {{ 'US'|country_name('fr') }}\n\n    {# 美國 #}\n    {{ 'US'|country_name('zh_Hant_HK') }}\n\n.. note::\n\n    The ``country_name`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/currency_name.rst",
    "content": "``currency_name``\n=================\n\nThe ``currency_name`` filter returns the currency name given its ISO 4217 code:\n\n.. code-block:: twig\n\n    {# Euro #}\n    {{ 'EUR'|currency_name }}\n\n    {# Japanese Yen #}\n    {{ 'JPY'|currency_name }}\n\nBy default, the filter uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# yen japonais #}\n    {{ 'JPY'|currency_name('fr_FR') }}\n\n.. note::\n\n    The ``currency_name`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/currency_symbol.rst",
    "content": "``currency_symbol``\n===================\n\nThe ``currency_symbol`` filter returns the currency symbol given its ISO 4217\ncode:\n\n.. code-block:: twig\n\n    {# € #}\n    {{ 'EUR'|currency_symbol }}\n\n    {# ¥ #}\n    {{ 'JPY'|currency_symbol }}\n\nBy default, the filter uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# ¥ #}\n    {{ 'JPY'|currency_symbol('fr') }}\n\n.. note::\n\n    The ``currency_symbol`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/data_uri.rst",
    "content": "``data_uri``\n============\n\nThe ``data_uri`` filter generates a URL using the data scheme as defined in\n`RFC 2397`_:\n\n.. code-block:: html+twig\n\n    {{ image_data|data_uri }}\n\n    {{ source('path_to_image')|data_uri }}\n\n    {# force the mime type, disable the guessing of the mime type #}\n    {{ image_data|data_uri(mime: \"image/svg\") }}\n\n    {# also works with plain text #}\n    {{ '<b>foobar</b>'|data_uri(mime: \"text/html\") }}\n\n    {# add some extra parameters #}\n    {{ '<b>foobar</b>'|data_uri(mime: \"text/html\", parameters: {charset: \"ascii\"}) }}\n\n.. note::\n\n    The ``data_uri`` filter is part of the ``HtmlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/html-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Html\\HtmlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new HtmlExtension());\n\n.. note::\n\n    The filter does not perform any length validation on purpose (limit depends\n    on the usage context), validation should be done before calling this filter.\n\nArguments\n---------\n\n* ``mime``: The mime type\n* ``parameters``: A mapping of parameters\n\n.. _RFC 2397: https://tools.ietf.org/html/rfc2397\n"
  },
  {
    "path": "doc/filters/date.rst",
    "content": "``date``\n========\n\nThe ``date`` filter formats a date to a given format:\n\n.. code-block:: twig\n\n    {{ post.published_at|date(\"m/d/Y\") }}\n\nThe format specifier is the same as supported by `date`_,\nexcept when the filtered data is of type `DateInterval`_, when the format must conform to\n`DateInterval::format`_ instead.\n\nThe ``date`` filter accepts strings (it must be in a format supported by the\n`strtotime`_ function), `DateTime`_ instances, or `DateInterval`_ instances. For\ninstance, to display the current date, filter the word \"now\":\n\n.. code-block:: twig\n\n    {{ \"now\"|date(\"m/d/Y\") }}\n\nTo escape words and characters in the date format use ``\\\\`` in front of each\ncharacter:\n\n.. code-block:: twig\n\n    {{ post.published_at|date(\"F jS \\\\a\\\\t g:ia\") }}\n\nIf the value passed to the ``date`` filter is ``null``, it will return the\ncurrent date by default. If an empty string is desired instead of the current\ndate, use a ternary operator:\n\n.. code-block:: twig\n\n    {{ post.published_at is empty ? \"\" : post.published_at|date(\"m/d/Y\") }}\n\nIf no format is provided, Twig will use the default one: ``F j, Y H:i``. This\ndefault can be changed by calling the ``setDateFormat()`` method on the\n``core`` extension instance. The first argument is the default format for\ndates and the second one is the default format for date intervals::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setDateFormat('d/m/Y', '%d days');\n\nTimezone\n--------\n\nBy default, the date is displayed by applying the default timezone (the one\nspecified in php.ini or declared in Twig -- see below), but you can override\nit by explicitly specifying a supported `timezone`_:\n\n.. code-block:: twig\n\n    {{ post.published_at|date(\"m/d/Y\", \"Europe/Paris\") }}\n\nIf the date is already a DateTime object, and if you want to keep its current\ntimezone, pass ``false`` as the timezone value:\n\n.. code-block:: twig\n\n    {{ post.published_at|date(\"m/d/Y\", false) }}\n\nThe default timezone can also be set globally by calling ``setTimezone()``::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setTimezone('Europe/Paris');\n\nArguments\n---------\n\n* ``format``:   The date format (default format is ``F j, Y H:i``, which will render as ``January 11, 2024 15:17``)\n* ``timezone``: The date timezone\n\n.. _`strtotime`:            https://www.php.net/strtotime\n.. _`DateTime`:             https://www.php.net/DateTime\n.. _`DateInterval`:         https://www.php.net/DateInterval\n.. _`date`:                 https://www.php.net/date\n.. _`DateInterval::format`: https://www.php.net/DateInterval.format\n.. _`timezone`:            https://www.php.net/manual/en/timezones.php\n"
  },
  {
    "path": "doc/filters/date_modify.rst",
    "content": "``date_modify``\n===============\n\nThe ``date_modify`` filter modifies a date with a given modifier string:\n\n.. code-block:: twig\n\n    {{ post.published_at|date_modify(\"+1 day\")|date(\"m/d/Y\") }}\n\nThe ``date_modify`` filter accepts strings (it must be in a format supported\nby the `strtotime`_ function) or `DateTime`_ instances. You can combine\nit with the :doc:`date<date>` filter for formatting.\n\nArguments\n---------\n\n* ``modifier``: The modifier\n\n.. _`strtotime`: https://www.php.net/strtotime\n.. _`DateTime`:  https://www.php.net/DateTime\n"
  },
  {
    "path": "doc/filters/default.rst",
    "content": "``default``\n===========\n\nThe ``default`` filter returns the passed default value if the value is\nundefined or empty, otherwise the value of the variable:\n\n.. code-block:: twig\n\n    {{ var|default('var is not defined') }}\n\n    {{ user.name|default('name item on user is not defined') }}\n\n    {{ user['name']|default('name item on user is not defined') }}\n\n    {{ ''|default('passed var is empty')  }}\n\nWhen using the ``default`` filter on an expression that uses variables in some\nmethod calls, be sure to use the ``default`` filter whenever a variable can be\nundefined:\n\n.. code-block:: twig\n\n    {{ user.value(name|default('username'))|default('not defined') }}\n    \nUsing the ``default`` filter on a boolean variable might trigger unexpected\nbehavior, as ``false`` is treated as an empty value. Consider using ``??``\ninstead:\n\n.. code-block:: twig\n\n    {% set value = false %}\n    {{ value|default(true) }} {# true #}\n    {{ value ?? true }} {# false #}\n\n.. note::\n\n    Read the documentation for the :doc:`defined<../tests/defined>` and\n    :doc:`empty<../tests/empty>` tests to learn more about their semantics.\n\nArguments\n---------\n\n* ``default``: The default value\n"
  },
  {
    "path": "doc/filters/escape.rst",
    "content": "``escape``\n==========\n\nThe ``escape`` filter escapes a string using strategies that depend on the\ncontext.\n\nBy default, it uses the HTML escaping strategy:\n\n.. code-block:: html+twig\n\n    <p>\n        {{ user.username|escape }}\n    </p>\n\nFor convenience, the ``e`` filter is defined as an alias:\n\n.. code-block:: html+twig\n\n    <p>\n        {{ user.username|e }}\n    </p>\n\nThe ``escape`` filter can also be used in other contexts than HTML thanks to\nan optional argument which defines the escaping strategy to use:\n\n.. code-block:: twig\n\n    {{ user.username|e }}\n    {# is equivalent to #}\n    {{ user.username|e('html') }}\n\nAnd here is how to escape variables included in JavaScript code:\n\n.. code-block:: twig\n\n    {{ user.username|escape('js') }}\n    {{ user.username|e('js') }}\n\nThe ``escape`` filter supports the following escaping strategies for HTML\ndocuments:\n\n* ``html``: escapes a string for the **HTML body** context,\n  or for HTML attributes values **inside quotes**.\n\n* ``js``: escapes a string for the **JavaScript** context. This is intended for\n  use in JavaScript or JSON strings, and encodes values using backslash escape\n  sequences.\n\n* ``css``: escapes a string for the **CSS** context. CSS escaping can be\n  applied to any string being inserted into CSS and escapes everything except\n  alphanumerics.\n\n* ``url``: escapes a string for the **URI or parameter** contexts. This should\n  not be used to escape an entire URI; only a subcomponent being inserted.\n\n* ``html_attr``: escapes a string when used as an **HTML attribute** name, and\n  also when used as the value of an HTML attribute **without quotes**\n  (e.g. ``data-attribute={{ some_value }}``).\n\n* ``html_attr_relaxed``: like ``html_attr``, but **does not** escape the ``@``, ``:``,\n  ``[`` and ``]`` characters. You may want to use this in combination with front-end\n  frameworks that use attribute names like ``v-bind:href`` or ``@click``. But, be\n  aware that in some processing contexts like XML, characters like the colon ``:``\n  may have meaning like for XML namespace separation.\n\n.. versionadded:: 3.24\n\n    The ``html_attr_relaxed`` strategy has been added in 3.23.\n\nNote that doing contextual escaping in HTML documents is hard and choosing the\nright escaping strategy depends on a lot of factors. Please, read related\ndocumentation like `the OWASP prevention cheat sheet\n<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md>`_\nto learn more about this topic.\n\n.. note::\n\n    Internally, ``escape`` uses the PHP native `htmlspecialchars`_ function\n    for the HTML escaping strategy.\n\n.. caution::\n\n    When using automatic escaping, Twig tries to not double-escape a variable\n    when the automatic escaping strategy is the same as the one applied by the\n    escape filter; but that does not work when using a variable as the\n    escaping strategy:\n\n    .. code-block:: twig\n\n        {% set strategy = 'html' %}\n\n        {% autoescape 'html' %}\n            {{ var|escape('html') }}   {# won't be double-escaped #}\n            {{ var|escape(strategy) }} {# will be double-escaped #}\n        {% endautoescape %}\n\n    When using a variable as the escaping strategy, you should disable\n    automatic escaping:\n\n    .. code-block:: twig\n\n        {% set strategy = 'html' %}\n\n        {% autoescape 'html' %}\n            {{ var|escape(strategy)|raw }} {# won't be double-escaped #}\n        {% endautoescape %}\n\n.. tip::\n\n    The ``html_attr`` escaping strategy can be useful when you need to escape a\n    **dynamic HTML attribute name**:\n\n    .. code-block:: html+twig\n\n        <p {{ your_html_attr|e('html_attr') }}=\"attribute value\">\n    \n    It can also be used for escaping a **dynamic HTML attribute value** if it is\n    not quoted, but this is **less performant**. Instead, it is recommended to\n    quote the HTML attribute value and use the ``html`` escaping strategy:\n\n    .. code-block:: html+twig\n\n        <p data-content=\"{{ content|e('html') }}\">\n\n        {# this is equivalent, but less performant #}\n        <p data-content={{ content|e('html_attr') }}>\n\nCustom Escapers\n---------------\n\n.. versionadded:: 3.10\n\n    The ``EscaperRuntime`` class has been added in 3.10. On previous versions,\n    you can define custom escapers by calling the ``setEscaper()`` method on\n    the escaper extension instance. The first argument is the escaper strategy\n    (to be used in the ``escape`` call) and the second one must be a valid PHP\n    callable::\n\n        use Twig\\Extension\\EscaperExtension;\n\n        $twig = new \\Twig\\Environment($loader);\n        $twig->getExtension(EscaperExtension::class)->setEscaper('csv', 'csv_escaper');\n\n    When called by Twig, the callable receives the Twig environment instance,\n    the string to escape, and the charset.\n\nYou can define custom escapers by calling the ``setEscaper()`` method on the\nescaper runtime instance. It accepts two arguments: the strategy name and a PHP\ncallable that accepts a string to escape and the charset::\n\n    use Twig\\Runtime\\EscaperRuntime;\n\n    $twig = new \\Twig\\Environment($loader);\n    $escaper = fn ($string, $charset) => $string;\n    $twig->getRuntime(EscaperRuntime::class)->setEscaper('identity', $escaper);\n\n    # Usage in a template:\n    # {{ 'Twig'|escape('identity') }}\n\n.. note::\n\n    Built-in escapers cannot be overridden mainly because they should be\n    considered as the final implementation and also for better performance.\n\nArguments\n---------\n\n* ``strategy``: The escaping strategy\n* ``charset``:  The string charset\n\n.. _`htmlspecialchars`: https://www.php.net/htmlspecialchars\n"
  },
  {
    "path": "doc/filters/filter.rst",
    "content": "``filter``\n==========\n\nThe ``filter`` filter filters elements of a sequence or a mapping using an arrow\nfunction. The arrow function receives the value of the sequence or mapping:\n\n.. code-block:: twig\n\n    {% set sizes = [34, 36, 38, 40, 42] %}\n\n    {{ sizes|filter(v => v > 38)|join(', ') }}\n    {# output 40, 42 #}\n\nCombined with the ``for`` tag, it allows you to filter the items to iterate over:\n\n.. code-block:: twig\n\n    {% for v in sizes|filter(v => v > 38) -%}\n        {{ v }}\n    {% endfor %}\n    {# output 40 42 #}\n\nIt also works with mappings:\n\n.. code-block:: twig\n\n    {% set sizes = {\n        xs: 34,\n        s:  36,\n        m:  38,\n        l:  40,\n        xl: 42,\n    } %}\n\n    {% for k, v in sizes|filter(v => v > 38) -%}\n        {{ k }} = {{ v }}\n    {% endfor %}\n    {# output l = 40 xl = 42 #}\n\nThe arrow function also receives the key as a second argument:\n\n.. code-block:: twig\n\n    {% for k, v in sizes|filter((v, k) => v > 38 and k != \"xl\") -%}\n        {{ k }} = {{ v }}\n    {% endfor %}\n    {# output l = 40 #}\n\nNote that the arrow function has access to the current context.\n\nArguments\n---------\n\n* ``array``: The sequence or mapping\n* ``arrow``: The arrow function\n"
  },
  {
    "path": "doc/filters/find.rst",
    "content": "``find``\n========\n\n.. versionadded:: 3.11\n\n    The ``find`` filter was added in Twig 3.11.\n\nThe ``find`` filter returns the first element of a sequence matching an arrow\nfunction. The arrow function receives the value of the sequence:\n\n.. code-block:: twig\n\n    {% set sizes = [34, 36, 38, 40, 42] %}\n\n    {{ sizes|find(v => v > 38) }}\n    {# output 40 #}\n\nIt also works with mappings:\n\n.. code-block:: twig\n\n    {% set sizes = {\n        xxs: 32,\n        xs:  34,\n        s:   36,\n        m:   38,\n        l:   40,\n        xl:  42,\n    } %}\n\n    {{ sizes|find(v => v > 38) }}\n\n    {# output 40 #}\n\nThe arrow function also receives the key as a second argument:\n\n.. code-block:: twig\n\n    {{ sizes|find((v, k) => 's' not in k) }}\n\n    {# output 38 #}\n\nNote that the arrow function has access to the current context:\n\n.. code-block:: twig\n\n    {% set my_size = 39 %}\n\n    {{ sizes|find(v => v >= my_size) }}\n\n    {# output 40 #}\n\nArguments\n---------\n\n* ``array``: The sequence or mapping\n* ``arrow``: The arrow function\n"
  },
  {
    "path": "doc/filters/first.rst",
    "content": "``first``\n=========\n\nThe ``first`` filter returns the first \"element\" of a sequence, a mapping, or\na string:\n\n.. code-block:: twig\n\n    {{ [1, 2, 3, 4]|first }}\n    {# outputs 1 #}\n\n    {{ {a: 1, b: 2, c: 3, d: 4}|first }}\n    {# outputs 1 #}\n\n    {{ '1234'|first }}\n    {# outputs 1 #}\n\n.. note::\n\n    It also works with objects implementing the `Traversable`_ interface.\n\n.. _`Traversable`: https://www.php.net/manual/en/class.traversable.php\n"
  },
  {
    "path": "doc/filters/format.rst",
    "content": "``format``\n==========\n\nThe ``format`` filter formats a given string by replacing the placeholders\n(placeholders follows the `sprintf`_ notation):\n\n.. code-block:: twig\n\n    {% set fruit = 'apples' %}\n    {{ \"I like %s and %s.\"|format(fruit, \"oranges\") }}\n\n    {# outputs I like apples and oranges #}\n\n.. seealso::\n\n    :doc:`replace<replace>`\n\n.. _`sprintf`: https://www.php.net/sprintf\n"
  },
  {
    "path": "doc/filters/format_currency.rst",
    "content": "``format_currency``\n===================\n\nThe ``format_currency`` filter formats a number as a currency:\n\n.. code-block:: twig\n\n    {# €1,000,000.00 #}\n    {{ '1000000'|format_currency('EUR') }}\n\nYou can pass attributes to tweak the output:\n\n.. code-block:: twig\n\n    {# €12.34 #}\n    {{ '12.345'|format_currency('EUR', {rounding_mode: 'floor'}) }}\n\n    {# €1,000,000.0000 #}\n    {{ '1000000'|format_currency('EUR', {fraction_digit: 4}) }}\n\nThe list of supported options:\n\n* ``grouping_used``: Specifies whether to use grouping separator for thousands::\n\n        {# €1,234,567.89 #}\n        {{ 1234567.89 | format_currency('EUR', {grouping_used:true}, 'en') }}\n\n* ``decimal_always_shown``: Specifies whether to always show the decimal part, even if it's zero::\n\n        {# €123.00 #}\n        {{ 123 | format_currency('EUR', {decimal_always_shown:true}, 'en') }}\n\n* ``max_integer_digit``:\n* ``min_integer_digit``:\n* ``integer_digit``: Define constraints on the integer part::\n\n        {# €345.68 #}\n        {{ 12345.6789 | format_currency('EUR', {max_integer_digit:3, min_integer_digit:2}, 'en') }}\n\n* ``max_fraction_digit``:\n* ``min_fraction_digit``:\n* ``fraction_digit``: Define constraints on the fraction part::\n\n        {# €123.46 #}\n        {{ 123.456789 | format_currency('EUR', {max_fraction_digit:2, min_fraction_digit:1}, 'en') }}\n\n* ``multiplier``: Multiplies the value before formatting::\n\n        {# €123,000.00 #}\n        {{ 123 | format_currency('EUR', {multiplier:1000}, 'en') }}\n\n* ``grouping_size``:\n* ``secondary_grouping_size``: Set the size of the primary and secondary grouping separators::\n\n        {# €1,23,45,678.00 #}\n        {{ 12345678 | format_currency('EUR', {grouping_size:3, secondary_grouping_size:2}, 'en') }}\n\n* ``rounding_mode``:\n* ``rounding_increment``: Control rounding behavior, here is a list of all rounding_mode available:\n\n    * ``ceil``: Ceiling rounding\n    * ``floor``: Floor rounding\n    * ``down``: Rounding towards zero\n    * ``up``: Rounding away from zero\n    * ``half_even``: Round halves to the nearest even integer\n    * ``half_up``: Round halves up\n    * ``half_down``: Round halves down\n\n    .. code-block:: twig\n\n      {# €123.50 #}\n      {{ 123.456 | format_currency('EUR', {rounding_mode:'ceiling', rounding_increment:0.05}, 'en') }}\n\n* ``format_width``:\n* ``padding_position``: Set width and padding for the formatted number, here is a list of all padding_position available:\n\n    * ``before_prefix``: Pad before the currency symbol\n    * ``after_prefix``: Pad after the currency symbol\n    * ``before_suffix``: Pad before the suffix (currency symbol)\n    * ``after_suffix``: Pad after the suffix (currency symbol)\n\n    .. code-block:: twig\n\n        {# €123.00 #}\n        {{ 123 | format_currency('EUR', {format_width:10, padding_position:'before_suffix'}, 'en') }}\n\n* ``significant_digits_used``:\n* ``min_significant_digits_used``:\n* ``max_significant_digits_used``: Control significant digits in formatting::\n\n        {# €123.4568 #}\n        {{ 123.456789 | format_currency('EUR', {significant_digits_used:true, min_significant_digits_used:4, max_significant_digits_used:7}, 'en') }}\n\n* ``lenient_parse``: If true, allows lenient parsing of the input::\n\n        {# €123.00 #}\n        {{ 123 | format_currency('EUR', {lenient_parse:true}, 'en') }}\n\nBy default, the filter uses the current locale. You can pass it explicitly::\n\n    {# 1.000.000,00 € #}\n    {{ '1000000'|format_currency('EUR', locale: 'de') }}\n\n.. note::\n\n    The ``format_currency`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``currency``: The currency (ISO 4217 code)\n* ``attrs``: A map of attributes\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. note::\n\n    Internally, Twig uses the PHP `NumberFormatter::formatCurrency`_ function.\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n.. _`NumberFormatter::formatCurrency`: https://www.php.net/manual/en/numberformatter.formatcurrency.php\n"
  },
  {
    "path": "doc/filters/format_date.rst",
    "content": "``format_date``\n===============\n\nThe ``format_date`` filter formats a date. It behaves in the exact same way as\nthe :doc:`format_datetime<format_datetime>` filter, but without the time.\n\n.. note::\n\n    The ``format_date`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n* ``dateFormat``: The date format\n* ``pattern``: A date time pattern\n* ``timezone``: The date timezone\n* ``calendar``: The calendar (\"gregorian\" by default)\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/format_datetime.rst",
    "content": "``format_datetime``\n===================\n\nThe ``format_datetime`` filter formats a date time:\n\n.. code-block:: twig\n\n    {# Aug 7, 2019, 11:39:12 PM #}\n    {{ '2019-08-07 23:39:12'|format_datetime() }}\n\n.. note::\n\n    The ``format_datetime`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\n\nFormat\n------\n\nYou can tweak the output for the date part and the time part:\n\n.. code-block:: twig\n\n    {# 23:39 #}\n    {{ '2019-08-07 23:39:12'|format_datetime('none', 'short', locale: 'fr') }}\n\n    {# 07/08/2019 #}\n    {{ '2019-08-07 23:39:12'|format_datetime('short', 'none', locale: 'fr') }}\n\n    {# mercredi 7 août 2019 23:39:12 UTC #}\n    {{ '2019-08-07 23:39:12'|format_datetime('full', 'full', locale: 'fr') }}\n\nSupported values are: ``none``, ``short``, ``medium``, ``long``, and ``full``.\n\n.. versionadded:: 3.6\n\n    ``relative_short``, ``relative_medium``, ``relative_long``, and ``relative_full`` are also supported when running on\n    PHP 8.0 and superior or when using a polyfill that define the ``IntlDateFormatter::RELATIVE_*`` constants and\n    associated behavior.\n\nFor greater flexibility, you can even define your own pattern\n(see the `ICU user guide`_ for supported patterns).\n\n.. code-block:: twig\n\n    {# 11 oclock PM, GMT #}\n    {{ '2019-08-07 23:39:12'|format_datetime(pattern: \"hh 'oclock' a, zzzz\") }}\n\nLocale\n------\n\nBy default, the filter uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# 7 août 2019 23:39:12 #}\n    {{ '2019-08-07 23:39:12'|format_datetime(locale: 'fr') }}\n\nTimezone\n--------\n\nBy default, the date is displayed by applying the default timezone (the one\nspecified in php.ini or declared in Twig -- see below), but you can override\nit by explicitly specifying a timezone:\n\n.. code-block:: twig\n\n    {{ datetime|format_datetime(locale: 'en', timezone: 'Pacific/Midway') }}\n\nIf the date is already a DateTime object, and if you want to keep its current\ntimezone, pass ``false`` as the timezone value:\n\n.. code-block:: twig\n\n    {{ datetime|format_datetime(locale: 'en', timezone: false) }}\n\nThe default timezone can also be set globally by calling ``setTimezone()``::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setTimezone('Europe/Paris');\n\n.. note::\n\n    The ``format_datetime`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n* ``dateFormat``: The date format\n* ``timeFormat``: The time format\n* ``pattern``: A date time pattern\n* ``timezone``: The date timezone name\n* ``calendar``: The calendar (\"gregorian\" by default)\n\n.. _ICU user guide: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/format_number.rst",
    "content": "``format_number``\n=================\n\nThe ``format_number`` filter formats a number:\n\n.. code-block:: twig\n\n    {{ '12.345'|format_number }}\n\nYou can pass attributes to tweak the output:\n\n.. code-block:: twig\n\n    {# 12.34 #}\n    {{ '12.345'|format_number({rounding_mode: 'floor'}) }}\n\n    {# 1000000.0000 #}\n    {{ '1000000'|format_number({fraction_digit: 4}) }}\n\nThe list of supported options:\n\n* ``grouping_used``: Specifies whether to use grouping separator for thousands::\n\n        {# 1,234,567.89 #}\n        {{ 1234567.89|format_number({grouping_used:true}, locale: 'en') }}\n\n* ``decimal_always_shown``: Specifies whether to always show the decimal part, even if it's zero::\n\n        {# 123. #}\n        {{ 123|format_number({decimal_always_shown:true}, locale: 'en') }}\n\n* ``max_integer_digit``:\n* ``min_integer_digit``:\n* ``integer_digit``: Define constraints on the integer part::\n\n        {# 345.679 #}\n        {{ 12345.6789|format_number({max_integer_digit:3, min_integer_digit:2}, locale: 'en') }}\n\n* ``max_fraction_digit``:\n* ``min_fraction_digit``:\n* ``fraction_digit``: Define constraints on the fraction part::\n\n        {# 123.46 #}\n        {{ 123.456789|format_number({max_fraction_digit:2, min_fraction_digit:1}, locale: 'en') }}\n\n* ``multiplier``: Multiplies the value before formatting::\n\n        {# 123,000 #}\n        {{ 123|format_number({multiplier:1000}, locale: 'en') }}\n\n* ``grouping_size``:\n* ``secondary_grouping_size``: Set the size of the primary and secondary grouping separators::\n\n        {# 1,23,45,678 #}\n        {{ 12345678|format_number({grouping_size:3, secondary_grouping_size:2}, locale: 'en') }}\n\n* ``rounding_mode``:\n* ``rounding_increment``: Control rounding behavior, here is a list of all rounding_mode available:\n    * ``ceil``: Ceiling rounding\n    * ``floor``: Floor rounding\n    * ``down``: Rounding towards zero\n    * ``up``: Rounding away from zero\n    * ``halfeven``: Round halves to the nearest even integer\n    * ``halfup``: Round halves up\n    * ``halfdown``: Round halves down\n\n    .. code-block:: twig\n\n        {# 123.5 #}\n        {{ 123.456|format_number({rounding_mode:'ceiling', rounding_increment:0.05}, locale: 'en') }}\n\n* ``format_width``:\n* ``padding_position``: Set width and padding for the formatted number, here is a list of all padding_position available:\n    * ``before_prefix``: Pad before the currency symbol\n    * ``after_prefix``: Pad after the currency symbol\n    * ``before_suffix``: Pad before the suffix (currency symbol)\n    * ``after_suffix``: Pad after the suffix (currency symbol)\n\n    .. code-block:: twig\n\n        {# 123 #}\n        {{ 123|format_number({format_width:10, padding_position:'before_suffix'}, locale: 'en') }}\n\n* ``significant_digits_used``:\n* ``min_significant_digits_used``:\n* ``max_significant_digits_used``: Control significant digits in formatting::\n\n        {# 123.4568 #}\n        {{ 123.456789|format_number({significant_digits_used:true, min_significant_digits_used:4, max_significant_digits_used:7}, locale: 'en') }}\n\n* ``lenient_parse``: If true, allows lenient parsing of the input::\n\n        {# 123 #}\n        {{ 123|format_number({lenient_parse:true}, locale: 'en') }}\n\nBesides plain numbers, the filter can also format numbers in various styles::\n\n    {# 1,234% #}\n    {{ '12.345'|format_number(style: 'percent') }}\n\n    {# twelve point three four five #}\n    {{ '12.345'|format_number(style: 'spellout') }}\n\n    {# 12 sec. #}\n    {{ '12'|format_duration_number }}\n\nThe list of supported styles:\n\n* ``decimal``::\n\n        {# 1,234.568 #}\n        {{ 1234.56789 | format_number(style: 'decimal', locale: 'en') }}\n\n* ``currency``::\n\n        {# $1,234.56 #}\n        {{ 1234.56 | format_number(style: 'currency', locale: 'en') }}\n\n* ``percent``::\n\n        {# 12% #}\n        {{ 0.1234 | format_number(style: 'percent', locale: 'en') }}\n\n* ``scientific``::\n\n        {# 1.23456789e+3 #}\n        {{ 1234.56789 | format_number(style: 'scientific', locale: 'en') }}\n\n* ``spellout``::\n\n        {# one thousand two hundred thirty-four point five six seven eight nine #}\n        {{ 1234.56789 | format_number(style: 'spellout', locale: 'en') }}\n\n* ``ordinal``::\n\n        {# 1st #}\n        {{ 1 | format_number(style: 'ordinal', locale: 'en') }}\n\n* ``duration``::\n\n        {# 2:30:00 #}\n        {{ 9000 | format_number(style: 'duration', locale: 'en') }}\n\nAs a shortcut, you can use the ``format_*_number`` filters by replacing ``*``\nwith a style::\n\n    {# 1,234% #}\n    {{ '12.345'|format_percent_number }}\n\n    {# twelve point three four five #}\n    {{ '12.345'|format_spellout_number }}\n\nYou can pass attributes to tweak the output::\n\n    {# 12.3% #}\n    {{ '0.12345'|format_percent_number({rounding_mode: 'floor', fraction_digit: 1}) }}\n\nBy default, the filter uses the current locale. You can pass it explicitly::\n\n    {# 12,345 #}\n    {{ '12.345'|format_number(locale: 'fr') }}\n\n.. note::\n\n    The ``format_number`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: sh\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: sh\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n* ``attrs``: A map of attributes\n* ``style``: The style of the number output\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/format_time.rst",
    "content": "``format_time``\n===============\n\nThe ``format_time`` filter formats a time. It behaves in the exact same way as\nthe :doc:`format_datetime<format_datetime>` filter, but without the date.\n\n.. note::\n\n    The ``format_time`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n* ``timeFormat``: The time format\n* ``pattern``: A date time pattern\n* ``timezone``: The date timezone\n* ``calendar``: The calendar (\"gregorian\" by default)\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/html_attr_merge.rst",
    "content": "``html_attr_merge``\n===================\n\n.. _html_attr_merge:\n\n.. versionadded:: 3.24\n\n    The ``html_attr_merge`` filter was added in Twig 3.24.\n\nThe ``html_attr_merge`` filter merges multiple mappings that represent\nHTML attribute values. Such mappings contain the names of the HTML attributes\nas keys, and the corresponding values represent the attributes' values.\n\nIt is primarily designed for working with arrays that are passed to the\n:ref:`html_attr` function. It closely resembles the :doc:`merge <../filters/merge>`\nfilter, but has different merge behavior for values that are iterables\nthemselves, as it will merge such values in turn.\n\nThe filter returns a new merged array:\n\n.. code-block:: twig\n\n    {% set base = {class: ['btn'], type: 'button'} %}\n    {% set variant = {class: ['btn-primary'], disabled: true} %}\n\n    {% set merged = base|html_attr_merge(variant) %}\n\n    {# merged is now: {\n        class: ['btn', 'btn-primary'],\n        type: 'button',\n        disabled: true\n    } #}\n\nThe filter accepts multiple arrays as arguments and merges them from left to right:\n\n.. code-block:: twig\n\n    {% set merged = base|html_attr_merge(variant1, variant2, variant3) %}\n\nA common use case is to build attribute mappings conditionally by merging multiple\nparts based on conditions. To make this conditional merging more convenient, filter\narguments that are ``false``, ``null`` or empty arrays are ignored:\n\n.. code-block:: twig\n\n    {% set button_attrs = {\n        type: 'button',\n        class: ['btn']\n    }|html_attr_merge(\n        variant == 'primary' ? { class: ['btn-primary'] },\n        variant == 'secondary' ? { class: ['btn-secondary'] },\n        size == 'large' ? { class: ['btn-lg'] },\n        size == 'small' ? { class: ['btn-sm'] },\n        disabled ? { disabled: true, class: ['btn-disabled'] },\n        loading ? { 'aria-busy': 'true', class: ['btn-loading'] },\n    ) %}\n\n    {# Example with variant='primary', size='large', disabled=false, loading=true:\n\n       The false values (secondary variant, small size, disabled state) are ignored.\n\n       button_attrs is:\n       {\n           type: 'button',\n           class: ['btn', 'btn-primary', 'btn-lg', 'btn-loading'],\n           'aria-busy': 'true'\n       }\n    #}\n\nMerging Rules\n-------------\n\nThe filter follows these rules when merging attribute values:\n\n**Scalar values**: Later values override earlier ones.\n\n.. code-block:: twig\n\n    {% set result = {id: 'old'}|html_attr_merge({id: 'new'}) %}\n    {# result: {id: 'new'} #}\n\n**Array values**: Arrays are merged like in PHP's ``array_merge`` function - numeric keys are\nappended, non-numeric keys replace.\n\n.. code-block:: twig\n\n    {# Numeric keys (appended): #}\n    {% set result = {class: ['btn']}|html_attr_merge({class: ['btn-primary']}) %}\n    {# result: {class: ['btn', 'btn-primary']} #}\n\n    {# Non-numeric keys (replaced): #}\n    {% set result = {class: {base: 'btn', size: 'small'}}|html_attr_merge({class: {variant: 'primary', size: 'large'}}) %}\n    {# result: {class: {base: 'btn', size: 'large', variant: 'primary'}} #}\n\n.. note::\n\n    Remember, attribute mappings passed to or returned from this filter are regular\n    Twig mappings after all. If you want to completely replace an attribute value\n    that is an iterable with another value, you can use the :doc:`merge <../filters/merge>`\n    filter to do that.\n\n**``MergeableInterface`` implementations**: For advanced use cases, attribute values can be objects\nthat implement the ``MergeableInterface``. These objects can define their own, custom merge\nbehavior that takes precedence over the default rules. See the docblocks in that interface\nfor details.\n\n.. note::\n\n    The ``html_attr_merge`` filter is part of the ``HtmlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/html-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Html\\HtmlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new HtmlExtension());\n\nArguments\n---------\n\nThe filter accepts a variadic list of arguments to merge. Each argument can be:\n\n* A map of attributes\n* ``false`` or ``null`` (ignored, useful for conditional merging)\n* An empty string ``''`` (ignored, to support implicit else in ternary operators)\n\n.. seealso::\n\n    :ref:`html_attr`,\n    :doc:`html_attr_type`\n"
  },
  {
    "path": "doc/filters/html_attr_type.rst",
    "content": "``html_attr_type``\n==================\n\n.. _html_attr_type:\n\n.. versionadded:: 3.24\n\n    The ``html_attr_type`` filter was added in Twig 3.24.\n\nThe ``html_attr_type`` filter converts arrays into specialized attribute value\nobjects that implement custom rendering logic. It is designed for use\nwith the :ref:`html_attr` function for attributes where\nthe attribute value follows special formatting rules.\n\n.. code-block:: html+twig\n\n    <img {{ html_attr({\n        srcset: ['small.jpg 480w', 'large.jpg 1200w']|html_attr_type('cst')\n    }) }}>\n\n    {# Output: <img srcset=\"small.jpg 480w, large.jpg 1200w\"> #}\n\nAvailable Types\n---------------\n\nSpace-Separated Token List (``sst``)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nUsed for attributes that expect space-separated values, like ``class`` or\n``aria-labelledby``:\n\n.. code-block:: html+twig\n\n    {% set classes = ['btn', 'btn-primary']|html_attr_type('sst') %}\n\n    <button {{ html_attr({class: classes}) }}>\n        Click me\n    </button>\n\n    {# Output: <button class=\"btn btn-primary\">Click me</button> #}\n\nThis is the default type used when the :ref:`html_attr` function encounters an\narray value (except for ``style`` attributes).\n\nComma-Separated Token List (``cst``)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nUsed for attributes that expect comma-separated values, like ``srcset`` or\n``sizes``:\n\n.. code-block:: html+twig\n\n    <img {{ html_attr({\n        srcset: ['image-1x.jpg 1x', 'image-2x.jpg 2x', 'image-3x.jpg 3x']|html_attr_type('cst'),\n        sizes: ['(max-width: 600px) 100vw', '50vw']|html_attr_type('cst')\n    }) }}>\n\n    {# Output: <img srcset=\"image-1x.jpg 1x, image-2x.jpg 2x, image-3x.jpg 3x\" sizes=\"(max-width: 600px) 100vw, 50vw\"> #}\n\nInline Style (``style``)\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nUsed for style attributes. Handles both maps (property - value pairs) and sequences (CSS declarations):\n\n.. code-block:: html+twig\n\n    {# Associative array #}\n    {% set styles = {color: 'red', 'font-size': '14px'}|html_attr_type('style') %}\n\n    <div {{ html_attr({style: styles}) }}>\n        Styled content\n    </div>\n\n    {# Output: <div style=\"color: red; font-size: 14px;\">Styled content</div> #}\n\n    {# Numeric array #}\n    {% set styles = ['color: red', 'font-size: 14px']|html_attr_type('style') %}\n\n    <div {{ html_attr({style: styles}) }}>\n        Styled content\n    </div>\n\n    {# Output: <div style=\"color: red; font-size: 14px;\">Styled content</div> #}\n\nThe ``style`` type is automatically applied by the :ref:`html_attr` function when\nit encounters an array value for the ``style`` attribute.\n\n.. note::\n\n    The ``html_attr_type`` filter is part of the ``HtmlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/html-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Html\\HtmlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new HtmlExtension());\n\nArguments\n---------\n\n* ``value``: The sequence of attributes to convert\n* ``type``: The attribute type. One of:\n\n  * ``sst`` (default): Space-separated token list\n  * ``cst``: Comma-separated token list\n  * ``style``: Inline CSS styles\n\n.. seealso::\n\n    :ref:`html_attr`,\n    :ref:`html_attr_merge`\n"
  },
  {
    "path": "doc/filters/html_to_markdown.rst",
    "content": "``html_to_markdown``\n====================\n\nThe ``html_to_markdown`` filter converts a block of HTML to Markdown:\n\n.. code-block:: html+twig\n\n    {% apply html_to_markdown %}\n        <html>\n            <h1>Hello!</h1>\n        </html>\n    {% endapply %}\n\nYou can also use the filter on an entire template which you ``include``:\n\n.. code-block:: twig\n\n    {{ include('some_template.html.twig')|html_to_markdown }}\n\n.. note::\n\n    The ``html_to_markdown`` filter is part of the ``MarkdownExtension`` which\n    is not installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/markdown-extra\n\n    On Symfony projects, you can automatically enable it by installing the\n    ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Or add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Markdown\\MarkdownExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new MarkdownExtension());\n\n    If you are not using Symfony, you must also register the extension runtime::\n\n        use Twig\\Extra\\Markdown\\DefaultMarkdown;\n        use Twig\\Extra\\Markdown\\MarkdownRuntime;\n        use Twig\\RuntimeLoader\\RuntimeLoaderInterface;\n\n        $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {\n            public function load($class) {\n                if (MarkdownRuntime::class === $class) {\n                    return new MarkdownRuntime(new DefaultMarkdown());\n                }\n            }\n        });\n\n``html_to_markdown`` is just a frontend; the actual conversion is done by one of\nthe following compatible libraries, from which you can choose:\n\n* `league/html-to-markdown`_\n* `michelf/php-markdown`_\n* `erusev/parsedown`_\n\nDepending on the library, you can also add some options by passing them as an argument\nto the filter. Example for ``league/html-to-markdown``:\n\n.. code-block:: html+twig\n\n    {% apply html_to_markdown({hard_break: false}) %}\n        <html>\n            <h1>Hello!</h1>\n        </html>\n    {% endapply %}\n    \n.. _league/html-to-markdown: https://github.com/thephpleague/html-to-markdown\n.. _michelf/php-markdown: https://github.com/michelf/php-markdown\n.. _erusev/parsedown: https://github.com/erusev/parsedown\n"
  },
  {
    "path": "doc/filters/index.rst",
    "content": "Filters\n=======\n\n.. toctree::\n    :maxdepth: 1\n\n    abs\n    batch\n    capitalize\n    column\n    convert_encoding\n    country_name\n    currency_name\n    currency_symbol\n    data_uri\n    date\n    date_modify\n    default\n    escape\n    filter\n    find\n    first\n    format\n    format_currency\n    format_date\n    format_datetime\n    format_number\n    format_time\n    html_attr_merge\n    html_attr_type\n    html_to_markdown\n    inline_css\n    inky_to_html\n    invoke\n    join\n    json_encode\n    keys\n    language_name\n    last\n    length\n    locale_name\n    lower\n    map\n    markdown_to_html\n    merge\n    nl2br\n    number_format\n    plural\n    raw\n    reduce\n    replace\n    reverse\n    round\n    shuffle\n    singular\n    slice\n    slug\n    sort\n    spaceless\n    split\n    striptags\n    timezone_name\n    title\n    trim\n    u\n    upper\n    url_encode\n"
  },
  {
    "path": "doc/filters/inky_to_html.rst",
    "content": "``inky_to_html``\n================\n\nThe ``inky_to_html`` filter processes an `inky email template\n<https://github.com/foundation/inky>`_:\n\n.. code-block:: html+twig\n\n    {% apply inky_to_html %}\n        <row>\n            <columns large=\"6\"></columns>\n            <columns large=\"6\"></columns>\n        </row>\n    {% endapply %}\n\nYou can also use the filter on an included file:\n\n.. code-block:: twig\n\n    {{ include('some_template.inky.twig')|inky_to_html }}\n\n.. note::\n\n    The ``inky_to_html`` filter is part of the ``InkyExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/inky-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Inky\\InkyExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new InkyExtension());\n"
  },
  {
    "path": "doc/filters/inline_css.rst",
    "content": "``inline_css``\n==============\n\nThe ``inline_css`` filter inlines CSS styles in HTML documents:\n\n.. code-block:: html+twig\n\n    {% apply inline_css %}\n        <html>\n            <head>\n                <style>\n                    p { color: red; }\n                </style>\n            </head>\n            <body>\n                <p>Hello CSS!</p>\n            </body>\n        </html>\n    {% endapply %}\n\nYou can also add some stylesheets by passing them as arguments to the filter:\n\n.. code-block:: html+twig\n\n    {% apply inline_css(source(\"some_styles.css\"), source(\"another.css\")) %}\n        <html>\n            <body>\n                <p>Hello CSS!</p>\n            </body>\n        </html>\n    {% endapply %}\n\nStyles loaded via the filter override the styles defined in the ``<style>`` tag\nof the HTML document.\n\nYou can also use the filter on an included file:\n\n.. code-block:: twig\n\n    {{ include('some_template.html.twig')|inline_css }}\n\n    {{ include('some_template.html.twig')|inline_css(source(\"some_styles.css\")) }}\n\nNote that the CSS inliner works on an entire HTML document, not a fragment.\n\n.. note::\n\n    The ``inline_css`` filter is part of the ``CssInlinerExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/cssinliner-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\CssInliner\\CssInlinerExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new CssInlinerExtension());\n"
  },
  {
    "path": "doc/filters/invoke.rst",
    "content": "``invoke``\n==========\n\n.. versionadded:: 3.19\n\n    The ``invoke`` filter has been added in Twig 3.19.\n\nThe ``invoke`` filter invokes an arrow function with the given arguments:\n\n.. code-block:: twig\n\n    {% set person = { first: \"Bob\", last: \"Smith\" } %}\n    {% set func = p => \"#{p.first} #{p.last}\" %}\n\n    {{ func|invoke(person) }}\n    {# outputs Bob Smith #}\n"
  },
  {
    "path": "doc/filters/join.rst",
    "content": "``join``\n========\n\nThe ``join`` filter returns a string which is the concatenation of the items\nof a sequence:\n\n.. code-block:: twig\n\n    {{ [1, 2, 3]|join }}\n    {# returns 123 #}\n\nThe separator between elements is an empty string per default, but you can\ndefine it with the optional first parameter:\n\n.. code-block:: twig\n\n    {{ [1, 2, 3]|join('|') }}\n    {# outputs 1|2|3 #}\n\nA second parameter can also be provided that will be the separator used between\nthe last two items of the sequence:\n\n.. code-block:: twig\n\n    {{ [1, 2, 3]|join(', ', ' and ') }}\n    {# outputs 1, 2 and 3 #}\n\nArguments\n---------\n\n* ``glue``: The separator\n* ``and``: The separator for the last pair of input items\n"
  },
  {
    "path": "doc/filters/json_encode.rst",
    "content": "``json_encode``\n===============\n\nThe ``json_encode`` filter returns the JSON representation of a value:\n\n.. code-block:: twig\n\n    {{ data|json_encode() }}\n\n.. note::\n\n    Internally, Twig uses the PHP `json_encode`_ function.\n\nArguments\n---------\n\n* ``options``: A bitmask of `json_encode options`_: ``{{\n  data|json_encode(constant('JSON_PRETTY_PRINT')) }}``.\n  Combine constants using :ref:`bitwise operators<template_logic>`:\n  ``{{ data|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_HEX_QUOT')) }}``\n\n.. _`json_encode`: https://www.php.net/json_encode\n.. _`json_encode options`: https://www.php.net/manual/en/json.constants.php\n"
  },
  {
    "path": "doc/filters/keys.rst",
    "content": "``keys``\n========\n\nThe ``keys`` filter returns the keys of a sequence or a mapping. It is useful\nwhen you want to iterate over the keys of a sequence or a mapping:\n\n.. code-block:: twig\n\n    {% for key in ['a', 'b', 'c', 'd']|keys %}\n        {{ key }}\n    {% endfor %}\n    {# outputs: 0 1 2 3 #}\n\n    {% for key in {a: 'a_value', b: 'b_value'}|keys %}\n        {{ key }}\n    {% endfor %}\n    {# outputs: a b #}\n\n.. note::\n\n    Internally, Twig uses the PHP `array_keys`_ function.\n\n.. _`array_keys`: https://www.php.net/array_keys\n"
  },
  {
    "path": "doc/filters/language_name.rst",
    "content": "``language_name``\n=================\n\nThe ``language_name`` filter returns the language name based on its ISO 639-1\ncode, ISO 639-2 code, or other specific localized code:\n\n.. code-block:: twig\n\n    {# German #}\n    {{ 'de'|language_name }}\n\nBy default, the filter uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# allemand #}\n    {{ 'de'|language_name('fr') }}\n\n    {# français canadien #}\n    {{ 'fr_CA'|language_name('fr_FR') }}\n\n.. note::\n\n    The ``language_name`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/last.rst",
    "content": "``last``\n========\n\nThe ``last`` filter returns the last \"element\" of a sequence, a mapping, or\na string:\n\n.. code-block:: twig\n\n    {{ [1, 2, 3, 4]|last }}\n    {# outputs 4 #}\n\n    {{ {a: 1, b: 2, c: 3, d: 4}|last }}\n    {# outputs 4 #}\n\n    {{ '1234'|last }}\n    {# outputs 4 #}\n\n.. note::\n\n    It also works with objects implementing the `Traversable`_ interface.\n\n.. _`Traversable`: https://www.php.net/manual/en/class.traversable.php\n"
  },
  {
    "path": "doc/filters/length.rst",
    "content": "``length``\n==========\n\nThe ``length`` filter returns the number of items of a sequence or mapping, or\nthe length of a string.\n\nFor objects that implement the ``Countable`` interface, ``length`` will use the\nreturn value of the ``count()`` method.\n\nFor objects that implement the ``__toString()`` magic method (and not ``Countable``),\nit will return the length of the string provided by that method.\n\nFor objects that implement the ``Traversable`` interface, ``length`` will use the return value of the ``iterator_count()`` method.\n\nFor strings, `mb_strlen()`_ is used.\n\n.. code-block:: twig\n\n    {% if users|length > 10 %}\n        ...\n    {% endif %}\n\n.. _mb_strlen(): https://www.php.net/manual/function.mb-strlen.php\n"
  },
  {
    "path": "doc/filters/locale_name.rst",
    "content": "``locale_name``\n===============\n\nThe ``locale_name`` filter returns the locale name given its code:\n\n.. code-block:: twig\n\n    {# German #}\n    {{ 'de'|locale_name }}\n\nBy default, the filter uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# allemand #}\n    {{ 'de'|locale_name('fr') }}\n\n    {# français (Canada) #}\n    {{ 'fr_CA'|locale_name('fr_FR') }}\n\n.. note::\n\n    The ``locale_name`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/lower.rst",
    "content": "``lower``\n=========\n\nThe ``lower`` filter converts a value to lowercase:\n\n.. code-block:: twig\n\n    {{ 'WELCOME'|lower }}\n\n    {# outputs 'welcome' #}\n\n.. note::\n\n    Internally, Twig uses the PHP `mb_strtolower`_ function.\n\n.. _`mb_strtolower`: https://www.php.net/manual/fr/function.mb-strtolower.php\n"
  },
  {
    "path": "doc/filters/map.rst",
    "content": "``map``\n=======\n\nThe ``map`` filter applies an arrow function to the elements of a sequence or a\nmapping. The arrow function receives the value of the sequence or mapping:\n\n.. code-block:: twig\n\n    {% set people = [\n        {first: \"Bob\", last: \"Smith\"},\n        {first: \"Alice\", last: \"Dupond\"},\n    ] %}\n\n    {{ people|map(p => \"#{p.first} #{p.last}\")|join(', ') }}\n    {# outputs Bob Smith, Alice Dupond #}\n\nThe arrow function also receives the key as a second argument:\n\n.. code-block:: twig\n\n    {% set people = {\n        \"Bob\": \"Smith\",\n        \"Alice\": \"Dupond\",\n    } %}\n\n    {{ people|map((value, key) => \"#{key} #{value}\")|join(', ') }}\n    {# outputs Bob Smith, Alice Dupond #}\n\nNote that the arrow function has access to the current context.\n\nArguments\n---------\n\n* ``arrow``: The arrow function\n"
  },
  {
    "path": "doc/filters/markdown_to_html.rst",
    "content": "``markdown_to_html``\n====================\n\nThe ``markdown_to_html`` filter converts a block of Markdown to HTML:\n\n.. code-block:: twig\n\n    {% apply markdown_to_html %}\n    Title\n    =====\n\n    Hello!\n    {% endapply %}\n\nNote that you can indent the Markdown content as leading whitespaces will be\nremoved consistently before conversion:\n\n.. code-block:: twig\n\n    {% apply markdown_to_html %}\n        Title\n        =====\n\n        Hello!\n    {% endapply %}\n\nYou can also use the filter on an included file or a variable:\n\n.. code-block:: twig\n\n    {{ include('some_template.markdown.twig')|markdown_to_html }}\n\n    {{ changelog|markdown_to_html }}\n\n.. note::\n\n    The ``markdown_to_html`` filter is part of the ``MarkdownExtension`` which\n    is not installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/markdown-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Markdown\\MarkdownExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new MarkdownExtension());\n\n    If you are not using Symfony, you must also register the extension runtime::\n\n        use Twig\\Extra\\Markdown\\DefaultMarkdown;\n        use Twig\\Extra\\Markdown\\MarkdownRuntime;\n        use Twig\\RuntimeLoader\\RuntimeLoaderInterface;\n\n        $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {\n            public function load($class) {\n                if (MarkdownRuntime::class === $class) {\n                    return new MarkdownRuntime(new DefaultMarkdown());\n                }\n            }\n        });\n\n    Afterwards you need to install a markdown library of your choice. Some of them are\n    mentioned in the ``require-dev`` section of the ``twig/markdown-extra`` package.\n\n.. note::\n\n    If using Symfony (full-stack), ``twig/extra-bundle`` with ``league/commonmark`` as\n    your Markdown library you can configure CommonMark extensions. Register the desired\n    extension(s) as a service, then tag the service with\n    ``twig.markdown.league_extension``.\n"
  },
  {
    "path": "doc/filters/merge.rst",
    "content": "``merge``\n=========\n\nThe ``merge`` filter merges sequences and mappings:\n\nFor sequences, new values are added at the end of the existing ones:\n\n.. code-block:: twig\n\n    {% set values = [1, 2] %}\n\n    {% set values = values|merge(['apple', 'orange']) %}\n\n    {# values now contains [1, 2, 'apple', 'orange'] #}\n\nFor mappings, the merging process occurs on the keys; if the key does not\nalready exist, it is added but if the key already exists, its value is\noverridden:\n\n.. code-block:: twig\n\n    {% set items = {'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown'} %}\n\n    {% set items = items|merge({ 'peugeot': 'car', 'renault': 'car' }) %}\n\n    {# items now contains {'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car'} #}\n\n.. tip::\n\n    If you want to ensure that some values are defined in a mapping (by given\n    default values), reverse the two elements in the call:\n\n    .. code-block:: twig\n\n        {% set items = {'apple': 'fruit', 'orange': 'fruit'} %}\n\n        {% set items = {'apple': 'unknown'}|merge(items) %}\n\n        {# items now contains {'apple': 'fruit', 'orange': 'fruit'} #}\n\n.. note::\n\n    Internally, Twig uses the PHP `array_merge`_ function. It supports\n    Traversable objects by transforming those to arrays.\n\n.. _`array_merge`: https://www.php.net/array_merge\n"
  },
  {
    "path": "doc/filters/nl2br.rst",
    "content": "``nl2br``\n=========\n\nThe ``nl2br`` filter inserts HTML line breaks before all newlines in a string:\n\n.. code-block:: html+twig\n\n    {{ \"I like Twig.\\nYou will like it too.\"|nl2br }}\n    {# outputs\n\n        I like Twig.<br />\n        You will like it too.\n\n    #}\n\n.. note::\n\n    The ``nl2br`` filter pre-escapes the input before applying the\n    transformation.\n"
  },
  {
    "path": "doc/filters/number_format.rst",
    "content": "``number_format``\n=================\n\nThe ``number_format`` filter formats numbers.  It is a wrapper around PHP's\n`number_format`_ function:\n\n.. code-block:: twig\n\n    {{ 200.35|number_format }}\n\nYou can control the number of decimal places, decimal point, and thousands\nseparator using the additional arguments:\n\n.. code-block:: twig\n\n    {{ 9800.333|number_format(2, '.', ',') }}\n\nTo format negative numbers, wrap the previous statement with parentheses (note\nthat as of Twig 3.21, not using parentheses is deprecated as the filter\noperator will change precedence in Twig 4.0):\n\n.. code-block:: twig\n\n    {{ -9800.333|number_format(2, '.', ',') }} {# outputs : -9 #}\n    {{ (-9800.333)|number_format(2, '.', ',') }} {# outputs : -9,800.33 #}\n\nTo format math calculation, wrap the previous statement with parentheses\n(needed because of Twig's :ref:`precedence of operators -<twig-expressions>`):\n\n.. code-block:: twig\n\n    {{ 1 + 0.2|number_format(2) }} {# outputs : 1.2 #}\n    {{ (1 + 0.2)|number_format(2) }} {# outputs : 1.20 #}\n\nIf no formatting options are provided then Twig will use the default formatting\noptions of:\n\n* 0 decimal places.\n* ``.`` as the decimal point.\n* ``,`` as the thousands separator.\n\nThese defaults can be changed through the core extension::\n\n    $twig = new \\Twig\\Environment($loader);\n    $twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setNumberFormat(3, '.', ',');\n\nThe defaults set for ``number_format`` can be over-ridden upon each call using the\nadditional parameters.\n\nArguments\n---------\n\n* ``decimal``:       The number of decimal points to display\n* ``decimal_point``: The character(s) to use for the decimal point\n* ``thousand_sep``:   The character(s) to use for the thousands separator\n\n.. _`number_format`: https://www.php.net/number_format\n"
  },
  {
    "path": "doc/filters/plural.rst",
    "content": "``plural``\n==========\n\n.. versionadded:: 3.11\n\n    The ``plural`` filter was added in Twig 3.11.\n\nThe ``plural`` filter transforms a given noun in its singular form into its\nplural version:\n\n.. code-block:: twig\n\n    {# English (en) rules are used by default #}\n    {{ 'animal'|plural() }}\n    animals\n\n    {{ 'animal'|plural('fr') }}\n    animaux\n\n.. note::\n\n    The ``plural`` filter is part of the ``StringExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/string-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\String\\StringExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new StringExtension());\n\nArguments\n---------\n\n* ``locale``: The locale of the original string (limited to languages supported by the from Symfony `inflector`_, part of the String component)\n* ``all``: Whether to return all possible plurals as an array, default is ``false``\n\n.. note::\n\n    Internally, Twig uses the `pluralize`_ method from the Symfony String component.\n\n.. _`inflector`: https://symfony.com/doc/current/components/string.html#inflector\n.. _`pluralize`: https://symfony.com/doc/current/components/string.html#inflector\n"
  },
  {
    "path": "doc/filters/raw.rst",
    "content": "``raw``\n=======\n\nThe ``raw`` filter marks the value as being \"safe\", which means that in an\nenvironment with automatic escaping enabled this variable will not be escaped\nif ``raw`` is the last filter applied to it:\n\n.. code-block:: twig\n\n    {% autoescape %}\n        {{ var|raw }} {# var won't be escaped #}\n    {% endautoescape %}\n"
  },
  {
    "path": "doc/filters/reduce.rst",
    "content": "``reduce``\n==========\n\nThe ``reduce`` filter iteratively reduces a sequence or a mapping to a single\nvalue using an arrow function, so as to reduce it to a single value. The arrow\nfunction receives the return value of the previous iteration and the current\nvalue and key of the sequence or mapping:\n\n.. code-block:: twig\n\n    {% set numbers = [1, 2, 3] %}\n\n    {{ numbers|reduce((carry, value, key) => carry + value * key) }}\n    {# output 8 #}\n\nThe ``reduce`` filter takes an ``initial`` value as a second argument:\n\n.. code-block:: twig\n\n    {{ numbers|reduce((carry, value, key) => carry + value * key, 10) }}\n    {# output 18 #}\n\nNote that the arrow function has access to the current context.\n\nArguments\n---------\n\n* ``arrow``: The arrow function\n* ``initial``: The initial value\n"
  },
  {
    "path": "doc/filters/replace.rst",
    "content": "``replace``\n===========\n\nThe ``replace`` filter replaces placeholders in a string (the placeholder\nformat is free-form):\n\n.. code-block:: twig\n\n    {% set fruit = 'apples' %}\n\n    {{ \"I like %this% and %that%.\"|replace({'%this%': fruit, '%that%': \"oranges\"}) }}\n    {# if the \"fruit\" variable is set to \"apples\", #}\n    {# it outputs \"I like apples and oranges\" #}\n\n    {# using % as a delimiter is purely conventional and optional #}\n    {{ \"I like this and --that--.\"|replace({'this': fruit, '--that--': \"oranges\"}) }}\n    {# outputs \"I like apples and oranges\" #}\n\nArguments\n---------\n\n* ``from``: The placeholder values as a mapping\n\n.. seealso::\n\n    :doc:`format<format>`\n"
  },
  {
    "path": "doc/filters/reverse.rst",
    "content": "``reverse``\n===========\n\nThe ``reverse`` filter reverses a sequence, a mapping, or a string:\n\n.. code-block:: twig\n\n    {% for user in users|reverse %}\n        ...\n    {% endfor %}\n\n    {{ '1234'|reverse }}\n\n    {# outputs 4321 #}\n\n.. tip::\n\n    For sequences and mappings, numeric keys are not preserved. To reverse\n    them as well, pass ``true`` as an argument to the ``reverse`` filter:\n\n    .. code-block:: twig\n\n        {% for key, value in {1: \"a\", 2: \"b\", 3: \"c\"}|reverse %}\n            {{ key }}: {{ value }}\n        {%- endfor %}\n\n        {# output: 0: c    1: b    2: a #}\n\n        {% for key, value in {1: \"a\", 2: \"b\", 3: \"c\"}|reverse(true) %}\n            {{ key }}: {{ value }}\n        {%- endfor %}\n\n        {# output: 3: c    2: b    1: a #}\n\n.. note::\n\n    It also works with objects implementing the `Traversable`_ interface.\n\nArguments\n---------\n\n* ``preserve_keys``: Preserve keys when reversing a mapping or a sequence.\n\n.. _`Traversable`: https://www.php.net/Traversable\n"
  },
  {
    "path": "doc/filters/round.rst",
    "content": "``round``\n=========\n\nThe ``round`` filter rounds a number to a given precision:\n\n.. code-block:: twig\n\n    {{ 42.55|round }}\n    {# outputs 43 #}\n\n    {{ 42.55|round(1, 'floor') }}\n    {# outputs 42.5 #}\n\nThe ``round`` filter takes two optional arguments; the first one specifies the\nprecision (default is ``0``) and the second the rounding method (default is\n``common``):\n\n* ``common`` rounds either up or down (rounds the value up to precision decimal\n  places away from zero, when it is half way there -- making 1.5 into 2 and\n  -1.5 into -2);\n\n* ``ceil`` always rounds up;\n\n* ``floor`` always rounds down.\n\n.. note::\n\n    The ``//`` operator is equivalent to ``|round(0, 'floor')``.\n\nArguments\n---------\n\n* ``precision``: The rounding precision\n* ``method``: The rounding method\n"
  },
  {
    "path": "doc/filters/shuffle.rst",
    "content": "``shuffle``\n===========\n\n.. versionadded:: 3.11\n\n    The ``shuffle`` filter was added in Twig 3.11.\n\nThe ``shuffle`` filter shuffles a sequence, a mapping, or a string:\n\n.. code-block:: twig\n\n    {% for user in users|shuffle %}\n        ...\n    {% endfor %}\n\n.. caution::\n\n    The shuffled array does not preserve keys. So if the input had not\n    sequential keys but indexed keys (using the user id for instance), it is\n    not the case anymore after shuffling it.\n\nExample 1:\n\n.. code-block:: html+twig\n\n    {% set items = [\n        'a',\n        'b',\n        'c',\n    ] %}\n\n    <ul>\n        {% for item in items|shuffle %}\n            <li>{{ item }}</li>\n        {% endfor %}\n    </ul>\n\nThe above example will be rendered as:\n\n.. code-block:: html\n\n    <ul>\n        <li>a</li>\n        <li>c</li>\n        <li>b</li>\n    </ul>\n\nThe result can also be: \"a, b, c\" or \"b, a, c\" or \"b, c, a\" or \"c, a, b\" or\n\"c, b, a\".\n\nExample 2:\n\n.. code-block:: html+twig\n\n    {% set items = {\n        'a': 'd',\n        'b': 'e',\n        'c': 'f',\n    } %}\n\n    <ul>\n        {% for index, item in items|shuffle %}\n            <li>{{ index }} - {{ item }}</li>\n        {% endfor %}\n    </ul>\n\nThe above example will be rendered as:\n\n.. code-block:: html\n\n    <ul>\n        <li>0 - d</li>\n        <li>1 - f</li>\n        <li>2 - e</li>\n    </ul>\n\nThe result can also be: \"d, e, f\" or \"e, d, f\" or \"e, f, d\" or \"f, d, e\" or\n\"f, e, d\".\n\n.. code-block:: html+twig\n\n    {% set string = 'ghi' %}\n\n    <p>{{ string|shuffle }}</p>\n\nThe above example will be rendered as:\n\n.. code-block:: html\n\n    <p>gih</p>\n\nThe result can also be: \"ghi\" or \"hgi\" or \"hig\" or \"igh\" or \"ihg\".\n"
  },
  {
    "path": "doc/filters/singular.rst",
    "content": "``singular``\n============\n\n.. versionadded:: 3.11\n\n    The ``singular`` filter was added in Twig 3.11.\n\nThe ``singular`` filter transforms a given noun in its plural form into its\nsingular version:\n\n.. code-block:: twig\n\n    {# English (en) rules are used by default #}\n    {{ 'partitions'|singular() }}\n    partition\n\n    {{ 'partitions'|singular('fr') }}\n    partition\n\n.. note::\n\n    The ``singular`` filter is part of the ``StringExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/string-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\String\\StringExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new StringExtension());\n\nArguments\n---------\n\n* ``locale``: The locale of the original string (limited to languages supported by the from Symfony `inflector`_, part of the String component)\n* ``all``: Whether to return all possible singulars as an array, default is ``false``\n\n.. note::\n\n    Internally, Twig uses the `singularize`_ method from the Symfony String component.\n\n.. _`inflector`: https://symfony.com/doc/current/components/string.html#inflector\n.. _`singularize`: https://symfony.com/doc/current/components/string.html#inflector\n"
  },
  {
    "path": "doc/filters/slice.rst",
    "content": "``slice``\n===========\n\nThe ``slice`` filter extracts a slice of a sequence, a mapping, or a string:\n\n.. code-block:: twig\n\n    {% for i in [1, 2, 3, 4, 5]|slice(1, 2) %}\n        {# will iterate over 2 and 3 #}\n    {% endfor %}\n\n    {{ '12345'|slice(1, 2) }}\n\n    {# outputs 23 #}\n\nYou can use any valid expression for both the start and the length:\n\n.. code-block:: twig\n\n    {% for i in [1, 2, 3, 4, 5]|slice(start, length) %}\n        {# ... #}\n    {% endfor %}\n\nAs syntactic sugar, you can also use the ``[]`` operator:\n\n.. code-block:: twig\n\n    {% for i in [1, 2, 3, 4, 5][start:length] %}\n        {# ... #}\n    {% endfor %}\n\n    {{ '12345'[1:2] }} {# will display \"23\" #}\n\n    {# you can omit the first argument -- which is the same as 0 #}\n    {{ '12345'[:2] }} {# will display \"12\" #}\n\n    {# you can omit the last argument -- which will select everything till the end #}\n    {{ '12345'[2:] }} {# will display \"345\" #}\n\n    {# you can use a negative value -- for example to remove characters at the end #}\n    {{ '12345'[:-2] }} {# will display \"123\" #}\n\nThe ``slice`` filter works as the `array_slice`_ PHP function for arrays and\n`mb_substr`_ for strings with a fallback to `substr`_.\n\nIf the start is non-negative, the sequence will start at that start in the\nvariable. If start is negative, the sequence will start that far from the end\nof the variable.\n\nIf length is given and is positive, then the sequence will have up to that\nmany elements in it. If the variable is shorter than the length, then only the\navailable variable elements will be present. If length is given and is\nnegative then the sequence will stop that many elements from the end of the\nvariable. If it is omitted, then the sequence will have everything from offset\nup until the end of the variable.\n\nThe argument ``preserve_keys`` is used to reset the index during the loop.\n\n.. code-block:: twig\n\n    {% for key, value in [1, 2, 3, 4, 5]|slice(1, 2, true) %}\n        {{ key }} - {{ value }}\n    {% endfor %}\n\n    {# output\n        1 - 2\n        2 - 3\n    #}\n\n    {% for key, value in [1, 2, 3, 4, 5]|slice(1, 2) %}\n        {{ key }} - {{ value }}\n    {% endfor %}\n\n    {# output\n        0 - 2\n        1 - 3\n    #}\n\n.. note::\n\n    It also works with objects implementing the `Traversable`_ interface.\n\nArguments\n---------\n\n* ``start``:         The start of the slice\n* ``length``:        The size of the slice\n* ``preserve_keys``: Whether to preserve key or not (when the input is an array), by default the value is ``false``.\n\n.. _`Traversable`: https://www.php.net/manual/en/class.traversable.php\n.. _`array_slice`: https://www.php.net/array_slice\n.. _`mb_substr`:   https://www.php.net/mb-substr\n.. _`substr`:      https://www.php.net/substr\n"
  },
  {
    "path": "doc/filters/slug.rst",
    "content": "``slug``\n========\n\nThe ``slug`` filter transforms a given string into another string that\nonly includes safe ASCII characters.\n\nHere is an example:\n\n.. code-block:: twig\n\n    {{ 'Wôrķšƥáçè ~~sèťtïñğš~~'|slug }}\n    Workspace-settings\n\nThe default separator between words is a dash (``-``), but you can\ndefine a separator of your choice by passing it as an argument:\n\n.. code-block:: twig\n\n    {{ 'Wôrķšƥáçè ~~sèťtïñğš~~'|slug('/') }}\n    Workspace/settings\n\nThe slugger automatically detects the language of the original\nstring, but you can also specify it explicitly using the second\nargument:\n\n.. code-block:: twig\n\n    {{ '...'|slug('-', 'ko') }}\n\nThe ``slug`` filter uses the method by the same name in Symfony's\n`AsciiSlugger <https://symfony.com/doc/current/components/string.html#slugger>`_.\n\n.. note::\n\n    The ``slug`` filter is part of the ``StringExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/string-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\String\\StringExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new StringExtension());\n\nArguments\n---------\n\n* ``separator``: The separator that is used to join words (defaults to ``-``)\n* ``locale``: The locale code of the original string as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/sort.rst",
    "content": "``sort``\n========\n\nThe ``sort`` filter sorts sequences and mappings:\n\n.. code-block:: twig\n\n    {% for user in users|sort %}\n        ...\n    {% endfor %}\n\n.. note::\n\n    Internally, Twig uses the PHP `asort`_ function to maintain index\n    association. It supports Traversable objects by transforming\n    those to arrays.\n\nYou can pass an arrow function to configure the sorting:\n\n.. code-block:: html+twig\n\n    {% set fruits = [\n        {name: 'Apples', quantity: 5},\n        {name: 'Oranges', quantity: 2},\n        {name: 'Grapes', quantity: 4},\n    ] %}\n\n    {% for fruit in fruits|sort((a, b) => a.quantity <=> b.quantity)|column('name') %}\n        {{ fruit }}\n    {% endfor %}\n\n    {# output in this order: Oranges, Grapes, Apples #}\n\nNote the usage of the `spaceship`_ operator to simplify the comparison.\n\nArguments\n---------\n\n* ``arrow``: An arrow function\n\n.. _`asort`: https://www.php.net/asort\n.. _`spaceship`: https://www.php.net/manual/en/language.operators.comparison.php\n"
  },
  {
    "path": "doc/filters/spaceless.rst",
    "content": "``spaceless``\n=============\n\n.. warning::\n\n    The ``spaceless`` filter is deprecated as of Twig 3.12. While not a full\n    replacement, you can check the :ref:`whitespace control features <templates-whitespace-control>`.\n\nUse the ``spaceless`` filter to remove whitespace *between HTML tags*, not\nwhitespace within HTML tags or whitespace in plain text:\n\n.. code-block:: html+twig\n\n    {{\n        \"<div>\n            <strong>foo</strong>\n        </div>\n        \"|spaceless }}\n\n    {# output will be <div><strong>foo</strong></div> #}\n\nYou can combine ``spaceless`` with the ``apply`` tag to apply the transformation\non large amounts of HTML:\n\n.. code-block:: html+twig\n\n    {% apply spaceless %}\n        <div>\n            <strong>foo</strong>\n        </div>\n    {% endapply %}\n\n    {# output will be <div><strong>foo</strong></div> #}\n\nThis tag is not meant to \"optimize\" the size of the generated HTML content but\nmerely to avoid extra whitespace between HTML tags to avoid browser rendering\nquirks under some circumstances.\n\n.. caution::\n\n    As the filter uses a regular expression behind the scenes, its performance\n    is directly related to the text size you are working on (remember that\n    filters are executed at runtime).\n\n.. tip::\n\n    If you want to optimize the size of the generated HTML content, gzip\n    compress the output instead.\n\n.. tip::\n\n    If you want to create a tag that actually removes all extra whitespace in\n    an HTML string, be warned that this is not as easy as it seems to be\n    (think of ``textarea`` or ``pre`` tags for instance). Using a third-party\n    library like Tidy is probably a better idea.\n\n.. tip::\n\n    For more information on whitespace control, read the\n    :ref:`dedicated section <templates-whitespace-control>` of the documentation and learn how\n    you can also use the whitespace control modifier on your tags.\n"
  },
  {
    "path": "doc/filters/split.rst",
    "content": "``split``\n=========\n\nThe ``split`` filter splits a string by the given delimiter and returns a list\nof strings:\n\n.. code-block:: twig\n\n    {% set items = \"one,two,three\"|split(',') %}\n    {# items contains ['one', 'two', 'three'] #}\n\nYou can also pass a ``limit`` argument:\n\n* If ``limit`` is positive, the returned sequence will contain a maximum of\n  limit elements with the last element containing the rest of string;\n\n* If ``limit`` is negative, all components except the last -limit are\n  returned;\n\n* If ``limit`` is zero, then this is treated as 1.\n\n.. code-block:: twig\n\n    {% set items = \"one,two,three,four,five\"|split(',', 3) %}\n    {# items contains ['one', 'two', 'three,four,five'] #}\n\nIf the ``delimiter`` is an empty string, then value will be split by equal\nchunks. Length is set by the ``limit`` argument (one character by default).\n\n.. code-block:: twig\n\n    {% set items = \"123\"|split('') %}\n    {# items contains ['1', '2', '3'] #}\n\n    {% set items = \"aabbcc\"|split('', 2) %}\n    {# items contains ['aa', 'bb', 'cc'] #}\n\n.. note::\n\n    Internally, Twig uses the PHP `explode`_ or `str_split`_ (if delimiter is\n    empty) functions for string splitting.\n\nArguments\n---------\n\n* ``delimiter``: The delimiter\n* ``limit``:     The limit argument\n\n.. _`explode`:   https://www.php.net/explode\n.. _`str_split`: https://www.php.net/str_split\n"
  },
  {
    "path": "doc/filters/striptags.rst",
    "content": "``striptags``\n=============\n\nThe ``striptags`` filter strips SGML/XML tags and replaces adjacent whitespace characters\nby one space:\n\n.. code-block:: twig\n\n    {{ some_html|striptags }}\n\nYou can also provide tags which should not be stripped:\n\n.. code-block:: html+twig\n\n    {{ some_html|striptags('<br><p>') }}\n\nIn this example, the ``<br/>``, ``<br>``, ``<p>``, and ``</p>`` tags won't be\nremoved from the string.\n\n.. note::\n\n    Internally, Twig uses the PHP `strip_tags`_ function.\n\nArguments\n---------\n\n* ``allowable_tags``: Tags which should not be stripped\n\n.. _`strip_tags`: https://www.php.net/strip_tags\n"
  },
  {
    "path": "doc/filters/timezone_name.rst",
    "content": "``timezone_name``\n=================\n\nThe ``timezone_name`` filter returns the timezone name given its ISO 8601 timezone identifier:\n\n.. code-block:: twig\n\n    {# Central European Time (Paris) #}\n    {{ 'Europe/Paris'|timezone_name }}\n\n    {# Pacific Time (Los Angeles) #}\n    {{ 'America/Los_Angeles'|timezone_name }}\n\nBy default, the filter uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# heure du Pacifique nord-américain (Los Angeles) #}\n    {{ 'America/Los_Angeles'|timezone_name('fr') }}\n\n.. note::\n\n    The ``timezone_name`` filter is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/filters/title.rst",
    "content": "``title``\n=========\n\nThe ``title`` filter returns a titlecased version of the value. Words will\nstart with uppercase letters, all remaining characters are lowercase:\n\n.. code-block:: twig\n\n    {{ 'my first car'|title }}\n\n    {# outputs 'My First Car' #}\n"
  },
  {
    "path": "doc/filters/trim.rst",
    "content": "``trim``\n========\n\nThe ``trim`` filter strips whitespace (or other characters) from the beginning\nand end of a string:\n\n.. code-block:: twig\n\n    {{ '  I like Twig.  '|trim }}\n\n    {# outputs 'I like Twig.' #}\n\n    {{ '  I like Twig.'|trim('.') }}\n\n    {# outputs '  I like Twig' #}\n\n    {{ '  I like Twig.  '|trim(side: 'left') }}\n\n    {# outputs 'I like Twig.  ' #}\n\n    {{ '  I like Twig.  '|trim(' ', 'right') }}\n\n    {# outputs '  I like Twig.' #}\n\n.. note::\n\n    Internally, Twig uses the PHP `trim`_, `ltrim`_, and `rtrim`_ functions.\n\nArguments\n---------\n\n* ``character_mask``: The characters to strip\n\n* ``side``: The default is to strip from the left and the right (``both``)\n  sides, but ``left`` and ``right`` will strip from either the left side or\n  right side only\n\n.. _`trim`: https://www.php.net/trim\n.. _`ltrim`: https://www.php.net/ltrim\n.. _`rtrim`: https://www.php.net/rtrim\n"
  },
  {
    "path": "doc/filters/u.rst",
    "content": "``u``\n=====\n\nThe ``u`` filter wraps a text in a Unicode object (a `Symfony UnicodeString\ninstance <https://symfony.com/doc/current/components/string.html>`_) that\nexposes methods to \"manipulate\" the string.\n\nLet's see some common use cases.\n\nWrapping a text to a given number of characters:\n\n.. code-block:: twig\n\n    {{ 'Symfony String + Twig = <3'|u.wordwrap(5) }}\n    Symfony\n    String\n    +\n    Twig\n    = <3\n\nHere, ``u`` is the filter and ``wordwrap(5)`` is a method called on the result\nof the filter; it's equivalent to ``(text|u).wordwrap(5)``.\n\nTruncating a string:\n\n.. code-block:: twig\n\n    {{ 'Lorem ipsum'|u.truncate(8) }}\n    Lorem ip\n\n    {{ 'Lorem ipsum'|u.truncate(8, '...') }}\n    Lorem...\n\nBy default, ``truncate`` cuts text at the exact length. Pass ``false`` as the third argument to preserve whole words:\n\n.. code-block:: twig\n\n    {{ 'Lorem ipsum dolor'|u.truncate(10, '...', false) }}\n    Lorem ipsum...\n\nConverting a string to *snake* case or *camelCase*:\n\n.. code-block:: twig\n\n    {{ 'SymfonyStringWithTwig'|u.snake }}\n    symfony_string_with_twig\n\n    {{ 'symfony_string with twig'|u.camel.title }}\n    SymfonyStringWithTwig\n\nYou can also chain methods:\n\n.. code-block:: twig\n\n    {{ 'Symfony String + Twig = <3'|u.wordwrap(5).upper }}\n    SYMFONY\n    STRING\n    +\n    TWIG\n    = <3\n\n.. note::\n\n    The ``u`` filter is part of the ``StringExtension`` which is not installed\n    by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/string-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\String\\StringExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new StringExtension());\n"
  },
  {
    "path": "doc/filters/upper.rst",
    "content": "``upper``\n=========\n\nThe ``upper`` filter converts a value to uppercase:\n\n.. code-block:: twig\n\n    {{ 'welcome'|upper }}\n\n    {# outputs 'WELCOME' #}\n\n.. note::\n\n    Internally, Twig uses the PHP `mb_strtoupper`_ function.\n\n.. _`mb_strtoupper`: https://www.php.net/mb_strtoupper\n"
  },
  {
    "path": "doc/filters/url_encode.rst",
    "content": "``url_encode``\n==============\n\nThe ``url_encode`` filter percent encodes a given string as URL segment or a\nmapping as query string:\n\n.. code-block:: twig\n\n    {{ \"path-seg*ment\"|url_encode }}\n    {# outputs \"path-seg%2Ament\" #}\n\n    {{ \"string with spaces\"|url_encode }}\n    {# outputs \"string%20with%20spaces\" #}\n\n    {{ {'name': 'Fabien', 'city': 'Paris'}|url_encode }}\n    {# outputs \"name=Fabien&city=Paris\" #}\n\n.. note::\n\n    Internally, Twig uses the PHP `rawurlencode`_ or the `http_build_query`_ function.\n\n.. _`rawurlencode`: https://www.php.net/rawurlencode\n.. _`http_build_query`: https://www.php.net/http_build_query\n"
  },
  {
    "path": "doc/functions/attribute.rst",
    "content": "``attribute``\n=============\n\n.. warning::\n\n    The ``attribute`` function is deprecated as of Twig 3.15. Use the\n    :ref:`dot operator <dot_operator>` that now accepts any expression\n    when wrapped with parenthesis.\n\n    Note that this function will still be available in Twig 4.0 to allow a\n    smoother upgrade path.\n\nThe ``attribute`` function lets you access an attribute, method, or property of\nan object or array when the name of that attribute, method, or property is stored\nin a variable or dynamically generated with an expression:\n\n.. code-block:: twig\n\n    {# method_name is a variable that stores the method to call #}\n    {{ attribute(object, method_name) }}\n\n    {# you can also pass arguments when calling a method #}\n    {{ attribute(object, method_name, arguments) }}\n\n    {# the method/property name can be the result of evaluating an expression #}\n    {{ attribute(object, 'some_property_' ~ user.type) }}\n\n    {# in addition to objects, this function works with plain arrays as well #}\n    {{ attribute(array, item_name) }}\n\nIn addition, the ``defined`` test can check for the existence of a dynamic\nattribute:\n\n.. code-block:: twig\n\n    {{ attribute(object, method) is defined ? 'Method exists' : 'Method does not exist' }}\n\n.. note::\n\n    The resolution algorithm is the same as the one used for the ``.``\n    operator.\n\nArguments\n---------\n\n* ``variable``: The variable\n* ``attribute``: The attribute name\n* ``arguments``: An array of arguments to pass to the call\n"
  },
  {
    "path": "doc/functions/block.rst",
    "content": "``block``\n=========\n\nWhen a template uses inheritance and if you want to render a block multiple\ntimes, use the ``block`` function:\n\n.. code-block:: html+twig\n\n    <title>{% block title %}{% endblock %}</title>\n\n    <h1>{{ block('title') }}</h1>\n\n    {% block body %}{% endblock %}\n\nThe ``block`` function can also be used to display one block from another\ntemplate:\n\n.. code-block:: twig\n\n    {{ block(\"title\", \"common_blocks.html.twig\") }}\n\nUse the ``defined`` test to check if a block exists in the context of the\ncurrent template:\n\n.. code-block:: twig\n\n    {% if block(\"footer\") is defined %}\n        ...\n    {% endif %}\n\n    {% if block(\"footer\", \"common_blocks.html.twig\") is defined %}\n        ...\n    {% endif %}\n\nArguments\n---------\n\n* ``name``: The block name\n* ``template``: The template where to look for the block\n\n.. seealso::\n\n    :doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>`\n"
  },
  {
    "path": "doc/functions/constant.rst",
    "content": "``constant``\n============\n\n``constant`` returns the constant value for a given string:\n\n.. code-block:: twig\n\n    {{ some_date|date(constant('DATE_W3C')) }}\n    {{ constant('Namespace\\\\Classname::CONSTANT_NAME') }}\n\nYou can read constants from object instances as well:\n\n.. code-block:: twig\n\n    {{ constant('RSS', date) }}\n\nRetrieve the fully qualified class name of an object:\n\n.. code-block:: twig\n\n    {{ constant('class', date) }}\n\nUse the ``defined`` test to check if a constant is defined:\n\n.. code-block:: twig\n\n    {% if constant('SOME_CONST') is defined %}\n        ...\n    {% endif %}\n"
  },
  {
    "path": "doc/functions/country_names.rst",
    "content": "``country_names``\n=================\n\n.. versionadded:: 3.5\n\n    The ``country_names`` function was added in Twig 3.5.\n\nThe ``country_names`` function returns the names of the countries:\n\n.. code-block:: twig\n\n    {# Afghanistan, Åland Islands, ... #}\n    {{ country_names()|join(', ') }}\n    \nBy default, the function uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# Afghanistan, Afrique du Sud, ... #}\n    {{ country_names('fr')|join(', ') }}\n\n.. note::\n\n    The ``country_names`` function is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/functions/country_timezones.rst",
    "content": "``country_timezones``\n=====================\n\nThe ``country_timezones`` function returns the names of the timezones associated\nwith a given country code (ISO-3166):\n\n.. code-block:: twig\n\n    {# Europe/Paris #}\n    {{ country_timezones('FR')|join(', ') }}\n\nIf the specified country is unknown, it will return an empty array.\n\n.. note::\n\n    The ``country_timezones`` function is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``country``: The country code"
  },
  {
    "path": "doc/functions/currency_names.rst",
    "content": "``currency_names``\n==================\n\n.. versionadded:: 3.5\n\n    The ``currency_names`` function was added in Twig 3.5.\n\nThe ``currency_names`` function returns the names of the currencies:\n\n.. code-block:: twig\n\n    {# Afghan Afghani, Afghan Afghani (1927–2002), ... #}\n    {{ currency_names()|join(', ') }}\n    \nBy default, the function uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# afghani (1927–2002), afghani afghan, ... #}\n    {{ currency_names('fr')|join(', ') }}\n\n.. note::\n\n    The ``currency_names`` function is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/functions/cycle.rst",
    "content": "``cycle``\n=========\n\nThe ``cycle`` function cycles on a sequence:\n\n.. code-block:: twig\n\n    {% set start_year = date() | date('Y') %}\n    {% set end_year = start_year + 5 %}\n\n    {% for year in start_year..end_year %}\n        {{ cycle(['odd', 'even'], loop.index0) }}\n    {% endfor %}\n    \n    {# outputs\n\n        odd\n        even\n        odd\n        even\n        odd\n        even\n        \n    #}\n\nThe ``cycle`` function takes two arguments: the ``sequence`` to cycle through and the ``position`` in the sequence.\n\nThe ``sequence`` must be non-empty and can contain any number of values:\n\n.. code-block:: twig\n\n    {% set fruits = ['apple', 'orange', 'citrus'] %}\n\n    {% for i in 0..10 %}\n        {{ cycle(fruits, i) }}\n    {% endfor %}\n    \n    {# outputs\n    \n        apple\n        orange\n        citrus\n        apple\n        orange\n        citrus\n        apple\n        orange\n        citrus\n        apple\n        orange\n    \n    #}\n\nArguments\n---------\n\n* ``values``: The sequence to cycle on\n* ``position``: The position in the sequence\n"
  },
  {
    "path": "doc/functions/date.rst",
    "content": "``date``\n========\n\nConverts an argument to a date to allow date comparison:\n\n.. code-block:: html+twig\n\n    {% if date(user.created_at) < date('-2days') %}\n        {# do something #}\n    {% endif %}\n\nThe argument must be in one of PHP's supported `date and time formats`_.\n\nYou can pass a timezone as the second argument:\n\n.. code-block:: html+twig\n\n    {% if date(user.created_at) < date('-2days', 'Europe/Paris') %}\n        {# do something #}\n    {% endif %}\n\nIf no argument is passed, the function returns the current date:\n\n.. code-block:: html+twig\n\n    {% if date(user.created_at) < date() %}\n        {# always! #}\n    {% endif %}\n\n.. note::\n\n    You can set the default timezone globally by calling ``setTimezone()`` on\n    the ``core`` extension instance::\n\n        $twig = new \\Twig\\Environment($loader);\n        $twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setTimezone('Europe/Paris');\n\nArguments\n---------\n\n* ``date``:     The date\n* ``timezone``: The timezone\n\n.. _`date and time formats`: https://www.php.net/manual/en/datetime.formats.php\n"
  },
  {
    "path": "doc/functions/dump.rst",
    "content": "``dump``\n========\n\nThe ``dump`` function dumps information about a template variable. This is\nmostly useful to debug a template that does not behave as expected by\nintrospecting its variables:\n\n.. code-block:: twig\n\n    {{ dump(user) }}\n\n.. note::\n\n    The ``dump`` function is not available by default. You must add the\n    ``\\Twig\\Extension\\DebugExtension`` extension explicitly when creating your Twig\n    environment::\n\n        $twig = new \\Twig\\Environment($loader, [\n            'debug' => true,\n            // ...\n        ]);\n        $twig->addExtension(new \\Twig\\Extension\\DebugExtension());\n\n    Even when enabled, the ``dump`` function won't display anything if the\n    ``debug`` option on the environment is not enabled (to avoid leaking debug\n    information on a production server).\n\nIn an HTML context, wrap the output with a ``pre`` tag to make it easier to\nread:\n\n.. code-block:: html+twig\n\n    <pre>\n        {{ dump(user) }}\n    </pre>\n\n.. tip::\n\n    Using a ``pre`` tag is not needed when `XDebug`_ is enabled and\n    ``html_errors`` is ``on``; as a bonus, the output is also nicer with\n    XDebug enabled.\n\nYou can debug several variables by passing them as additional arguments:\n\n.. code-block:: twig\n\n    {{ dump(user, categories) }}\n\nIf you don't pass any value, all variables from the current context are\ndumped:\n\n.. code-block:: twig\n\n    {{ dump() }}\n\n.. note::\n\n    Internally, Twig uses the PHP `var_dump`_ function.\n\nArguments\n---------\n\n* ``context``: The context to dump\n\n.. _`XDebug`:   https://xdebug.org/docs/display\n.. _`var_dump`: https://www.php.net/var_dump\n"
  },
  {
    "path": "doc/functions/enum.rst",
    "content": "``enum``\n========\n\n.. versionadded:: 3.15\n\n    The ``enum`` function was added in Twig 3.15.\n\n``enum`` gives access to enums:\n\n.. code-block:: twig\n\n    {# display one specific case of a backed enum #}\n    {{ enum('App\\\\CardSuite').Clubs.value }} {# \"clubs\" #}\n\n    {# display one specific case of a backed enum, with a dynamic name #}\n    {% set case_name = 'Spades' %}\n    {{ enum('App\\\\CardSuite').(case_name).name }} {# \"Spades\" #}\n\n    {# get all cases of an enum #}\n    {% for case in enum('App\\\\CardSuite').cases %}\n        {{ case.value }}\n    {% endfor %}\n    {# \"clubs\", \"spades\", \"hearts\", \"diamonds\" #}\n\n    {# get a specific case of an enum by value #}\n    {% set card_suite = enum('App\\\\CardSuite').from('hearts') %}\n    {{ card_suite.name }} {# \"Hearts\" #}\n    {{ card_suite.value }} {# \"hearts\" #}\n\n    {# call any methods of the enum class #}\n    {{ enum('App\\\\CardSuite').someMethod() }}\n\nWhen using a string literal for the ``enum`` argument, it will be validated during compile time to be a valid enum name.\n\nArguments\n---------\n\n* ``enum``: The FQCN of the enum\n"
  },
  {
    "path": "doc/functions/enum_cases.rst",
    "content": "``enum_cases``\n==============\n\n.. versionadded:: 3.12\n\n    The ``enum_cases`` function was added in Twig 3.12.\n\n``enum_cases`` returns the list of cases for a given enum:\n\n.. code-block:: twig\n\n    {% for case in enum_cases('App\\\\CardSuite') %}\n        {{ case.value }}\n    {% endfor %}\n    {# \"clubs\", \"spades\", \"hearts\", \"diamonds\" #}\n\nWhen using a string literal for the ``enum`` argument, it will be validated during compile time to be a valid enum name.\n\nArguments\n---------\n\n* ``enum``: The FQCN of the enum\n"
  },
  {
    "path": "doc/functions/html_attr.rst",
    "content": "``html_attr``\n=============\n\n.. _html_attr:\n\n.. versionadded:: 3.23\n\n    The ``html_attr`` function was added in Twig 3.24.\n\nThe ``html_attr`` function renders HTML attributes from one or more mappings,\ntaking care of proper escaping. The mappings contain the names of HTML\nattributes as keys, and the corresponding values represent the attributes'\nvalues.\n\n.. note::\n\n    Attribute names are escaped using the ``html_attr_relaxed`` strategy.\n\n.. code-block:: html+twig\n\n    <div {{ html_attr({class: ['foo', 'bar'], id: 'main'}) }}>\n        Content\n    </div>\n\n    {# Output: <div class=\"foo bar\" id=\"main\">Content</div> #}\n\nThe function accepts multiple attribute maps. Internally, it uses\n:ref:`html_attr_merge` to combine the arguments:\n\n.. code-block:: html+twig\n\n    {% set base_attrs = {class: ['btn']} %}\n    {% set variant_attrs = {class: ['btn-primary'], disabled: true} %}\n\n    <button {{ html_attr(base_attrs, variant_attrs) }}>\n        Click me\n    </button>\n\n    {# Output: <button class=\"btn btn-primary\" disabled=\"\">Click me</button> #}\n\n.. note::\n\n    To make best use of the special merge behavior of ``html_attr_merge`` and\n    to avoid confusion, you should consistently use iterables (mappings or sequences)\n    for attributes that can take multiple values, like ``class``, ``srcset`` or ``aria-describedby``.\n\n    Use non-iterable values for attributes that contain a single value only, like\n    ``id`` or ``href``.\n\nShorthand notation for mappings can be particularly helpful:\n\n.. code-block:: html+twig\n\n    {% set id = 'user-123' %}\n    {% set href = '/profile' %}\n\n    <a {{ html_attr({id, href}) }}>Profile</a>\n\n    {# Output: <a id=\"user-123\" href=\"/profile\">Profile</a> #}\n\n``null`` and Boolean Attribute Values\n-------------------------------------\n\n``null`` values always omit printing an attribute altogether.\n\nThe boolean ``false`` value also omits the attribute altogether, with an\nexception for ``aria-*`` attribute names, see below.\n\n.. code-block:: html+twig\n\n    {# null omits the attribute entirely, and so does false for non-\"aria-*\" #}\n    <input {{ html_attr({disabled: false, title: null}) }}>\n    {# Output: <input> #}\n\n``true`` will print the attribute with the empty value ``\"\"``. This is XHTML compatible,\nand in HTML 5 equivalent to using the short attribute notation without a value. An exception\nis made for ``data-*`` and ``aria-*`` attributes, see below.\n\n.. code-block:: html+twig\n\n    {# true becomes an empty string value #}\n    <input {{ html_attr({required: true}) }}>\n    {# Output: <input required=\"\">, which is equivalent to <input required> #}\n\nArray Values\n------------\n\nAttribute values that are iterables are automatically converted to space-separated\ntoken lists of the values. Exceptions apply for ``data-*`` and ``style`` attributes,\ndescribed further below.\n\n.. code-block:: html+twig\n\n    <div {{ html_attr({class: ['btn', 'btn-primary', 'btn-lg']}) }}>\n        Button\n    </div>\n\n    {# Output: <div class=\"btn btn-primary btn-lg\">Button</div> #}\n\n.. note::\n\n    This is not bound to the ``class`` attribute name, but works for any attribute.\n\nYou can use the :ref:`html_attr_type` filter to specify a different strategy for\nconcatenating values (e.g., comma-separated for ``srcset`` attributes). This would\nalso override the special behavior for ``data-*`` and ``style``.\n\nWAI-ARIA Attributes\n-------------------\n\nTo make it more convenient to work with the `WAI-ARIA type mapping for HTML\n<https://www.w3.org/TR/wai-aria-1.2/#typemapping>_`, boolean values for ``aria-*``\nattributes are converted to strings ``\"true\"`` and ``\"false\"``.\n\n.. code-block:: html+twig\n\n    <button {{ html_attr({'aria-pressed': true, 'aria-hidden': false}) }}>\n        Toggle\n    </button>\n\n    {# Output: <button aria-pressed=\"true\" aria-hidden=\"false\">Toggle</button> #}\n\nData Attributes\n---------------\n\nFor ``data-*`` attributes, boolean ``true`` values will be converted to ``\"true\"``.\nValues that are not scalars are automatically JSON-encoded.\n\n.. code-block:: html+twig\n\n    <div {{ html_attr({'data-config': {theme: 'dark', size: 'large'}, 'data-bool': true, 'data-false': false}) }}>\n        Content\n    </div>\n\n    {# Output: <div data-config=\"{&quot;theme&quot;:&quot;dark&quot;,&quot;size&quot;:&quot;large&quot;}\" data-bool=\"true\">Content</div> #}\n\nStyle Attribute\n----------------\n\nThe ``style`` attribute name has special handling when its value is iterable:\n\n.. code-block:: html+twig\n\n    {# Non-numeric keys will be used as CSS properties and printed #}\n    <div {{ html_attr({style: {color: 'red', 'font-size': '16px'}}) }}>\n        Styled text\n    </div>\n\n    {# Output: <div style=\"color: red; font-size: 16px;\">Styled text</div> #}\n\n    {# Numeric keys will be assumed to have values that are individual CSS declarations #}\n    <div {{ html_attr({style: ['color: red', 'font-size: 16px']}) }}>\n        Styled text\n    </div>\n\n    {# Output: <div style=\"color: red; font-size: 16px;\">Styled text</div> #}\n\n    {# Merging style attributes #}\n    <div {{ html_attr({style: {color: 'red'}}, {style: {color: 'blue', background: 'white'}}) }}>\n        Styled text\n    </div>\n\n    {# Output: <div style=\"color: blue; background: white;\">Styled text</div> #}\n\n.. warning::\n\n    No additional escaping specific to CSS is applied to key or values from this array.\n    Do not use it to pass untrusted, user-provided data, neither as key nor as value.\n\n``AttributeValueInterface`` Implementations\n-------------------------------------------\n\nFor advanced use cases, attribute values can be objects that implement the ``AttributeValueInterface``.\nThese objects can define their own conversion logic for the ``html_attr`` function that will take\nprecedence over all rules described here. See the docblocks in that interface for details.\n\n.. note::\n\n    The ``html_attr`` function is part of the ``HtmlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/html-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Html\\HtmlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new HtmlExtension());\n\n.. seealso::\n\n    :ref:`html_attr_merge`,\n    :ref:`html_attr_type`\n"
  },
  {
    "path": "doc/functions/html_classes.rst",
    "content": "``html_classes``\n================\n\nThe ``html_classes`` function returns a string by conditionally joining class\nnames together:\n\n.. code-block:: html+twig\n\n    <p class=\"{{ html_classes('a-class', 'another-class', {\n        'errored': object.errored,\n        'finished': object.finished,\n        'pending': object.pending,\n    }) }}\">How are you doing?</p>\n\n.. note::\n\n    The ``html_classes`` function is part of the ``HtmlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/html-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Html\\HtmlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new HtmlExtension());\n"
  },
  {
    "path": "doc/functions/html_cva.rst",
    "content": "``html_cva``\n============\n\n.. versionadded:: 3.12\n\n    The ``html_cva`` function was added in Twig 3.12.\n\n`CVA (Class Variant Authority)`_ is a concept from the JavaScript world and used\nby the well-known `shadcn/ui`_ library.\nThe CVA concept is used to render multiple variations of components, applying\na set of conditions and recipes to dynamically compose CSS class strings (color, size, etc.),\nto create highly reusable and customizable templates.\n\nThe concept of CVA is powered by a ``html_cva()`` Twig\nfunction where you define ``base`` classes that should always be present and then different\n``variants`` and the corresponding classes:\n\n.. code-block:: html+twig\n\n    {# templates/alert.html.twig #}\n    {% set alert = html_cva(\n        base: 'alert',\n        variants: {\n            color: {\n                blue: 'bg-blue',\n                red: 'bg-red',\n                green: 'bg-green',\n            },\n            size: {\n                sm: 'text-sm',\n                md: 'text-md',\n                lg: 'text-lg',\n            }\n        }\n    ) %}\n\n    <div class=\"{{ alert.apply({color, size}, class) }}\">\n        ...\n    </div>\n\nThen use the ``color`` and ``size`` variants to select the needed classes:\n\n.. code-block:: twig\n\n    {# index.html.twig #}\n    {{ include('alert.html.twig', {'color': 'blue', 'size': 'md'}) }}\n    {# class=\"alert bg-blue text-md\" #}\n\n    {{ include('alert.html.twig', {'color': 'green', 'size': 'sm'}) }}\n    {# class=\"alert bg-green text-sm\" #}\n\n    {{ include('alert.html.twig', {'color': 'red', 'class': 'flex items-center justify-center'}) }}\n    {# class=\"alert bg-red flex items-center justify-center\" #}\n\nCVA and Tailwind CSS\n--------------------\n\nCVA work perfectly with Tailwind CSS. The only drawback is that you can have class conflicts.\nTo \"merge\" conflicting classes together and keep only the ones you need, use the\n``tailwind_merge()`` filter from `tales-from-a-dev/twig-tailwind-extra`_\nwith the ``html_cva()`` function:\n\n.. code-block:: bash\n\n    $ composer require tales-from-a-dev/twig-tailwind-extra\n\n.. code-block:: html+twig\n\n    {% set alert = html_cva(\n        ...\n    ) %}\n\n    <div class=\"{{ alert.apply({color, size}, class)|tailwind_merge }}\">\n        ...\n    </div>\n\nCompound Variants\n-----------------\n\nYou can define compound variants. A compound variant is a variant that applies\nwhen multiple other variant conditions are met:\n\n.. code-block:: html+twig\n\n    {% set alert = html_cva(\n        base: 'alert',\n        variants: {\n            color: {\n                blue: 'bg-blue',\n                red: 'bg-red',\n                green: 'bg-green',\n            },\n            size: {\n                sm: 'text-sm',\n                md: 'text-md',\n                lg: 'text-lg',\n            }\n        },\n        compound_variants: [{\n            # if color = red AND size = (md or lg), add the `font-bold` class\n            color: ['red'],\n            size: ['md', 'lg'],\n            class: 'font-bold',\n        }]\n    ) %}\n\n    <div class=\"{{ alert.apply({color, size}) }}\">\n        ...\n    </div>\n\n    {# index.html.twig #}\n\n    {{ include('alert.html.twig', {color: 'red', size: 'lg'}) }}\n    {# class=\"alert bg-red text-lg font-bold\" #}\n\n    {{ include('alert.html.twig', {color: 'green', size: 'sm'}) }}\n    {# class=\"alert bg-green text-sm\" #}\n\n    {{ include('alert.html.twig', {color: 'red', size: 'md'}) }}\n    {# class=\"alert bg-green text-md font-bold\" #}\n\nDefault Variants\n----------------\n\nIf no variants match, you can define a default set of classes to apply:\n\n.. code-block:: html+twig\n\n    {% set alert = html_cva(\n        base: 'alert',\n        variants: {\n            color: {\n                blue: 'bg-blue',\n                red: 'bg-red',\n                green: 'bg-green',\n            },\n            size: {\n                sm: 'text-sm',\n                md: 'text-md',\n                lg: 'text-lg',\n            },\n            rounded: {\n                sm: 'rounded-sm',\n                md: 'rounded-md',\n                lg: 'rounded-lg',\n            }\n        },\n        default_variant: {\n            rounded: 'md',\n        }\n    ) %}\n\n    <div class=\"{{ alert.apply({color, size}) }}\">\n         ...\n    </div>\n\n    {# index.html.twig #}\n\n    {{ include('alert.html.twig', {color: 'red', size: 'lg'}) }}\n    {# class=\"alert bg-red text-lg rounded-md\" #}\n\n.. note::\n\n    The ``html_cva`` function is part of the ``HtmlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/html-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n            $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n            use Twig\\Extra\\Html\\HtmlExtension;\n\n            $twig = new \\Twig\\Environment(...);\n            $twig->addExtension(new HtmlExtension());\n\nThis function works best when used with `TwigComponent`_.\n\n.. _`CVA (Class Variant Authority)`: https://cva.style/docs/getting-started/variants\n.. _`shadcn/ui`: https://ui.shadcn.com\n.. _`tales-from-a-dev/twig-tailwind-extra`: https://github.com/tales-from-a-dev/twig-tailwind-extra\n.. _`TwigComponent`: https://symfony.com/bundles/ux-twig-component/current/index.html\n"
  },
  {
    "path": "doc/functions/include.rst",
    "content": "``include``\n===========\n\nThe ``include`` function returns the rendered content of a template:\n\n.. code-block:: twig\n\n    {{ include('template.html.twig') }}\n    {{ include(some_var) }}\n\nIncluded templates have access to the variables of the active context.\n\nIf you are using the filesystem loader, the templates are looked for in the\npaths defined by it.\n\nThe context is passed by default to the template but you can also pass\nadditional variables:\n\n.. code-block:: twig\n\n    {# template.html.twig will have access to the variables from the current context and the additional ones provided #}\n    {{ include('template.html.twig', {name: 'Fabien'}) }}\n\nYou can disable access to the context by setting ``with_context`` to\n``false``:\n\n.. code-block:: twig\n\n    {# only the name variable will be accessible #}\n    {{ include('template.html.twig', {name: 'Fabien'}, with_context: false) }}\n\n.. code-block:: twig\n\n    {# no variables will be accessible #}\n    {{ include('template.html.twig', with_context: false) }}\n\nAnd if the expression evaluates to a ``\\Twig\\Template`` or a\n``\\Twig\\TemplateWrapper`` instance, Twig will use it directly::\n\n    // {{ include(template) }}\n\n    $template = $twig->load('some_template.html.twig');\n\n    $twig->display('template.html.twig', ['template' => $template]);\n\nWhen you set the ``ignore_missing`` flag, Twig will return an empty string if\nthe template does not exist:\n\n.. code-block:: twig\n\n    {{ include('sidebar.html.twig', ignore_missing: true) }}\n\nYou can also provide a list of templates that are checked for existence before\ninclusion. The first template that exists will be rendered:\n\n.. code-block:: twig\n\n    {{ include(['page_detailed.html.twig', 'page.html.twig']) }}\n\nIf ``ignore_missing`` is set, it will fall back to rendering nothing if none\nof the templates exist, otherwise it will throw an exception.\n\nWhen including a template created by an end user, you should consider\n:doc:`sandboxing<../sandbox>` it:\n\n.. code-block:: twig\n\n    {{ include('page.html.twig', sandboxed: true) }}\n\nArguments\n---------\n\n* ``template``:       The template to render\n* ``variables``:      The variables to pass to the template\n* ``with_context``:   Whether to pass the current context variables or not\n* ``ignore_missing``: Whether to ignore missing templates or not\n* ``sandboxed``:      Whether to sandbox the template or not\n"
  },
  {
    "path": "doc/functions/index.rst",
    "content": "Functions\n=========\n\n.. toctree::\n    :maxdepth: 1\n\n    attribute\n    block\n    constant\n    cycle\n    date\n    dump\n    enum\n    enum_cases\n    html_attr\n    html_classes\n    html_cva\n    include\n    max\n    min\n    parent\n    random\n    range\n    source\n    country_timezones\n    country_names\n    currency_names\n    language_names\n    locale_names\n    script_names\n    timezone_names\n    template_from_string\n"
  },
  {
    "path": "doc/functions/language_names.rst",
    "content": "``language_names``\n==================\n\n.. versionadded:: 3.5\n\n    The ``language_names`` function was added in Twig 3.5.\n\nThe ``language_names`` function returns the names of the languages:\n\n.. code-block:: twig\n\n    {# Abkhazian, Achinese, ... #}\n    {{ language_names()|join(', ') }}\n    \nBy default, the function uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# abkhaze, aceh, ... #}\n    {{ language_names('fr')|join(', ') }}\n\n.. note::\n\n    The ``language_names`` function is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/functions/locale_names.rst",
    "content": "``locale_names``\n================\n\n.. versionadded:: 3.5\n\n    The ``locale_names`` function was added in Twig 3.5.\n\nThe ``locale_names`` function returns the names of the locales:\n\n.. code-block:: twig\n\n    {# Afrikaans, Afrikaans (Namibia), ... #}\n    {{ locale_names()|join(', ') }}\n    \nBy default, the function uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# afrikaans, afrikaans (Afrique du Sud), ... #}\n    {{ locale_names('fr')|join(', ') }}\n\n.. note::\n\n    The ``locale_names`` function is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/functions/max.rst",
    "content": "``max``\n=======\n\n``max`` returns the biggest value of a sequence or a set of values:\n\n.. code-block:: twig\n\n    {{ max(1, 3, 2) }}\n    {{ max([1, 3, 2]) }}\n\nWhen called with a mapping, max ignores keys and only compares values:\n\n.. code-block:: twig\n\n    {{ max({2: \"e\", 1: \"a\", 3: \"b\", 5: \"d\", 4: \"c\"}) }}\n    {# returns \"e\" #}\n\n"
  },
  {
    "path": "doc/functions/min.rst",
    "content": "``min``\n=======\n\n``min`` returns the lowest value of a sequence or a set of values:\n\n.. code-block:: twig\n\n    {{ min(1, 3, 2) }}\n    {{ min([1, 3, 2]) }}\n\nWhen called with a mapping, min ignores keys and only compares values:\n\n.. code-block:: twig\n\n    {{ min({2: \"e\", 3: \"a\", 1: \"b\", 5: \"d\", 4: \"c\"}) }}\n    {# returns \"a\" #}\n\n"
  },
  {
    "path": "doc/functions/parent.rst",
    "content": "``parent``\n==========\n\nWhen a template uses inheritance, it's possible to render the contents of the\nparent block when overriding a block by using the ``parent`` function:\n\n.. code-block:: html+twig\n\n    {% extends \"base.html.twig\" %}\n\n    {% block sidebar %}\n        <h3>Table Of Contents</h3>\n        ...\n        {{ parent() }}\n    {% endblock %}\n\nThe ``parent()`` call will return the content of the ``sidebar`` block as\ndefined in the ``base.html.twig`` template.\n\n.. seealso::\n\n    :doc:`extends<../tags/extends>`, :doc:`block<../functions/block>`, :doc:`block<../tags/block>`\n"
  },
  {
    "path": "doc/functions/random.rst",
    "content": "``random``\n==========\n\nThe ``random`` function returns a random value depending on the supplied\nparameter type:\n\n* a random item from a sequence;\n* a random character from a string;\n* a random integer between 0 and the integer parameter (inclusive).\n* a random integer between the integer parameter (when negative) and 0 (inclusive).\n* a random integer between the first integer and the second integer parameter (inclusive).\n\n.. caution::\n\n    The ``random`` function does not produce cryptographically secure random numbers.\n    Do not use them for purposes that require returned values to be unguessable. \n\n.. code-block:: twig\n\n    {{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}\n    {{ random('ABC') }}                         {# example output: C #}\n    {{ random() }}                              {# example output: 15386094 (works as the native PHP mt_rand function) #}\n    {{ random(5) }}                             {# example output: 3 #}\n    {{ random(50, 100) }}                       {# example output: 63 #}\n\nArguments\n---------\n\n* ``values``: The values\n* ``max``: The max value when values is an integer\n"
  },
  {
    "path": "doc/functions/range.rst",
    "content": "``range``\n=========\n\nReturns a list containing an arithmetic progression of integers:\n\n.. code-block:: twig\n\n    {% for i in range(0, 3) %}\n        {{ i }},\n    {% endfor %}\n\n    {# outputs 0, 1, 2, 3, #}\n\nWhen step is given (as the third parameter), it specifies the increment (or\ndecrement for negative values):\n\n.. code-block:: twig\n\n    {% for i in range(0, 6, 2) %}\n        {{ i }},\n    {% endfor %}\n\n    {# outputs 0, 2, 4, 6, #}\n\n.. note::\n\n    Note that if the start is greater than the end, ``range`` assumes a step of\n    ``-1``:\n\n    .. code-block:: twig\n\n        {% for i in range(3, 0) %}\n            {{ i }},\n        {% endfor %}\n\n        {# outputs 3, 2, 1, 0, #}\n\nThe Twig built-in ``..`` operator is just syntactic sugar for the ``range``\nfunction (with a step of ``1``, or ``-1`` if the start is greater than the end):\n\n.. code-block:: twig\n\n    {% for i in 0..3 %}\n        {{ i }},\n    {% endfor %}\n\n.. tip::\n\n    The ``range`` function works as the native PHP `range`_ function.\n\nArguments\n---------\n\n* ``low``:  The first value of the sequence.\n* ``high``: The highest possible value of the sequence.\n* ``step``: The increment between elements of the sequence.\n\n.. _`range`: https://www.php.net/range\n"
  },
  {
    "path": "doc/functions/script_names.rst",
    "content": "``script_names``\n================\n\n.. versionadded:: 3.5\n\n    The ``script_names`` function was added in Twig 3.5.\n\nThe ``script_names`` function returns the names of the scripts:\n\n.. code-block:: twig\n\n    {# Adlam, Afaka, ... #}\n    {{ script_names()|join(', ') }}\n    \nBy default, the function uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# Adlam, Afaka, ... #}\n    {{ script_names('fr')|join(', ') }}\n\n.. note::\n\n    The ``script_names`` function is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/functions/source.rst",
    "content": "``source``\n==========\n\nThe ``source`` function returns the content of a template without rendering it:\n\n.. code-block:: twig\n\n    {{ source('template.html.twig') }}\n    {{ source(some_var) }}\n\nWhen you set the ``ignore_missing`` flag, Twig will return an empty string if\nthe template does not exist:\n\n.. code-block:: twig\n\n    {{ source('template.html.twig', ignore_missing = true) }}\n\nThe function uses the same template loaders as the ones used to include\ntemplates. So, if you are using the filesystem loader, the templates are looked\nfor in the paths defined by it.\n\nArguments\n---------\n\n* ``name``: The name of the template to read\n* ``ignore_missing``: Whether to ignore missing templates or not\n"
  },
  {
    "path": "doc/functions/template_from_string.rst",
    "content": "``template_from_string``\n========================\n\nThe ``template_from_string`` function loads a template from a string:\n\n.. code-block:: twig\n\n    {{ include(template_from_string(\"Hello {{ name }}\")) }}\n    {{ include(template_from_string(page.template)) }}\n\nTo ease debugging, you can also give the template a name that will be part of\nany related error message:\n\n.. code-block:: twig\n\n    {{ include(template_from_string(page.template, \"template for page \" ~ page.name)) }}\n\n.. note::\n\n    The ``template_from_string`` function is not available by default.\n\n    On Symfony projects, you need to load it in your ``services.yaml`` file:\n\n    .. code-block:: yaml\n\n        services:\n            Twig\\Extension\\StringLoaderExtension:\n\n    or ``services.php`` file::\n\n        $services->set(\\Twig\\Extension\\StringLoaderExtension::class);\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extension\\StringLoaderExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new StringLoaderExtension());\n\n.. note::\n\n    Even if you will probably always use the ``template_from_string`` function\n    with the ``include`` function, you can use it with any tag or function that\n    takes a template as an argument (like the ``embed`` or ``extends`` tags).\n\nArguments\n---------\n\n* ``template``: The template\n* ``name``: A name for the template\n"
  },
  {
    "path": "doc/functions/timezone_names.rst",
    "content": "``timezone_names``\n==================\n\n.. versionadded:: 3.5\n\n    The ``timezone_names`` function was added in Twig 3.5.\n\nThe ``timezone_names`` function returns the names of the timezones:\n\n.. code-block:: twig\n\n    {# Acre Time (Eirunepe), Acre Time (Rio Branco), ... #}\n    {{ timezone_names()|join(', ') }}\n    \nBy default, the function uses the current locale. You can pass it explicitly:\n\n.. code-block:: twig\n\n    {# heure : Antarctique (Casey), heure : Canada (Montreal), ... #}\n    {{ timezone_names('fr')|join(', ') }}\n\n.. note::\n\n    The ``timezone_names`` function is part of the ``IntlExtension`` which is not\n    installed by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/intl-extra\n\n    Then, on Symfony projects, install the ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Otherwise, add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Intl\\IntlExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new IntlExtension());\n\nArguments\n---------\n\n* ``locale``: The locale code as defined in `RFC 5646`_\n\n.. _RFC 5646: https://www.rfc-editor.org/info/rfc5646\n"
  },
  {
    "path": "doc/index.rst",
    "content": "Twig\n====\n\n.. toctree::\n    :maxdepth: 2\n\n    intro\n    installation\n    templates\n    api\n    advanced\n    sandbox\n    internals\n    deprecated\n    recipes\n    coding_standards\n    operators_precedence\n    tags/index\n    filters/index\n    functions/index\n    tests/index\n"
  },
  {
    "path": "doc/installation.rst",
    "content": "Installation\n============\n\nInstall `Composer`_ and run the following command to get the latest version:\n\n.. code-block:: bash\n\n    composer require \"twig/twig:^3.0\"\n\n.. _`Composer`: https://getcomposer.org/download/\n"
  },
  {
    "path": "doc/internals.rst",
    "content": "Twig Internals\n==============\n\nTwig is very extensible and you can hack it. Keep in mind that you\nshould probably try to create an extension before hacking the core, as most\nfeatures and enhancements can be handled with extensions. This chapter is also\nuseful for people who want to understand how Twig works under the hood.\n\nHow does Twig work?\n-------------------\n\nThe rendering of a Twig template can be summarized into four key steps:\n\n* **Load** the template: If the template is already compiled, load it and go\n  to the *evaluation* step, otherwise:\n\n  * First, the **lexer** tokenizes the template source code into small pieces\n    for easier processing;\n\n  * Then, the **parser** converts the token stream into a meaningful tree\n    of nodes (the Abstract Syntax Tree);\n\n  * Finally, the **compiler** transforms the AST into PHP code.\n\n* **Evaluate** the template: It means calling the ``display()``\n  method of the compiled template and passing it the context.\n\nThe Lexer\n---------\n\nThe lexer tokenizes a template source code into a token stream (each token is\nan instance of ``\\Twig\\Token``, and the stream is an instance of\n``\\Twig\\TokenStream``). The default lexer recognizes 15 different token types:\n\n* ``\\Twig\\Token::BLOCK_START_TYPE``, ``\\Twig\\Token::BLOCK_END_TYPE``: Delimiters for blocks (``{% %}``)\n* ``\\Twig\\Token::VAR_START_TYPE``, ``\\Twig\\Token::VAR_END_TYPE``: Delimiters for variables (``{{ }}``)\n* ``\\Twig\\Token::TEXT_TYPE``: A text outside an expression;\n* ``\\Twig\\Token::NAME_TYPE``: A name in an expression;\n* ``\\Twig\\Token::NUMBER_TYPE``: A number in an expression;\n* ``\\Twig\\Token::STRING_TYPE``: A string in an expression;\n* ``\\Twig\\Token::OPERATOR_TYPE``: An operator;\n* ``\\Twig\\Token::ARROW_TYPE``: An arrow function operator (``=>``);\n* ``\\Twig\\Token::SPREAD_TYPE``: A spread operator (``...``);\n* ``\\Twig\\Token::PUNCTUATION_TYPE``: A punctuation sign;\n* ``\\Twig\\Token::INTERPOLATION_START_TYPE``, ``\\Twig\\Token::INTERPOLATION_END_TYPE``: Delimiters for string interpolation;\n* ``\\Twig\\Token::EOF_TYPE``: Ends of template.\n\nYou can manually convert a source code into a token stream by calling the\n``tokenize()`` method of an environment::\n\n    $stream = $twig->tokenize(new \\Twig\\Source($source, $identifier));\n\nAs the stream has a ``__toString()`` method, you can have a textual\nrepresentation of it by echoing the object::\n\n    echo $stream.\"\\n\";\n\nHere is the output for the ``Hello {{ name }}`` template:\n\n.. code-block:: text\n\n    TEXT_TYPE(Hello )\n    VAR_START_TYPE()\n    NAME_TYPE(name)\n    VAR_END_TYPE()\n    EOF_TYPE()\n\n.. note::\n\n    The default lexer (``\\Twig\\Lexer``) can be changed by calling\n    the ``setLexer()`` method::\n\n        $twig->setLexer($lexer);\n\nThe Parser\n----------\n\nThe parser converts the token stream into an AST (Abstract Syntax Tree), or a\nnode tree (an instance of ``\\Twig\\Node\\ModuleNode``). The core extension defines\nthe basic nodes like: ``for``, ``if``, ... and the expression nodes.\n\nYou can manually convert a token stream into a node tree by calling the\n``parse()`` method of an environment::\n\n    $nodes = $twig->parse($stream);\n\nEchoing the node object gives you a nice representation of the tree::\n\n    echo $nodes.\"\\n\";\n\nHere is the output for the ``Hello {{ name }}`` template:\n\n.. code-block:: text\n\n    \\Twig\\Node\\ModuleNode(\n      \\Twig\\Node\\TextNode(Hello )\n      \\Twig\\Node\\PrintNode(\n        \\Twig\\Node\\Expression\\NameExpression(name)\n      )\n    )\n\n.. note::\n\n    The default parser (``\\Twig\\TokenParser\\AbstractTokenParser``) can be changed by calling the\n    ``setParser()`` method::\n\n        $twig->setParser($parser);\n\nThe Compiler\n------------\n\nThe last step is done by the compiler. It takes a node tree as an input and\ngenerates PHP code usable for runtime execution of the template.\n\nYou can manually compile a node tree to PHP code with the ``compile()`` method\nof an environment::\n\n    $php = $twig->compile($nodes);\n\nThe generated template for a ``Hello {{ name }}`` template reads as follows\n(the actual output can differ depending on the version of Twig you are\nusing)::\n\n    /* Hello {{ name }} */\n    class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Template\n    {\n        protected function doDisplay(array $context, array $blocks = []): iterable\n        {\n            $macros = $this->macros;\n            // line 1\n            yield \"Hello \";\n            // line 2\n            yield $this->env->getRuntime('Twig\\Runtime\\EscaperRuntime')->escape((isset($context[\"name\"]) || array_key_exists(\"name\", $context) ? $context[\"name\"] : (function () { throw new RuntimeError('Variable \"name\" does not exist.', 2, $this->source); })()), \"html\", null, true);\n            return; yield '';\n        }\n\n        // some more code\n    }\n\n.. note::\n\n    The default compiler (``\\Twig\\Compiler``) can be changed by calling the\n    ``setCompiler()`` method::\n\n        $twig->setCompiler($compiler);\n"
  },
  {
    "path": "doc/intro.rst",
    "content": "Introduction\n============\n\nWelcome to the documentation for Twig, the flexible, fast, and secure template\nengine for PHP.\n\nTwig is both designer and developer friendly by sticking to PHP's principles and\nadding functionality useful for templating environments.\n\nThe key-features are...\n\n* *Fast*: Twig compiles templates down to plain optimized PHP code. The\n  overhead compared to regular PHP code was reduced to the very minimum.\n\n* *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This\n  allows Twig to be used as a template language for applications where users\n  may modify the template design.\n\n* *Flexible*: Twig is powered by a flexible lexer and parser. This allows the\n  developer to define their own custom tags and filters, and to create their own DSL.\n\nTwig is used by many Open-Source projects like Symfony, Drupal8, eZPublish,\nphpBB, Matomo, OroCRM; and many frameworks have support for it as well like\nSlim, Yii, Laravel, and Codeigniter — just to name a few.\n\n.. admonition:: Screencast\n\n    Like to learn from video tutorials? Check out the `SymfonyCasts Twig Tutorial`_!\n\nPrerequisites\n-------------\n\nTwig 3.x needs at least **PHP 8.1.0** to run.\n\nInstallation\n------------\n\nThe recommended way to install Twig is via Composer:\n\n.. code-block:: bash\n\n    composer require \"twig/twig:^3.0\"\n\nBasic API Usage\n---------------\n\nThis section gives you a brief introduction to the PHP API for Twig::\n\n    require_once '/path/to/vendor/autoload.php';\n\n    $loader = new \\Twig\\Loader\\ArrayLoader([\n        'index' => 'Hello {{ name }}!',\n    ]);\n    $twig = new \\Twig\\Environment($loader);\n\n    echo $twig->render('index', ['name' => 'Fabien']);\n\nTwig uses a loader (``\\Twig\\Loader\\ArrayLoader``) to locate templates, and an\nenvironment (``\\Twig\\Environment``) to store its configuration.\n\nThe ``render()`` method loads the template passed as a first argument and\nrenders it with the variables passed as a second argument.\n\nAs templates are generally stored on the filesystem, Twig also comes with a\nfilesystem loader::\n\n    $loader = new \\Twig\\Loader\\FilesystemLoader('/path/to/templates');\n    $twig = new \\Twig\\Environment($loader, [\n        'cache' => '/path/to/compilation_cache',\n    ]);\n\n    echo $twig->render('index.html.twig', ['name' => 'Fabien']);\n\n.. _`SymfonyCasts Twig Tutorial`: https://symfonycasts.com/screencast/twig\n"
  },
  {
    "path": "doc/operators_precedence.rst",
    "content": "\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| Precedence | Operator         | Type    | Associativity | Description                                                       |\n+============+==================+=========+===============+===================================================================+\n| 512        | ``...``          | prefix  | n/a           | Spread operator                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|  => 300    | ``|``            | infix   | Left          | Twig filter call                                                  |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``(``            |         |               | Twig function call                                                |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``.``, ``?.``    |         |               | Get an attribute on a variable                                    |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``[``            |         |               | Array access                                                      |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 500        | ``-``            | prefix  | n/a           |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``+``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 300 => 5   | ``??``           | infix   | Right         | Null coalescing operator (a ?? b)                                 |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 250        | ``=>``           | infix   | Left          | Arrow function (x => expr)                                        |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 200        | ``**``           | infix   | Right         | Exponentiation operator                                           |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 100        | ``is``           | infix   | Left          | Twig tests                                                        |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``is not``       |         |               | Twig tests                                                        |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 60         | ``*``            | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``/``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``//``           |         |               | Floor division                                                    |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``%``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 50 => 70   | ``not``          | prefix  | n/a           |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 40 => 27   | ``~``            | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 30         | ``+``            | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``-``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 25         | ``..``           | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 20         | ``==``           | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``!=``           |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``<=>``          |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``<``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``>``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``>=``           |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``<=``           |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``not in``       |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``in``           |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``matches``      |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``starts with``  |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``ends with``    |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``has some``     |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``has every``    |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``===``          |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``!==``          |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 18         | ``b-and``        | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 17         | ``b-xor``        | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 16         | ``b-or``         | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 15         | ``and``          | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 12         | ``xor``          | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 10         | ``or``           | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 5          | ``?:``, ``? :``  | infix   | Right         | Elvis operator (a ?: b)                                           |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 0          | ``(``            | prefix  | n/a           | Explicit group expression (a)                                     |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``?``            | infix   | Left          | Conditional operator (a ? b : c)                                  |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``=``            |         | Right         | Assignment operator                                               |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``literal``      | prefix  | n/a           | A literal value (boolean, string, number, sequence, mapping, ...) |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n\nWhen a precedence will change in 4.0, the new precedence is indicated by the arrow ``=>``.\n\nHere is the same table for Twig 4.0 with adjusted precedences:\n\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| Precedence | Operator         | Type    | Associativity | Description                                                       |\n+============+==================+=========+===============+===================================================================+\n| 512        | ``...``          | prefix  | n/a           | Spread operator                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``(``            | infix   | Left          | Twig function call                                                |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``.``, ``?.``    |         |               | Get an attribute on a variable                                    |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``[``            |         |               | Array access                                                      |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 500        | ``-``            | prefix  | n/a           |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``+``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 300        | ``|``            | infix   | Left          | Twig filter call                                                  |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 250        | ``=>``           | infix   | Left          | Arrow function (x => expr)                                        |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 200        | ``**``           | infix   | Right         | Exponentiation operator                                           |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 100        | ``is``           | infix   | Left          | Twig tests                                                        |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``is not``       |         |               | Twig tests                                                        |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 70         | ``not``          | prefix  | n/a           |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 60         | ``*``            | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``/``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``//``           |         |               | Floor division                                                    |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``%``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 30         | ``+``            | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``-``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 27         | ``~``            | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 25         | ``..``           | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 20         | ``==``           | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``!=``           |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``<=>``          |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``<``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``>``            |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``>=``           |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``<=``           |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``not in``       |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``in``           |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``matches``      |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``starts with``  |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``ends with``    |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``has some``     |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``has every``    |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``===``          |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``!==``          |         |               |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 18         | ``b-and``        | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 17         | ``b-xor``        | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 16         | ``b-or``         | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 15         | ``and``          | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 12         | ``xor``          | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 10         | ``or``           | infix   | Left          |                                                                   |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 5          | ``??``           | infix   | Right         | Null coalescing operator (a ?? b)                                 |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``?:``, ``? :``  |         |               | Elvis operator (a ?: b)                                           |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n| 0          | ``(``            | prefix  | n/a           | Explicit group expression (a)                                     |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``?``            | infix   | Left          | Conditional operator (a ? b : c)                                  |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``=``            |         | Right         | Assignment operator                                               |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n|            | ``literal``      | prefix  | n/a           | A literal value (boolean, string, number, sequence, mapping, ...) |\n+------------+------------------+---------+---------------+-------------------------------------------------------------------+\n"
  },
  {
    "path": "doc/recipes.rst",
    "content": "Recipes\n=======\n\n.. _deprecation-notices:\n\nDisplaying Deprecation Notices\n------------------------------\n\nDeprecated features generate deprecation notices (via a call to the\n``trigger_error()`` PHP function). By default, they are silenced and never\ndisplayed nor logged.\n\nTo remove all deprecated feature usages from your templates, write and run a\nscript along the lines of the following::\n\n    require_once __DIR__.'/vendor/autoload.php';\n\n    $twig = create_your_twig_env();\n\n    $deprecations = new \\Twig\\Util\\DeprecationCollector($twig);\n\n    print_r($deprecations->collectDir(__DIR__.'/templates'));\n\nThe ``collectDir()`` method compiles all templates found in a directory,\ncatches deprecation notices, and return them.\n\n.. tip::\n\n    If your templates are not stored on the filesystem, use the ``collect()``\n    method instead. ``collect()`` takes a ``Traversable`` which must return\n    template names as keys and template contents as values (as done by\n    ``\\Twig\\Util\\TemplateDirIterator``).\n\nHowever, this code won't find all deprecations (like using some deprecated Twig\nclasses). To catch all notices, register a custom error handler like the one\nbelow::\n\n    $deprecations = [];\n    set_error_handler(function ($type, $msg) use (&$deprecations) {\n        if (E_USER_DEPRECATED === $type) {\n            $deprecations[] = $msg;\n        }\n    });\n\n    // run your application\n\n    print_r($deprecations);\n\nNote that most deprecation notices are triggered during **compilation**, so\nthey won't be generated when templates are already cached.\n\n.. tip::\n\n    If you want to manage the deprecation notices from your PHPUnit tests, have\n    a look at the `symfony/phpunit-bridge\n    <https://github.com/symfony/phpunit-bridge>`_ package, which eases the\n    process.\n\nMaking a Layout conditional\n---------------------------\n\nWorking with Ajax means that the same content is sometimes displayed as is,\nand sometimes decorated with a layout. As Twig layout template names can be\nany valid expression, you can pass a variable that evaluates to ``true`` when\nthe request is made via Ajax and choose the layout accordingly:\n\n.. code-block:: twig\n\n    {% extends request.ajax ? \"base_ajax.html.twig\" : \"base.html.twig\" %}\n\n    {% block content %}\n        This is the content to be displayed.\n    {% endblock %}\n\nMaking an Include dynamic\n-------------------------\n\nWhen including a template, its name does not need to be a string. For\ninstance, the name can depend on the value of a variable:\n\n.. code-block:: twig\n\n    {% include var ~ '_foo.html.twig' %}\n\nIf ``var`` evaluates to ``index``, the ``index_foo.html.twig`` template will be\nrendered.\n\nAs a matter of fact, the template name can be any valid expression, such as\nthe following:\n\n.. code-block:: twig\n\n    {% include var|default('index') ~ '_foo.html.twig' %}\n\nOverriding a Template that also extends itself\n----------------------------------------------\n\nA template can be customized in two different ways:\n\n* *Inheritance*: A template *extends* a parent template and overrides some\n  blocks;\n\n* *Replacement*: If you use the filesystem loader, Twig loads the first\n  template it finds in a list of configured directories; a template found in a\n  directory *replaces* another one from a directory further in the list.\n\nBut how do you combine both: *replace* a template that also extends itself\n(aka a template in a directory further in the list)?\n\nLet's say that your templates are loaded from both ``.../templates/mysite``\nand ``.../templates/default`` in this order. The ``page.html.twig`` template,\nstored in ``.../templates/default`` reads as follows:\n\n.. code-block:: twig\n\n    {# page.html.twig #}\n    {% extends \"layout.html.twig\" %}\n\n    {% block content %}\n    {% endblock %}\n\nYou can replace this template by putting a file with the same name in\n``.../templates/mysite``. And if you want to extend the original template, you\nmight be tempted to write the following:\n\n.. code-block:: twig\n\n    {# page.html.twig in .../templates/mysite #}\n    {% extends \"page.html.twig\" %} {# from .../templates/default #}\n\nHowever, this will not work as Twig will always load the template from\n``.../templates/mysite``.\n\nIt turns out it is possible to get this to work, by adding a directory right\nat the end of your template directories, which is the parent of all of the\nother directories: ``.../templates`` in our case. This has the effect of\nmaking every template file within our system uniquely addressable. Most of the\ntime you will use the \"normal\" paths, but in the special case of wanting to\nextend a template with an overriding version of itself we can reference its\nparent's full, unambiguous template path in the extends tag:\n\n.. code-block:: twig\n\n    {# page.html.twig in .../templates/mysite #}\n    {% extends \"default/page.html.twig\" %} {# from .../templates #}\n\n.. note::\n\n    This recipe was inspired by the following Django wiki page:\n    https://code.djangoproject.com/wiki/ExtendingTemplates\n\nCustomizing the Syntax\n----------------------\n\nTwig allows some syntax customization for the block delimiters. It's **not**\nrecommended to use this feature as templates will be tied with your custom\nsyntax. But for specific projects, it can make sense to change the defaults.\n\nTo change the block delimiters, you need to create your own lexer object::\n\n    $twig = new \\Twig\\Environment(...);\n\n    $lexer = new \\Twig\\Lexer($twig, [\n        'tag_comment'   => ['{#', '#}'],\n        'tag_block'     => ['{%', '%}'],\n        'tag_variable'  => ['{{', '}}'],\n        'interpolation' => ['#{', '}'],\n    ]);\n    $twig->setLexer($lexer);\n\nHere are some configuration example that simulates some other template engines\nsyntax::\n\n    // Ruby erb syntax\n    $lexer = new \\Twig\\Lexer($twig, [\n        'tag_comment'  => ['<%#', '%>'],\n        'tag_block'    => ['<%', '%>'],\n        'tag_variable' => ['<%=', '%>'],\n    ]);\n\n    // SGML Comment Syntax\n    $lexer = new \\Twig\\Lexer($twig, [\n        'tag_comment'  => ['<!--#', '-->'],\n        'tag_block'    => ['<!--', '-->'],\n        'tag_variable' => ['${', '}'],\n    ]);\n\n    // Smarty like\n    $lexer = new \\Twig\\Lexer($twig, [\n        'tag_comment'  => ['{*', '*}'],\n        'tag_block'    => ['{', '}'],\n        'tag_variable' => ['{$', '}'],\n    ]);\n\nUsing dynamic Object Properties\n-------------------------------\n\nWhen Twig encounters a variable like ``article.title``, it tries to find a\n``title`` public property in the ``article`` object.\n\nIt also works if the property does not exist but is rather defined dynamically\nthanks to the magic ``__get()`` method; you need to also implement the\n``__isset()`` magic method like shown in the following snippet of code::\n\n    class Article\n    {\n        public function __get($name)\n        {\n            if ('title' == $name) {\n                return 'The title';\n            }\n\n            // throw some kind of error\n        }\n\n        public function __isset($name)\n        {\n            if ('title' == $name) {\n                return true;\n            }\n\n            return false;\n        }\n    }\n\nAccessing the parent Context in Nested Loops\n--------------------------------------------\n\nSometimes, when using nested loops, you need to access the parent context. The\nparent context is always accessible via the ``loop.parent`` variable. For\ninstance, if you have the following template data::\n\n    $data = [\n        'topics' => [\n            'topic1' => ['Message 1 of topic 1', 'Message 2 of topic 1'],\n            'topic2' => ['Message 1 of topic 2', 'Message 2 of topic 2'],\n        ],\n    ];\n\nAnd the following template to display all messages in all topics:\n\n.. code-block:: twig\n\n    {% for topic, messages in topics %}\n        * {{ loop.index }}: {{ topic }}\n      {% for message in messages %}\n          - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}\n      {% endfor %}\n    {% endfor %}\n\nThe output will be similar to:\n\n.. code-block:: text\n\n    * 1: topic1\n      - 1.1: The message 1 of topic 1\n      - 1.2: The message 2 of topic 1\n    * 2: topic2\n      - 2.1: The message 1 of topic 2\n      - 2.2: The message 2 of topic 2\n\nIn the inner loop, the ``loop.parent`` variable is used to access the outer\ncontext. So, the index of the current ``topic`` defined in the outer for loop\nis accessible via the ``loop.parent.loop.index`` variable.\n\nDefining undefined Functions, Filters, and Tags on the Fly\n----------------------------------------------------------\n\n.. versionadded:: 3.2\n\n    The ``registerUndefinedTokenParserCallback()`` method was added in Twig\n    3.2.\n\n.. versionadded:: 3.22\n\n    The ``registerUndefinedTestCallback()`` method was added in Twig\n    3.22.\n\nWhen a function/filter/test/tag is not defined, Twig defaults to throw a\n``\\Twig\\Error\\SyntaxError`` exception. However, it can also call a `callback`_\n(any valid PHP callable) which should return a function/filter/test/tag.\n\nFor tags, register callbacks with ``registerUndefinedTokenParserCallback()``.\nFor filters, register callbacks with ``registerUndefinedFilterCallback()``.\nFor functions, use ``registerUndefinedFunctionCallback()``.\nFor tests, use ``registerUndefinedTestCallback()``::\n\n    // auto-register all native PHP functions as Twig functions\n    // NEVER do this in a project as it's NOT secure\n    $twig->registerUndefinedFunctionCallback(function ($name) {\n        if (function_exists($name)) {\n            return new \\Twig\\TwigFunction($name, $name);\n        }\n\n        return false;\n    });\n\nIf the callable is not able to return a valid function/filter/test/tag, it must\nreturn ``false``.\n\nIf you register more than one callback, Twig will call them in turn until one\ndoes not return ``false``.\n\n.. tip::\n\n    As the resolution of functions/filters/tests/tags is done during compilation,\n    there is no overhead when registering these callbacks.\n\n.. warning::\n\n    As parsing a tag is specific to each tag (the syntax is free form), the\n    ``registerUndefinedTokenParserCallback()`` cannot be used to define a\n    default implementation for all unknown tags. It's mainly useful to override\n    the default exception or to register on the fly TokenParser instances for\n    specific known tags.\n\nValidating the Template Syntax\n------------------------------\n\nWhen template code is provided by a third-party (through a web interface for\ninstance), it might be interesting to validate the template syntax before\nsaving it. If the template code is stored in a ``$template`` variable, here is\nhow you can do it::\n\n    try {\n        $twig->parse($twig->tokenize(new \\Twig\\Source($template)));\n\n        // the $template is valid\n    } catch (\\Twig\\Error\\SyntaxError $e) {\n        // $template contains one or more syntax errors\n    }\n\nIf you iterate over a set of files, you can pass the filename to the\n``tokenize()`` method to get the filename in the exception message::\n\n    foreach ($files as $file) {\n        try {\n            $twig->parse($twig->tokenize(new \\Twig\\Source($template, $file->getFilename(), $file)));\n\n            // the $template is valid\n        } catch (\\Twig\\Error\\SyntaxError $e) {\n            // $template contains one or more syntax errors\n        }\n    }\n\n.. note::\n\n    This method won't catch any sandbox policy violations because the policy\n    is enforced during template rendering (as Twig needs the context for some\n    checks like allowed methods on objects).\n\nRefreshing modified Templates when OPcache is enabled\n-----------------------------------------------------\n\nWhen using OPcache with ``opcache.validate_timestamps`` set to ``0``,\nTwig cache enabled and auto reload disabled, clearing the template\ncache won't update the cache.\n\nTo get around this, force Twig to invalidate the bytecode cache::\n\n    $twig = new \\Twig\\Environment($loader, [\n        'cache' => new \\Twig\\Cache\\FilesystemCache('/some/cache/path', \\Twig\\Cache\\FilesystemCache::FORCE_BYTECODE_INVALIDATION),\n        // ...\n    ]);\n\nReusing a stateful Node Visitor\n-------------------------------\n\nWhen attaching a visitor to a ``\\Twig\\Environment`` instance, Twig uses it to\nvisit *all* templates it compiles. If you need to keep some state information\naround, you probably want to reset it when visiting a new template.\n\nThis can be achieved with the following code::\n\n    protected $someTemplateState = [];\n\n    public function enterNode(\\Twig\\Node\\Node $node, \\Twig\\Environment $env)\n    {\n        if ($node instanceof \\Twig\\Node\\ModuleNode) {\n            // reset the state as we are entering a new template\n            $this->someTemplateState = [];\n        }\n\n        // ...\n\n        return $node;\n    }\n\nUsing a Database to store Templates\n-----------------------------------\n\nIf you are developing a CMS, templates are usually stored in a database. This\nrecipe gives you a simple PDO template loader you can use as a starting point\nfor your own.\n\nFirst, let's create a temporary in-memory SQLite3 database to work with::\n\n    $dbh = new PDO('sqlite::memory:');\n    $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');\n    $base = '{% block content %}{% endblock %}';\n    $index = '\n    {% extends \"base.html.twig\" %}\n    {% block content %}Hello {{ name }}{% endblock %}\n    ';\n    $now = time();\n    $dbh->prepare('INSERT INTO templates (name, source, last_modified) VALUES (?, ?, ?)')->execute(['base.html.twig', $base, $now]);\n    $dbh->prepare('INSERT INTO templates (name, source, last_modified) VALUES (?, ?, ?)')->execute(['index.html.twig', $index, $now]);\n\nWe have created a simple ``templates`` table that hosts two templates:\n``base.html.twig`` and ``index.html.twig``.\n\nNow, let's define a loader able to use this database::\n\n    class DatabaseTwigLoader implements \\Twig\\Loader\\LoaderInterface\n    {\n        protected $dbh;\n\n        public function __construct(PDO $dbh)\n        {\n            $this->dbh = $dbh;\n        }\n\n        public function getSourceContext(string $name): Source\n        {\n            if (false === $source = $this->getValue('source', $name)) {\n                throw new \\Twig\\Error\\LoaderError(sprintf('Template \"%s\" does not exist.', $name));\n            }\n\n            return new \\Twig\\Source($source, $name);\n        }\n\n        public function exists(string $name)\n        {\n            return $name === $this->getValue('name', $name);\n        }\n\n        public function getCacheKey(string $name): string\n        {\n            return $name;\n        }\n\n        public function isFresh(string $name, int $time): bool\n        {\n            if (false === $lastModified = $this->getValue('last_modified', $name)) {\n                return false;\n            }\n\n            return $lastModified <= $time;\n        }\n\n        protected function getValue($column, $name)\n        {\n            $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');\n            $sth->execute([':name' => (string) $name]);\n\n            return $sth->fetchColumn();\n        }\n    }\n\nFinally, here is an example on how you can use it::\n\n    $loader = new DatabaseTwigLoader($dbh);\n    $twig = new \\Twig\\Environment($loader);\n\n    echo $twig->render('index.html.twig', ['name' => 'Fabien']);\n\nUsing different Template Sources\n--------------------------------\n\nThis recipe is the continuation of the previous one. Even if you store the\ncontributed templates in a database, you might want to keep the original/base\ntemplates on the filesystem. When templates can be loaded from different\nsources, you need to use the ``\\Twig\\Loader\\ChainLoader`` loader.\n\nAs you can see in the previous recipe, we reference the template in the exact\nsame way as we would have done it with a regular filesystem loader. This is\nthe key to be able to mix and match templates coming from the database, the\nfilesystem, or any other loader for that matter: the template name should be a\nlogical name, and not the path from the filesystem::\n\n    $loader1 = new DatabaseTwigLoader($dbh);\n    $loader2 = new \\Twig\\Loader\\ArrayLoader([\n        'base.html.twig' => '{% block content %}{% endblock %}',\n    ]);\n    $loader = new \\Twig\\Loader\\ChainLoader([$loader1, $loader2]);\n\n    $twig = new \\Twig\\Environment($loader);\n\n    echo $twig->render('index.html.twig', ['name' => 'Fabien']);\n\nNow that the ``base.html.twig`` templates is defined in an array loader, you can\nremove it from the database, and everything else will still work as before.\n\nLoading a Template from a String\n--------------------------------\n\nFrom a template, you can load a template stored in a string via the\n``template_from_string`` function (via the\n``\\Twig\\Extension\\StringLoaderExtension`` extension):\n\n.. code-block:: twig\n\n    {{ include(template_from_string(\"Hello {{ name }}\")) }}\n\nFrom PHP, it's also possible to load a template stored in a string via\n``\\Twig\\Environment::createTemplate()``::\n\n    $template = $twig->createTemplate('hello {{ name }}');\n    echo $template->render(['name' => 'Fabien']);\n\nUsing Twig and AngularJS in the same Templates\n----------------------------------------------\n\nMixing different template syntaxes in the same file is not a recommended\npractice as both AngularJS and Twig use the same delimiters in their syntax:\n``{{`` and ``}}``.\n\nStill, if you want to use AngularJS and Twig in the same template, there are\ntwo ways to make it work depending on the amount of AngularJS you need to\ninclude in your templates:\n\n* Escaping the AngularJS delimiters by wrapping AngularJS sections with the\n  ``{% verbatim %}`` tag or by escaping each delimiter via ``{{ '{{' }}`` and\n  ``{{ '}}' }}``;\n\n* Changing the delimiters of one of the template engines (depending on which\n  engine you introduced last):\n\n  * For AngularJS, change the interpolation tags using the\n    ``interpolateProvider`` service, for instance at the module initialization\n    time:\n\n    .. code-block:: javascript\n\n        angular.module('myApp', []).config(function($interpolateProvider) {\n            $interpolateProvider.startSymbol('{[').endSymbol(']}');\n        });\n\n  * For Twig, change the delimiters via the ``tag_variable`` Lexer option::\n\n        $env->setLexer(new \\Twig\\Lexer($env, [\n            'tag_variable' => ['{[', ']}'],\n        ]));\n\nMarking a Node as being safe\n----------------------------\n\nWhen using the escaper extension, you might want to mark some nodes as being\nsafe to avoid any escaping. You can do so by wrapping your expression with a\n``RawFilter`` node::\n\n    use Twig\\Node\\Expression\\Filter\\RawFilter;\n\n    $safeExpr = new RawFilter(new YourSafeNode());\n\n.. _callback: https://www.php.net/manual/en/function.is-callable.php\n"
  },
  {
    "path": "doc/sandbox.rst",
    "content": "Twig Sandbox\n============\n\nThe ``sandbox`` extension can be used to evaluate untrusted code.\n\nRegistering the Sandbox\n-----------------------\n\nRegister the ``SandboxExtension`` extension via the ``addExtension()`` method::\n\n    $twig->addExtension(new \\Twig\\Extension\\SandboxExtension($policy));\n\nConfiguring the Sandbox Policy\n------------------------------\n\nThe sandbox security is managed by a policy instance, which must be passed to\nthe ``SandboxExtension`` constructor.\n\nBy default, Twig comes with one policy class: ``\\Twig\\Sandbox\\SecurityPolicy``.\nThis class allows you to allow-list some tags, filters, functions, and\nproperties and methods on objects::\n\n    $tags = ['if'];\n    $filters = ['upper'];\n    $methods = [\n        'Article' => ['getTitle', 'getBody'],\n    ];\n    $properties = [\n        'Article' => ['title', 'body'],\n    ];\n    $functions = ['range'];\n    $policy = new \\Twig\\Sandbox\\SecurityPolicy($tags, $filters, $methods, $properties, $functions);\n\nWith the above configuration, the security policy will only allow usage of the\n``if`` tag, and the ``upper`` filter. Moreover, the templates will only be able\nto call the ``getTitle()`` and ``getBody()`` methods on ``Article`` objects,\nand the ``title`` and ``body`` public properties. Everything else won't be\nallowed and will generate a ``\\Twig\\Sandbox\\SecurityError`` exception.\n\n.. note::\n\n    As of Twig 3.14.1 (and on Twig 3.11.2), if the ``Article`` class implements\n    the ``ArrayAccess`` interface, the templates will only be able to access\n    the ``title`` and ``body`` attributes.\n\n    Note that native array-like classes (like ``ArrayObject``) are always\n    allowed, you don't need to configure them.\n\n.. caution::\n\n    The ``extends`` and ``use`` tags are always allowed in a sandboxed\n    template. That behavior will change in 4.0 where these tags will need to be\n    explicitly allowed like any other tag.\n\nEnabling the Sandbox\n--------------------\n\nBy default, the sandbox mode is disabled and should be enabled when including\nuntrusted template code by using the ``sandboxed`` option of the ``include``\nfunction:\n\n.. code-block:: twig\n\n    {{ include('user.html.twig', sandboxed: true) }}\n\nYou can sandbox all templates by passing ``true`` as the second argument of\nthe extension constructor::\n\n    $twig->addExtension(new \\Twig\\Extension\\SandboxExtension($policy, true));\n\nAccepting Callables Arguments\n-----------------------------\n\nThe Twig sandbox allows you to configure which functions, filters, tests and\ndot operations are allowed. Many of these calls can accept arguments. As these\narguments are not validated by the sandbox, you must be very careful.\n\nFor instance, accepting a PHP ``callable`` as an argument is dangerous as it\nallows end user to call any PHP function (by passing a ``string``) or any\nstatic methods (by passing an ``array``). For instance, it would accept any PHP\nbuilt-in functions like ``system()`` or ``exec()``::\n\n    $twig->addFilter(new \\Twig\\TwigFilter('custom', function (callable $callable) {\n        // ...\n        $callable();\n        // ...\n    }));\n\nTo avoid this security issue, don't type-hint such arguments with ``callable``\nbut use ``\\Closure`` instead (not using a type-hint would also be problematic).\nThis restricts the allowed callables to PHP closures only, which is enough to\naccept Twig arrow functions::\n\n    $twig->addFilter(new \\Twig\\TwigFilter('custom', function (\\Closure $callable) {\n        // ...\n        $callable();\n        // ...\n    }));\n\n    {{ people|custom(p => p.username|join(', ') }}\n\nAny PHP callable can easily be converted to a closure by using the `first-class callable syntax`_.\n\n.. _`first-class callable syntax`: https://www.php.net/manual/en/functions.first_class_callable_syntax.php\n"
  },
  {
    "path": "doc/tags/apply.rst",
    "content": "``apply``\n=========\n\nThe ``apply`` tag allows you to apply Twig filters on a block of template data:\n\n.. code-block:: twig\n\n    {% apply upper %}\n        This text becomes uppercase\n    {% endapply %}\n\nYou can also chain filters and pass arguments to them:\n\n.. code-block:: html+twig\n\n    {% apply lower|escape('html') %}\n        <strong>SOME TEXT</strong>\n    {% endapply %}\n\n    {# outputs \"&lt;strong&gt;some text&lt;/strong&gt;\" #}\n"
  },
  {
    "path": "doc/tags/autoescape.rst",
    "content": "``autoescape``\n==============\n\nWhether automatic escaping is enabled or not, you can mark a section of a\ntemplate to be escaped or not by using the ``autoescape`` tag:\n\n.. code-block:: twig\n\n    {% autoescape %}\n        Everything will be automatically escaped in this block\n        using the HTML strategy\n    {% endautoescape %}\n\n    {% autoescape 'html' %}\n        Everything will be automatically escaped in this block\n        using the HTML strategy\n    {% endautoescape %}\n\n    {% autoescape 'js' %}\n        Everything will be automatically escaped in this block\n        using the js escaping strategy\n    {% endautoescape %}\n\n    {% autoescape false %}\n        Everything will be outputted as is in this block\n    {% endautoescape %}\n\nWhen automatic escaping is enabled everything is escaped by default except for\nvalues explicitly marked as safe. Those can be marked in the template by using\nthe :doc:`raw<../filters/raw>` filter:\n\n.. code-block:: twig\n\n    {% autoescape %}\n        {{ safe_value|raw }}\n    {% endautoescape %}\n\nFunctions returning template data (like :doc:`macros<macro>` and\n:doc:`parent<../functions/parent>`) always return safe markup.\n\n.. note::\n\n    Twig is smart enough to not escape an already escaped value by the\n    :doc:`escape<../filters/escape>` filter when the automatic escaping\n    strategy is the same as the one applied by the escape filter.\n\n.. note::\n\n    Twig does not escape static expressions:\n\n    .. code-block:: html+twig\n\n        {% set hello = \"<strong>Hello</strong>\" %}\n        {{ hello }}\n        {{ \"<strong>world</strong>\" }}\n\n    Will be rendered \"<strong>Hello</strong> **world**\".\n\n.. note::\n\n    The chapter :doc:`Twig for Developers<../api>` gives more information\n    about when and how automatic escaping is applied.\n"
  },
  {
    "path": "doc/tags/block.rst",
    "content": "``block``\n=========\n\nBlocks are used for inheritance and act as placeholders and replacements at\nthe same time. They are documented in detail in the documentation for the\n:doc:`extends<../tags/extends>` tag.\n\nBlock names must consist of alphanumeric characters, and underscores. The first character can't be a digit and dashes are not permitted.\n\n.. seealso::\n\n    :doc:`block<../functions/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`, :doc:`extends<../tags/extends>`\n"
  },
  {
    "path": "doc/tags/cache.rst",
    "content": "``cache``\n=========\n\n.. versionadded:: 3.2\n\n    The ``cache`` tag was added in Twig 3.2.\n\nThe ``cache`` tag tells Twig to cache a template fragment:\n\n.. code-block:: twig\n\n    {% cache \"cache key\" %}\n        Cached forever (depending on the cache implementation)\n    {% endcache %}\n\nIf you want to expire the cache after a certain amount of time, specify an\nexpiration in seconds via the ``ttl()`` modifier:\n\n.. code-block:: twig\n\n    {% cache \"cache key\" ttl(300) %}\n        Cached for 300 seconds\n    {% endcache %}\n\nThe cache key can be any string that does not use the following reserved\ncharacters ``{}()/\\@:``; a good practice is to embed some useful information in\nthe key that allows the cache to automatically expire when it must be\nrefreshed:\n\n* Give each cache a unique name and namespace it like your templates;\n\n* Embed an integer that you increment whenever the template code changes (to\n  automatically invalidate all current caches);\n\n* Embed a unique key that is updated whenever the variables used in the\n  template code changes.\n\nFor instance, I would use ``{% cache \"blog_post;v1;\" ~ post.id ~ \";\" ~\npost.updated_at %}`` to cache a blog content template fragment where\n``blog_post`` describes the template fragment, ``v1`` represents the first\nversion of the template code, ``post.id`` represent the id of the blog post,\nand ``post.updated_at`` returns a timestamp that represents the time where the\nblog post was last modified.\n\nUsing such a strategy for naming cache keys allows you to avoid using a ``ttl``.\nIt's like using a \"validation\" strategy instead of an \"expiration\" strategy as\nwe do for HTTP caches.\n\nIf your cache implementation supports tags, you can also tag your cache items:\n\n.. code-block:: twig\n\n    {% cache \"cache key\" tags('blog') %}\n        Some code\n    {% endcache %}\n\n    {% cache \"cache key\" tags(['cms', 'blog']) %}\n        Some code\n    {% endcache %}\n\nThe ``cache`` tag creates a new \"scope\" for variables, meaning that the changes\nare local to the template fragment:\n\n.. code-block:: twig\n\n    {% set count = 1 %}\n\n    {% cache \"cache key\" tags('blog') %}\n        {# Won't affect the value of count outside of the cache tag #}\n        {% set count = 2 %}\n        Some code\n    {% endcache %}\n\n    {# Displays 1 #}\n    {{ count }}\n\n.. note::\n\n    The ``cache`` tag is part of the ``CacheExtension`` which is not installed\n    by default. Install it first:\n\n    .. code-block:: bash\n\n        $ composer require twig/cache-extra\n\n    On Symfony projects, you can automatically enable it by installing the\n    ``twig/extra-bundle``:\n\n    .. code-block:: bash\n\n        $ composer require twig/extra-bundle\n\n    Or add the extension explicitly on the Twig environment::\n\n        use Twig\\Extra\\Cache\\CacheExtension;\n\n        $twig = new \\Twig\\Environment(...);\n        $twig->addExtension(new CacheExtension());\n\n    If you are not using Symfony, you must also register the extension runtime::\n\n        use Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter;\n        use Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter;\n        use Twig\\Extra\\Cache\\CacheRuntime;\n        use Twig\\RuntimeLoader\\RuntimeLoaderInterface;\n\n        $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {\n            public function load($class) {\n                if (CacheRuntime::class === $class) {\n                    return new CacheRuntime(new TagAwareAdapter(new FilesystemAdapter()));\n                }\n            }\n        });\n"
  },
  {
    "path": "doc/tags/deprecated.rst",
    "content": "``deprecated``\n==============\n\nTwig generates a deprecation notice (via a call to the ``trigger_error()``\nPHP function) where the ``deprecated`` tag is used in a template:\n\n.. code-block:: twig\n\n    {# base.html.twig #}\n    {% deprecated 'The \"base.html.twig\" template is deprecated, use \"layout.html.twig\" instead.' %}\n    {% extends 'layout.html.twig' %}\n\nYou can also deprecate a macro in the following way:\n\n.. code-block:: twig\n\n    {% macro welcome(name) %}\n        {% deprecated 'The \"welcome\" macro is deprecated, use \"hello\" instead.' %}\n\n        ...\n    {% endmacro %}\n\nNote that by default, the deprecation notices are silenced and never displayed nor logged.\nSee :ref:`deprecation-notices` to learn how to handle them.\n\n.. versionadded:: 3.11\n\n    The ``package`` and ``version`` options were added in Twig 3.11.\n\nYou can optionally add the package and the version that introduced the deprecation:\n\n.. code-block:: twig\n\n    {% deprecated 'The \"base.html.twig\" template is deprecated, use \"layout.html.twig\" instead.' package='twig/twig' %}\n    {% deprecated 'The \"base.html.twig\" template is deprecated, use \"layout.html.twig\" instead.' package='twig/twig' version='3.11' %}\n\n.. note::\n\n    Don't use the ``deprecated`` tag to deprecate a ``block`` as the\n    deprecation cannot always be triggered correctly.\n"
  },
  {
    "path": "doc/tags/do.rst",
    "content": "``do``\n======\n\nThe ``do`` tag works exactly like the regular variable expression (``{{ ...\n}}``) just that it doesn't print anything:\n\n.. code-block:: twig\n\n    {% do 1 + 2 %}\n"
  },
  {
    "path": "doc/tags/embed.rst",
    "content": "``embed``\n=========\n\nThe ``embed`` tag combines the behavior of :doc:`include<include>` and\n:doc:`extends<extends>`.\nIt allows you to include another template's contents, just like ``include``\ndoes. But it also allows you to override any block defined inside the\nincluded template, like when extending a template.\n\nThink of an embedded template as a \"micro layout skeleton\".\n\n.. code-block:: twig\n\n    {% embed \"teasers_skeleton.html.twig\" %}\n        {# These blocks are defined in \"teasers_skeleton.html.twig\" #}\n        {# and we override them right here:                    #}\n        {% block left_teaser %}\n            Some content for the left teaser box\n        {% endblock %}\n        {% block right_teaser %}\n            Some content for the right teaser box\n        {% endblock %}\n    {% endembed %}\n\nThe ``embed`` tag takes the idea of template inheritance to the level of\ncontent fragments. While template inheritance allows for \"document skeletons\",\nwhich are filled with life by child templates, the ``embed`` tag allows you to\ncreate \"skeletons\" for smaller units of content and re-use and fill them\nanywhere you like.\n\nSince the use case may not be obvious, let's look at a simplified example.\nImagine a base template shared by multiple HTML pages, defining a single block\nnamed \"content\":\n\n.. code-block:: text\n\n    ┌─── page layout ─────────────────────┐\n    │                                     │\n    │           ┌── block \"content\" ──┐   │\n    │           │                     │   │\n    │           │                     │   │\n    │           │ (child template to  │   │\n    │           │  put content here)  │   │\n    │           │                     │   │\n    │           │                     │   │\n    │           └─────────────────────┘   │\n    │                                     │\n    └─────────────────────────────────────┘\n\nSome pages (\"page_1\" and \"page_2\") share the same content structure -\ntwo vertically stacked boxes:\n\n.. code-block:: text\n\n    ┌─── page layout ─────────────────────┐\n    │                                     │\n    │           ┌── block \"content\" ──┐   │\n    │           │ ┌─ block \"top\" ───┐ │   │\n    │           │ │                 │ │   │\n    │           │ └─────────────────┘ │   │\n    │           │ ┌─ block \"bottom\" ┐ │   │\n    │           │ │                 │ │   │\n    │           │ └─────────────────┘ │   │\n    │           └─────────────────────┘   │\n    │                                     │\n    └─────────────────────────────────────┘\n\nWhile other pages (\"page_3\" and \"page_4\") share a different content structure -\ntwo boxes side by side:\n\n.. code-block:: text\n\n    ┌─── page layout ─────────────────────┐\n    │                                     │\n    │           ┌── block \"content\" ──┐   │\n    │           │                     │   │    \n    │           │ ┌ block ┐ ┌ block ┐ │   │\n    │           │ │\"left\" │ │\"right\"│ │   │\n    │           │ │       │ │       │ │   │\n    │           │ │       │ │       │ │   │\n    │           │ └───────┘ └───────┘ │   │\n    │           └─────────────────────┘   │\n    │                                     │\n    └─────────────────────────────────────┘\n\nWithout the ``embed`` tag, you have two ways to design your templates:\n\n* Create two \"intermediate\" base templates that extend the master layout\n  template: one with vertically stacked boxes to be used by the \"page_1\" and\n  \"page_2\" pages and another one with side-by-side boxes for the \"page_3\" and\n  \"page_4\" pages.\n\n* Embed the markup for the top/bottom and left/right boxes into each page\n  template directly.\n\nThese two solutions do not scale well because they each have a major drawback:\n\n* The first solution may indeed work for this simplified example. But imagine\n  we add a sidebar, which may again contain different, recurring structures\n  of content. Now we would need to create intermediate base templates for\n  all occurring combinations of content structure and sidebar structure...\n  and so on.\n\n* The second solution involves duplication of common code with all its negative\n  consequences: any change involves finding and editing all affected copies\n  of the structure, correctness has to be verified for each copy, copies may\n  go out of sync by careless modifications etc.\n\nIn such a situation, the ``embed`` tag comes in handy. The common layout\ncode can live in a single base template, and the two different content structures,\nlet's call them \"micro layouts\" go into separate templates which are embedded\nas necessary:\n\nPage template ``page_1.html.twig``:\n\n.. code-block:: twig\n\n    {% extends \"layout_skeleton.html.twig\" %}\n\n    {% block content %}\n        {% embed \"vertical_boxes_skeleton.html.twig\" %}\n            {% block top %}\n                Some content for the top box\n            {% endblock %}\n\n            {% block bottom %}\n                Some content for the bottom box\n            {% endblock %}\n        {% endembed %}\n    {% endblock %}\n\nAnd here is the code for ``vertical_boxes_skeleton.html.twig``:\n\n.. code-block:: html+twig\n\n    <div class=\"top_box\">\n        {% block top %}\n            Top box default content\n        {% endblock %}\n    </div>\n\n    <div class=\"bottom_box\">\n        {% block bottom %}\n            Bottom box default content\n        {% endblock %}\n    </div>\n\nThe goal of the ``vertical_boxes_skeleton.html.twig`` template being to factor\nout the HTML markup for the boxes.\n\nThe ``embed`` tag takes the exact same arguments as the ``include`` tag:\n\n.. code-block:: twig\n\n    {% embed \"base\" with {'name': 'Fabien'} %}\n        ...\n    {% endembed %}\n\n    {% embed \"base\" with {'name': 'Fabien'} only %}\n        ...\n    {% endembed %}\n\n    {% embed \"base\" ignore missing %}\n        ...\n    {% endembed %}\n\n.. warning::\n\n    As embedded templates do not have \"names\", auto-escaping strategies based\n    on the template name won't work as expected if you change the context (for\n    instance, if you embed a CSS/JavaScript template into an HTML one). In that\n    case, explicitly set the default auto-escaping strategy with the\n    ``autoescape`` tag.\n\n.. seealso::\n\n    :doc:`include<../tags/include>`\n"
  },
  {
    "path": "doc/tags/extends.rst",
    "content": "``extends``\n===========\n\nThe ``extends`` tag can be used to extend a template from another one.\n\n.. note::\n\n    Like PHP, Twig does not support multiple inheritance. So you can only have\n    one extends tag called per rendering. However, Twig supports horizontal\n    :doc:`reuse<use>`.\n\nLet's define a base template, ``base.html.twig``, which defines a simple HTML\nskeleton document:\n\n.. code-block:: html+twig\n\n    <!DOCTYPE html>\n    <html>\n        <head>\n            {% block head %}\n                <link rel=\"stylesheet\" href=\"style.css\"/>\n                <title>{% block title %}{% endblock %} - My Webpage</title>\n            {% endblock %}\n        </head>\n        <body>\n            <main id=\"content\">{% block content %}{% endblock %}</main>\n            <footer id=\"footer\">\n                {% block footer %}\n                    &copy; Copyright 2011 by <a href=\"https://example.com/\">you</a>.\n                {% endblock %}\n            </footer>\n        </body>\n    </html>\n\nIn this example, the :doc:`block<block>` tags define four blocks that child\ntemplates can fill in.\n\nAll the ``block`` tag does is to tell the template engine that a child\ntemplate may override those portions of the template.\n\nChild Template\n--------------\n\nA child template might look like this:\n\n.. code-block:: html+twig\n\n    {% extends \"base.html.twig\" %}\n\n    {% block title %}Index{% endblock %}\n    {% block head %}\n        {{ parent() }}\n        <style type=\"text/css\">\n            .important { color: #336699; }\n        </style>\n    {% endblock %}\n    {% block content %}\n        <h1>Index</h1>\n        <p class=\"important\">\n            Welcome on my awesome homepage.\n        </p>\n    {% endblock %}\n\nThe ``extends`` tag is the key here. It tells the template engine that this\ntemplate \"extends\" another template. When the template system evaluates this\ntemplate, first it locates the parent. The extends tag should be the first tag\nin the template.\n\nNote that since the child template doesn't define the ``footer`` block, the\nvalue from the parent template is used instead.\n\nYou can't define multiple ``block`` tags with the same name in the same\ntemplate. This limitation exists because a block tag works in \"both\"\ndirections. That is, a block tag doesn't just provide a hole to fill - it also\ndefines the content that fills the hole in the *parent*. If there were two\nsimilarly-named ``block`` tags in a template, that template's parent wouldn't\nknow which one of the blocks' content to use.\n\nIf you want to print a block multiple times you can however use the\n``block`` function:\n\n.. code-block:: html+twig\n\n    <title>{% block title %}{% endblock %}</title>\n    <h1>{{ block('title') }}</h1>\n    {% block body %}{% endblock %}\n\nParent Blocks\n-------------\n\nIt's possible to render the contents of the parent block by using the\n:doc:`parent<../functions/parent>` function. This gives back the results of\nthe parent block:\n\n.. code-block:: html+twig\n\n    {% block sidebar %}\n        <h3>Table Of Contents</h3>\n        ...\n        {{ parent() }}\n    {% endblock %}\n\nNamed Block End-Tags\n--------------------\n\nTwig allows you to put the name of the block after the end tag for better\nreadability (the name after the ``endblock`` word must match the block name):\n\n.. code-block:: twig\n\n    {% block sidebar %}\n        {% block inner_sidebar %}\n            ...\n        {% endblock inner_sidebar %}\n    {% endblock sidebar %}\n\nBlock Nesting and Scope\n-----------------------\n\nBlocks can be nested for more complex layouts. Per default, blocks have access\nto variables from outer scopes:\n\n.. code-block:: html+twig\n\n    {% for item in seq %}\n        <li>{% block loop_item %}{{ item }}{% endblock %}</li>\n    {% endfor %}\n\nBlock Shortcuts\n---------------\n\nFor blocks with little content, it's possible to use a shortcut syntax. The\nfollowing constructs do the same thing:\n\n.. code-block:: twig\n\n    {% block title %}\n        {{ page_title|title }}\n    {% endblock %}\n\n.. code-block:: twig\n\n    {% block title page_title|title %}\n\nDynamic Inheritance\n-------------------\n\nTwig supports dynamic inheritance by using a variable as the base template:\n\n.. code-block:: twig\n\n    {% extends some_var %}\n\nIf the variable evaluates to a ``\\Twig\\Template`` or a ``\\Twig\\TemplateWrapper``\ninstance, Twig will use it as the parent template::\n\n    // {% extends layout %}\n\n    $layout = $twig->load('some_layout_template.html.twig');\n\n    $twig->display('template.html.twig', ['layout' => $layout]);\n\nYou can also provide a list of templates that are checked for existence. The\nfirst template that exists will be used as a parent:\n\n.. code-block:: twig\n\n    {% extends ['layout.html.twig', 'base_layout.html.twig'] %}\n\nConditional Inheritance\n-----------------------\n\nAs the template name for the parent can be any valid Twig expression, it's\npossible to make the inheritance mechanism conditional:\n\n.. code-block:: twig\n\n    {% extends standalone ? \"minimum.html.twig\" : \"base.html.twig\" %}\n\nIn this example, the template will extend the \"minimum.html.twig\" layout template\nif the ``standalone`` variable evaluates to ``true``, and \"base.html.twig\"\notherwise.\n\nHow do blocks work?\n-------------------\n\nA block provides a way to change how a certain part of a template is rendered\nbut it does not interfere in any way with the logic around it.\n\nLet's take the following example to illustrate how a block works and more\nimportantly, how it does not work:\n\n.. code-block:: html+twig\n\n    {# base.html.twig #}\n    {% for post in posts %}\n        {% block post %}\n            <h1>{{ post.title }}</h1>\n            <p>{{ post.body }}</p>\n        {% endblock %}\n    {% endfor %}\n\nIf you render this template, the result would be exactly the same with or\nwithout the ``block`` tag. The ``block`` inside the ``for`` loop is just a way\nto make it overridable by a child template:\n\n.. code-block:: html+twig\n\n    {# child.html.twig #}\n    {% extends \"base.html.twig\" %}\n\n    {% block post %}\n        <article>\n            <header>{{ post.title }}</header>\n            <section>{{ post.text }}</section>\n        </article>\n    {% endblock %}\n\nNow, when rendering the child template, the loop is going to use the block\ndefined in the child template instead of the one defined in the base one; the\nexecuted template is then equivalent to the following one:\n\n.. code-block:: html+twig\n\n    {% for post in posts %}\n        <article>\n            <header>{{ post.title }}</header>\n            <section>{{ post.text }}</section>\n        </article>\n    {% endfor %}\n\nLet's take another example: a block included within an ``if`` statement:\n\n.. code-block:: html+twig\n\n    {% if posts is empty %}\n        {% block head %}\n            {{ parent() }}\n\n            <meta name=\"robots\" content=\"noindex, follow\">\n        {% endblock head %}\n    {% endif %}\n\nContrary to what you might think, this template does not define a block\nconditionally; it just makes overridable by a child template the output of\nwhat will be rendered when the condition is ``true``.\n\nIf you want the output to be displayed conditionally, use the following\ninstead:\n\n.. code-block:: html+twig\n\n    {% block head %}\n        {{ parent() }}\n\n        {% if posts is empty %}\n            <meta name=\"robots\" content=\"noindex, follow\">\n        {% endif %}\n    {% endblock head %}\n\n.. seealso::\n\n    :doc:`block<../functions/block>`, :doc:`block<../tags/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`\n"
  },
  {
    "path": "doc/tags/flush.rst",
    "content": "``flush``\n=========\n\nThe ``flush`` tag tells Twig to flush the output buffer:\n\n.. code-block:: twig\n\n    {% flush %}\n\n.. note::\n\n    Internally, Twig uses the PHP `flush`_ function.\n\n.. _`flush`: https://www.php.net/flush\n"
  },
  {
    "path": "doc/tags/for.rst",
    "content": "``for``\n=======\n\nLoop over each item in a sequence or a mapping. For example, to display a list\nof users provided in a variable called ``users``:\n\n.. code-block:: html+twig\n\n    <h1>Members</h1>\n    <ul>\n        {% for user in users %}\n            <li>{{ user.username|e }}</li>\n        {% endfor %}\n    </ul>\n\n.. note::\n\n    A sequence or a mapping can be either an array or an object implementing\n    the ``Traversable`` interface.\n\nIf you do need to iterate over a sequence of numbers, you can use the ``..``\noperator:\n\n.. code-block:: twig\n\n    {% for i in 0..10 %}\n        * {{ i }}\n    {% endfor %}\n\nThe above snippet of code would print all numbers from 0 to 10.\n\nIt can be also useful with letters:\n\n.. code-block:: twig\n\n    {% for letter in 'a'..'z' %}\n        * {{ letter }}\n    {% endfor %}\n\nThe ``..`` operator can take any expression at both sides:\n\n.. code-block:: twig\n\n    {% for letter in 'a'|upper..'z'|upper %}\n        * {{ letter }}\n    {% endfor %}\n\n.. tip::\n\n    If you need a step different from 1, you can use the ``range`` function\n    instead.\n\nThe ``loop`` variable\n---------------------\n\nInside of a ``for`` loop block you can access some special variables:\n\n===================== =============================================================\nVariable              Description\n===================== =============================================================\n``loop.index``        The current iteration of the loop. (1 indexed)\n``loop.index0``       The current iteration of the loop. (0 indexed)\n``loop.revindex``     The number of iterations from the end of the loop (1 indexed)\n``loop.revindex0``    The number of iterations from the end of the loop (0 indexed)\n``loop.first``        True if first iteration\n``loop.last``         True if last iteration\n``loop.length``       The number of items in the sequence\n``loop.parent``       The parent context\n===================== =============================================================\n\n.. code-block:: twig\n\n    {% for user in users %}\n        {{ loop.index }} - {{ user.username }}\n    {% endfor %}\n\n.. note::\n\n    The ``loop.length``, ``loop.revindex``, ``loop.revindex0``, and\n    ``loop.last`` variables are only available for PHP arrays, or objects that\n    implement the ``Countable`` interface.\n\nThe ``else`` Clause\n-------------------\n\nIf no iteration took place because the sequence was empty, you can render a\nreplacement block by using ``else``:\n\n.. code-block:: html+twig\n\n    <ul>\n        {% for user in users %}\n            <li>{{ user.username|e }}</li>\n        {% else %}\n            <li><em>no user found</em></li>\n        {% endfor %}\n    </ul>\n\nIterating over Keys\n-------------------\n\nBy default, a loop iterates over the values of the sequence. You can iterate\non keys by using the ``keys`` filter:\n\n.. code-block:: html+twig\n\n    <h1>Members</h1>\n    <ul>\n        {% for key in users|keys %}\n            <li>{{ key }}</li>\n        {% endfor %}\n    </ul>\n\nIterating over Keys and Values\n------------------------------\n\nYou can also access both keys and values:\n\n.. code-block:: html+twig\n\n    <h1>Members</h1>\n    <ul>\n        {% for key, user in users %}\n            <li>{{ key }}: {{ user.username|e }}</li>\n        {% endfor %}\n    </ul>\n\nIterating over a Subset\n-----------------------\n\nYou might want to iterate over a subset of values. This can be achieved using\nthe :doc:`slice <../filters/slice>` filter:\n\n.. code-block:: html+twig\n\n    <h1>Top Ten Members</h1>\n    <ul>\n        {% for user in users|slice(0, 10) %}\n            <li>{{ user.username|e }}</li>\n        {% endfor %}\n    </ul>\n\nIterating over a String\n-----------------------\n\nTo iterate over the characters of a string, use the\n:doc:`split <../filters/split>` filter:\n\n.. code-block:: html+twig\n\n    <h1>Characters</h1>\n    <ul>\n        {% for char in \"諺 / ことわざ\"|split('') -%}\n            <li>{{ char }}</li>\n        {%- endfor %}\n    </ul>\n"
  },
  {
    "path": "doc/tags/from.rst",
    "content": "``from``\n========\n\nThe ``from`` tag imports :doc:`macro<../tags/macro>` names into the current\nnamespace. The tag is documented in detail in the documentation for the\n:doc:`macro<../tags/macro>` tag.\n"
  },
  {
    "path": "doc/tags/guard.rst",
    "content": "``guard``\n=========\n\n.. versionadded:: 3.15\n\n    The ``guard`` tag was added in Twig 3.15.\n\nThe ``guard`` statement checks if some Twig callables are available at\n**compilation time** to bypass code compilation that would otherwise fail.\n\n.. code-block:: twig\n\n    {% guard function importmap %}\n        {{ importmap('app') }}\n    {% endguard %}\n\nThe first argument is the Twig callable to test: ``filter``, ``function``, or\n``test``. The second argument is the Twig callable name you want to test.\n\nYou can also generate different code if the callable does not exist:\n\n.. code-block:: twig\n\n    {% guard function importmap %}\n        {{ importmap('app') }}\n    {% else %}\n        {# the importmap function doesn't exist, generate fallback code #}\n    {% endguard %}\n"
  },
  {
    "path": "doc/tags/if.rst",
    "content": "``if``\n======\n\nThe ``if`` statement in Twig is comparable with the if statements of PHP.\n\nIn the simplest form you can use it to test if an expression evaluates to\n``true``:\n\n.. code-block:: html+twig\n\n    {% if online == false %}\n        <p>Our website is in maintenance mode. Please, come back later.</p>\n    {% endif %}\n\nYou can also test if a sequence or a mapping is not empty:\n\n.. code-block:: html+twig\n\n    {% if users %}\n        <ul>\n            {% for user in users %}\n                <li>{{ user.username|e }}</li>\n            {% endfor %}\n        </ul>\n    {% endif %}\n\n.. note::\n\n    If you want to test if the variable is defined, use ``if users is defined`` instead.\n\nYou can also use ``not`` to check for values that evaluate to ``false``:\n\n.. code-block:: html+twig\n\n    {% if not user.subscribed %}\n        <p>You are not subscribed to our mailing list.</p>\n    {% endif %}\n\nFor multiple conditions, ``and`` and ``or`` can be used:\n\n.. code-block:: html+twig\n\n    {% if temperature > 18 and temperature < 27 %}\n        <p>It's a nice day for a walk in the park.</p>\n    {% endif %}\n\nFor multiple branches ``elseif`` and ``else`` can be used like in PHP. You can\nuse more complex ``expressions`` there too:\n\n.. code-block:: twig\n\n    {% if product.stock > 10 %}\n       Available\n    {% elseif product.stock > 0 %}\n       Only {{ product.stock }} left!\n    {% else %}\n       Sold-out!\n    {% endif %}\n\n.. note::\n\n    The rules to determine if an expression is ``true`` or ``false`` are the\n    same as in PHP; here are the edge cases rules:\n\n    ====================== ====================\n    Value                  Boolean evaluation\n    ====================== ====================\n    empty string           false\n    numeric zero           false\n    NAN (Not A Number)     true\n    INF (Infinity)         true\n    whitespace-only string true\n    string \"0\" or '0'      false\n    empty sequence         false\n    empty mapping          false\n    null                   false\n    non-empty sequence     true\n    non-empty mapping      true\n    object                 true\n    ====================== ====================\n"
  },
  {
    "path": "doc/tags/import.rst",
    "content": "``import``\n==========\n\nThe ``import`` tag imports :doc:`macro<../tags/macro>` names in a local\nvariable. The tag is documented in detail in the documentation for the\n:doc:`macro<../tags/macro>` tag.\n"
  },
  {
    "path": "doc/tags/include.rst",
    "content": "``include``\n===========\n\nThe ``include`` statement includes a template and outputs the rendered content\nof that file:\n\n.. code-block:: twig\n\n    {% include 'header.html.twig' %}\n        Body\n    {% include 'footer.html.twig' %}\n\n.. note::\n\n    It is recommended to use the :doc:`include<../functions/include>` function\n    instead as it provides the same features with a bit more flexibility:\n\n    * The ``include`` function is semantically more \"correct\" (including a\n      template outputs its rendered contents in the current scope; a tag should\n      not display anything);\n\n    * The ``include`` function is more \"composable\":\n\n      .. code-block:: twig\n\n          {# Store a rendered template in a variable #}\n          {% set content %}\n              {% include 'template.html.twig' %}\n          {% endset %}\n          {# vs #}\n          {% set content = include('template.html.twig') %}\n\n          {# Apply filter on a rendered template #}\n          {% apply upper %}\n              {% include 'template.html.twig' %}\n          {% endapply %}\n          {# vs #}\n          {{ include('template.html.twig')|upper }}\n\n    * The ``include`` function does not impose any specific order for\n      arguments thanks to :ref:`named arguments <named-arguments>`.\n\nIncluded templates have access to the variables of the active context.\n\nIf you are using the filesystem loader, the templates are looked for in the\npaths defined by it.\n\nYou can add additional variables by passing them after the ``with`` keyword:\n\n.. code-block:: twig\n\n    {# template.html.twig will have access to the variables from the current context and the additional ones provided #}\n    {% include 'template.html.twig' with {'name': 'Fabien'} %}\n\n    {% set vars = {'name': 'Fabien'} %}\n    {% include 'template.html.twig' with vars %}\n\nYou can disable access to the context by appending the ``only`` keyword:\n\n.. code-block:: twig\n\n    {# only the name variable will be accessible #}\n    {% include 'template.html.twig' with {'name': 'Fabien'} only %}\n\n.. code-block:: twig\n\n    {# no variables will be accessible #}\n    {% include 'template.html.twig' only %}\n\n.. tip::\n\n    When including a template created by an end user, you should consider\n    sandboxing it. More information in the :doc:`Twig Sandbox<../sandbox>`\n    chapter.\n\nThe template name can be any valid Twig expression:\n\n.. code-block:: twig\n\n    {% include some_var %}\n    {% include ajax ? 'ajax.html.twig' : 'not_ajax.html.twig' %}\n\nAnd if the expression evaluates to a ``\\Twig\\Template`` or a\n``\\Twig\\TemplateWrapper`` instance, Twig will use it directly::\n\n    // {% include template %}\n\n    $template = $twig->load('some_template.html.twig');\n\n    $twig->display('template.html.twig', ['template' => $template]);\n\nYou can mark an include with ``ignore missing`` in which case Twig will ignore\nthe statement if the template to be included does not exist. It has to be\nplaced just after the template name. Here some valid examples:\n\n.. code-block:: twig\n\n    {% include 'sidebar.html.twig' ignore missing %}\n    {% include 'sidebar.html.twig' ignore missing with {'name': 'Fabien'} %}\n    {% include 'sidebar.html.twig' ignore missing only %}\n\nYou can also provide a list of templates that are checked for existence before\ninclusion. The first template that exists will be included:\n\n.. code-block:: twig\n\n    {% include ['page_detailed.html.twig', 'page.html.twig'] %}\n\nIf ``ignore missing`` is given, it will fall back to rendering nothing if none\nof the templates exist, otherwise it will throw an exception.\n"
  },
  {
    "path": "doc/tags/index.rst",
    "content": "Tags\n====\n\n.. toctree::\n    :maxdepth: 1\n\n    apply\n    autoescape\n    block\n    cache\n    deprecated\n    do\n    embed\n    extends\n    flush\n    for\n    from\n    guard\n    if\n    import\n    include\n    macro\n    sandbox\n    set\n    types\n    use\n    verbatim\n    with\n"
  },
  {
    "path": "doc/tags/macro.rst",
    "content": "``macro``\n=========\n\nMacros are comparable with functions in regular programming languages. They\nare useful to reuse template fragments to not repeat yourself.\n\nMacros are defined in regular templates.\n\nImagine having a generic helper template that define how to render HTML forms\nvia macros (called ``forms.twig``):\n\n.. code-block:: html+twig\n\n    {% macro input(name, value, type = \"text\", size = 20) %}\n        <input type=\"{{ type }}\" name=\"{{ name }}\" value=\"{{ value|e }}\" size=\"{{ size }}\"/>\n    {% endmacro %}\n\n    {% macro textarea(name, value, rows = 10, cols = 40) %}\n        <textarea name=\"{{ name }}\" rows=\"{{ rows }}\" cols=\"{{ cols }}\">{{ value|e }}</textarea>\n    {% endmacro %}\n\nEach macro argument can have a default value (here ``text`` is the default value\nfor ``type`` if not provided in the call).\n\nMacros differ from native PHP functions in a few ways:\n\n* Arguments of a macro are always optional.\n\n* If extra positional arguments are passed to a macro, they end up in the\n  special ``varargs`` variable as a list of values.\n\nBut as with PHP functions, macros don't have access to the current template\nvariables.\n\n.. tip::\n\n    You can pass the whole context as an argument by using the special\n    ``_context`` variable.\n\nImporting Macros\n----------------\n\nThere are two ways to import macros. You can import the complete template\ncontaining the macros into a local variable (via the ``import`` tag) or only\nimport specific macros from the template (via the ``from`` tag).\n\nTo import all macros from a template into a local variable, use the ``import``\ntag:\n\n.. code-block:: twig\n\n    {% import \"forms.html.twig\" as forms %}\n\nThe above ``import`` call imports the ``forms.html.twig`` file (which can contain\nonly macros, or a template and some macros), and import the macros as\nattributes of the ``forms`` local variable.\n\nThe macros can then be called at will in the *current* template:\n\n.. code-block:: html+twig\n\n    <p>{{ forms.input('username') }}</p>\n    <p>{{ forms.input('password', null, 'password') }}</p>\n    {# You can also use named arguments #}\n    <p>{{ forms.input(name: 'password', type: 'password') }}</p>\n\nAlternatively you can import names from the template into the current namespace\nvia the ``from`` tag:\n\n.. code-block:: html+twig\n\n    {% from 'forms.html.twig' import input as input_field, textarea %}\n\n    <p>{{ input_field('password', '', 'password') }}</p>\n    <p>{{ input_field(name: 'password', type: 'password') }}</p>\n    <p>{{ textarea('comment') }}</p>\n\n.. caution::\n\n    As macros imported via ``from`` are called like functions, be careful that\n    they shadow existing functions:\n\n    .. code-block:: twig\n\n        {% from 'forms.html.twig' import input as include %}\n\n        {# include refers to the macro and not to the built-in \"include\" function #}\n        {{ include() }}\n\n.. tip::\n\n    When macro usages and definitions are in the same template, you don't need to\n    import the macros as they are automatically available under the special\n    ``_self`` variable:\n\n    .. code-block:: html+twig\n\n        <p>{{ _self.input('password', '', 'password') }}</p>\n\n        {% macro input(name, value, type = \"text\", size = 20) %}\n            <input type=\"{{ type }}\" name=\"{{ name }}\" value=\"{{ value|e }}\" size=\"{{ size }}\"/>\n        {% endmacro %}\n\nMacros Scoping\n--------------\n\nThe scoping rules are the same whether you imported macros via ``import`` or\n``from``.\n\nImported macros are always **local** to the current template. It means that\nmacros are available in all blocks and other macros defined in the current\ntemplate, but they are not available in included templates or child templates;\nyou need to explicitly re-import macros in each template.\n\nImported macros are not available in the body of ``embed`` tags, you need\nto explicitly re-import macros inside the tag.\n\nWhen calling ``import`` or ``from`` from a ``block`` tag, the imported macros\nare only defined in the current block and they shadow macros defined at the\ntemplate level with the same names.\n\nChecking if a Macro is defined\n------------------------------\n\nYou can check if a macro is defined via the ``defined`` test:\n\n.. code-block:: twig\n\n    {% import \"macros.html.twig\" as macros %}\n\n    {% from \"macros.html.twig\" import hello %}\n\n    {% if macros.hello is defined -%}\n        OK\n    {% endif %}\n\n    {% if hello is defined -%}\n        OK\n    {% endif %}\n\nNamed Macro End-Tags\n--------------------\n\nTwig allows you to put the name of the macro after the end tag for better\nreadability (the name after the ``endmacro`` word must match the macro name):\n\n.. code-block:: twig\n\n    {% macro input() %}\n        ...\n    {% endmacro input %}\n"
  },
  {
    "path": "doc/tags/sandbox.rst",
    "content": "``sandbox``\n===========\n\n.. warning::\n\n    The ``sandbox`` tag is deprecated as of Twig 3.15.\n    Use the ``sandboxed`` option of the ``include`` function instead.\n\nThe ``sandbox`` tag can be used to enable the sandboxing mode for an included\ntemplate, when sandboxing is not enabled globally for the Twig environment:\n\n.. code-block:: twig\n\n    {% sandbox %}\n        {% include 'user.html.twig' %}\n    {% endsandbox %}\n\n.. warning::\n\n    The ``sandbox`` tag is only available when the sandbox extension is\n    enabled (see the :doc:`Twig for Developers<../api>` chapter).\n\n.. note::\n\n    The ``sandbox`` tag can only be used to sandbox an include tag and it\n    cannot be used to sandbox a section of a template. The following example\n    won't work:\n\n    .. code-block:: twig\n\n        {% sandbox %}\n            {% for i in 1..2 %}\n                {{ i }}\n            {% endfor %}\n        {% endsandbox %}\n"
  },
  {
    "path": "doc/tags/set.rst",
    "content": "``set``\n=======\n\nInside code blocks you can also assign values to variables. Assignments use\nthe ``set`` tag and can have multiple targets.\n\nHere is how you can assign the ``Fabien`` value to the ``name`` variable:\n\n.. code-block:: twig\n\n    {% set name = 'Fabien' %}\n\nAfter the ``set`` call, the ``name`` variable is available in the template like\nany other ones:\n\n.. code-block:: twig\n\n    {# displays Fabien #}\n    {{ name }}\n\nThe assigned value can be any valid :ref:`Twig expression\n<twig-expressions>`:\n\n.. code-block:: twig\n\n    {% set numbers = [1, 2] %}\n    {% set user = {'name': 'Fabien'} %}\n    {% set name = 'Fabien' ~ ' ' ~ 'Potencier' %}\n\n.. tip::\n\n    To assign a value within an expression, use the :ref:`= operator\n    <templates-assignment-operator>`:\n\n    .. code-block:: twig\n\n        {# use assignment within a larger expression #}\n        {{ (result = fetch_data()) ? result : 'default' }}\n\nSeveral variables can be assigned in one block:\n\n.. code-block:: twig\n\n    {% set first, last = 'Fabien', 'Potencier' %}\n\n    {# is equivalent to #}\n\n    {% set first = 'Fabien' %}\n    {% set last = 'Potencier' %}\n\nThe ``set`` tag can also be used to \"capture\" chunks of text:\n\n.. code-block:: html+twig\n\n    {% set content %}\n        <div id=\"pagination\">\n            ...\n        </div>\n    {% endset %}\n\n.. caution::\n\n    If you enable automatic output escaping, Twig will only consider the\n    content to be safe when capturing chunks of text.\n\n.. note::\n\n    Note that loops are scoped in Twig; therefore a variable declared inside a\n    ``for`` loop is not accessible outside the loop itself:\n\n    .. code-block:: twig\n\n        {% for item in items %}\n            {% set value = item %}\n        {% endfor %}\n\n        {# value is NOT available #}\n\n    If you want to access the variable, just declare it before the loop:\n\n    .. code-block:: twig\n\n        {% set value = \"\" %}\n        {% for item in items %}\n            {% set value = item %}\n        {% endfor %}\n\n        {# value is available #}\n"
  },
  {
    "path": "doc/tags/types.rst",
    "content": "``types``\n=========\n\n.. versionadded:: 3.13\n\n    The ``types`` tag was added in Twig 3.13. This tag is **experimental** and\n    can change based on usage and feedback.\n\nUse the ``types`` tag to declare the type of a variable:\n\n.. code-block:: twig\n\n    {% types is_correct: 'boolean' %}\n    {% types score: 'number' %}\n\nOr multiple variables:\n\n.. code-block:: twig\n\n    {% types\n        is_correct: 'boolean',\n        score: 'number',\n    %}\n\nYou can also enclose types with ``{}``:\n\n.. code-block:: twig\n\n    {% types {\n        is_correct: 'boolean',\n        score: 'number',\n    } %}\n\nDeclare optional variables by adding a ``?`` suffix:\n\n.. code-block:: twig\n\n    {% types {\n        is_correct: 'boolean',\n        score?: 'number',\n    } %}\n\nBy default, this tag does not affect the template compilation or runtime behavior.\n\nIts purpose is to enable designers and developers to document and specify the\ncontext's available and/or required variables. While Twig itself does not\nvalidate variables or their types, this tag enables extensions to do this.\n\nAdditionally, :ref:`Twig extensions <creating_extensions>` can analyze these\ntags to perform compile-time and runtime analysis of templates.\n\n.. note::\n\n    The types declared in a template are local to that template and must not be\n    propagated to included templates. This is because a template can be\n    included from multiple different places, each potentially having different\n    variable types.\n\n.. note::\n\n    The syntax for and contents of type strings are intentionally left out of\n    scope.\n"
  },
  {
    "path": "doc/tags/use.rst",
    "content": "``use``\n=======\n\n.. note::\n\n    Horizontal reuse is an advanced Twig feature that is hardly ever needed in\n    regular templates. It is mainly used by projects that need to make\n    template blocks reusable without using inheritance.\n\nTemplate inheritance is one of the most powerful features of Twig but it is\nlimited to single inheritance; a template can only extend one other template.\nThis limitation makes template inheritance simple to understand and easy to\ndebug:\n\n.. code-block:: twig\n\n    {% extends \"base.html.twig\" %}\n\n    {% block title %}{% endblock %}\n    {% block content %}{% endblock %}\n\nHorizontal reuse is a way to achieve the same goal as multiple inheritance,\nbut without the associated complexity:\n\n.. code-block:: twig\n\n    {% extends \"base.html.twig\" %}\n\n    {% use \"blocks.html.twig\" %}\n\n    {% block title %}{% endblock %}\n    {% block content %}{% endblock %}\n\nThe ``use`` statement tells Twig to import the blocks defined in\n``blocks.html.twig`` into the current template (it's like macros, but for blocks):\n\n.. code-block:: twig\n\n    {# blocks.html.twig #}\n    {% block sidebar %}{% endblock %}\n\nIn this example, the ``use`` statement imports the ``sidebar`` block into the\nmain template. The code is mostly equivalent to the following one (the\nimported blocks are not outputted automatically):\n\n.. code-block:: twig\n\n    {% extends \"base.html.twig\" %}\n\n    {% block sidebar %}{% endblock %}\n    {% block title %}{% endblock %}\n    {% block content %}{% endblock %}\n\n.. note::\n\n    The ``use`` tag only imports a template if it does not extend another\n    template, if it does not define macros, and if the body is empty. But it\n    can *use* other templates.\n\n.. note::\n\n    Because ``use`` statements are resolved independently of the context\n    passed to the template, the template reference cannot be an expression.\n\nThe main template can also override any imported block. If the template\nalready defines the ``sidebar`` block, then the one defined in ``blocks.html.twig``\nis ignored. To avoid name conflicts, you can rename imported blocks:\n\n.. code-block:: twig\n\n    {% extends \"base.html.twig\" %}\n\n    {% use \"blocks.html.twig\" with sidebar as base_sidebar, title as base_title %}\n\n    {% block sidebar %}{% endblock %}\n    {% block title %}{% endblock %}\n    {% block content %}{% endblock %}\n\nThe ``parent()`` function automatically determines the correct inheritance\ntree, so it can be used when overriding a block defined in an imported\ntemplate:\n\n.. code-block:: twig\n\n    {% extends \"base.html.twig\" %}\n\n    {% use \"blocks.html.twig\" %}\n\n    {% block sidebar %}\n        {{ parent() }}\n    {% endblock %}\n\n    {% block title %}{% endblock %}\n    {% block content %}{% endblock %}\n\nIn this example, ``parent()`` will correctly call the ``sidebar`` block from\nthe ``blocks.html.twig`` template.\n\n.. tip::\n\n    Renaming allows you to simulate inheritance by calling the \"parent\" block:\n\n    .. code-block:: twig\n\n        {% extends \"base.html.twig\" %}\n\n        {% use \"blocks.html.twig\" with sidebar as parent_sidebar %}\n\n        {% block sidebar %}\n            {{ block('parent_sidebar') }}\n        {% endblock %}\n\n.. note::\n\n    You can use as many ``use`` statements as you want in any given template.\n    If two imported templates define the same block, the latest one wins.\n"
  },
  {
    "path": "doc/tags/verbatim.rst",
    "content": "``verbatim``\n============\n\nThe ``verbatim`` tag marks sections as being raw text that should not be\nparsed. For example to put Twig syntax as example into a template you can use\nthis snippet:\n\n.. code-block:: html+twig\n\n    {% verbatim %}\n        <ul>\n        {% for item in seq %}\n            <li>{{ item }}</li>\n        {% endfor %}\n        </ul>\n    {% endverbatim %}\n"
  },
  {
    "path": "doc/tags/with.rst",
    "content": "``with``\n========\n\nUse the ``with`` tag to create a new inner scope. Variables set within this\nscope are not visible outside of the scope:\n\n.. code-block:: twig\n\n    {% with %}\n        {% set value = 42 %}\n        {{ value }} {# value is 42 here #}\n    {% endwith %}\n    value is not visible here any longer\n\nInstead of defining variables at the beginning of the scope, you can pass a\nmapping of variables you want to define in the ``with`` tag; the previous\nexample is equivalent to the following one:\n\n.. code-block:: twig\n\n    {% with {value: 42} %}\n        {{ value }} {# value is 42 here #}\n    {% endwith %}\n    value is not visible here any longer\n\n    {# it works with any expression that resolves to a mapping #}\n    {% set vars = {value: 42} %}\n    {% with vars %}\n        ...\n    {% endwith %}\n\nBy default, the inner scope has access to the outer scope context; you can\ndisable this behavior by appending the ``only`` keyword:\n\n.. code-block:: twig\n\n    {% set zero = 0 %}\n    {% with {value: 42} only %}\n        {# only value is defined #}\n        {# zero is not defined #}\n    {% endwith %}\n"
  },
  {
    "path": "doc/templates.rst",
    "content": "Twig for Template Designers\n===========================\n\nThis document describes the syntax and semantics of the template engine and\nwill be most useful as reference to those creating Twig templates.\n\nSynopsis\n--------\n\nA template is a regular text file. It can generate any text-based format (HTML,\nXML, CSV, LaTeX, etc.). It doesn't have a specific extension, ``.html`` or\n``.xml`` are just fine.\n\nA template contains **variables** or **expressions**, which get replaced with\nvalues when the template is evaluated, and **tags**, which control the\ntemplate's logic.\n\nBelow is a minimal template that illustrates a few basics. We will cover further\ndetails later on:\n\n.. code-block:: html+twig\n\n    <!DOCTYPE html>\n    <html>\n        <head>\n            <title>My Webpage</title>\n        </head>\n        <body>\n            <ul id=\"navigation\">\n            {% for item in navigation %}\n                <li><a href=\"{{ item.href }}\">{{ item.caption }}</a></li>\n            {% endfor %}\n            </ul>\n\n            <h1>My Webpage</h1>\n            {{ a_variable }}\n        </body>\n    </html>\n\nThere are two kinds of delimiters: ``{% ... %}`` and ``{{ ... }}``. The first\none is used to execute statements such as for-loops, the latter outputs the\nresult of an expression.\n\n.. tip::\n\n    To experiment with Twig, you can use the `Twig Playground\n    <https://twig.symfony.com/play>`_.\n\nThird-party Integrations\n------------------------\n\nMany IDEs support syntax highlighting and auto-completion for Twig:\n\n* *Textmate* via the `Twig bundle`_\n* *Vim* via the `vim-twig plugin`_\n* *Netbeans* (native as of 7.2)\n* *PhpStorm* (native as of 2.1)\n* *Eclipse* via the `Twig plugin`_\n* *Sublime Text* via the `Twig bundle`_\n* *GtkSourceView* via the `Twig language definition`_ (used by gedit and other projects)\n* *Coda* and *SubEthaEdit* via the `Twig syntax mode`_\n* *Coda 2* via the `other Twig syntax mode`_\n* *Komodo* and *Komodo Edit* via the Twig highlight/syntax check mode\n* *Notepad++* via the `Notepad++ Twig Highlighter`_\n* *Emacs* via `web-mode.el`_\n* *Atom* via the `PHP-twig for atom`_\n* *Visual Studio Code* via the `Twig pack`_, `Modern Twig`_ or `Twiggy`_\n\nYou might also be interested in:\n\n* `Twig CS Fixer`_: a tool to check/fix your templates code style\n* `Twig Language Server`_: provides some language features like syntax\n  highlighting, diagnostics, auto complete, ...\n* `TwigQI`_: an extension which analyzes your templates for common bugs during compilation\n* `TwigStan`_: a static analyzer for Twig templates powered by PHPStan\n\nVariables\n---------\n\nTwig templates have access to variables provided by the PHP application and\nvariables created in templates via the :doc:`set <tags/set>` tag. These\nvariables can be manipulated and displayed in the template.\n\nTwig tries to abstract PHP types as much as possible and works with a few basic\ntypes, supported by ``filters``, ``functions``, and ``tests`` among others:\n\n===================  ===============================\nTwig Type            PHP Type\n===================  ===============================\nstring               A string or a Stringable object\nnumber               An integer or a float\nboolean              ``true`` or ``false``\nnull                 ``null``\niterable (mapping)   An array\niterable (sequence)  An array\niterable (object)    An iterable object\nobject               An object\n===================  ===============================\n\nThe ``iterable`` and ``object`` types expose attributes you can access via the\ndot (``.``) operator:\n\n.. code-block:: twig\n\n    {{ user.name }}\n\n.. note::\n\n    It's important to know that the curly braces are *not* part of the\n    variable but the print statement. When accessing variables inside tags,\n    don't put the braces around them.\n\nIf a variable or attribute does not exist, the behavior depends on the\n``strict_variables`` option value (see :ref:`environment options\n<environment_options_strict_variables>`):\n\n* When ``false``, it returns ``null``;\n* When ``true``, it throws an exception.\n\nLearn more about the :ref:`dot operator <dot_operator>`.\n\nGlobal Variables\n~~~~~~~~~~~~~~~~\n\nThe following variables are always available in templates:\n\n* ``_self``: references the current template name;\n* ``_context``: references the current context;\n* ``_charset``: references the current charset.\n\nSetting Variables\n~~~~~~~~~~~~~~~~~\n\nYou can assign values to variables inside code blocks using either the\n:doc:`set<tags/set>` tag or the :ref:`= operator <templates-assignment-operator>`:\n\n.. code-block:: twig\n\n    {% set name = 'Fabien' %}\n    {% set numbers = [1, 2] %}\n    {% set map = {'city': 'Paris'} %}\n    {% set first, last = 'Fabien', 'Potencier' %}\n\n    {# or #}\n\n    {% do name = 'Fabien' %}\n    {% do numbers = [1, 2] %}\n    {% do map = {'city': 'Paris'} %}\n    {% do [first, last] = ['Fabien', 'Potencier'] %}\n\nThe ``set`` tag can also be used to capture template content into\na variable:\n\n  .. code-block:: html+twig\n\n      {% set content %}\n          <div id=\"pagination\">...</div>\n      {% endset %}\n\nSee the :doc:`set<tags/set>` tag documentation for more details.\n\nFilters\n-------\n\nVariables and expressions can be modified by **filters**. Filters are separated\nfrom the variable by a pipe symbol (``|``). Multiple filters can be chained.\nThe output of one filter is applied to the next.\n\nThe following example removes all HTML tags from the ``name`` and title-cases\nit:\n\n.. code-block:: twig\n\n    {{ name|striptags|title }}\n\nFilters that accept arguments have parentheses around the arguments. This\nexample joins the elements of a list by commas:\n\n.. code-block:: twig\n\n    {{ list|join(', ') }}\n\nTo apply a filter on a section of code, wrap it with the\n:doc:`apply<tags/apply>` tag:\n\n.. code-block:: twig\n\n    {% apply upper %}\n        This text becomes uppercase\n    {% endapply %}\n\nGo to the :doc:`filters<filters/index>` page to learn more about built-in\nfilters.\n\n.. warning::\n\n    As the ``filter`` operator has the highest :ref:`precedence\n    <twig-expressions>`, use parentheses when filtering more \"complex\"\n    expressions:\n\n    .. code-block:: twig\n\n        {{ (1..5)|join(', ') }}\n\n        {{ ('HELLO' ~ 'FABIEN')|lower }}\n\nFunctions\n---------\n\nFunctions can be called to generate content. Functions are called by their\nname followed by parentheses (``()``) and may have arguments.\n\nFor instance, the ``range`` function returns a list containing an arithmetic\nprogression of integers:\n\n.. code-block:: twig\n\n    {% for i in range(0, 3) %}\n        {{ i }},\n    {% endfor %}\n\nGo to the :doc:`functions<functions/index>` page to learn more about the\nbuilt-in functions.\n\n.. _named-arguments:\n\nNamed Arguments\n---------------\n\nNamed arguments are supported everywhere you can pass arguments: functions,\nfilters, tests, macros, and dot operator arguments.\n\n.. versionadded:: 3.15\n\n    Named arguments for macros and dot operator arguments were added in Twig\n    3.15.\n\n.. versionadded:: 3.12\n\n    Twig supports both ``=`` and ``:`` as separators between argument names and\n    values, but support for ``:`` was introduced in Twig 3.12.\n\n.. code-block:: twig\n\n    {% for i in range(low: 1, high: 10, step: 2) %}\n        {{ i }},\n    {% endfor %}\n\nUsing named arguments makes your templates more explicit about the meaning of\nthe values you pass as arguments:\n\n.. code-block:: twig\n\n    {{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}\n\n    {# versus #}\n\n    {{ data|convert_encoding(from: 'iso-2022-jp', to: 'UTF-8') }}\n\nNamed arguments also allow you to skip some arguments for which you don't want\nto change the default value:\n\n.. code-block:: twig\n\n    {# the first argument is the date format, which defaults to the global date format if null is passed #}\n    {{ \"now\"|date(null, \"Europe/Paris\") }}\n\n    {# or skip the format value by using a named argument for the time zone #}\n    {{ \"now\"|date(timezone: \"Europe/Paris\") }}\n\nYou can also use both positional and named arguments in one call, in which\ncase positional arguments must always come before named arguments:\n\n.. code-block:: twig\n\n    {{ \"now\"|date('d/m/Y H:i', timezone: \"Europe/Paris\") }}\n\n.. tip::\n\n    Each function, filter, and test documentation page has a section where the\n    names of all supported arguments are listed.\n\nControl Structure\n-----------------\n\nA control structure refers to all those things that control the flow of a\nprogram - conditionals (i.e. ``if``/``elseif``/``else``), ``for``-loops, as\nwell as things like blocks. Control structures appear inside ``{% ... %}``\nblocks.\n\nFor example, to display a list of users provided in a variable called\n``users``, use the :doc:`for<tags/for>` tag:\n\n.. code-block:: html+twig\n\n    <h1>Members</h1>\n    <ul>\n        {% for user in users %}\n            <li>{{ user.username|e }}</li>\n        {% endfor %}\n    </ul>\n\nThe :doc:`if<tags/if>` tag can be used to test an expression:\n\n.. code-block:: html+twig\n\n    {% if users|length > 0 %}\n        <ul>\n            {% for user in users %}\n                <li>{{ user.username|e }}</li>\n            {% endfor %}\n        </ul>\n    {% endif %}\n\nGo to the :doc:`tags<tags/index>` page to learn more about the built-in tags.\n\nComments\n--------\n\nTo comment-out part of a template, use the comment syntax ``{# ... #}``. This\nis useful for debugging or to add information for other template designers or\nyourself:\n\n.. code-block:: twig\n\n    {# note: disabled template because we no longer use this\n        {% for user in users %}\n            ...\n        {% endfor %}\n    #}\n\n.. versionadded:: 3.15\n\n    Inline comments were added in Twig 3.15.\n\nIf you want to add comments inside a block, variable, or comment, use an inline\ncomment. They start with ``#`` and continue to the end of the line:\n\n.. code-block:: twig\n\n    {{\n        # this is an inline comment\n        \"Hello World\"|upper\n        # this is an inline comment\n    }}\n\n    {{\n        {\n            # this is an inline comment\n            fruit: 'apple', # this is an inline comment\n            color: 'red', # this is an inline comment\n        }|join(', ')\n    }}\n\nInline comments can also be on the same line as the expression:\n\n.. code-block:: twig\n\n    {{\n        \"Hello World\"|upper # this is an inline comment\n    }}\n\nAs inline comments continue until the end of the current line, the following\ncode does not work as ``}}`` would be part of the comment:\n\n.. code-block:: twig\n\n    {{ \"Hello World\"|upper # this is an inline comment }}\n\nIncluding other Templates\n-------------------------\n\nThe :doc:`include<functions/include>` function is useful to include a template\nand return the rendered content of that template into the current one:\n\n.. code-block:: twig\n\n    {{ include('sidebar.html.twig') }}\n\nBy default, included templates have access to the same context as the template\nwhich includes them. This means that any variable defined in the main template\nwill be available in the included template too:\n\n.. code-block:: twig\n\n    {% for box in boxes %}\n        {{ include('render_box.html.twig') }}\n    {% endfor %}\n\nThe included template ``render_box.html.twig`` is able to access the ``box`` variable.\n\nThe name of the template depends on the template loader. For instance, the\n``\\Twig\\Loader\\FilesystemLoader`` allows you to access other templates by giving the\nfilename. You can access templates in subdirectories with a slash:\n\n.. code-block:: twig\n\n    {{ include('sections/articles/sidebar.html.twig') }}\n\nThis behavior depends on the application embedding Twig.\n\nTemplate Inheritance\n--------------------\n\nThe most powerful part of Twig is template inheritance. Template inheritance\nallows you to build a base \"skeleton\" template that contains all the common\nelements of your site and defines **blocks** that child templates can\noverride.\n\nIt's easier to understand the concept by starting with an example.\n\nLet's define a base template, ``base.html.twig``, which defines an HTML skeleton\ndocument that might be used for a two-column page:\n\n.. code-block:: html+twig\n\n    <!DOCTYPE html>\n    <html>\n        <head>\n            {% block head %}\n                <link rel=\"stylesheet\" href=\"style.css\"/>\n                <title>{% block title %}{% endblock %} - My Webpage</title>\n            {% endblock %}\n        </head>\n        <body>\n            <div id=\"content\">{% block content %}{% endblock %}</div>\n            <div id=\"footer\">\n                {% block footer %}\n                    &copy; Copyright 2011 by <a href=\"https://example.com/\">you</a>.\n                {% endblock %}\n            </div>\n        </body>\n    </html>\n\nIn this example, the :doc:`block<tags/block>` tags define four blocks that\nchild templates can fill in. All the ``block`` tag does is to tell the\ntemplate engine that a child template may override those portions of the\ntemplate.\n\nA child template might look like this:\n\n.. code-block:: html+twig\n\n    {% extends \"base.html.twig\" %}\n\n    {% block title %}Index{% endblock %}\n    {% block head %}\n        {{ parent() }}\n        <style type=\"text/css\">\n            .important { color: #336699; }\n        </style>\n    {% endblock %}\n    {% block content %}\n        <h1>Index</h1>\n        <p class=\"important\">\n            Welcome to my awesome homepage.\n        </p>\n    {% endblock %}\n\nThe :doc:`extends<tags/extends>` tag is the key here. It tells the template\nengine that this template \"extends\" another template. When the template system\nevaluates this template, first it locates the parent. The extends tag should\nbe the first tag in the template.\n\nNote that since the child template doesn't define the ``footer`` block, the\nvalue from the parent template is used instead.\n\nIt's possible to render the contents of the parent block by using the\n:doc:`parent<functions/parent>` function. This gives back the results of the\nparent block:\n\n.. code-block:: html+twig\n\n    {% block sidebar %}\n        <h3>Table Of Contents</h3>\n        ...\n        {{ parent() }}\n    {% endblock %}\n\n.. tip::\n\n    The documentation page for the :doc:`extends<tags/extends>` tag describes\n    more advanced features like block nesting, scope, dynamic inheritance, and\n    conditional inheritance.\n\n.. note::\n\n    Twig also supports multiple inheritance via \"horizontal reuse\" with the help\n    of the :doc:`use<tags/use>` tag.\n\nHTML Escaping\n-------------\n\nWhen generating HTML from templates, there's always a risk that a variable\nwill include characters that affect the resulting HTML. There are two\napproaches: manually escaping each variable or automatically escaping\neverything by default.\n\nTwig supports both, automatic escaping is enabled by default.\n\nThe automatic escaping strategy can be configured via the\n:ref:`autoescape<environment_options>` option and defaults to ``html``.\n\nWorking with Manual Escaping\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf manual escaping is enabled, it is **your** responsibility to escape variables\nif needed. What to escape? Any variable that comes from an untrusted source.\n\nEscaping works by using the :doc:`escape<filters/escape>` or ``e`` filter:\n\n.. code-block:: twig\n\n    {{ user.username|e }}\n\nBy default, the ``escape`` filter uses the ``html`` strategy, but depending on\nthe escaping context, you might want to explicitly use another strategy:\n\n.. code-block:: twig\n\n    {{ user.username|e('js') }}\n    {{ user.username|e('css') }}\n    {{ user.username|e('url') }}\n    {{ user.username|e('html_attr') }}\n\nWorking with Automatic Escaping\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhether automatic escaping is enabled or not, you can mark a section of a\ntemplate to be escaped or not by using the :doc:`autoescape<tags/autoescape>`\ntag:\n\n.. code-block:: twig\n\n    {% autoescape %}\n        Everything will be automatically escaped in this block (using the HTML strategy)\n    {% endautoescape %}\n\nBy default, auto-escaping uses the ``html`` escaping strategy. If you output\nvariables in other contexts, you need to explicitly escape them with the\nappropriate escaping strategy:\n\n.. code-block:: twig\n\n    {% autoescape 'js' %}\n        Everything will be automatically escaped in this block (using the JS strategy)\n    {% endautoescape %}\n\nEscaping\n--------\n\nIt is sometimes desirable or even necessary to have Twig ignore parts it would\notherwise handle as variables or blocks. For example if the default syntax is\nused and you want to use ``{{`` as raw string in the template and not start a\nvariable you have to use a trick.\n\nThe easiest way is to output the variable delimiter (``{{``) by using a variable\nexpression:\n\n.. code-block:: twig\n\n    {{ '{{' }}\n\nFor bigger sections it makes sense to mark a block\n:doc:`verbatim<tags/verbatim>`.\n\nMacros\n------\n\nMacros are comparable with functions in regular programming languages. They are\nuseful to reuse HTML fragments to not repeat yourself. They are described in the\n:doc:`macro<tags/macro>` tag documentation.\n\n.. _twig-expressions:\n\nExpressions\n-----------\n\nTwig allows expressions everywhere.\n\nLiterals\n~~~~~~~~\n\nThe simplest form of expressions are literals. Literals are representations\nfor PHP types such as strings, numbers, and arrays. The following literals\nexist:\n\n* ``\"Hello World\"``: Everything between two double or single quotes is a\n  string. They are useful whenever you need a string in the template (for\n  example as arguments to function calls, filters or just to extend or include\n  a template).\n\n  Note that certain characters require escaping:\n   * ``\\f``: Form feed\n   * ``\\n``: New line\n   * ``\\r``: Carriage return\n   * ``\\t``: Horizontal tab\n   * ``\\v``: Vertical tab\n   * ``\\x``: Hexadecimal escape sequence\n   * ``\\0`` to ``\\377``: Octal escape sequences representing characters\n   * ``\\``: Backslash\n\n   When using single-quoted strings, the single quote character (``'``) needs to be escaped with a backslash (``\\'``).\n   When using double-quoted strings, the double quote character (``\"``) needs to be escaped with a backslash (``\\\"``).\n\n   For example, a single quoted string can contain a delimiter if it is preceded by a\n   backslash (``\\``) -- like in ``'It\\'s good'``. If the string contains a\n   backslash (e.g. ``'c:\\Program Files'``) escape it by doubling it\n   (e.g. ``'c:\\\\Program Files'``).\n\n* ``42`` / ``42.23``: Integers and floating point numbers are created by\n  writing the number down. If a dot is present the number is a float,\n  otherwise an integer. Underscores can be used as digits separator to\n  improve readability (``-3_141.592_65`` is equivalent to ``-3141.59265``).\n\n* ``[\"first_name\", \"last_name\"]``: Sequences are defined by a sequence of expressions\n  separated by a comma (``,``) and wrapped with squared brackets (``[]``).\n\n* ``{\"name\": \"Fabien\"}``: Mappings are defined by a list of keys and values\n  separated by a comma (``,``) and wrapped with curly braces (``{}``):\n\n  .. code-block:: twig\n\n    {# keys as string #}\n    {'name': 'Fabien', 'city': 'Paris'}\n\n    {# keys as names (equivalent to the previous mapping) #}\n    {name: 'Fabien', city: 'Paris'}\n\n    {# keys as integer #}\n    {2: 'Twig', 4: 'Symfony'}\n\n    {# keys can be omitted if it is the same as the variable name #}\n    {Paris}\n    {# is equivalent to the following #}\n    {'Paris': Paris}\n\n    {# keys as expressions (the expression must be enclosed into parentheses) #}\n    {% set key = 'name' %}\n    {(key): 'Fabien', (1 + 1): 2, ('ci' ~ 'ty'): 'city'}\n\n* ``true`` / ``false``: ``true`` represents the true value, ``false``\n  represents the false value.\n\n* ``null``: ``null`` represents no specific value. This is the value returned\n  when a variable does not exist. ``none`` is an alias for ``null``.\n\nSequences and mappings can be nested:\n\n.. code-block:: twig\n\n    {% set complex = [1, {\"name\": \"Fabien\"}] %}\n\n.. tip::\n\n    Using double-quoted or single-quoted strings has no impact on performance\n    but :ref:`string interpolation <templates-string-interpolation>` is only\n    supported in double-quoted strings.\n\n.. _templates-string-interpolation:\n\nString Interpolation\n~~~~~~~~~~~~~~~~~~~~\n\nString interpolation (``#{expression}``) allows any valid expression to appear\nwithin a *double-quoted string*. The result of evaluating that expression is\ninserted into the string:\n\n.. code-block:: twig\n\n    {{ \"first #{middle} last\" }}\n    {{ \"first #{1 + 2} last\" }}\n\n.. tip::\n\n    String interpolations can be ignored by escaping them with a backslash\n    (``\\``):\n\n    .. code-block:: twig\n\n        {# outputs first #{1 + 2} last #}\n        {{ \"first \\#{1 + 2} last\" }}\n\nMath\n~~~~\n\nTwig allows you to do math in templates; the following operators are supported:\n\n* ``+``: Adds two numbers together (the operands are casted to numbers). ``{{\n  1 + 1 }}`` is ``2``.\n\n* ``-``: Subtracts the second number from the first one. ``{{ 3 - 2 }}`` is\n  ``1``.\n\n* ``/``: Divides two numbers. The returned value will be a floating point\n  number. ``{{ 1 / 2 }}`` is ``{{ 0.5 }}``.\n\n* ``%``: Calculates the remainder of an integer division. ``{{ 11 % 7 }}`` is\n  ``4``.\n\n* ``//``: Divides two numbers and returns the floored integer result. ``{{ 20\n  // 7 }}`` is ``2``, ``{{ -20 // 7 }}`` is ``-3`` (this is just syntactic\n  sugar for the :doc:`round<filters/round>` filter).\n\n* ``*``: Multiplies the left operand with the right one. ``{{ 2 * 2 }}`` would\n  return ``4``.\n\n* ``**``: Raises the left operand to the power of the right operand. ``{{ 2 **\n  3 }}`` would return ``8``. Be careful as the ``**`` operator is right\n  associative, which means that ``{{ -1**0 }}`` is equivalent to ``{{ -(1**0)\n  }}`` and not ``{{ (-1)**0 }}``.\n\n.. _template_logic:\n\nLogic\n~~~~~\n\nYou can combine multiple expressions with the following operators:\n\n* ``and``: Returns true if the left and the right operands are both true.\n\n* ``xor``: Returns true if **either** the left or the right operand is true, but not both.\n\n* ``or``: Returns true if the left or the right operand is true.\n\n* ``not``: Negates a statement.\n\n* ``(expr)``: Groups an expression.\n\n.. note::\n\n    Twig also supports bitwise operators (``b-and``, ``b-xor``, and ``b-or``).\n\n.. note::\n\n    Operators are case sensitive.\n\nComparisons\n~~~~~~~~~~~\n\nThe following mathematical comparison operators are supported in any\nexpression: ``==``, ``!=``, ``<``, ``>``, ``>=``, and ``<=``.\n\nIn addition, the ``===`` and ``!==`` strict comparison operators are supported\n(they are equivalent to the ``same as`` and ``not same as`` tests).\n\nSpaceship Operator\n~~~~~~~~~~~~~~~~~~\n\nThe spaceship operator (``<=>``) is used for comparing two expressions. It\nreturns ``-1``, ``0`` or ``1`` when the first operand is respectively less\nthan, equal to, or greater than the second operand.\n\n.. note::\n\n    Read more about in the `PHP spaceship operator documentation`_.\n\nIterable Operators\n~~~~~~~~~~~~~~~~~~\n\nCheck that an iterable ``has every`` or ``has some`` of its elements return\n``true`` using an arrow function. The arrow function receives the value of the\niterable as its argument:\n\n.. code-block:: twig\n\n    {% set sizes = [34, 36, 38, 40, 42] %}\n\n    {% set hasOnlyOver38 = sizes has every v => v > 38 %}\n    {# hasOnlyOver38 is false #}\n\n    {% set hasOver38 = sizes has some v => v > 38 %}\n    {# hasOver38 is true #}\n\nFor an empty iterable, ``has every`` returns ``true`` and ``has some`` returns\n``false``.\n\nContainment Operators\n~~~~~~~~~~~~~~~~~~~~~\n\nThe ``in`` operator performs containment test. It returns ``true`` if the left\noperand is contained in the right:\n\n.. code-block:: twig\n\n    {# returns true #}\n\n    {{ 1 in [1, 2, 3] }}\n\n    {{ 'cd' in 'abcde' }}\n\n.. tip::\n\n    You can use this operator to perform a containment test on strings,\n    sequences, mappings, or objects implementing the ``Traversable`` interface.\n\nTo perform a negative test, use the ``not in`` operator:\n\n.. code-block:: twig\n\n    {% if 1 not in [1, 2, 3] %}\n\n    {# is equivalent to #}\n    {% if not (1 in [1, 2, 3]) %}\n\nThe ``starts with`` and ``ends with`` operators are used to check if a string\nstarts or ends with a given substring:\n\n.. code-block:: twig\n\n    {% if 'Fabien' starts with 'F' %}\n    {% endif %}\n\n    {% if 'Fabien' ends with 'n' %}\n    {% endif %}\n\n.. note::\n\n    For complex string comparisons, the ``matches`` operator allows you to use\n    `regular expressions`_:\n\n    .. code-block:: twig\n\n        {% if phone matches '/^[\\\\d\\\\.]+$/' %}\n        {% endif %}\n\nTest Operator\n~~~~~~~~~~~~~\n\nThe ``is`` operator performs tests. Tests can be used to test a variable against\na common expression. The right operand is name of the test:\n\n.. code-block:: twig\n\n    {# find out if a variable is odd #}\n\n    {{ name is odd }}\n\nTests can accept arguments too:\n\n.. code-block:: twig\n\n    {% if post.status is constant('Post::PUBLISHED') %}\n\nTests can be negated by using the ``is not`` operator:\n\n.. code-block:: twig\n\n    {% if post.status is not constant('Post::PUBLISHED') %}\n\n    {# is equivalent to #}\n    {% if not (post.status is constant('Post::PUBLISHED')) %}\n\nGo to the :doc:`tests<tests/index>` page to learn more about the built-in\ntests.\n\nOther Operators\n~~~~~~~~~~~~~~~\n\nThe following operators don't fit into any of the other categories:\n\n* ``|``: Applies a filter.\n\n* ``..``: Creates a sequence based on the operand before and after the operator\n  (this is syntactic sugar for the :doc:`range<functions/range>` function):\n\n  .. code-block:: twig\n\n      {% for i in 1..5 %}{{ i }}{% endfor %}\n\n      {# is equivalent to #}\n      {% for i in range(1, 5) %}{{ i }}{% endfor %}\n\n  Note that you must use parentheses when combining it with the filter operator\n  due to the :ref:`operator precedence rules <twig-expressions>`:\n\n  .. code-block:: twig\n\n      {{ (1..5)|join(', ') }}\n\n* ``~``: Converts all operands into strings and concatenates them. ``{{ \"Hello\n  \" ~ name ~ \"!\" }}`` would return (assuming ``name`` is ``'John'``) ``Hello\n  John!``.\n\n.. _dot_operator:\n\n* ``.``, ``?.``, ``[]``: Gets an attribute of a variable.\n\n  The (``.``) operator abstracts getting an attribute of a variable (methods,\n  properties or constants of a PHP object, or items of a PHP array):\n\n  .. code-block:: twig\n\n      {{ user.name }}\n\n  The null-safe operator (``?.``) works like the dot operator but returns\n  ``null`` instead of throwing an exception when the left operand is ``null``.\n  If the operand is part of a chain, the rest of the chain is skipped:\n\n  .. code-block:: twig\n\n      {{ user?.name }}\n      {# returns null if user is null, otherwise returns user.name #}\n\n      {{ user?.address?.city }}\n      {# can be chained for safe navigation through potentially null values #}\n\n      {{ user?.address.city }}\n      {# returns null if user is null, the rest of the chain is skipped (address.city is not evaluated) #}\n\n  .. versionadded:: 3.23\n\n      The null-safe operator was added in Twig 3.23.\n\n  After the ``.`` or ``?.``, you can use any expression by wrapping it with\n  parenthesis ``()``.\n\n  One use case is when the attribute contains special characters (like ``-``\n  that would be interpreted as the minus operator):\n\n  .. code-block:: twig\n\n      {# equivalent to the non-working user.first-name #}\n      {{ user.('first-name') }}\n      {{ user?.('first-name') }}\n\n  Another use case is when the attribute is \"dynamic\" (defined via a variable):\n\n  .. code-block:: twig\n\n      {{ user.(name) }}\n      {{ user.('get' ~ name) }}\n      {{ user?.(name) }}\n\n  Before Twig 3.15, use the :doc:`attribute <functions/attribute>` function\n  instead for the two previous use cases.\n\n  Twig supports a specific syntax via the ``[]`` operator for accessing items\n  on sequences and mappings:\n\n  .. code-block:: twig\n\n      {{ user['name'] }}\n\n  When calling a method, you can pass arguments using the ``()`` operator:\n\n  .. code-block:: twig\n\n      {{ html.generate_input() }}\n      {{ html.generate_input('pwd', 'password') }}\n      {# or using named arguments #}\n      {{ html.generate_input(name: 'pwd', type: 'password') }}\n\n  .. sidebar:: PHP Implementation\n\n      To resolve ``user.name`` to a PHP call, Twig uses the following algorithm\n      at runtime:\n\n      * check if ``user`` is a PHP array or an ArrayObject/ArrayAccess object and\n        ``name`` a valid element;\n      * if not, and if ``user`` is a PHP object, check that ``name`` is a valid property;\n      * if not, and if ``user`` is a PHP object, check that ``name`` is a class constant;\n      * if not, and if ``user`` is a PHP object, check the following methods and\n        call the first valid one: ``name()``, ``getName()``, ``isName()``, or\n        ``hasName()``;\n      * if not, and if ``strict_variables`` is ``false``, return ``null``;\n      * if not, throw an exception.\n\n      To resolve ``user?.name`` to a PHP call, Twig checks if ``user`` is\n      ``null`` first:\n\n      * if ``user`` is ``null``, return ``null``;\n      * otherwise, use the same algorithm as for ``user.name``.\n\n      To resolve ``user['name']`` to a PHP call, Twig uses the following algorithm\n      at runtime:\n\n      * check if ``user`` is an array and ``name`` a valid element;\n      * if not, and if ``strict_variables`` is ``false``, return ``null``;\n      * if not, throw an exception.\n\n      Twig supports a specific syntax via the ``()`` operator for calling methods\n      on objects, like in ``user.name()``:\n\n      * check if ``user`` is an object and has the ``name()``, ``getName()``,\n        ``isName()``, or ``hasName()`` method;\n      * if not, and if ``strict_variables`` is ``false``, return ``null``;\n      * if not, throw an exception.\n\n* ``?:``: The ternary operator:\n\n  .. code-block:: twig\n\n      {{ result ? 'yes' : 'no' }}\n      {{ result ?: 'no' }} is the same as {{ result ? result : 'no' }}\n      {{ result ? 'yes' }} is the same as {{ result ? 'yes' : '' }}\n\n* ``??``: The null-coalescing operator:\n\n  .. code-block:: twig\n\n      {# returns the value of result if it is defined and not null, 'no' otherwise #}\n      {{ result ?? 'no' }}\n\n* ``...``: The spread operator can be used to expand sequences or mappings or\n  to expand the arguments of a function call:\n\n  .. code-block:: twig\n\n      {% set numbers = [1, 2, ...moreNumbers] %}\n      {% set ratings = {'q1': 10, 'q2': 5, ...moreRatings} %}\n\n      {{ 'Hello %s %s!'|format(...['Fabien', 'Potencier']) }}\n\n  .. versionadded:: 3.15\n\n    Support for expanding the arguments of a function call was introduced in\n    Twig 3.15.\n\n.. _templates-assignment-operator:\n\n* ``=``: The assignment operator assigns a value to a variable within an\n  expression:\n\n  .. code-block:: twig\n\n      {# assign #}\n      {% do b = 1 + 3 %}\n\n      {# assign and output the result #}\n      {{ b = 1 + 3 }}\n\n      {# assignments can be chained #}\n      {% do a = b = 'foo' %}\n\n      {# assignment can be used inside other expressions #}\n      {% do a = (b = 4) + 5 %}\n\n  The assignment operator also supports :ref:`destructuring\n  <templates-destructuring>`.\n\n  .. versionadded:: 3.23\n\n      The ``=`` assignment operator was added in Twig 3.23.\n\n* ``=>``: The arrow operator allows the creation of functions. A function is\n  made of arguments (use parentheses for multiple arguments) and an arrow\n  (``=>``) followed by an expression to execute. The expression has access to\n  all passed arguments. Arrow functions are supported as arguments for filters,\n  functions, tests, macros, and method calls.\n\n  For instance, the built-in ``map``, ``reduce``, ``sort``, ``filter``, and\n  ``find`` filters accept arrow functions as arguments:\n\n  .. code-block:: twig\n\n      {{ people|map(p => p.first_name)|join(', ') }}\n\n  Arrow functions can be stored in variables:\n\n  .. code-block:: twig\n\n      {% set first_name_fn = (p) => p.first_name %}\n\n      {{ people|map(first_name_fn)|join(', ') }}\n\n  .. versionadded:: 3.15\n\n    Arrow function support for functions, macros, and method calls was added in\n    Twig 3.15 (filters and tests were already supported).\n\n  Arrow functions can be called using the :doc:`invoke </filters/invoke>`\n  filter.\n\n  .. versionadded:: 3.19\n\n    The ``invoke`` filter has been added in Twig 3.19.\n\nOperators\n~~~~~~~~~\n\nTwig uses operators to perform various operations within templates.\nUnderstanding the precedence of these operators is crucial for writing correct\nand efficient Twig templates.\n\nThe operator precedence rules are as follows, with the highest-precedence\noperators listed first.\n\n.. include:: operators_precedence.rst\n\nWithout using any parentheses, the operator precedence rules are used to\ndetermine how to convert the code to PHP:\n\n.. code-block:: twig\n\n    {{ 6 b-and 2 or 6 b-and 16 }}\n\n    {# it is converted to the following PHP code: (6 & 2) || (6 & 16) #}\n\nChange the default precedence by explicitly grouping expressions with\nparentheses:\n\n.. code-block:: twig\n\n    {% set greeting = 'Hello ' %}\n    {% set name = 'Fabien' %}\n\n    {{ greeting ~ name|lower }}   {# Hello fabien #}\n\n    {# use parenthesis to change precedence #}\n    {{ (greeting ~ name)|lower }} {# hello fabien #}\n\n.. _templates-destructuring:\n\nDestructuring\n-------------\n\n.. versionadded:: 3.23\n\n    Destructuring was added in Twig 3.23.\n\nDestructuring allows you to extract values from sequences and assign them to\nvariables in a single operation using the ``=`` :ref:`assignment operator\n<templates-assignment-operator>`.\n\nLike in PHP, destructuring expressions return the right-hand side value, not\nthe extracted values:\n\n.. code-block:: twig\n\n    {# returns the full user object, allowing chained access #}\n    {{ ({name} = user).email }}\n\nSequence Destructuring\n~~~~~~~~~~~~~~~~~~~~~~\n\nUse square brackets on the left side of an assignment to destructure a\nsequence:\n\n.. code-block:: twig\n\n    {% do [first, last] = ['Fabien', 'Potencier'] %}\n\n    {{ first }} {# Fabien #}\n    {{ last }}  {# Potencier #}\n\nIf there are more variables than values, the extra variables are set to\n``null``:\n\n.. code-block:: twig\n\n    {# extra will be null #}\n    {% do [first, last, extra] = ['Fabien', 'Potencier'] %}\n\nYou can skip values by leaving a slot empty:\n\n.. code-block:: twig\n\n    {# only assign the second value #}\n    {% do [, last] = ['Fabien', 'Potencier'] %}\n\nObject Destructuring\n~~~~~~~~~~~~~~~~~~~~\n\nUse curly braces on the left side of an assignment to destructure an object\nor mapping by extracting values based on property/key names:\n\n.. code-block:: twig\n\n    {% do {name, email} = user %}\n\n    {{ name }}  {# user.name #}\n    {{ email }} {# user.email #}\n\nYou can rename variables during destructuring by using the ``key: variable``\nsyntax, where the key is the property to extract and the variable is the name\nto assign to:\n\n.. code-block:: twig\n\n    {% do {name: userName, email: userEmail} = user %}\n\n    {{ userName }}  {# user.name #}\n    {{ userEmail }} {# user.email #}\n\nThis is especially useful when you need to destructure multiple objects that\nshare the same property names:\n\n.. code-block:: twig\n\n    {% do {data: product, error: productError} = loadProduct() %}\n    {% do {data: stock, error: stockError} = loadStock() %}\n\n    {{ product }}      {# loadProduct().data #}\n    {{ productError }} {# loadProduct().error #}\n    {{ stock }}        {# loadStock().data #}\n    {{ stockError }}   {# loadStock().error #}\n\n.. note::\n\n    Object destructuring uses the :ref:`dot operator <dot_operator>` to access\n    values, so ``{name} = user`` is equivalent to ``name = user.name`` or\n    ``name = user[\"name\"]`` depending on the type of the variable.\n\n.. _templates-whitespace-control:\n\nWhitespace Control\n------------------\n\nThe first newline after a template tag is removed automatically (like in PHP).\nWhitespace is not further modified by the template engine, so each whitespace\n(spaces, tabs, newlines etc.) is returned unchanged.\n\nYou can also control whitespace on a per tag level. By using the whitespace\ncontrol modifiers on your tags, you can trim leading and or trailing whitespace.\n\nTwig supports two modifiers:\n\n* *Whitespace trimming* via the ``-`` modifier: Removes all whitespace\n  (including newlines);\n\n* *Line whitespace trimming* via the ``~`` modifier: Removes all whitespace\n  (excluding newlines). Using this modifier on the right disables the default\n  removal of the first newline inherited from PHP.\n\nThe modifiers can be used on either side of the tags like in ``{%-`` or ``-%}``\nand they consume all whitespace for that side of the tag. It is possible to use\nthe modifiers on one side of a tag or on both sides:\n\n.. code-block:: html+twig\n\n    {% set value = 'no spaces' %}\n    {#- No leading/trailing whitespace -#}\n    {%- if true -%}\n        {{- value -}}\n    {%- endif -%}\n    {# output 'no spaces' #}\n\n    <li>\n        {{ value }}    </li>\n    {# outputs '<li>\\n    no spaces    </li>' #}\n\n    <li>\n        {{- value }}    </li>\n    {# outputs '<li>no spaces    </li>' #}\n\n    <li>\n        {{~ value }}    </li>\n    {# outputs '<li>\\nno spaces    </li>' #}\n\nExtensions\n----------\n\nTwig can be extended. If you want to create your own extensions, read the\n:ref:`Creating an Extension <creating_extensions>` chapter.\n\n.. _`Twig bundle`:                          https://github.com/uhnomoli/PHP-Twig.tmbundle\n.. _`vim-twig plugin`:                      https://github.com/lumiliet/vim-twig\n.. _`Twig plugin`:                          https://github.com/pulse00/Twig-Eclipse-Plugin\n.. _`Twig language definition`:             https://github.com/gabrielcorpse/gedit-twig-template-language\n.. _`Twig syntax mode`:                     https://github.com/bobthecow/Twig-HTML.mode\n.. _`other Twig syntax mode`:               https://github.com/muxx/Twig-HTML.mode\n.. _`Notepad++ Twig Highlighter`:           https://github.com/Banane9/notepadplusplus-twig\n.. _`web-mode.el`:                          https://web-mode.org/\n.. _`regular expressions`:                  https://www.php.net/manual/en/pcre.pattern.php\n.. _`PHP-twig for atom`:                    https://github.com/reesef/php-twig\n.. _`TwigQI`:                               https://github.com/alisqi/TwigQI\n.. _`TwigStan`:                             https://github.com/twigstan/twigstan\n.. _`Twig pack`:                            https://marketplace.visualstudio.com/items?itemName=bajdzis.vscode-twig-pack\n.. _`Modern Twig`:                          https://marketplace.visualstudio.com/items?itemName=Stanislav.vscode-twig\n.. _`Twig CS Fixer`:                        https://github.com/VincentLanglet/Twig-CS-Fixer\n.. _`Twig Language Server`:                 https://github.com/kaermorchen/twig-language-server/tree/master/packages/language-server\n.. _`Twiggy`:                               https://marketplace.visualstudio.com/items?itemName=moetelo.twiggy\n.. _`PHP spaceship operator documentation`: https://www.php.net/manual/en/language.operators.comparison.php\n"
  },
  {
    "path": "doc/tests/constant.rst",
    "content": "``constant``\n============\n\n``constant`` checks if a variable has the exact same value as a constant. You\ncan use either global constants or class constants:\n\n.. code-block:: twig\n\n    {% if post.status is constant('Post::PUBLISHED') %}\n        the status attribute is exactly the same as Post::PUBLISHED\n    {% endif %}\n\nYou can test constants from object instances as well:\n\n.. code-block:: twig\n\n    {% if post.status is constant('PUBLISHED', post) %}\n        the status attribute is exactly the same as Post::PUBLISHED\n    {% endif %}\n"
  },
  {
    "path": "doc/tests/defined.rst",
    "content": "``defined``\n===========\n\n``defined`` checks if a variable is defined in the current context. This is very\nuseful if you use the ``strict_variables`` option:\n\n.. code-block:: twig\n\n    {# defined works with variable names #}\n    {% if user is defined %}\n        ...\n    {% endif %}\n\n    {# and attributes on variables names #}\n    {% if user.name is defined %}\n        ...\n    {% endif %}\n\n    {% if user['name'] is defined %}\n        ...\n    {% endif %}\n\nWhen using the ``defined`` test on an expression that uses variables in some\nmethod calls, be sure that they are all defined first:\n\n.. code-block:: twig\n\n    {% if var is defined and user.name(var) is defined %}\n        ...\n    {% endif %}\n"
  },
  {
    "path": "doc/tests/divisibleby.rst",
    "content": "``divisible by``\n================\n\n``divisible by`` checks if a variable is divisible by a number:\n\n.. code-block:: twig\n\n    {% if loop.index is divisible by(3) %}\n        ...\n    {% endif %}\n"
  },
  {
    "path": "doc/tests/empty.rst",
    "content": "``empty``\n=========\n\n``empty`` checks if a variable is an empty string, an empty sequence, an empty\nmapping, exactly ``false``, or exactly ``null``.\n\nFor objects that implement the ``Countable`` interface, ``empty`` will check the\nreturn value of the ``count()`` method.\n\nFor objects that implement the ``__toString()`` magic method (and not ``Countable``),\nit will check if an empty string is returned.\n\n.. code-block:: twig\n\n    {% if user is empty %}\n        ...\n    {% endif %}\n\n"
  },
  {
    "path": "doc/tests/even.rst",
    "content": "``even``\n========\n\n``even`` returns ``true`` if the given number is even:\n\n.. code-block:: twig\n\n    {{ var is even }}\n\n.. seealso::\n\n    :doc:`odd<../tests/odd>`\n"
  },
  {
    "path": "doc/tests/index.rst",
    "content": "Tests\n=====\n\n.. toctree::\n    :maxdepth: 1\n\n    constant\n    defined\n    divisibleby\n    empty\n    even\n    iterable\n    mapping\n    null\n    odd\n    sameas\n    sequence\n"
  },
  {
    "path": "doc/tests/iterable.rst",
    "content": "``iterable``\n============\n\n``iterable`` checks if a variable is an array or a traversable object:\n\n.. code-block:: twig\n\n    {# evaluates to true if the users variable is iterable #}\n    {% if users is iterable %}\n        {% for user in users %}\n            Hello {{ user }}!\n        {% endfor %}\n    {% else %}\n        {# users is probably a string #}\n        Hello {{ users }}!\n    {% endif %}\n"
  },
  {
    "path": "doc/tests/mapping.rst",
    "content": "``mapping``\n===========\n\n``mapping`` checks if a variable is a mapping:\n\n.. code-block:: twig\n\n    {% set users = {alice: \"Alice Dupond\", bob: \"Bob Smith\"} %}\n    {# evaluates to true if the users variable is a mapping #}\n    {% if users is mapping %}\n        {% for key, user in users %}\n            {{ key }}: {{ user }};\n        {% endfor %}\n    {% endif %}\n"
  },
  {
    "path": "doc/tests/null.rst",
    "content": "``null``\n========\n\n``null`` returns ``true`` if the variable is ``null``:\n\n.. code-block:: twig\n\n    {{ var is null }}\n\n.. note::\n\n    ``none`` is an alias for ``null``.\n"
  },
  {
    "path": "doc/tests/odd.rst",
    "content": "``odd``\n=======\n\n``odd`` returns ``true`` if the given number is odd:\n\n.. code-block:: twig\n\n    {{ var is odd }}\n\n.. seealso::\n\n    :doc:`even<../tests/even>`\n"
  },
  {
    "path": "doc/tests/sameas.rst",
    "content": "``same as``\n===========\n\n``same as`` checks if a variable is the same as another variable.\nThis is equivalent to ``===`` in PHP:\n\n.. code-block:: twig\n\n    {% if user.name is same as(false) %}\n        the user attribute is the 'false' PHP value\n    {% endif %}\n"
  },
  {
    "path": "doc/tests/sequence.rst",
    "content": "``sequence``\n============\n\n``sequence`` checks if a variable is a sequence:\n\n.. code-block:: twig\n\n    {% set users = [\"Alice\", \"Bob\"] %}\n    {# evaluates to true if the users variable is a sequence #}\n    {% if users is sequence %}\n        {% for user in users %}\n            Hello {{ user }}!\n        {% endfor %}\n    {% endif %}\n"
  },
  {
    "path": "extra/cache-extra/.gitattributes",
    "content": "/Tests export-ignore\n/.git* export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": "extra/cache-extra/.gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n"
  },
  {
    "path": "extra/cache-extra/CacheExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Cache;\n\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\Extra\\Cache\\TokenParser\\CacheTokenParser;\n\nfinal class CacheExtension extends AbstractExtension\n{\n    public function getTokenParsers(): array\n    {\n        return [\n            new CacheTokenParser(),\n        ];\n    }\n}\n"
  },
  {
    "path": "extra/cache-extra/CacheRuntime.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Cache;\n\nuse Symfony\\Contracts\\Cache\\CacheInterface;\n\nclass CacheRuntime\n{\n    private $cache;\n\n    public function __construct(CacheInterface $cache)\n    {\n        $this->cache = $cache;\n    }\n\n    public function getCache(): CacheInterface\n    {\n        return $this->cache;\n    }\n}\n"
  },
  {
    "path": "extra/cache-extra/LICENSE",
    "content": "Copyright (c) 2021-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "extra/cache-extra/Node/CacheNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Cache\\Node;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\CaptureNode;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Node;\n\nclass CacheNode extends AbstractExpression\n{\n    public function __construct(AbstractExpression $key, ?AbstractExpression $ttl, ?AbstractExpression $tags, Node $body, int $lineno)\n    {\n        $body = new CaptureNode($body, $lineno);\n        $body->setAttribute('raw', true);\n\n        $nodes = ['key' => $key, 'body' => $body];\n        if (null !== $ttl) {\n            $nodes['ttl'] = $ttl;\n        }\n        if (null !== $tags) {\n            $nodes['tags'] = $tags;\n        }\n\n        parent::__construct($nodes, [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->raw('$this->env->getRuntime(\\'Twig\\Extra\\Cache\\CacheRuntime\\')->getCache()->get(')\n            ->subcompile($this->getNode('key'))\n            ->raw(\", function (\\Symfony\\Contracts\\Cache\\ItemInterface \\$item) use (\\$context, \\$macros, \\$blocks) {\\n\")\n            ->indent()\n        ;\n\n        if ($this->hasNode('tags')) {\n            $compiler\n                ->write('$item->tag(')\n                ->subcompile($this->getNode('tags'))\n                ->raw(\");\\n\")\n            ;\n        }\n\n        if ($this->hasNode('ttl')) {\n            $compiler\n                ->write('$item->expiresAfter(')\n                ->subcompile($this->getNode('ttl'))\n                ->raw(\");\\n\")\n            ;\n        }\n\n        $compiler\n            ->write('return ')\n            ->subcompile($this->getNode('body'))\n            ->raw(\";\\n\")\n            ->outdent()\n            ->write(\"})\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "extra/cache-extra/README.md",
    "content": "Cache Extension\n===============\n\nThis package is a Twig extension that provides integration with the Symfony\nCache component.\n\nIt provides a single [`cache`][1] tag that allows to cache template fragments.\n\n[1]: https://twig.symfony.com/cache\n"
  },
  {
    "path": "extra/cache-extra/Tests/Fixtures/cache.test",
    "content": "--TEST--\n\"cache\" tag\n--TEMPLATE--\n{% set foo = \"bar\" %}\n{% set value1 %}\n    {% cache \"test;v1\" ttl(3) %}\n        {% set foo = \"bar1\" %}\n        {{ random(1, 1000000) }}\n    {% endcache %}\n{% endset %}\n{% set value2 %}\n    {% cache \"test;v1\" ttl(3) %}\n        {{ random(1, 1000000) }}\n    {% endcache %}\n{% endset %}\n{{ value1 == value2 ? 'OK' : 'KO' }}\n{{ foo == \"bar\" ? 'OK' : 'KO' }}\n--DATA--\nreturn []\n--EXPECT--\nOK\nOK\n"
  },
  {
    "path": "extra/cache-extra/Tests/Fixtures/cache_complex.test",
    "content": "--TEST--\n\"cache\" tag\n--TEMPLATE--\n{% cache 'test_%s_%s'|format(10, 10000) ttl(36000) %}\n   {% set content %}\n      ok\n   {% endset %}\n   {% apply upper %}\n       {{ content }}\n    {% endapply %}\n{% endcache %}\n--DATA--\nreturn []\n--EXPECT--\nOK\n"
  },
  {
    "path": "extra/cache-extra/Tests/Fixtures/cache_with_blocks.test",
    "content": "--TEST--\n\"cache\" tag\n--TEMPLATE--\n{% extends \"layout.twig\" %}\n{% block bar %}\n    {% cache \"foo\" %}\n        {%- block content %}FOO{% endblock %}\n    {% endcache %}\n{% endblock %}\n--TEMPLATE(layout.twig)--\n{% block content %}{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nFOO\n"
  },
  {
    "path": "extra/cache-extra/Tests/Fixtures/macro.test",
    "content": "--TEST--\nmacro call inside \"cache\" tag\n--TEMPLATE--\n{% macro macro_out(bar) %}{{ bar }}{% endmacro %}\n1\n{% cache \"testmacro1\" ttl(3) %}\n    {%~ macro macro_in(bar) %}{{ bar }}{% endmacro %}\n    2\n    {{ _self.macro_out(3) }}\n    {{ _self.macro_in(4) }}\n{% endcache %}\n5\n{{ _self.macro_in(6) }}\n--DATA--\nreturn []\n--EXPECT--\n1\n    2\n    3\n    4\n5\n6\n"
  },
  {
    "path": "extra/cache-extra/Tests/FunctionalTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Cache\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Cache\\Adapter\\ArrayAdapter;\nuse Symfony\\Contracts\\Cache\\CacheInterface;\nuse Twig\\Environment;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Extra\\Cache\\CacheExtension;\nuse Twig\\Extra\\Cache\\CacheRuntime;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\RuntimeLoader\\RuntimeLoaderInterface;\n\nclass FunctionalTest extends TestCase\n{\n    public function testIsCached()\n    {\n        $cache = new ArrayAdapter();\n        $twig = $this->createEnvironment(['index' => '{% cache \"city;v1\" %}{{- city -}}{% endcache %}'], $cache);\n\n        $this->assertSame('Paris', $twig->render('index', ['city' => 'Paris']));\n        $value = $cache->get('city;v1', static function () { throw new \\RuntimeException('Key should be in the cache'); });\n        $this->assertSame('Paris', $value);\n    }\n\n    public function testTtlNoArgs()\n    {\n        $twig = $this->createEnvironment(['index' => '{% cache \"ttl_no_args\" ttl() %}{% endcache %}']);\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('The \"ttl\" modifier takes exactly one argument (0 given) in \"index\" at line 1.');\n        $twig->render('index');\n    }\n\n    public function testTtlTooManyArgs()\n    {\n        $twig = $this->createEnvironment(['index' => '{% cache \"ttl_too_many_args\" ttl(0, 1) %}{% endcache %}']);\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('The \"ttl\" modifier takes exactly one argument (2 given) in \"index\" at line 1.');\n        $twig->render('index');\n    }\n\n    public function testTagsNoArgs()\n    {\n        $twig = $this->createEnvironment(['index' => '{% cache \"tags_no_args\" tags() %}{% endcache %}']);\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('The \"tags\" modifier takes exactly one argument (0 given) in \"index\" at line 1.');\n        $twig->render('index');\n    }\n\n    public function testTagsTooManyArgs()\n    {\n        $twig = $this->createEnvironment(['index' => '{% cache \"tags_too_many_args\" tags([\"foo\"], 1) %}{% endcache %}']);\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('The \"tags\" modifier takes exactly one argument (2 given) in \"index\" at line 1.');\n        $twig->render('index');\n    }\n\n    private function createEnvironment(array $templates, ?ArrayAdapter $cache = null): Environment\n    {\n        $twig = new Environment(new ArrayLoader($templates));\n        $cache = $cache ?? new ArrayAdapter();\n        $twig->addExtension(new CacheExtension());\n        $twig->addRuntimeLoader(new class($cache) implements RuntimeLoaderInterface {\n            private $cache;\n\n            public function __construct(CacheInterface $cache)\n            {\n                $this->cache = $cache;\n            }\n\n            public function load(string $class): ?object\n            {\n                return CacheRuntime::class === $class ? new CacheRuntime($this->cache) : null;\n            }\n        });\n\n        return $twig;\n    }\n}\n"
  },
  {
    "path": "extra/cache-extra/Tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Cache\\Tests;\n\nuse Symfony\\Component\\Cache\\Adapter\\ArrayAdapter;\nuse Twig\\Extra\\Cache\\CacheExtension;\nuse Twig\\Extra\\Cache\\CacheRuntime;\nuse Twig\\RuntimeLoader\\RuntimeLoaderInterface;\nuse Twig\\Test\\IntegrationTestCase;\n\nclass IntegrationTest extends IntegrationTestCase\n{\n    public function getExtensions()\n    {\n        return [\n            new CacheExtension(),\n        ];\n    }\n\n    protected function getRuntimeLoaders()\n    {\n        return [\n            new class implements RuntimeLoaderInterface {\n                public function load(string $class): ?object\n                {\n                    return CacheRuntime::class === $class ? new CacheRuntime(new ArrayAdapter()) : null;\n                }\n            },\n        ];\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        return __DIR__.'/Fixtures/';\n    }\n}\n"
  },
  {
    "path": "extra/cache-extra/TokenParser/CacheTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Cache\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Extra\\Cache\\Node\\CacheNode;\nuse Twig\\Node\\Expression\\Filter\\RawFilter;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Token;\nuse Twig\\TokenParser\\AbstractTokenParser;\n\nclass CacheTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $stream = $this->parser->getStream();\n        $key = $this->parser->parseExpression();\n\n        $ttl = null;\n        $tags = null;\n        while ($stream->test(Token::NAME_TYPE)) {\n            $k = $stream->getCurrent()->getValue();\n            if (!\\in_array($k, ['ttl', 'tags'], true)) {\n                throw new SyntaxError(\\sprintf('Unknown \"%s\" configuration.', $k), $stream->getCurrent()->getLine(), $stream->getSourceContext());\n            }\n\n            $stream->next();\n            $stream->expect(Token::OPERATOR_TYPE, '(');\n            $line = $stream->getCurrent()->getLine();\n            if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {\n                throw new SyntaxError(\\sprintf('The \"%s\" modifier takes exactly one argument (0 given).', $k), $line, $stream->getSourceContext());\n            }\n            $arg = $this->parser->parseExpression();\n            if ($stream->test(Token::PUNCTUATION_TYPE, ',')) {\n                throw new SyntaxError(\\sprintf('The \"%s\" modifier takes exactly one argument (2 given).', $k), $line, $stream->getSourceContext());\n            }\n            $stream->expect(Token::PUNCTUATION_TYPE, ')');\n\n            if ('ttl' === $k) {\n                $ttl = $arg;\n            } elseif ('tags' === $k) {\n                $tags = $arg;\n            }\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $body = $this->parser->subparse([$this, 'decideCacheEnd'], true);\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        $body = new CacheNode($key, $ttl, $tags, $body, $token->getLine());\n\n        return new PrintNode(new RawFilter($body), $token->getLine());\n    }\n\n    public function decideCacheEnd(Token $token): bool\n    {\n        return $token->test('endcache');\n    }\n\n    public function getTag(): string\n    {\n        return 'cache';\n    }\n}\n"
  },
  {
    "path": "extra/cache-extra/composer.json",
    "content": "{\n    \"name\": \"twig/cache-extra\",\n    \"type\": \"library\",\n    \"description\": \"A Twig extension for Symfony Cache\",\n    \"keywords\": [\"twig\", \"html\", \"cache\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"MIT\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"symfony/cache\": \"^5.4|^6.4|^7.0|^8.0\",\n        \"twig/twig\": \"^3.21|^4.0\"\n    },\n    \"require-dev\": {\n        \"symfony/phpunit-bridge\": \"^6.4|^7.0\"\n    },\n    \"autoload\": {\n        \"psr-4\" : { \"Twig\\\\Extra\\\\Cache\\\\\" : \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    }\n}\n"
  },
  {
    "path": "extra/cache-extra/phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\" backupGlobals=\"false\" colors=\"true\" bootstrap=\"vendor/autoload.php\" failOnRisky=\"true\" failOnWarning=\"true\">\n  <coverage>\n    <include>\n      <directory>./</directory>\n    </include>\n    <exclude>\n      <directory>./Tests</directory>\n      <directory>./vendor</directory>\n    </exclude>\n  </coverage>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n  </php>\n  <testsuites>\n    <testsuite name=\"Twig Cache Extension Test Suite\">\n      <directory>./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "extra/cssinliner-extra/.gitattributes",
    "content": "/Tests export-ignore\n/.git* export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": "extra/cssinliner-extra/.gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n"
  },
  {
    "path": "extra/cssinliner-extra/CssInlinerExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\CssInliner;\n\nuse TijsVerkoyen\\CssToInlineStyles\\CssToInlineStyles;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\TwigFilter;\n\nclass CssInlinerExtension extends AbstractExtension\n{\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('inline_css', [self::class, 'inlineCss'], ['is_safe' => ['all']]),\n        ];\n    }\n\n    /**\n     * @internal\n     */\n    public static function inlineCss(string $body, string ...$css): string\n    {\n        static $inliner;\n        if (null === $inliner) {\n            $inliner = new CssToInlineStyles();\n        }\n\n        return $inliner->convert($body, implode(\"\\n\", $css));\n    }\n}\n"
  },
  {
    "path": "extra/cssinliner-extra/LICENSE",
    "content": "Copyright (c) 2019-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "extra/cssinliner-extra/README.md",
    "content": "Twig CssInliner Extension\n=========================\n\nThis package is a Twig extension that provides the following:\n\n * [`inline_css`][1] filter: inlines CSS styles in HTML documents.\n\n[1]: https://twig.symfony.com/inline_css\n"
  },
  {
    "path": "extra/cssinliner-extra/Resources/functions.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\CssInliner;\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9.0\n */\nfunction twig_inline_css(string $body, string ...$css): string\n{\n    trigger_deprecation('twig/cssinliner-extra', '3.9.0', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CssInlinerExtension::inlineCss($body, ...$css);\n}\n"
  },
  {
    "path": "extra/cssinliner-extra/Tests/Fixtures/inline_css.test",
    "content": "--TEST--\n\"inline_css\" filter\n--TEMPLATE--\n{% apply inline_css %}\n    <html><style>p { color: red }</style><p>Great!</p></html>\n{% endapply %}\n\n\n{% apply inline_css(source('css')) %}\n    <html><p>Great!</p></html>\n{% endapply %}\n\n\n{% apply inline_css(source('css'), source('more_css')) %}\n    <html><p>Great!</p></html>\n{% endapply %}\n\n\n{% apply inline_css(source('css') ~ source('more_css')) %}\n    <html><p>Great!</p></html>\n{% endapply %}\n\n\n{{ include('html')|inline_css(source('css') ~ source('more_css')) }}\n--TEMPLATE(html)--\n    <html><p>Great!</p></html>\n--TEMPLATE(css)--\np { color: red }\n--TEMPLATE(more_css)--\np { color: blue }\n--DATA--\nreturn []\n--EXPECT--\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html>\n<head><style>p { color: red }</style></head>\n<body><p style=\"color: red;\">Great!</p></body>\n</html>\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p style=\"color: red;\">Great!</p></body></html>\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p style=\"color: blue;\">Great!</p></body></html>\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p style=\"color: blue;\">Great!</p></body></html>\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p style=\"color: blue;\">Great!</p></body></html>\n"
  },
  {
    "path": "extra/cssinliner-extra/Tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\CssInliner\\Tests;\n\nuse Twig\\Extra\\CssInliner\\CssInlinerExtension;\nuse Twig\\Test\\IntegrationTestCase;\n\nclass IntegrationTest extends IntegrationTestCase\n{\n    public function getExtensions()\n    {\n        return [\n            new CssInlinerExtension(),\n        ];\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        return __DIR__.'/Fixtures/';\n    }\n}\n"
  },
  {
    "path": "extra/cssinliner-extra/Tests/LegacyFunctionsTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\CssInliner\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Extra\\CssInliner\\CssInlinerExtension;\n\nuse function Twig\\Extra\\CssInliner\\twig_inline_css;\n\n/**\n * @group legacy\n */\nclass LegacyFunctionsTest extends TestCase\n{\n    public function testInlineCss()\n    {\n        $this->assertSame(CssInlinerExtension::inlineCss('<p>body</p>', 'p { color: red }'), twig_inline_css('<p>body</p>', 'p { color: red }'));\n    }\n}\n"
  },
  {
    "path": "extra/cssinliner-extra/composer.json",
    "content": "{\n    \"name\": \"twig/cssinliner-extra\",\n    \"type\": \"library\",\n    \"description\": \"A Twig extension to allow inlining CSS\",\n    \"keywords\": [\"twig\", \"css\", \"inlining\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"MIT\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"symfony/deprecation-contracts\": \"^2.5|^3\",\n        \"tijsverkoyen/css-to-inline-styles\": \"^2.0\",\n        \"twig/twig\": \"^3.13|^4.0\"\n    },\n    \"require-dev\": {\n        \"symfony/phpunit-bridge\": \"^6.4|^7.0\"\n    },\n    \"autoload\": {\n        \"files\": [ \"Resources/functions.php\" ],\n        \"psr-4\" : { \"Twig\\\\Extra\\\\CssInliner\\\\\" : \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    }\n}\n"
  },
  {
    "path": "extra/cssinliner-extra/phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\" backupGlobals=\"false\" colors=\"true\" bootstrap=\"vendor/autoload.php\" failOnRisky=\"true\" failOnWarning=\"true\">\n  <coverage>\n    <include>\n      <directory>./</directory>\n    </include>\n    <exclude>\n      <directory>./Tests</directory>\n      <directory>./vendor</directory>\n    </exclude>\n  </coverage>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n  </php>\n  <testsuites>\n    <testsuite name=\"Twig CssInlinder Extension Test Suite\">\n      <directory>./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "extra/html-extra/.gitattributes",
    "content": "/Tests export-ignore\n/.git* export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": "extra/html-extra/.gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n"
  },
  {
    "path": "extra/html-extra/Cva.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html;\n\n/**\n * Class Variant Authority (CVA) resolver.\n *\n * @author Mathéo Daninos <matheo.daninos@gmail.com>\n */\nfinal class Cva\n{\n    /**\n     * @var list<string|null>\n     */\n    private array $base;\n\n    /**\n     * @param string|list<string|null> $base The base classes to apply to the component\n     */\n    public function __construct(\n        string|array $base = [],\n        /**\n         * The variants to apply based on recipes.\n         *\n         * Format: [variantCategory => [variantName => classes]]\n         *\n         * Example:\n         *      'colors' => [\n         *          'primary' => 'bleu-8000',\n         *          'danger' => 'red-800 text-bold',\n         *       ],\n         *      'size' => [...],\n         *\n         * @var array<string, array<string, string|list<string>>>\n         */\n        private array $variants = [],\n\n        /**\n         * The compound variants to apply based on recipes.\n         *\n         * Format: [variantsCategory => ['variantName', 'variantName'], class: classes]\n         *\n         * Example:\n         *   [\n         *      'colors' => ['primary'],\n         *      'size' => ['small'],\n         *      'class' => 'text-red-500',\n         *   ],\n         *   [\n         *      'size' => ['large'],\n         *      'class' => 'font-weight-500',\n         *   ]\n         *\n         * @var array<array<string, string|array<string>>>\n         */\n        private array $compoundVariants = [],\n\n        /**\n         * The default variants to apply if specific recipes aren't provided.\n         *\n         * Format: [variantCategory => variantName]\n         *\n         * Example:\n         *     'colors' => 'primary',\n         *\n         * @var array<string, string>\n         */\n        private array $defaultVariants = [],\n    ) {\n        $this->base = (array) $base;\n    }\n\n    public function apply(array $recipes, ?string ...$additionalClasses): string\n    {\n        $classes = $this->base;\n\n        // Resolve recipes against variants\n        foreach ($recipes as $recipeName => $recipeValue) {\n            if (\\is_bool($recipeValue)) {\n                $recipeValue = $recipeValue ? 'true' : 'false';\n            }\n            $recipeClasses = $this->variants[$recipeName][$recipeValue] ?? [];\n            $classes = [...$classes, ...(array) $recipeClasses];\n        }\n\n        // Resolve compound variants\n        foreach ($this->compoundVariants as $compound) {\n            $compoundClasses = $this->resolveCompoundVariant($compound, $recipes) ?? [];\n            $classes = [...$classes, ...$compoundClasses];\n        }\n\n        // Apply default variants if specific recipes aren't provided\n        foreach ($this->defaultVariants as $defaultVariantName => $defaultVariantValue) {\n            if (!isset($recipes[$defaultVariantName])) {\n                $variantClasses = $this->variants[$defaultVariantName][$defaultVariantValue] ?? [];\n                $classes = [...$classes, ...(array) $variantClasses];\n            }\n        }\n        $classes = [...$classes, ...array_values($additionalClasses)];\n\n        $classes = implode(' ', array_filter($classes, 'is_string'));\n        $classes = preg_split('#\\s+#', $classes, -1, \\PREG_SPLIT_NO_EMPTY) ?: [];\n\n        return implode(' ', array_unique($classes));\n    }\n\n    private function resolveCompoundVariant(array $compound, array $recipes): array\n    {\n        foreach ($compound as $compoundName => $compoundValues) {\n            if ('class' === $compoundName) {\n                continue;\n            }\n            if (!isset($recipes[$compoundName]) || !\\in_array($recipes[$compoundName], (array) $compoundValues, true)) {\n                return [];\n            }\n        }\n\n        return (array) ($compound['class'] ?? []);\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/HtmlAttr/AttributeValueInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\HtmlAttr;\n\n/**\n * Interface for custom attribute value objects.\n *\n * Implement this interface to create custom conversion logic when printing out objects as attribute\n * values in the `html_attr` function.\n *\n * @author Matthias Pigulla <mp@webfactory.de>\n */\ninterface AttributeValueInterface\n{\n    /**\n     * Returns the string representation of the attribute value. The returned value\n     * will automatically be escaped for the HTML attribute context.\n     *\n     * @return string|null the attribute value as a string, or null to omit the attribute\n     */\n    public function getValue(): ?string;\n}\n"
  },
  {
    "path": "extra/html-extra/HtmlAttr/InlineStyle.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\HtmlAttr;\n\nuse Twig\\Error\\RuntimeError;\n\n/**\n * @author Matthias Pigulla <mp@webfactory.de>\n */\nfinal class InlineStyle implements MergeableInterface, AttributeValueInterface\n{\n    private readonly array $value;\n\n    public function __construct(mixed $value)\n    {\n        if (!is_iterable($value)) {\n            throw new RuntimeError('InlineStyle can only be created from iterable values.');\n        }\n\n        $this->value = [...$value];\n    }\n\n    public function mergeInto(mixed $previous): mixed\n    {\n        if ($previous instanceof self) {\n            return new self([...$previous->value, ...$this->value]);\n        }\n\n        if (is_iterable($previous)) {\n            return new self([...$previous, ...$this->value]);\n        }\n\n        throw new RuntimeError('Attributes using InlineStyle can only be merged with iterables or other InlineStyle instances.');\n    }\n\n    public function appendFrom(mixed $newValue): mixed\n    {\n        if (!is_iterable($newValue)) {\n            throw new RuntimeError('Only iterable values can be appended to InlineStyle.');\n        }\n\n        return new self([...$this->value, ...$newValue]);\n    }\n\n    public function getValue(): ?string\n    {\n        $style = '';\n        foreach ($this->value as $name => $value) {\n            if (empty($value) || true === $value) {\n                continue;\n            }\n            if (is_numeric($name)) {\n                $style .= trim($value, '; ').'; ';\n            } else {\n                $style .= $name.': '.$value.'; ';\n            }\n        }\n\n        return trim($style) ?: null;\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/HtmlAttr/MergeableInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\HtmlAttr;\n\n/**\n * Interface for attribute values that support custom merge behavior.\n *\n * Implement this interface to define how attribute values should be merged when multiple\n * attribute arrays are combined using `html_attr_merge`. This allows for\n * sophisticated merging logic beyond simple array merging.\n *\n * Objects implementing this interface probably want to implement either {@see AttributeValueInterface}\n * or `\\Stringable` as well, in order to provide a reasonable to-string-conversion when being\n * the last value after a merge.\n *\n * @author Matthias Pigulla <mp@webfactory.de>\n */\ninterface MergeableInterface\n{\n    /**\n     * Merge value from $this with another, previous value. In the merge arguments list, $this is on the right\n     * hand side of $previous. This is the preferred merge method, so that newer (right) values have control\n     * over the result.\n     *\n     * The `$previous` value is whatever value was present for a particular attribute before. Implementations\n     * are free to completely ignore this value, effectively implementing \"override only\" merge behavior.\n     * They can merge it with their own value, resulting in array-like merge behavior. Or they could throw\n     * an exception when only particular types can be merged.\n     *\n     * Returns the new, resulting value as a new instance. Does not modify either $this not $previous.\n     */\n    public function mergeInto(mixed $previous): mixed;\n\n    /**\n     * Merge value from $this with a new value. In the merge arguments list, $this is on the left hand side of\n     * $newValue, but $newValue does not implement this interface itself.\n     *\n     * The `$newValue` value is whatever was given in the right hand side merge argument. Implementations are\n     * free to return either the new value, implementing \"override\" merge behavior. They could also return\n     * a value that represents a merge result between their current and the new value. Or they could throw\n     * an exception when only particular types can be merged.\n     *\n     * Returns the new, resulting set as a new instance. Does not modify $this.\n     */\n    public function appendFrom(mixed $newValue): mixed;\n}\n"
  },
  {
    "path": "extra/html-extra/HtmlAttr/SeparatedTokenList.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\HtmlAttr;\n\nuse Twig\\Error\\RuntimeError;\n\n/**\n * @author Matthias Pigulla <mp@webfactory.de>\n */\nfinal class SeparatedTokenList implements AttributeValueInterface, MergeableInterface\n{\n    private readonly array $value;\n\n    public function __construct(mixed $value, private readonly string $separator = ' ')\n    {\n        if (is_iterable($value)) {\n            $this->value = [...$value];\n        } elseif (\\is_scalar($value)) {\n            $this->value = [$value];\n        } else {\n            throw new RuntimeError('SeparatedTokenList can only be constructed from iterable or scalar values.');\n        }\n    }\n\n    public function mergeInto(mixed $previous): mixed\n    {\n        if ($previous instanceof self && $previous->separator === $this->separator) {\n            return new self([...$previous->value, ...$this->value], $this->separator);\n        }\n\n        if (is_iterable($previous)) {\n            return new self([...$previous, ...$this->value], $this->separator);\n        }\n\n        throw new RuntimeError('SeparatedTokenList can only be merged with iterables or other SeparatedTokenList instances using the same separator.');\n    }\n\n    public function appendFrom(mixed $newValue): mixed\n    {\n        if (!is_iterable($newValue)) {\n            throw new RuntimeError('Only iterable values can be appended to SeparatedTokenList.');\n        }\n\n        return new self([...$this->value, ...$newValue], $this->separator);\n    }\n\n    public function getValue(): ?string\n    {\n        $filtered = array_filter($this->value, static fn ($v) => null !== $v && false !== $v);\n\n        // Omit attribute if list contains only false or null values\n        if (!$filtered) {\n            return null;\n        }\n\n        // true values are not printed, but result in the attribute not being omitted\n        return trim(implode($this->separator, array_filter($filtered, static fn ($v) => true !== $v)));\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/HtmlExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html;\n\nuse Symfony\\Component\\Mime\\MimeTypes;\nuse Twig\\Environment;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\Extra\\Html\\HtmlAttr\\AttributeValueInterface;\nuse Twig\\Extra\\Html\\HtmlAttr\\InlineStyle;\nuse Twig\\Extra\\Html\\HtmlAttr\\MergeableInterface;\nuse Twig\\Extra\\Html\\HtmlAttr\\SeparatedTokenList;\nuse Twig\\Markup;\nuse Twig\\Runtime\\EscaperRuntime;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\n\nfinal class HtmlExtension extends AbstractExtension\n{\n    private $mimeTypes;\n\n    public function __construct(?MimeTypes $mimeTypes = null)\n    {\n        $this->mimeTypes = $mimeTypes;\n    }\n\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('data_uri', [$this, 'dataUri']),\n            new TwigFilter('html_attr_merge', [self::class, 'htmlAttrMerge']),\n            new TwigFilter('html_attr_type', [self::class, 'htmlAttrType']),\n        ];\n    }\n\n    public function getFunctions(): array\n    {\n        return [\n            new TwigFunction('html_classes', [self::class, 'htmlClasses']),\n            new TwigFunction('html_cva', [self::class, 'htmlCva']),\n            new TwigFunction('html_attr', [self::class, 'htmlAttr'], ['needs_environment' => true, 'is_safe' => ['html']]),\n        ];\n    }\n\n    /**\n     * Creates a data URI (RFC 2397).\n     *\n     * Length validation is not performed on purpose, validation should\n     * be done before calling this filter.\n     *\n     * @return string The generated data URI\n     *\n     * @internal\n     */\n    public function dataUri(string $data, ?string $mime = null, array $parameters = []): string\n    {\n        $repr = 'data:';\n\n        if (null === $mime) {\n            if (null === $this->mimeTypes) {\n                $this->mimeTypes = new MimeTypes();\n            }\n\n            $tmp = tempnam(sys_get_temp_dir(), 'mime');\n            file_put_contents($tmp, $data);\n            try {\n                if (null === $mime = $this->mimeTypes->guessMimeType($tmp)) {\n                    $mime = 'text/plain';\n                }\n            } finally {\n                @unlink($tmp);\n            }\n        }\n        $repr .= $mime;\n\n        foreach ($parameters as $key => $value) {\n            $repr .= ';'.$key.'='.rawurlencode($value);\n        }\n\n        if (str_starts_with($mime, 'text/')) {\n            $repr .= ','.rawurlencode($data);\n        } else {\n            $repr .= ';base64,'.base64_encode($data);\n        }\n\n        return $repr;\n    }\n\n    /**\n     * @internal\n     */\n    public static function htmlClasses(...$args): string\n    {\n        $classes = [];\n        foreach ($args as $i => $arg) {\n            if (\\is_string($arg) || $arg instanceof Markup) {\n                $classes[] = (string) $arg;\n            } elseif (\\is_array($arg)) {\n                foreach ($arg as $class => $condition) {\n                    if (!\\is_string($class)) {\n                        throw new RuntimeError(\\sprintf('The \"html_classes\" function argument %d (key %d) should be a string, got \"%s\".', $i, $class, get_debug_type($class)));\n                    }\n                    if (!$condition) {\n                        continue;\n                    }\n                    $classes[] = $class;\n                }\n            } else {\n                throw new RuntimeError(\\sprintf('The \"html_classes\" function argument %d should be either a string or an array, got \"%s\".', $i, get_debug_type($arg)));\n            }\n        }\n\n        return implode(' ', array_unique(array_filter($classes, static function ($v) { return '' !== $v; })));\n    }\n\n    /**\n     * @param string|list<string|null>                           $base\n     * @param array<string, array<string, string|array<string>>> $variants\n     * @param array<array<string, string|array<string>>>         $compoundVariants\n     * @param array<string, string>                              $defaultVariant\n     *\n     * @internal\n     */\n    public static function htmlCva(array|string $base = [], array $variants = [], array $compoundVariants = [], array $defaultVariant = []): Cva\n    {\n        return new Cva($base, $variants, $compoundVariants, $defaultVariant);\n    }\n\n    /** @internal */\n    public static function htmlAttrType(mixed $value, string $type = 'sst'): AttributeValueInterface\n    {\n        return match ($type) {\n            'sst' => new SeparatedTokenList($value, ' '),\n            'cst' => new SeparatedTokenList($value, ', '),\n            'style' => new InlineStyle($value),\n            default => throw new RuntimeError(\\sprintf('Unknown attribute type \"%s\" The only supported types are \"sst\", \"cst\" and \"style\".', $type)),\n        };\n    }\n\n    /** @internal */\n    public static function htmlAttrMerge(iterable|string|false|null ...$arrays): array\n    {\n        $result = [];\n\n        foreach ($arrays as $array) {\n            if (!$array) {\n                continue;\n            }\n\n            if (\\is_string($array)) {\n                throw new RuntimeError('Only empty strings may be passed as string arguments to html_attr_merge. This is to support the implicit else clause for ternary operators.');\n            }\n\n            foreach ($array as $key => $value) {\n                if (!isset($result[$key])) {\n                    $result[$key] = $value;\n\n                    continue;\n                }\n\n                $existing = $result[$key];\n\n                switch (true) {\n                    case $value instanceof MergeableInterface:\n                        $result[$key] = $value->mergeInto($existing);\n                        break;\n                    case $existing instanceof MergeableInterface:\n                        $result[$key] = $existing->appendFrom($value);\n                        break;\n                    case is_iterable($existing) && is_iterable($value):\n                        $result[$key] = [...$existing, ...$value];\n                        break;\n                    case (\\is_scalar($existing) || \\is_object($existing)) && (\\is_scalar($value) || \\is_object($value)):\n                        $result[$key] = $value;\n                        break;\n                    default:\n                        throw new RuntimeError(\\sprintf('Cannot merge incompatible values for key \"%s\".', $key));\n                }\n            }\n        }\n\n        return $result;\n    }\n\n    /** @internal */\n    public static function htmlAttr(Environment $env, iterable|string|false|null ...$args): string\n    {\n        $attr = self::htmlAttrMerge(...$args);\n\n        $result = '';\n        $runtime = $env->getRuntime(EscaperRuntime::class);\n\n        foreach ($attr as $name => $value) {\n            if (str_starts_with($name, 'aria-')) {\n                // For aria-*, convert booleans to \"true\" and \"false\" strings\n                if (true === $value) {\n                    $value = 'true';\n                } elseif (false === $value) {\n                    $value = 'false';\n                }\n            }\n\n            if (str_starts_with($name, 'data-')) {\n                if (!$value instanceof AttributeValueInterface && null !== $value && !\\is_scalar($value)) {\n                    // ... encode non-null non-scalars as JSON\n                    try {\n                        $value = json_encode($value, \\JSON_THROW_ON_ERROR);\n                    } catch (\\JsonException $e) {\n                        throw new RuntimeError(\\sprintf('The \"%s\" attribute value cannot be JSON encoded.', $name), previous: $e);\n                    }\n                } elseif (true === $value) {\n                    // ... and convert boolean true to a 'true'  string.\n                    $value = 'true';\n                }\n            }\n\n            // Convert iterable values to token lists\n            if (!$value instanceof AttributeValueInterface && is_iterable($value)) {\n                if ('style' === $name) {\n                    $value = new InlineStyle($value);\n                } else {\n                    $value = new SeparatedTokenList($value);\n                }\n            }\n\n            if ($value instanceof AttributeValueInterface) {\n                $value = $value->getValue();\n            }\n\n            // In general, ...\n            if (true === $value) {\n                // ... use attribute=\"\" for boolean true,\n                // which is XHTML compliant and indicates the \"empty value default\", see\n                // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 and\n                // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes\n                $value = '';\n            }\n\n            if (null === $value || false === $value) {\n                // omit null-valued and false attributes completely (note aria-* has been processed before)\n                continue;\n            }\n\n            if (\\is_object($value) && !$value instanceof \\Stringable) {\n                throw new RuntimeError(\\sprintf('The \"%s\" attribute value should be a scalar, an iterable, or an object implementing \"%s\", got \"%s\".', $name, \\Stringable::class, get_debug_type($value)));\n            }\n\n            $result .= $runtime->escape($name, 'html_attr_relaxed').'=\"'.$runtime->escape((string) $value).'\" ';\n        }\n\n        return trim($result);\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/LICENSE",
    "content": "Copyright (c) 2019-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "extra/html-extra/README.md",
    "content": "Twig HTML Extension\n===================\n\nThis package is a Twig extension that provides the following:\n\n * [`data_uri`][1] filter: generates a URL using the data scheme as defined in\n   RFC 2397;\n\n * [`html_classes`][2] function: returns a string by conditionally joining class\n   names together.\n\n * [`html_cva`][3] function: returns a `Cva` object to handle class variants.\n\n[1]: https://twig.symfony.com/data_uri\n[2]: https://twig.symfony.com/html_classes\n[3]: https://twig.symfony.com/html_cva\n"
  },
  {
    "path": "extra/html-extra/Resources/functions.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Extra\\Html\\HtmlExtension;\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9.0\n */\nfunction twig_html_classes(...$args): string\n{\n    trigger_deprecation('twig/html-extra', '3.9.0', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return HtmlExtension::htmlClasses(...$args);\n}\n"
  },
  {
    "path": "extra/html-extra/Tests/CvaTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Extra\\Html\\Cva;\n\nclass CvaTest extends TestCase\n{\n    /**\n     * @dataProvider recipeProvider\n     */\n    public function testRecipes(array $recipe, array $recipes, string $expected)\n    {\n        $recipeClass = new Cva($recipe['base'] ?? '', $recipe['variants'] ?? [], $recipe['compounds'] ?? [], $recipe['defaultVariants'] ?? []);\n\n        $this->assertEquals($expected, $recipeClass->apply($recipes));\n    }\n\n    public function testApply()\n    {\n        $recipe = new Cva('font-semibold border rounded', [\n            'colors' => [\n                'primary' => 'text-primary',\n                'secondary' => 'text-secondary',\n            ],\n            'sizes' => [\n                'sm' => 'text-sm',\n                'md' => 'text-md',\n                'lg' => 'text-lg',\n            ],\n        ], [\n            [\n                'colors' => ['primary'],\n                'sizes' => ['sm'],\n                'class' => 'text-red-500',\n            ],\n        ]);\n\n        $this->assertEquals('font-semibold border rounded text-primary text-sm text-red-500', $recipe->apply(['colors' => 'primary', 'sizes' => 'sm']));\n    }\n\n    public function testApplyWithNullString()\n    {\n        $recipe = new Cva('font-semibold border rounded', [\n            'colors' => [\n                'primary' => 'text-primary',\n                'secondary' => 'text-secondary',\n            ],\n            'sizes' => [\n                'sm' => 'text-sm',\n                'md' => 'text-md',\n                'lg' => 'text-lg',\n            ],\n        ], [\n            [\n                'colors' => ['primary'],\n                'sizes' => ['sm'],\n                'class' => 'text-red-500',\n            ],\n        ]);\n\n        $this->assertEquals('font-semibold border rounded text-primary text-sm text-red-500 flex justify-center', $recipe->apply(['colors' => 'primary', 'sizes' => 'sm'], 'flex', null, 'justify-center'));\n    }\n\n    public static function recipeProvider(): iterable\n    {\n        yield 'base null' => [\n            ['variants' => [\n                'colors' => [\n                    'primary' => 'text-primary',\n                    'secondary' => 'text-secondary',\n                ],\n                'sizes' => [\n                    'sm' => 'text-sm',\n                    'md' => 'text-md',\n                    'lg' => 'text-lg',\n                ],\n            ]],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'text-primary text-sm',\n        ];\n\n        yield 'base empty' => [\n            [\n                'base' => '',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ]],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'text-primary text-sm',\n        ];\n\n        yield 'base array' => [\n            [\n                'base' => ['font-semibold', 'border', 'rounded'],\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ]],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary text-sm',\n        ];\n\n        yield 'no recipes match' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n            ],\n            ['colors' => 'red', 'sizes' => 'test'],\n            'font-semibold border rounded',\n        ];\n\n        yield 'simple variants' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary text-sm',\n        ];\n\n        yield 'simple variants as array' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => ['text-primary', 'uppercase'],\n                        'secondary' => ['text-secondary', 'uppercase'],\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary uppercase text-sm',\n        ];\n\n        yield 'simple variants with custom' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n            ],\n            ['colors' => 'secondary', 'sizes' => 'md'],\n            'font-semibold border rounded text-secondary text-md',\n        ];\n\n        yield 'compound variants' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => 'primary',\n                        'sizes' => ['sm'],\n                        'class' => 'text-red-100',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary text-sm text-red-100',\n        ];\n\n        yield 'compound variants with true' => [\n            [\n                'base' => 'button',\n                'variants' => [\n                    'colors' => [\n                        'blue' => 'btn-blue',\n                        'red' => 'btn-red',\n                    ],\n                    'disabled' => [\n                        'true' => 'disabled',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => 'blue',\n                        'disabled' => ['true'],\n                        'class' => 'font-bold',\n                    ],\n                ],\n            ],\n            ['colors' => 'blue', 'disabled' => 'true'],\n            'button btn-blue disabled font-bold',\n        ];\n\n        yield 'compound variants as array' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => ['primary'],\n                        'sizes' => ['sm'],\n                        'class' => ['text-red-900', 'bold'],\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary text-sm text-red-900 bold',\n        ];\n\n        yield 'multiple compound variants' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => ['primary'],\n                        'sizes' => ['sm'],\n                        'class' => 'text-red-300',\n                    ],\n                    [\n                        'colors' => ['primary'],\n                        'sizes' => ['md'],\n                        'class' => 'text-blue-300',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary text-sm text-red-300',\n        ];\n\n        yield 'compound with multiple variants' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => ['primary', 'secondary'],\n                        'sizes' => ['sm'],\n                        'class' => 'text-red-800',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary text-sm text-red-800',\n        ];\n\n        yield 'compound doesn\\'t match' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => ['danger', 'secondary'],\n                        'sizes' => ['sm'],\n                        'class' => 'text-red-500',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary text-sm',\n        ];\n\n        yield 'default variables' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                    'rounded' => [\n                        'sm' => 'rounded-sm',\n                        'md' => 'rounded-md',\n                        'lg' => 'rounded-lg',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => ['danger', 'secondary'],\n                        'sizes' => 'sm',\n                        'class' => 'text-red-500',\n                    ],\n                ],\n                'defaultVariants' => [\n                    'colors' => 'primary',\n                    'sizes' => 'sm',\n                    'rounded' => 'md',\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm'],\n            'font-semibold border rounded text-primary text-sm rounded-md',\n        ];\n\n        yield 'default variables all overwrite' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                    'rounded' => [\n                        'sm' => 'rounded-sm',\n                        'md' => 'rounded-md',\n                        'lg' => 'rounded-lg',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => ['danger', 'secondary'],\n                        'sizes' => ['sm'],\n                        'class' => 'text-red-500',\n                    ],\n                ],\n                'defaultVariants' => [\n                    'colors' => 'primary',\n                    'sizes' => 'sm',\n                    'rounded' => 'md',\n                ],\n            ],\n            ['colors' => 'primary', 'sizes' => 'sm', 'rounded' => 'lg'],\n            'font-semibold border rounded text-primary text-sm rounded-lg',\n        ];\n\n        yield 'default variables without matching variants' => [\n            [\n                'base' => 'font-semibold border rounded',\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'sizes' => [\n                        'sm' => 'text-sm',\n                        'md' => 'text-md',\n                        'lg' => 'text-lg',\n                    ],\n                    'rounded' => [\n                        'sm' => 'rounded-sm',\n                        'md' => 'rounded-md',\n                        'lg' => 'rounded-lg',\n                    ],\n                ],\n                'compounds' => [\n                    [\n                        'colors' => ['danger', 'secondary'],\n                        'sizes' => ['sm'],\n                        'class' => 'text-red-500',\n                    ],\n                ],\n                'defaultVariants' => [\n                    'colors' => 'primary',\n                    'sizes' => 'sm',\n                    'rounded' => 'md',\n                ],\n            ],\n            [],\n            'font-semibold border rounded text-primary text-sm rounded-md',\n        ];\n\n        yield 'default variables with boolean' => [\n            [\n                'base' => 'button',\n                'variants' => [\n                    'colors' => [\n                        'blue' => 'btn-blue',\n                        'red' => 'btn-red',\n                    ],\n                    'disabled' => [\n                        'true' => 'disabled',\n                        'false' => 'opacity-100',\n                    ],\n                ],\n                'defaultVariants' => [\n                    'colors' => 'blue',\n                    'disabled' => 'false',\n                ],\n            ],\n            [],\n            'button btn-blue opacity-100',\n        ];\n\n        yield 'boolean string variants true / true' => [\n            [\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'disabled' => [\n                        'true' => 'disable',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'disabled' => true],\n            'text-primary disable',\n        ];\n\n        yield 'boolean string variants true / false' => [\n            [\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'disabled' => [\n                        'true' => 'disable',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'disabled' => false],\n            'text-primary',\n        ];\n\n        yield 'boolean string variants false / true' => [\n            [\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'disabled' => [\n                        'false' => 'disable',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'disabled' => true],\n            'text-primary',\n        ];\n\n        yield 'boolean string variants false / false' => [\n            [\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'disabled' => [\n                        'false' => 'disable',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'disabled' => false],\n            'text-primary disable',\n        ];\n\n        yield 'boolean string variants missing' => [\n            [\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'disabled' => [\n                        'true' => 'disable',\n                    ],\n                ],\n            ],\n            ['colors' => 'primary'],\n            'text-primary',\n        ];\n\n        yield 'boolean list variants true' => [\n            [\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'disabled' => [\n                        'true' => ['disable', 'opacity-50'],\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'disabled' => true],\n            'text-primary disable opacity-50',\n        ];\n\n        yield 'boolean list variants false' => [\n            [\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'disabled' => [\n                        'true' => ['disable', 'opacity-50'],\n                    ],\n                ],\n            ],\n            ['colors' => 'primary', 'disabled' => false],\n            'text-primary',\n        ];\n\n        yield 'boolean list variants missing' => [\n            [\n                'variants' => [\n                    'colors' => [\n                        'primary' => 'text-primary',\n                        'secondary' => 'text-secondary',\n                    ],\n                    'disabled' => [\n                        'true' => ['disable', 'opacity-50'],\n                    ],\n                ],\n            ],\n            ['colors' => 'primary'],\n            'text-primary',\n        ];\n    }\n\n    /**\n     * @dataProvider provideAdditionalClassesCases\n     */\n    public function testAdditionalClasses(string|array $base, array|string $additionals, string $expected)\n    {\n        $cva = new Cva($base);\n        if (!$additionals) {\n            $this->assertEquals($expected, $cva->apply([]));\n        } else {\n            $this->assertEquals($expected, $cva->apply([], ...(array) $additionals));\n        }\n    }\n\n    public static function provideAdditionalClassesCases(): iterable\n    {\n        yield 'additionals_are_optional' => [\n            '',\n            'foo',\n            'foo',\n        ];\n\n        yield 'additional_are_used' => [\n            '',\n            'foo',\n            'foo',\n        ];\n\n        yield 'additionals_are_used' => [\n            '',\n            ['foo', 'bar'],\n            'foo bar',\n        ];\n\n        yield 'additionals_preserve_order' => [\n            ['foo'],\n            ['bar', 'foo'],\n            'foo bar',\n        ];\n\n        yield 'additional_are_deduplicated' => [\n            '',\n            ['bar', 'bar'],\n            'bar',\n        ];\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/Tests/Fixtures/data_uri.test",
    "content": "--TEST--\n\"data_uri\" filter\n--TEMPLATE--\n{{ 'foobar#'|data_uri(parameters={charset: \"utf-8\", foo: \"$bar\"}) }}\n{{ '<b>foobar</b>'|data_uri(mime=\"text/html\", parameters={charset: \"ascii\"}) }}\n<img src=\"{{ sf_logo|data_uri }}\" />\n--DATA--\nreturn [\n    'sf_logo' => base64_decode('iVBORw0KGgoAAAANSUhEUgAAAG8AAAAlCAQAAADmD3/hAAAE70lEQVRoBd3BAUTdiQMH8C+PR0TEOCIiRsQ4xvgTY4zHGDHGOCIeMY7HccQYESPG8YhjRETEY4wYcYwYMUbEI0bEiEc8Pv/f+9X+3brtrduM9f98ks9QVbNk07b3eGPbit/dyOXnujVHPq2tYSCXlXGrvuSdGZVcPuYcu5hXRnKZqHrq33jnei4LVS/8Wx03czn4w0Xt2rbnxHvj+fGpu5hlE2YtqLqjo+etofzYjDn2ZcfuGrSD3RQsOdHMj82Ki5hJPNazkYI/neiayI/LNRfRSgw41NP1h6YzrXySW37X1HAtX0XVkre6buTrWfNPx7Y0vdJ14r2RRM3nTOQcVRs4tm3b7XwV81h3XzVfy7CO81pGVdwxbcyKnsUUPNKza8N5CznHLB7lm9i2r5JvYdp5W6qJZT2H7nmoaywFL/RcTaz72E7OsYKBfBNtr/JtPHVeLQW7PlhwLSUHeu4lXjrvSj5iGbfzPxrmVVNS8ZuZxIIbRv1h25/GEzUbWuZVDWhqOvJOU9N4YkjDqpbHxlIy54Gqhk3rGiqJOY9VU1LRUI+XzhtPwawz91IwbNu2Z0Yt+qepfMR1XR0NAylp4H5K7uKXRNsre15Zd6xtTkfLX1hX0bLt2JFtm35y3b5jmzYcOnIvBS2HXti36jXWErOop6SG2dhzXi0lT3wwmoJxs+Y07fmUX3KOKbvYN5OCEV2tlKzoGEy0sZiCeT23UrCJ0RS0baWgqu3YVApG7Toykmhh23AKNjFhWMfzlCzrGI73znuRkqpXevZS8rt+HuYfDJh3hDWVxAsdA4kBHaspaDs2mILr+CslDUyloG0rBffxJKfcw2KihVpKZnA/sa5rOFFxaD2J9868s+w95lIyoedZSlb108gnGfYSs4kHuJe4i9spaHubkkk8SUkdtRS0baXgEWo5ZRCbiRaGUnIH9cRd/JK4jbtJvHPmRuIBuqZTcoCHKXmjn3o+wxWsJgZ0rCRWHKikoG0nJZNYSEkdtRS0baXgEe7klGFsJlrklBrqiapDrUTToYEkXjsznpjW814lMegYN1IwpKufO/kMA1hLwTNHhnUspaRtJyWTWEhJHbUUtG2l4D4e55QaFhItckoN9RQ0dVxxqJkeq84sJFb1tFPwUM9QCm7pbyKfYREPU3ALy7iekradlExiISV11FLQtpWCqtcOTaZg2GtHRhItckoN9RTcwDKm0uNXfzfvmqcWTSRmdPA2JUv6OVTJR9Ts2LDiAFuqKajYx9uc0vY6JZNYSEkdtRS0baXkmj1H1j3zzpHpFLTIKTXUU7KHdk6Y9LFDK55oeuvESkra+lnPOa5Z8pdtTQ9UcsoS5nPKglpKRjRNpGTKIwMpWHA3pwyZt2HToqspmfMwp0x6aiQlj/A4H3ijn3oKpvQ3nQvxRNdYvivzuJoPPNTP1RQ818+BgVyASUdW810Zd+h5zhh04HPaKZjS32/5IiNe6to3lu/GsE1d70zk79T93Z663xzqaSSGtPWzazBfZFTTr37Kd+QnTQ2j+ZiKl87cTME8DgyqWNNP16386Fyx54OJRNWmjuuq1vTXyGVg0pETGwb97IlJN+3o71kuC1MOnDiw4Zm2L/lTNZeHUTsuqquRy8agp4592Rs3czkZs6KftplcbkbMea7rY/ueuq2S/w+qfvYfs2ZN+9lYLp3/AoptWBePkE2wAAAAAElFTkSuQmCC'),\n]\n--EXPECT--\ndata:text/plain;charset=utf-8;foo=%24bar,foobar%23\ndata:text/html;charset=ascii,%3Cb%3Efoobar%3C%2Fb%3E\n<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG8AAAAlCAQAAADmD3/hAAAE70lEQVRoBd3BAUTdiQMH8C+PR0TEOCIiRsQ4xvgTY4zHGDHGOCIeMY7HccQYESPG8YhjRETEY4wYcYwYMUbEI0bEiEc8Pv/f+9X+3brtrduM9f98ks9QVbNk07b3eGPbit/dyOXnujVHPq2tYSCXlXGrvuSdGZVcPuYcu5hXRnKZqHrq33jnei4LVS/8Wx03czn4w0Xt2rbnxHvj+fGpu5hlE2YtqLqjo+etofzYjDn2ZcfuGrSD3RQsOdHMj82Ki5hJPNazkYI/neiayI/LNRfRSgw41NP1h6YzrXySW37X1HAtX0XVkre6buTrWfNPx7Y0vdJ14r2RRM3nTOQcVRs4tm3b7XwV81h3XzVfy7CO81pGVdwxbcyKnsUUPNKza8N5CznHLB7lm9i2r5JvYdp5W6qJZT2H7nmoaywFL/RcTaz72E7OsYKBfBNtr/JtPHVeLQW7PlhwLSUHeu4lXjrvSj5iGbfzPxrmVVNS8ZuZxIIbRv1h25/GEzUbWuZVDWhqOvJOU9N4YkjDqpbHxlIy54Gqhk3rGiqJOY9VU1LRUI+XzhtPwawz91IwbNu2Z0Yt+qepfMR1XR0NAylp4H5K7uKXRNsre15Zd6xtTkfLX1hX0bLt2JFtm35y3b5jmzYcOnIvBS2HXti36jXWErOop6SG2dhzXi0lT3wwmoJxs+Y07fmUX3KOKbvYN5OCEV2tlKzoGEy0sZiCeT23UrCJ0RS0baWgqu3YVApG7Toykmhh23AKNjFhWMfzlCzrGI73znuRkqpXevZS8rt+HuYfDJh3hDWVxAsdA4kBHaspaDs2mILr+CslDUyloG0rBffxJKfcw2KihVpKZnA/sa5rOFFxaD2J9868s+w95lIyoedZSlb108gnGfYSs4kHuJe4i9spaHubkkk8SUkdtRS0baXgEWo5ZRCbiRaGUnIH9cRd/JK4jbtJvHPmRuIBuqZTcoCHKXmjn3o+wxWsJgZ0rCRWHKikoG0nJZNYSEkdtRS0baXgEe7klGFsJlrklBrqiapDrUTToYEkXjsznpjW814lMegYN1IwpKufO/kMA1hLwTNHhnUspaRtJyWTWEhJHbUUtG2l4D4e55QaFhItckoN9RQ0dVxxqJkeq84sJFb1tFPwUM9QCm7pbyKfYREPU3ALy7iekradlExiISV11FLQtpWCqtcOTaZg2GtHRhItckoN9RTcwDKm0uNXfzfvmqcWTSRmdPA2JUv6OVTJR9Ts2LDiAFuqKajYx9uc0vY6JZNYSEkdtRS0baXkmj1H1j3zzpHpFLTIKTXUU7KHdk6Y9LFDK55oeuvESkra+lnPOa5Z8pdtTQ9UcsoS5nPKglpKRjRNpGTKIwMpWHA3pwyZt2HToqspmfMwp0x6aiQlj/A4H3ijn3oKpvQ3nQvxRNdYvivzuJoPPNTP1RQ818+BgVyASUdW810Zd+h5zhh04HPaKZjS32/5IiNe6to3lu/GsE1d70zk79T93Z663xzqaSSGtPWzazBfZFTTr37Kd+QnTQ2j+ZiKl87cTME8DgyqWNNP16386Fyx54OJRNWmjuuq1vTXyGVg0pETGwb97IlJN+3o71kuC1MOnDiw4Zm2L/lTNZeHUTsuqquRy8agp4592Rs3czkZs6KftplcbkbMea7rY/ueuq2S/w+qfvYfs2ZN+9lYLp3/AoptWBePkE2wAAAAAElFTkSuQmCC\" />\n"
  },
  {
    "path": "extra/html-extra/Tests/Fixtures/html_attr.test",
    "content": "--TEST--\n\"html_attr\" function\n--TEMPLATE--\nSimple attributes: <tag {{ html_attr({ foo: 'bar' }, { bar: 'baz' }) }}/>\nRelaxed attribute name escaping: <tag {{ html_attr({ 'v-bind:href': 'url', ':[key]': 'url', '@click': 'doSomething' }) }} />\nAppropriate escaping: <tag {{ html_attr({ 'untrusted name': '<>&\\'\"' }) }}/>\nEmpty attribute list: <tag {{ html_attr([], {}, null) }}/>\nUsing a short ternary: <tag {{ html_attr({foo: 'bar'}, false ? {bar: 'baz'}) }}/>\nboolean true attribute: <tag {{ html_attr({ checked: true}) }}/>\nboolean false attribute: <tag {{ html_attr({ checked: false}) }}/>\nnull attribute value: <tag {{ html_attr({ something: null}) }}/>\nempty string attribute value: <tag {{ html_attr({ title: '' }) }}/>\nARIA attribute boolean conversion: <tag {{ html_attr({ 'aria-yes': true, 'aria-no': false, 'aria-nada': null, 'aria-empty': '' }) }}/>\ndata-* attribute handling : <tag {{ html_attr({ 'data-array': { foo: 'bar' }, 'data-true': true, 'data-false': false, 'data-null': null }) }}/>\narray value concatenation ex. 1: <tag {{ html_attr({ class: ['foo', 'bar'] }, { class: [''] }, { class: [] }) }}/>\narray value concatenation ex. 2: <tag {{ html_attr({ 'aria-describedby': ['id1'] }, { 'aria-describedby': ['id2'] }) }}/>\ninline styles: <tag {{ html_attr({ style: { color: 'red' }}, { style: ['background-color: blue'] }, { style: { color: 'green', 'font-family': 'Arial' } }) }}/>\nstyle with a plain value: <tag {{ html_attr({ style: 'color: red;' }) }}/>\nusing a \"comma separated token list\" attribute: <img {{ html_attr({ srcset: ['small.jpg 480w', 'medium.jpg 800w', 'large.jpg 1200w']|html_attr_type('cst') }) }} />\nmerging a \"comma separated token list\" value with more array values: <img {{ html_attr({ srcset: ['small.jpg 480w']|html_attr_type('cst') }, { srcset: ['medium.jpg 800w', 'large.jpg 1200w'] }) }} />\n--DATA--\nreturn []\n--EXPECT--\nSimple attributes: <tag foo=\"bar\" bar=\"baz\"/>\nRelaxed attribute name escaping: <tag v-bind:href=\"url\" :[key]=\"url\" @click=\"doSomething\" />\nAppropriate escaping: <tag untrusted&#x20;name=\"&lt;&gt;&amp;&#039;&quot;\"/>\nEmpty attribute list: <tag />\nUsing a short ternary: <tag foo=\"bar\"/>\nboolean true attribute: <tag checked=\"\"/>\nboolean false attribute: <tag />\nnull attribute value: <tag />\nempty string attribute value: <tag title=\"\"/>\nARIA attribute boolean conversion: <tag aria-yes=\"true\" aria-no=\"false\" aria-empty=\"\"/>\ndata-* attribute handling : <tag data-array=\"{&quot;foo&quot;:&quot;bar&quot;}\" data-true=\"true\"/>\narray value concatenation ex. 1: <tag class=\"foo bar\"/>\narray value concatenation ex. 2: <tag aria-describedby=\"id1 id2\"/>\ninline styles: <tag style=\"color: green; background-color: blue; font-family: Arial;\"/>\nstyle with a plain value: <tag style=\"color: red;\"/>\nusing a \"comma separated token list\" attribute: <img srcset=\"small.jpg 480w, medium.jpg 800w, large.jpg 1200w\" />\nmerging a \"comma separated token list\" value with more array values: <img srcset=\"small.jpg 480w, medium.jpg 800w, large.jpg 1200w\" />\n"
  },
  {
    "path": "extra/html-extra/Tests/Fixtures/html_attr_merge.test",
    "content": "--TEST--\n\"html_attr_merge\" filter\n--TEMPLATE--\n{% autoescape false %}\n{{ { foo: 'bar' } | html_attr_merge({ bar: 'baz', foo: 'qux' }, { foo: 'quux' }) | json_encode }}\n{% endautoescape %}\n--DATA--\nreturn []\n--EXPECT--\n{\"foo\":\"quux\",\"bar\":\"baz\"}\n"
  },
  {
    "path": "extra/html-extra/Tests/Fixtures/html_classes.test",
    "content": "--TEST--\n\"html_classes\" function\n--TEMPLATE--\n{{ html_classes('a', {'b': true, 'c': false}, 'd', false ? 'e', true ? 'f', '0') }}\n{% set class_a = 'a' %}\n{%- set class_b -%}\nb\n{%- endset -%}\n{{ html_classes(class_a) }}\n{{ html_classes(class_b) }}\n{{ html_classes({ (class_a): true }) }}\n{{ html_classes({ (class_b): true }) }}\n--DATA--\nreturn []\n--EXPECT--\na b d f 0\na\nb\na\nb\n"
  },
  {
    "path": "extra/html-extra/Tests/Fixtures/html_classes_with_unsupported_arg.test",
    "content": "--TEST--\n\"html_classes\" function\n--TEMPLATE--\n{{ html_classes(true) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: The \"html_classes\" function argument 0 should be either a string or an array, got \"bool\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "extra/html-extra/Tests/Fixtures/html_classes_with_unsupported_key.test",
    "content": "--TEST--\n\"html_classes\" function\n--TEMPLATE--\n{{ html_classes(['foo']) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: The \"html_classes\" function argument 0 (key 0) should be a string, got \"int\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "extra/html-extra/Tests/Fixtures/html_cva.test",
    "content": "--TEST--\n\"html_cva\" function\n--TEMPLATE--\n{% set alert = html_cva(\n    ['alert'],\n    {\n       color: {\n           blue: 'alert-blue',\n           red: 'alert-red',\n           green: 'alert-green',\n           yellow: 'alert-yellow',\n       },\n       size: {\n           sm: 'alert-sm',\n           md: 'alert-md',\n           lg: 'alert-lg',\n       },\n       rounded: {\n           sm: 'rounded-sm',\n           md: 'rounded-md',\n           lg: 'rounded-lg',\n       }\n    },\n    [{\n       color: ['red'],\n       size: ['lg'],\n       class: 'font-semibold'\n    }],\n    {\n       rounded: 'md'\n    }\n) %}\n{{ alert.apply({color: 'blue', size: 'sm'}) }}\n--DATA--\nreturn []\n--EXPECT--\nalert alert-blue alert-sm rounded-md\n"
  },
  {
    "path": "extra/html-extra/Tests/Fixtures/html_cva_pass_to_template.test",
    "content": "--TEST--\npass Cva object to template\n--TEMPLATE--\n{{ alert.apply({colors: 'primary', sizes: 'sm'}) }}\n--DATA--\nreturn [\n    'alert' => new Twig\\Extra\\Html\\Cva('font-semibold border rounded', [\n        'colors' => [\n            'primary' => 'text-primary',\n            'secondary' => 'text-secondary'\n        ],\n        'sizes' => [\n            'sm' => 'text-sm',\n            'lg' => 'text-lg'\n        ]\n    ])\n];\n--EXPECT--\nfont-semibold border rounded text-primary text-sm\n"
  },
  {
    "path": "extra/html-extra/Tests/HtmlAttrMergeTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extra\\Html\\HtmlAttr\\MergeableInterface;\nuse Twig\\Extra\\Html\\HtmlExtension;\n\nclass HtmlAttrMergeTest extends TestCase\n{\n    /**\n     * @dataProvider htmlAttrProvider\n     */\n    public function testMerge(array $expected, array $inputs)\n    {\n        $result = HtmlExtension::htmlAttrMerge(...$inputs);\n\n        self::assertEquals($expected, $result);\n    }\n\n    public static function htmlAttrProvider(): \\Generator\n    {\n        yield 'merging different attributes from two arrays' => [\n            ['id' => 'some-id', 'label' => 'some-label'],\n            [\n                ['id' => 'some-id'],\n                ['label' => 'some-label'],\n            ],\n        ];\n\n        yield 'merging different attributes from three arrays' => [\n            ['id' => 'some-id', 'label' => 'some-label', 'role' => 'main'],\n            [\n                ['id' => 'some-id'],\n                ['label' => 'some-label'],\n                ['role' => 'main'],\n            ],\n        ];\n\n        yield 'merging different attributes from Traversables' => [\n            ['id' => 'some-id', 'label' => 'some-label', 'role' => 'main'],\n            [\n                new \\ArrayIterator(['id' => 'some-id']),\n                new \\ArrayIterator(['label' => 'some-label']),\n                new \\ArrayIterator(['role' => 'main']),\n            ],\n        ];\n\n        yield 'later keys override previous ones' => [\n            ['key' => 'other'],\n            [\n                ['key' => 'this'],\n                ['key' => 'that'],\n                ['key' => 'other'],\n            ],\n        ];\n\n        yield 'later keys override previous ones - as before, but there is no magic in attribute names like \"id\" or \"class\"' => [\n            ['class' => 'other'],\n            [\n                ['class' => 'this'],\n                ['class' => 'that'],\n                ['class' => 'other'],\n            ],\n        ];\n\n        yield 'in \"merge array\" mode, array_merge semantics will override non-numerical keys, but combine numerical ones' => [\n            ['something' => ['first' => 'baz', 'second' => 'bar', 0 => 'other', 1 => 'more']],\n            [\n                ['something' => ['first' => 'foo']],\n                ['something' => ['second' => 'bar']],\n                ['something' => ['first' => 'baz']],\n                ['something' => ['other']],\n                ['something' => ['more']],\n            ],\n        ];\n\n        yield 'ignore empty arrays, null or false values passed as arguments' => [\n            ['something' => 'foo'],\n            [\n                ['something' => 'foo'],\n                [],\n                null,\n                false,\n            ],\n        ];\n\n        yield 'there is no special handling for scalars like true, false or null' => [\n            ['this' => true, 'that' => false, 'other' => null],\n            [\n                ['this' => true],\n                ['that' => false],\n                ['other' => null],\n            ],\n        ];\n\n        yield 'inline style values with numerical keys are merely collected' => [\n            ['style' => ['font-weight: light', 'color: green', 'font-weight: bold']],\n            [\n                ['style' => ['font-weight: light']],\n                ['style' => ['color: green', 'font-weight: bold']],\n            ],\n        ];\n\n        yield 'inline style values can be overridden when they use names (array keys)' => [\n            ['style' => ['font-weight' => 'bold', 'color' => 'red']],\n            [\n                ['style' => ['font-weight' => 'light']],\n                ['style' => ['color' => 'green', 'font-weight' => 'bold']],\n                ['style' => ['color' => 'red']],\n            ],\n        ];\n\n        yield 'no merging happens when mixing numerically indexed inline styles with named ones' => [\n            ['style' => ['color: green', 'color' => 'red']],\n            [\n                ['style' => ['color: green']],\n                ['style' => ['color' => 'red']],\n            ],\n        ];\n\n        // MergeableInterface\n        yield 'MergeableInterface mergeInto is called when new value implements interface' => [\n            ['class' => 'merged: old + new'],\n            [\n                ['class' => 'old'],\n                ['class' => new MergeableStub('new')],\n            ],\n        ];\n\n        yield 'MergeableInterface appendFrom is called when existing value implements interface' => [\n            ['class' => 'appended: old + new'],\n            [\n                ['class' => new MergeableStub('old')],\n                ['class' => 'new'],\n            ],\n        ];\n\n        yield 'MergeableInterface mergeInto is called when both implement interface' => [\n            ['class' => 'merged: value1 + value2'],\n            [\n                ['class' => new MergeableStub('value1')],\n                ['class' => new MergeableStub('value2')],\n            ],\n        ];\n\n        yield 'MergeableInterface with array value' => [\n            ['class' => 'appended: base + extra1, extra2'],\n            [\n                ['class' => new MergeableStub('base')],\n                ['class' => ['extra1', 'extra2']],\n            ],\n        ];\n\n        // Scalar and object merging\n        yield 'string replaces object' => [\n            ['value' => 'new-string'],\n            [\n                ['value' => new \\stdClass()],\n                ['value' => 'new-string'],\n            ],\n        ];\n\n        yield 'object replaces string' => [\n            ['value' => new \\stdClass()],\n            [\n                ['value' => 'old-string'],\n                ['value' => new \\stdClass()],\n            ],\n        ];\n    }\n\n    public function testIncompatibleValuesMergeThrowsException()\n    {\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('Cannot merge incompatible values for key \"test\"');\n\n        HtmlExtension::htmlAttrMerge(\n            ['test' => ['array']],\n            ['test' => 'scalar']\n        );\n    }\n}\n\nclass MergeableStub implements MergeableInterface\n{\n    public function __construct(private readonly mixed $value)\n    {\n    }\n\n    public function mergeInto(mixed $previous): mixed\n    {\n        $previousValue = $previous instanceof self ? $previous->value : $previous;\n\n        return new self(\"merged: {$previousValue} + {$this->value}\");\n    }\n\n    public function appendFrom(mixed $newValue): mixed\n    {\n        if (\\is_array($newValue)) {\n            $newValue = implode(', ', $newValue);\n        } elseif ($newValue instanceof self) {\n            $newValue = $newValue->value;\n        }\n\n        return new self(\"appended: {$this->value} + {$newValue}\");\n    }\n\n    public function __toString(): string\n    {\n        return (string) $this->value;\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/Tests/HtmlAttrTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extra\\Html\\HtmlAttr\\AttributeValueInterface;\nuse Twig\\Extra\\Html\\HtmlAttr\\SeparatedTokenList;\nuse Twig\\Extra\\Html\\HtmlExtension;\nuse Twig\\Loader\\ArrayLoader;\n\nclass HtmlAttrTest extends TestCase\n{\n    /**\n     * @dataProvider htmlAttrProvider\n     */\n    public function testPrintingAttributes(string $expected, array $inputs)\n    {\n        $result = HtmlExtension::htmlAttr(new Environment(new ArrayLoader()), ...$inputs);\n\n        self::assertEquals($expected, $result);\n    }\n\n    public static function htmlAttrProvider(): \\Generator\n    {\n        yield 'merging from variadic arguments; ignoring null, false and empty string values' => [\n            'id=\"some-id\" label=\"some-label\" role=\"main\"',\n            [\n                ['id' => 'some-id'],\n                null,\n                '',\n                false,\n                ['label' => 'some-label'],\n                ['role' => 'main'],\n            ],\n        ];\n\n        // Boolean attribute handling\n        yield 'boolean true renders as empty string, except for aria-* and data-* it uses \"true\"' => [\n            'required=\"\" aria-disabled=\"true\" data-yes=\"true\"',\n            [\n                ['required' => true, 'aria-disabled' => true, 'data-yes' => true],\n            ],\n        ];\n\n        yield 'boolean false omits attribute, except for aria-* it uses \"false\"' => [\n            'aria-disabled=\"false\"',\n            [\n                ['disabled' => false, 'aria-disabled' => false, 'data-gone' => false],\n            ],\n        ];\n\n        yield 'null value omits attribute, also for special cases' => [\n            '',\n            [\n                ['title' => null, 'style' => null, 'aria-gone' => null, 'data-nil' => null],\n            ],\n        ];\n\n        yield 'empty string renders as empty attribute value' => [\n            'title=\"\"',\n            [\n                ['title' => ''],\n            ],\n        ];\n\n        // Data attributes\n        yield 'data attribute with array is JSON encoded' => [\n            'data-config=\"{&quot;theme&quot;:&quot;dark&quot;}\"',\n            [\n                ['data-config' => ['theme' => 'dark']],\n            ],\n        ];\n\n        // In general, array values are printed as space-separated token lists\n        yield 'array value renders as space-separated token list' => [\n            'class=\"btn btn-primary btn-lg\"',\n            [\n                ['class' => ['btn', 'btn-primary', 'btn-lg']],\n            ],\n        ];\n\n        yield 'arrays with just an empty string produce the empty attribute' => [\n            'foo=\"\"',\n            [\n                ['foo' => ['']],\n            ],\n        ];\n\n        yield 'arrays with just a true value produce the empty attribute' => [\n            'foo=\"\"',\n            [\n                ['foo' => [true]],\n            ],\n        ];\n\n        yield 'arrays with just a null value are not printed' => [\n            '',\n            [\n                ['foo' => [null]],\n            ],\n        ];\n\n        // Style attributes\n        yield 'style with plain string value' => [\n            'style=\"color: red;\"',\n            [\n                ['style' => 'color: red;'],\n            ],\n        ];\n\n        yield 'style with associative array' => [\n            'style=\"color: red; font-size: 16px;\"',\n            [\n                ['style' => ['color' => 'red', 'font-size' => '16px']],\n            ],\n        ];\n\n        yield 'style with numeric array' => [\n            'style=\"color: red; font-size: 16px;\"',\n            [\n                ['style' => ['color: red', 'font-size: 16px']],\n            ],\n        ];\n\n        yield 'merging style attributes overrides by key' => [\n            'style=\"color: blue; font-size: 14px;\"',\n            [\n                ['style' => ['color' => 'red', 'font-size' => '14px']],\n                ['style' => ['color' => 'blue']],\n            ],\n        ];\n\n        // Escaping\n        yield 'attribute name is escaped' => [\n            'data-user&#x20;id=\"123\"',\n            [\n                ['data-user id' => '123'],\n            ],\n        ];\n\n        yield 'attribute value is escaped' => [\n            'title=\"&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;\"',\n            [\n                ['title' => '<script>alert(\"xss\")</script>'],\n            ],\n        ];\n\n        // Variadic merging scenarios\n        yield 'scalar value overrides from left to right' => [\n            'id=\"final\"',\n            [\n                ['id' => 'first'],\n                ['id' => 'second'],\n                ['id' => 'final'],\n            ],\n        ];\n\n        yield 'variadic with mixed false and null values' => [\n            'id=\"test\"',\n            [\n                ['id' => 'test'],\n                null,\n                false,\n                null,\n            ],\n        ];\n\n        yield 'variadic with empty arrays' => [\n            'id=\"test\"',\n            [\n                [],\n                ['id' => 'test'],\n                [],\n            ],\n        ];\n\n        yield 'variadic with empty string values' => [\n            'id=\"test\"',\n            [\n                '',\n                ['id' => 'test'],\n                '',\n            ],\n        ];\n\n        // AttributeValueInterface\n        yield 'AttributeValueInterface with string value' => [\n            'custom=\"custom-value\"',\n            [\n                ['custom' => new AttributeValueStub('custom-value')],\n            ],\n        ];\n\n        yield 'AttributeValueInterface with null value omits attribute' => [\n            '',\n            [\n                ['custom' => new AttributeValueStub(null)],\n            ],\n        ];\n\n        yield 'AttributeValueInterface wins over special case handling for style and data-*' => [\n            'style=\"some style\" data-custom=\"not JSON\"',\n            [\n                ['style' => new AttributeValueStub('some style'), 'data-custom' => new AttributeValueStub('not JSON')],\n            ],\n        ];\n\n        // Edge cases\n        yield 'numeric attribute value' => [\n            'tabindex=\"0\"',\n            [\n                ['tabindex' => 0],\n            ],\n        ];\n\n        yield 'zero is not treated as falsy' => [\n            'data-count=\"0\"',\n            [\n                ['data-count' => 0],\n            ],\n        ];\n\n        // Scalar and object merging in rendering\n        yield 'string replaces object in rendering' => [\n            'value=\"new-string\"',\n            [\n                ['value' => new \\stdClass()],\n                ['value' => 'new-string'],\n            ],\n        ];\n\n        yield 'object replaces string in rendering uses __toString if available' => [\n            'value=\"stringable-object\"',\n            [\n                ['value' => 'old-string'],\n                ['value' => new StringableStub('stringable-object')],\n            ],\n        ];\n    }\n\n    public function testIterableObjectCastedToArray()\n    {\n        /*\n            This test case demonstrates how objects could e. g. implement helper logic\n            to construct more complex attribute combinations and sets, and be passed as\n            one argument to html_attr as well.\n        */\n        $object = new class implements \\IteratorAggregate {\n            public function getIterator(): \\Traversable\n            {\n                return new \\ArrayIterator([\n                    'data-controller' => new SeparatedTokenList(['dropdown', 'tooltip']),\n                    'data-action' => new SeparatedTokenList(['click->dropdown#toggle', 'mouseover->tooltip#show']),\n                ]);\n            }\n        };\n\n        $result = HtmlExtension::htmlAttr(new Environment(new ArrayLoader()), $object);\n\n        self::assertSame('data-controller=\"dropdown tooltip\" data-action=\"click-&gt;dropdown#toggle mouseover-&gt;tooltip#show\"', $result);\n    }\n\n    public function testDataAttributeWithNonJsonEncodableValueThrowsRuntimeError()\n    {\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('The \"data-bad\" attribute value cannot be JSON encoded.');\n\n        HtmlExtension::htmlAttr(\n            new Environment(new ArrayLoader()),\n            ['data-bad' => [\\INF]]  // INF cannot be JSON-encoded\n        );\n    }\n\n    public function testNonStringableObjectAsAttributeValueThrowsRuntimeError()\n    {\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('The \"title\" attribute value should be a scalar, an iterable, or an object implementing \"Stringable\"');\n\n        HtmlExtension::htmlAttr(\n            new Environment(new ArrayLoader()),\n            ['title' => new \\stdClass()]\n        );\n    }\n}\n\nclass StringableStub implements \\Stringable\n{\n    public function __construct(private readonly string $value)\n    {\n    }\n\n    public function __toString(): string\n    {\n        return $this->value;\n    }\n}\n\nclass AttributeValueStub implements AttributeValueInterface\n{\n    public function __construct(private readonly ?string $value)\n    {\n    }\n\n    public function getValue(): ?string\n    {\n        return $this->value;\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/Tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\Tests;\n\nuse Twig\\Extra\\Html\\HtmlExtension;\nuse Twig\\Test\\IntegrationTestCase;\n\nclass IntegrationTest extends IntegrationTestCase\n{\n    public function getExtensions()\n    {\n        return [\n            new HtmlExtension(),\n        ];\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        return __DIR__.'/Fixtures/';\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/Tests/LegacyFunctionsTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Html\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Extra\\Html\\HtmlExtension;\n\n/**\n * @group legacy\n */\nclass LegacyFunctionsTest extends TestCase\n{\n    public function testHtmlToMarkdown()\n    {\n        $this->assertSame(HtmlExtension::htmlClasses(['charset' => 'utf-8']), twig_html_classes(['charset' => 'utf-8']));\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/composer.json",
    "content": "{\n    \"name\": \"twig/html-extra\",\n    \"type\": \"library\",\n    \"description\": \"A Twig extension for HTML\",\n    \"keywords\": [\"twig\", \"html\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"MIT\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"symfony/deprecation-contracts\": \"^2.5|^3\",\n        \"symfony/mime\": \"^5.4|^6.4|^7.0|^8.0\",\n        \"twig/twig\": \"^3.13|^4.0\"\n    },\n    \"require-dev\": {\n        \"symfony/phpunit-bridge\": \"^6.4|^7.0\"\n    },\n    \"autoload\": {\n        \"files\": [ \"Resources/functions.php\" ],\n        \"psr-4\" : { \"Twig\\\\Extra\\\\Html\\\\\" : \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    }\n}\n"
  },
  {
    "path": "extra/html-extra/phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\" backupGlobals=\"false\" colors=\"true\" bootstrap=\"vendor/autoload.php\" failOnRisky=\"true\" failOnWarning=\"true\">\n  <coverage>\n    <include>\n      <directory>./</directory>\n    </include>\n    <exclude>\n      <directory>./Tests</directory>\n      <directory>./vendor</directory>\n    </exclude>\n  </coverage>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n  </php>\n  <testsuites>\n    <testsuite name=\"Twig HTML Extension Test Suite\">\n      <directory>./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "extra/inky-extra/.gitattributes",
    "content": "/Tests export-ignore\n/.git* export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": "extra/inky-extra/.gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n"
  },
  {
    "path": "extra/inky-extra/InkyExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Inky;\n\nuse Pinky;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\TwigFilter;\n\nclass InkyExtension extends AbstractExtension\n{\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('inky_to_html', [self::class, 'inky'], ['is_safe' => ['html']]),\n        ];\n    }\n\n    /**\n     * @internal\n     */\n    public static function inky(string $body): string\n    {\n        return false === ($html = Pinky\\transformString($body)->saveHTML()) ? '' : $html;\n    }\n}\n"
  },
  {
    "path": "extra/inky-extra/LICENSE",
    "content": "Copyright (c) 2019-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "extra/inky-extra/README.md",
    "content": "Twig Inky Extension\n===================\n\nThis package is a Twig extension that provides the following:\n\n * [`inky_to_html`][1] filter: processes an [inky email template](https://github.com/zurb/inky).\n\n[1]: https://twig.symfony.com/inky_to_html\n"
  },
  {
    "path": "extra/inky-extra/Resources/functions.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Inky;\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9.0\n */\nfunction twig_inky(string $body): string\n{\n    trigger_deprecation('twig/inky-extra', '3.9.0', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return InkyExtension::inky($body);\n}\n"
  },
  {
    "path": "extra/inky-extra/Tests/Fixtures/inky.test",
    "content": "--TEST--\n\"inky_to_html\" filter\n--TEMPLATE--\n{% apply inky_to_html %}\n    <container class=\"extra\">{{ var }}</container>\n{%- endapply %}\n\n{{ include(\"inky\")|inky_to_html }}\n--TEMPLATE(inky)--\n<container class=\"extra\">{{ var }}</container>\n--DATA--\nreturn ['var' => 'value<br />']\n--EXPECT--\n<html><body><table align=\"center\" class=\"extra container\"><tbody><tr><td>value&lt;br /&gt;</td></tr></tbody></table></body></html>\n\n<html><body><table align=\"center\" class=\"extra container\"><tbody><tr><td>value&lt;br /&gt;</td></tr></tbody></table></body></html>\n"
  },
  {
    "path": "extra/inky-extra/Tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Inky\\Tests;\n\nuse Twig\\Extra\\Inky\\InkyExtension;\nuse Twig\\Test\\IntegrationTestCase;\n\nclass IntegrationTest extends IntegrationTestCase\n{\n    public function getExtensions()\n    {\n        return [\n            new InkyExtension(),\n        ];\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        return __DIR__.'/Fixtures/';\n    }\n}\n"
  },
  {
    "path": "extra/inky-extra/Tests/LegacyFunctionsTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Inky\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Extra\\Inky\\InkyExtension;\n\nuse function Twig\\Extra\\Inky\\twig_inky;\n\n/**\n * @group legacy\n */\nclass LegacyFunctionsTest extends TestCase\n{\n    public function testInlineCss()\n    {\n        $this->assertSame(InkyExtension::inky('<p>Foo</p>'), twig_inky('<p>Foo</p>'));\n    }\n}\n"
  },
  {
    "path": "extra/inky-extra/composer.json",
    "content": "{\n    \"name\": \"twig/inky-extra\",\n    \"type\": \"library\",\n    \"description\": \"A Twig extension for the inky email templating engine\",\n    \"keywords\": [\"twig\", \"inky\", \"email\", \"emails\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"MIT\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"symfony/deprecation-contracts\": \"^2.5|^3\",\n        \"lorenzo/pinky\": \"^1.0.5\",\n        \"twig/twig\": \"^3.13|^4.0\"\n    },\n    \"require-dev\": {\n        \"symfony/phpunit-bridge\": \"^6.4|^7.0\"\n    },\n    \"autoload\": {\n        \"files\": [ \"Resources/functions.php\" ],\n        \"psr-4\" : { \"Twig\\\\Extra\\\\Inky\\\\\" : \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    }\n}\n"
  },
  {
    "path": "extra/inky-extra/phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\" backupGlobals=\"false\" colors=\"true\" bootstrap=\"vendor/autoload.php\" failOnRisky=\"true\" failOnWarning=\"true\">\n  <coverage>\n    <include>\n      <directory>./</directory>\n    </include>\n    <exclude>\n      <directory>./Tests</directory>\n      <directory>./vendor</directory>\n    </exclude>\n  </coverage>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n  </php>\n  <testsuites>\n    <testsuite name=\"Twig Inky Extension Test Suite\">\n      <directory>./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "extra/intl-extra/.gitattributes",
    "content": "/Tests export-ignore\n/.git* export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": "extra/intl-extra/.gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n"
  },
  {
    "path": "extra/intl-extra/IntlExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Intl;\n\nuse Symfony\\Component\\Intl\\Countries;\nuse Symfony\\Component\\Intl\\Currencies;\nuse Symfony\\Component\\Intl\\Exception\\MissingResourceException;\nuse Symfony\\Component\\Intl\\Languages;\nuse Symfony\\Component\\Intl\\Locales;\nuse Symfony\\Component\\Intl\\Scripts;\nuse Symfony\\Component\\Intl\\Timezones;\nuse Twig\\Environment;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\n\nfinal class IntlExtension extends AbstractExtension\n{\n    private static function availableDateFormats(): array\n    {\n        static $formats = null;\n\n        if (null !== $formats) {\n            return $formats;\n        }\n\n        $formats = [\n            'none' => \\IntlDateFormatter::NONE,\n            'short' => \\IntlDateFormatter::SHORT,\n            'medium' => \\IntlDateFormatter::MEDIUM,\n            'long' => \\IntlDateFormatter::LONG,\n            'full' => \\IntlDateFormatter::FULL,\n        ];\n\n        // Assuming that each `RELATIVE_*` constant are defined when one of them is.\n        if (\\defined('IntlDateFormatter::RELATIVE_FULL')) {\n            $formats = array_merge($formats, [\n                'relative_short' => \\IntlDateFormatter::RELATIVE_SHORT,\n                'relative_medium' => \\IntlDateFormatter::RELATIVE_MEDIUM,\n                'relative_long' => \\IntlDateFormatter::RELATIVE_LONG,\n                'relative_full' => \\IntlDateFormatter::RELATIVE_FULL,\n            ]);\n        }\n\n        return $formats;\n    }\n\n    private const TIME_FORMATS = [\n        'none' => \\IntlDateFormatter::NONE,\n        'short' => \\IntlDateFormatter::SHORT,\n        'medium' => \\IntlDateFormatter::MEDIUM,\n        'long' => \\IntlDateFormatter::LONG,\n        'full' => \\IntlDateFormatter::FULL,\n    ];\n    private const NUMBER_TYPES = [\n        'default' => \\NumberFormatter::TYPE_DEFAULT,\n        'int32' => \\NumberFormatter::TYPE_INT32,\n        'int64' => \\NumberFormatter::TYPE_INT64,\n        'double' => \\NumberFormatter::TYPE_DOUBLE,\n    ];\n    private const NUMBER_STYLES = [\n        'decimal' => \\NumberFormatter::DECIMAL,\n        'currency' => \\NumberFormatter::CURRENCY,\n        'percent' => \\NumberFormatter::PERCENT,\n        'scientific' => \\NumberFormatter::SCIENTIFIC,\n        'spellout' => \\NumberFormatter::SPELLOUT,\n        'ordinal' => \\NumberFormatter::ORDINAL,\n        'duration' => \\NumberFormatter::DURATION,\n    ];\n    private const NUMBER_ATTRIBUTES = [\n        'grouping_used' => \\NumberFormatter::GROUPING_USED,\n        'decimal_always_shown' => \\NumberFormatter::DECIMAL_ALWAYS_SHOWN,\n        'max_integer_digit' => \\NumberFormatter::MAX_INTEGER_DIGITS,\n        'min_integer_digit' => \\NumberFormatter::MIN_INTEGER_DIGITS,\n        'integer_digit' => \\NumberFormatter::INTEGER_DIGITS,\n        'max_fraction_digit' => \\NumberFormatter::MAX_FRACTION_DIGITS,\n        'min_fraction_digit' => \\NumberFormatter::MIN_FRACTION_DIGITS,\n        'fraction_digit' => \\NumberFormatter::FRACTION_DIGITS,\n        'multiplier' => \\NumberFormatter::MULTIPLIER,\n        'grouping_size' => \\NumberFormatter::GROUPING_SIZE,\n        'rounding_mode' => \\NumberFormatter::ROUNDING_MODE,\n        'rounding_increment' => \\NumberFormatter::ROUNDING_INCREMENT,\n        'format_width' => \\NumberFormatter::FORMAT_WIDTH,\n        'padding_position' => \\NumberFormatter::PADDING_POSITION,\n        'secondary_grouping_size' => \\NumberFormatter::SECONDARY_GROUPING_SIZE,\n        'significant_digits_used' => \\NumberFormatter::SIGNIFICANT_DIGITS_USED,\n        'min_significant_digits_used' => \\NumberFormatter::MIN_SIGNIFICANT_DIGITS,\n        'max_significant_digits_used' => \\NumberFormatter::MAX_SIGNIFICANT_DIGITS,\n        'lenient_parse' => \\NumberFormatter::LENIENT_PARSE,\n    ];\n    private const NUMBER_ROUNDING_ATTRIBUTES = [\n        'ceiling' => \\NumberFormatter::ROUND_CEILING,\n        'floor' => \\NumberFormatter::ROUND_FLOOR,\n        'down' => \\NumberFormatter::ROUND_DOWN,\n        'up' => \\NumberFormatter::ROUND_UP,\n        'halfeven' => \\NumberFormatter::ROUND_HALFEVEN,\n        'halfdown' => \\NumberFormatter::ROUND_HALFDOWN,\n        'halfup' => \\NumberFormatter::ROUND_HALFUP,\n    ];\n    private const NUMBER_PADDING_ATTRIBUTES = [\n        'before_prefix' => \\NumberFormatter::PAD_BEFORE_PREFIX,\n        'after_prefix' => \\NumberFormatter::PAD_AFTER_PREFIX,\n        'before_suffix' => \\NumberFormatter::PAD_BEFORE_SUFFIX,\n        'after_suffix' => \\NumberFormatter::PAD_AFTER_SUFFIX,\n    ];\n    private const NUMBER_TEXT_ATTRIBUTES = [\n        'positive_prefix' => \\NumberFormatter::POSITIVE_PREFIX,\n        'positive_suffix' => \\NumberFormatter::POSITIVE_SUFFIX,\n        'negative_prefix' => \\NumberFormatter::NEGATIVE_PREFIX,\n        'negative_suffix' => \\NumberFormatter::NEGATIVE_SUFFIX,\n        'padding_character' => \\NumberFormatter::PADDING_CHARACTER,\n        'currency_code' => \\NumberFormatter::CURRENCY_CODE,\n        'default_ruleset' => \\NumberFormatter::DEFAULT_RULESET,\n        'public_rulesets' => \\NumberFormatter::PUBLIC_RULESETS,\n    ];\n    private const NUMBER_SYMBOLS = [\n        'decimal_separator' => \\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL,\n        'grouping_separator' => \\NumberFormatter::GROUPING_SEPARATOR_SYMBOL,\n        'pattern_separator' => \\NumberFormatter::PATTERN_SEPARATOR_SYMBOL,\n        'percent' => \\NumberFormatter::PERCENT_SYMBOL,\n        'zero_digit' => \\NumberFormatter::ZERO_DIGIT_SYMBOL,\n        'digit' => \\NumberFormatter::DIGIT_SYMBOL,\n        'minus_sign' => \\NumberFormatter::MINUS_SIGN_SYMBOL,\n        'plus_sign' => \\NumberFormatter::PLUS_SIGN_SYMBOL,\n        'currency' => \\NumberFormatter::CURRENCY_SYMBOL,\n        'intl_currency' => \\NumberFormatter::INTL_CURRENCY_SYMBOL,\n        'monetary_separator' => \\NumberFormatter::MONETARY_SEPARATOR_SYMBOL,\n        'exponential' => \\NumberFormatter::EXPONENTIAL_SYMBOL,\n        'permill' => \\NumberFormatter::PERMILL_SYMBOL,\n        'pad_escape' => \\NumberFormatter::PAD_ESCAPE_SYMBOL,\n        'infinity' => \\NumberFormatter::INFINITY_SYMBOL,\n        'nan' => \\NumberFormatter::NAN_SYMBOL,\n        'significant_digit' => \\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL,\n        'monetary_grouping_separator' => \\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL,\n    ];\n\n    private $dateFormatters = [];\n    private $numberFormatters = [];\n    private $dateFormatterPrototype;\n    private $numberFormatterPrototype;\n\n    public function __construct(?\\IntlDateFormatter $dateFormatterPrototype = null, ?\\NumberFormatter $numberFormatterPrototype = null)\n    {\n        $this->dateFormatterPrototype = $dateFormatterPrototype;\n        $this->numberFormatterPrototype = $numberFormatterPrototype;\n    }\n\n    public function getFilters(): array\n    {\n        return [\n            // internationalized names\n            new TwigFilter('country_name', [$this, 'getCountryName']),\n            new TwigFilter('currency_name', [$this, 'getCurrencyName']),\n            new TwigFilter('currency_symbol', [$this, 'getCurrencySymbol']),\n            new TwigFilter('language_name', [$this, 'getLanguageName']),\n            new TwigFilter('locale_name', [$this, 'getLocaleName']),\n            new TwigFilter('timezone_name', [$this, 'getTimezoneName']),\n\n            // localized formatters\n            new TwigFilter('format_currency', [$this, 'formatCurrency']),\n            new TwigFilter('format_number', [$this, 'formatNumber']),\n            new TwigFilter('format_*_number', [$this, 'formatNumberStyle']),\n            new TwigFilter('format_datetime', [$this, 'formatDateTime'], ['needs_environment' => true]),\n            new TwigFilter('format_date', [$this, 'formatDate'], ['needs_environment' => true]),\n            new TwigFilter('format_time', [$this, 'formatTime'], ['needs_environment' => true]),\n        ];\n    }\n\n    public function getFunctions(): array\n    {\n        return [\n            // internationalized names\n            new TwigFunction('country_timezones', [$this, 'getCountryTimezones']),\n            new TwigFunction('language_names', [$this, 'getLanguageNames']),\n            new TwigFunction('script_names', [$this, 'getScriptNames']),\n            new TwigFunction('country_names', [$this, 'getCountryNames']),\n            new TwigFunction('locale_names', [$this, 'getLocaleNames']),\n            new TwigFunction('currency_names', [$this, 'getCurrencyNames']),\n            new TwigFunction('timezone_names', [$this, 'getTimezoneNames']),\n        ];\n    }\n\n    public function getCountryName(?string $country, ?string $locale = null): string\n    {\n        if (null === $country) {\n            return '';\n        }\n\n        try {\n            return Countries::getName($country, $locale);\n        } catch (MissingResourceException $exception) {\n            return $country;\n        }\n    }\n\n    public function getCurrencyName(?string $currency, ?string $locale = null): string\n    {\n        if (null === $currency) {\n            return '';\n        }\n\n        try {\n            return Currencies::getName($currency, $locale);\n        } catch (MissingResourceException $exception) {\n            return $currency;\n        }\n    }\n\n    public function getCurrencySymbol(?string $currency, ?string $locale = null): string\n    {\n        if (null === $currency) {\n            return '';\n        }\n\n        try {\n            return Currencies::getSymbol($currency, $locale);\n        } catch (MissingResourceException $exception) {\n            return $currency;\n        }\n    }\n\n    public function getLanguageName(?string $language, ?string $locale = null): string\n    {\n        if (null === $language) {\n            return '';\n        }\n\n        try {\n            return Languages::getName($language, $locale);\n        } catch (MissingResourceException $exception) {\n            return $language;\n        }\n    }\n\n    public function getLocaleName(?string $data, ?string $locale = null): string\n    {\n        if (null === $data) {\n            return '';\n        }\n\n        try {\n            return Locales::getName($data, $locale);\n        } catch (MissingResourceException $exception) {\n            return $data;\n        }\n    }\n\n    public function getTimezoneName(?string $timezone, ?string $locale = null): string\n    {\n        if (null === $timezone) {\n            return '';\n        }\n\n        try {\n            return Timezones::getName($timezone, $locale);\n        } catch (MissingResourceException $exception) {\n            return $timezone;\n        }\n    }\n\n    public function getCountryTimezones(string $country): array\n    {\n        try {\n            return Timezones::forCountryCode($country);\n        } catch (MissingResourceException $exception) {\n            return [];\n        }\n    }\n\n    public function getLanguageNames(?string $locale = null): array\n    {\n        try {\n            return Languages::getNames($locale);\n        } catch (MissingResourceException $exception) {\n            return [];\n        }\n    }\n\n    public function getScriptNames(?string $locale = null): array\n    {\n        try {\n            return Scripts::getNames($locale);\n        } catch (MissingResourceException $exception) {\n            return [];\n        }\n    }\n\n    public function getCountryNames(?string $locale = null): array\n    {\n        try {\n            return Countries::getNames($locale);\n        } catch (MissingResourceException $exception) {\n            return [];\n        }\n    }\n\n    public function getLocaleNames(?string $locale = null): array\n    {\n        try {\n            return Locales::getNames($locale);\n        } catch (MissingResourceException $exception) {\n            return [];\n        }\n    }\n\n    public function getCurrencyNames(?string $locale = null): array\n    {\n        try {\n            return Currencies::getNames($locale);\n        } catch (MissingResourceException $exception) {\n            return [];\n        }\n    }\n\n    public function getTimezoneNames(?string $locale = null): array\n    {\n        try {\n            return Timezones::getNames($locale);\n        } catch (MissingResourceException $exception) {\n            return [];\n        }\n    }\n\n    public function formatCurrency($amount, string $currency, array $attrs = [], ?string $locale = null): string\n    {\n        $formatter = $this->createNumberFormatter($locale, 'currency', $attrs);\n\n        if (false === $ret = $formatter->formatCurrency($amount, $currency)) {\n            throw new RuntimeError('Unable to format the given number as a currency.');\n        }\n\n        return $ret;\n    }\n\n    public function formatNumber($number, array $attrs = [], string $style = 'decimal', string $type = 'default', ?string $locale = null): string\n    {\n        if (!isset(self::NUMBER_TYPES[$type])) {\n            throw new RuntimeError(\\sprintf('The type \"%s\" does not exist, known types are: \"%s\".', $type, implode('\", \"', array_keys(self::NUMBER_TYPES))));\n        }\n\n        $formatter = $this->createNumberFormatter($locale, $style, $attrs);\n\n        if (false === $ret = $formatter->format($number, self::NUMBER_TYPES[$type])) {\n            throw new RuntimeError('Unable to format the given number.');\n        }\n\n        return $ret;\n    }\n\n    public function formatNumberStyle(string $style, $number, array $attrs = [], string $type = 'default', ?string $locale = null): string\n    {\n        return $this->formatNumber($number, $attrs, $style, $type, $locale);\n    }\n\n    /**\n     * @param \\DateTimeInterface|string|null  $date     A date or null to use the current time\n     * @param \\DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged\n     */\n    public function formatDateTime(Environment $env, $date, ?string $dateFormat = 'medium', ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string\n    {\n        $date = $env->getExtension(CoreExtension::class)->convertDate($date, $timezone);\n\n        $formatterTimezone = $timezone;\n        if (null === $formatterTimezone || false === $formatterTimezone) {\n            $formatterTimezone = $date->getTimezone();\n        } elseif (\\is_string($formatterTimezone)) {\n            $formatterTimezone = new \\DateTimeZone($timezone);\n        }\n        $formatter = $this->createDateFormatter($locale, $dateFormat, $timeFormat, $pattern, $formatterTimezone, $calendar);\n\n        if (false === $ret = $formatter->format($date)) {\n            throw new RuntimeError('Unable to format the given date.');\n        }\n\n        return $ret;\n    }\n\n    /**\n     * @param \\DateTimeInterface|string|null  $date     A date or null to use the current time\n     * @param \\DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged\n     */\n    public function formatDate(Environment $env, $date, ?string $dateFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string\n    {\n        return $this->formatDateTime($env, $date, $dateFormat, 'none', $pattern, $timezone, $calendar, $locale);\n    }\n\n    /**\n     * @param \\DateTimeInterface|string|null  $date     A date or null to use the current time\n     * @param \\DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged\n     */\n    public function formatTime(Environment $env, $date, ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', ?string $locale = null): string\n    {\n        return $this->formatDateTime($env, $date, 'none', $timeFormat, $pattern, $timezone, $calendar, $locale);\n    }\n\n    private function createDateFormatter(?string $locale, ?string $dateFormat, ?string $timeFormat, string $pattern, ?\\DateTimeZone $timezone, string $calendar): \\IntlDateFormatter\n    {\n        $dateFormats = self::availableDateFormats();\n\n        if (null !== $dateFormat && !isset($dateFormats[$dateFormat])) {\n            throw new RuntimeError(\\sprintf('The date format \"%s\" does not exist, known formats are: \"%s\".', $dateFormat, implode('\", \"', array_keys($dateFormats))));\n        }\n\n        if (null !== $timeFormat && !isset(self::TIME_FORMATS[$timeFormat])) {\n            throw new RuntimeError(\\sprintf('The time format \"%s\" does not exist, known formats are: \"%s\".', $timeFormat, implode('\", \"', array_keys(self::TIME_FORMATS))));\n        }\n\n        if (null === $locale) {\n            if ($this->dateFormatterPrototype) {\n                $locale = $this->dateFormatterPrototype->getLocale();\n            }\n            $locale = $locale ?: \\Locale::getDefault();\n        }\n\n        $calendar = 'gregorian' === $calendar ? \\IntlDateFormatter::GREGORIAN : \\IntlDateFormatter::TRADITIONAL;\n\n        $dateFormatValue = $dateFormats[$dateFormat] ?? null;\n        $timeFormatValue = self::TIME_FORMATS[$timeFormat] ?? null;\n\n        if ($this->dateFormatterPrototype) {\n            $dateFormatValue = $dateFormatValue ?: $this->dateFormatterPrototype->getDateType();\n            $timeFormatValue = $timeFormatValue ?: $this->dateFormatterPrototype->getTimeType();\n            $timezone = $timezone ?: $this->dateFormatterPrototype->getTimeZone()->toDateTimeZone();\n            $calendar = $calendar ?: $this->dateFormatterPrototype->getCalendar();\n            $pattern = $pattern ?: $this->dateFormatterPrototype->getPattern();\n        }\n\n        $timezoneName = $timezone ? $timezone->getName() : '(none)';\n\n        $hash = $locale.'|'.$dateFormatValue.'|'.$timeFormatValue.'|'.$timezoneName.'|'.$calendar.'|'.$pattern;\n\n        if (!isset($this->dateFormatters[$hash])) {\n            $this->dateFormatters[$hash] = new \\IntlDateFormatter($locale, $dateFormatValue, $timeFormatValue, $timezone, $calendar, $pattern);\n        }\n\n        return $this->dateFormatters[$hash];\n    }\n\n    private function createNumberFormatter(?string $locale, string $style, array $attrs = []): \\NumberFormatter\n    {\n        if (!isset(self::NUMBER_STYLES[$style])) {\n            throw new RuntimeError(\\sprintf('The style \"%s\" does not exist, known styles are: \"%s\".', $style, implode('\", \"', array_keys(self::NUMBER_STYLES))));\n        }\n\n        if (null === $locale) {\n            $locale = \\Locale::getDefault();\n        }\n\n        // textAttrs and symbols can only be set on the prototype as there is probably no\n        // use case for setting it on each call.\n        $textAttrs = [];\n        $symbols = [];\n        if ($this->numberFormatterPrototype) {\n            foreach (self::NUMBER_ATTRIBUTES as $name => $const) {\n                if (!isset($attrs[$name])) {\n                    $value = $this->numberFormatterPrototype->getAttribute($const);\n                    if ('rounding_mode' === $name) {\n                        $value = array_flip(self::NUMBER_ROUNDING_ATTRIBUTES)[$value];\n                    } elseif ('padding_position' === $name) {\n                        $value = array_flip(self::NUMBER_PADDING_ATTRIBUTES)[$value];\n                    }\n                    $attrs[$name] = $value;\n                }\n            }\n\n            foreach (self::NUMBER_TEXT_ATTRIBUTES as $name => $const) {\n                $textAttrs[$name] = $this->numberFormatterPrototype->getTextAttribute($const);\n            }\n\n            foreach (self::NUMBER_SYMBOLS as $name => $const) {\n                $symbols[$name] = $this->numberFormatterPrototype->getSymbol($const);\n            }\n        }\n\n        ksort($attrs);\n        $hash = $locale.'|'.$style.'|'.json_encode($attrs).'|'.json_encode($textAttrs).'|'.json_encode($symbols);\n\n        if (!isset($this->numberFormatters[$hash])) {\n            $this->numberFormatters[$hash] = new \\NumberFormatter($locale, self::NUMBER_STYLES[$style]);\n        }\n\n        foreach ($attrs as $name => $value) {\n            if (!isset(self::NUMBER_ATTRIBUTES[$name])) {\n                throw new RuntimeError(\\sprintf('The number formatter attribute \"%s\" does not exist, known attributes are: \"%s\".', $name, implode('\", \"', array_keys(self::NUMBER_ATTRIBUTES))));\n            }\n\n            if ('rounding_mode' === $name) {\n                if (!isset(self::NUMBER_ROUNDING_ATTRIBUTES[$value])) {\n                    throw new RuntimeError(\\sprintf('The number formatter rounding mode \"%s\" does not exist, known modes are: \"%s\".', $value, implode('\", \"', array_keys(self::NUMBER_ROUNDING_ATTRIBUTES))));\n                }\n\n                $value = self::NUMBER_ROUNDING_ATTRIBUTES[$value];\n            } elseif ('padding_position' === $name) {\n                if (!isset(self::NUMBER_PADDING_ATTRIBUTES[$value])) {\n                    throw new RuntimeError(\\sprintf('The number formatter padding position \"%s\" does not exist, known positions are: \"%s\".', $value, implode('\", \"', array_keys(self::NUMBER_PADDING_ATTRIBUTES))));\n                }\n\n                $value = self::NUMBER_PADDING_ATTRIBUTES[$value];\n            }\n\n            $this->numberFormatters[$hash]->setAttribute(self::NUMBER_ATTRIBUTES[$name], $value);\n        }\n\n        foreach ($textAttrs as $name => $value) {\n            $this->numberFormatters[$hash]->setTextAttribute(self::NUMBER_TEXT_ATTRIBUTES[$name], $value);\n        }\n\n        foreach ($symbols as $name => $value) {\n            $this->numberFormatters[$hash]->setSymbol(self::NUMBER_SYMBOLS[$name], $value);\n        }\n\n        return $this->numberFormatters[$hash];\n    }\n}\n"
  },
  {
    "path": "extra/intl-extra/LICENSE",
    "content": "Copyright (c) 2019-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "extra/intl-extra/README.md",
    "content": "Twig Intl Extension\n===================\n\nThis package is a Twig extension that provides the following:\n\n * [`country_name`][1] filter: returns the country name given its two-letter/five-letter code;\n * [`currency_name`][2] filter: returns the currency name given its three-letter code;\n * [`currency_symbol`][3] filter: returns the currency symbol given its three-letter code;\n * [`language_name`][4] filter: returns the language name given its two-letter/five-letter code;\n * [`locale_name`][5] filter: returns the language name given its two-letter/five-letter code;\n * [`timezone_name`][6] filter: returns the timezone name given its identifier;\n * [`country_timezones`][7] filter: returns the timezone identifiers of the given country code;\n * [`format_currency`][8] filter: formats a number as a currency;\n * [`format_number`][9] filter: formats a number;\n * [`format_datetime`][10] filter: formats a date time;\n * [`format_date`][11] filter: formats a date;\n * [`format_time`][12] filter: formats a time.\n\n[1]: https://twig.symfony.com/country_name\n[2]: https://twig.symfony.com/currency_name\n[3]: https://twig.symfony.com/currency_symbol\n[4]: https://twig.symfony.com/language_name\n[5]: https://twig.symfony.com/locale_name\n[6]: https://twig.symfony.com/timezone_name\n[7]: https://twig.symfony.com/country_timezones\n[8]: https://twig.symfony.com/format_currency\n[9]: https://twig.symfony.com/format_number\n[10]: https://twig.symfony.com/format_datetime\n[11]: https://twig.symfony.com/format_date\n[12]: https://twig.symfony.com/format_time\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/country_name.test",
    "content": "--TEST--\n\"country_name\" filter\n--TEMPLATE--\n{{ 'UNKNOWN'|country_name }}\n{{ null|country_name }}\n{{ 'FR'|country_name }}\n{{ 'US'|country_name }}\n{{ 'US'|country_name('fr') }}\n{{ 'CH'|country_name('fr_CA') }}\n--DATA--\nreturn [];\n--EXPECT--\nUNKNOWN\n\nFrance\nUnited States\nÉtats-Unis\nSuisse\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/country_names.test",
    "content": "--TEST--\n\"country_names\" function\n--TEMPLATE--\n{{ country_names('UNKNOWN')|length }}\n{{ country_names()|length }}\n{{ country_names('fr')|length }}\n{{ country_names()['BE'] }}\n{{ country_names('fr')['BE'] }}\n--DATA--\nreturn [];\n--EXPECT--\n0\n249\n249\nBelgium\nBelgique\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/country_timezones.test",
    "content": "--TEST--\n\"country_timezones\" function\n--TEMPLATE--\n{{ country_timezones('UNKNOWN')|length }}\n{{ country_timezones('FR')|join(', ') }}\n{{ country_timezones('US')[0:2]|join(', ') }}\n--DATA--\nreturn [];\n--EXPECT--\n0\nEurope/Paris\nAmerica/Adak, America/Anchorage\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/currency_name.test",
    "content": "--TEST--\n\"currency_name\" filter\n--TEMPLATE--\n{{ 'UNKNOWN'|currency_name }}\n{{ null|currency_name }}\n{{ 'EUR'|currency_name }}\n{{ 'JPY'|currency_name }}\n{{ 'EUR'|currency_name('fr') }}\n{{ 'JPY'|currency_name('fr_FR') }}\n--DATA--\nreturn [];\n--EXPECT--\nUNKNOWN\n\nEuro\nJapanese Yen\neuro\nyen japonais\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/currency_names.test",
    "content": "--TEST--\n\"currency_names\" function\n--TEMPLATE--\n{{ currency_names('UNKNOWN')|length }}\n{{ currency_names()|length }}\n{{ currency_names('fr')|length }}\n{{ currency_names()['USD'] }}\n{{ currency_names('fr')['USD'] }}\n--DATA--\nreturn [];\n--EXPECT--\n0\n294\n294\nUS Dollar\ndollar des États-Unis\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/currency_symbol.test",
    "content": "--TEST--\n\"currency_symbol\" filter\n--TEMPLATE--\n{{ 'UNKNOWN'|currency_symbol }}\n{{ null|currency_symbol }}\n{{ 'EUR'|currency_symbol }}\n{{ 'JPY'|currency_symbol }}\n--DATA--\nreturn [];\n--EXPECT--\nUNKNOWN\n\n€\n¥\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/format_currency.test",
    "content": "--TEST--\n\"format_currency\" filter\n--TEMPLATE--\n{{ '1000000'|format_currency('EUR') }}\n{{ '1000000'|format_currency('EUR', locale='de') }}\n{{ '1000000'|format_currency('EUR', {fraction_digit: 2}) }}\n{{ '12.345'|format_currency('EUR', {rounding_mode: 'floor'}) }}\n{{ '125000'|format_currency('YEN') }}\n--DATA--\nreturn [];\n--EXPECT--\n€1,000,000.00\n1.000.000,00 €\n€1,000,000.00\n€12.34\nYEN 125,000.00\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/format_date.test",
    "content": "--TEST--\n\"format_date\" filter\n--CONDITION--\nversion_compare(Symfony\\Component\\Intl\\Intl::getIcuVersion(), '72.1', '<')\n--TEMPLATE--\n{{ '2019-08-07 23:39:12'|format_datetime() }}\n{{ '2019-08-07 23:39:12'|format_datetime(locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_datetime('none', 'short', locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_datetime('short', 'none', locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_datetime('full', 'full', locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_datetime(pattern=\"hh 'oclock' a, zzzz\") }}\n\n{{ '2019-08-07 23:39:12'|format_date }}\n{{ '2019-08-07 23:39:12'|format_date(locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_time }}\n--DATA--\nreturn [];\n--EXPECT--\nAug 7, 2019, 11:39:12 PM\n7 août 2019, 23:39:12\n23:39\n07/08/2019\nmercredi 7 août 2019 à 23:39:12 temps universel coordonné\n11 oclock PM, Coordinated Universal Time\n\nAug 7, 2019\n7 août 2019\n11:39:12 PM\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/format_date_ICU72.test",
    "content": "--TEST--\n\"format_date\" filter\n--CONDITION--\nversion_compare(Symfony\\Component\\Intl\\Intl::getIcuVersion(), '72.1', '>=')\n--TEMPLATE--\n{{ '2019-08-07 23:39:12'|format_datetime() }}\n{{ '2019-08-07 23:39:12'|format_datetime(locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_datetime('none', 'short', locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_datetime('short', 'none', locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_datetime('full', 'full', locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_datetime(pattern=\"hh 'oclock' a, zzzz\") }}\n\n{{ '2019-08-07 23:39:12'|format_date }}\n{{ '2019-08-07 23:39:12'|format_date(locale='fr') }}\n{{ '2019-08-07 23:39:12'|format_time }}\n--DATA--\nreturn [];\n--EXPECT--\nAug 7, 2019, 11:39:12 PM\n7 août 2019, 23:39:12\n23:39\n07/08/2019\nmercredi 7 août 2019 à 23:39:12 temps universel coordonné\n11 oclock PM, Coordinated Universal Time\n\nAug 7, 2019\n7 août 2019\n11:39:12 PM\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/format_date_php8.test",
    "content": "--TEST--\n\"format_date\" filter\n--CONDITION--\nPHP_VERSION_ID >= 80000 && version_compare(Symfony\\Component\\Intl\\Intl::getIcuVersion(), '72.1', '<')\n--TEMPLATE--\n{{ 'today 23:39:12'|format_datetime('relative_short', 'none', locale='fr') }}\n{{ 'today 23:39:12'|format_datetime('relative_full', 'full', locale='fr') }}\n--DATA--\nreturn [];\n--EXPECT--\naujourd’hui\naujourd’hui à 23:39:12 temps universel coordonné\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/format_date_php8_ICU72.test",
    "content": "--TEST--\n\"format_date\" filter\n--CONDITION--\nPHP_VERSION_ID >= 80000 && version_compare(Symfony\\Component\\Intl\\Intl::getIcuVersion(), '72.1', '>=')\n--TEMPLATE--\n{{ 'today 23:39:12'|format_datetime('relative_short', 'none', locale='fr') }}\n{{ 'today 23:39:12'|format_datetime('relative_full', 'full', locale='fr') }}\n--DATA--\nreturn [];\n--EXPECT--\naujourd’hui\naujourd’hui, 23:39:12 temps universel coordonné\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/format_number.test",
    "content": "--TEST--\n\"format_number\" filter\n--TEMPLATE--\n{{ '12.345'|format_number }}\n{{ '12.345'|format_number(locale='fr') }}\n{{ '12.345'|format_number(style='percent') }}\n{{ '12.345'|format_number(style='spellout') }}\n{{ '12.345'|format_percent_number }}\n{{ '12.345'|format_spellout_number }}\n{{ '80.345'|format_spellout_number(locale='fr_FR') }}\n{{ '80.345'|format_spellout_number(locale='fr_CH') }}\n{{ '12'|format_duration_number }}\n{{ '0.12'|format_percent_number({fraction_digit: 1}) }}\n{{ '0.12345'|format_percent_number({rounding_mode: 'ceiling'}) }}\n--DATA--\nreturn [];\n--EXPECT--\n12.345\n12,345\n1,234%\ntwelve point three four five\n1,234%\ntwelve point three four five\nquatre-vingts virgule trois quatre cinq\nhuitante virgule trois quatre cinq\n12 sec.\n12.0%\n13%\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/language_name.test",
    "content": "--TEST--\n\"language_name\" filter\n--TEMPLATE--\n{{ 'UNKNOWN'|language_name }}\n{{ null|language_name }}\n{{ 'de'|language_name }}\n{{ 'fr'|language_name }}\n{{ 'de'|language_name('fr') }}\n{{ 'fr'|language_name('fr_FR') }}\n{{ 'fr_CA'|language_name('fr_FR') }}\n--DATA--\nreturn [];\n--EXPECT--\nUNKNOWN\n\nGerman\nFrench\nallemand\nfrançais\nfrançais canadien\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/language_names.test",
    "content": "--TEST--\n\"language_names\" function\n--TEMPLATE--\n{{ language_names('UNKNOWN')|length }}\n{{ language_names()|length > 600 ? 'ok' : 'ko' }}\n{{ language_names('fr')|length > 600 ? 'ok' : 'ko' }}\n{{ language_names()['fr'] }}\n{{ language_names('fr')['fr'] }}\n--DATA--\nreturn [];\n--EXPECT--\n0\nok\nok\nFrench\nfrançais\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/locale_name.test",
    "content": "--TEST--\n\"locale_name\" filter\n--TEMPLATE--\n{{ 'UNKNOWN'|locale_name }}\n{{ null|locale_name }}\n{{ 'de'|locale_name }}\n{{ 'fr'|locale_name }}\n{{ 'de'|locale_name('fr') }}\n{{ 'fr'|locale_name('fr_FR') }}\n{{ 'fr_CA'|locale_name('fr_FR') }}\n--DATA--\nreturn [];\n--EXPECT--\nUNKNOWN\n\nGerman\nFrench\nallemand\nfrançais\nfrançais (Canada)\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/locale_names.test",
    "content": "--TEST--\n\"locale_names\" function\n--TEMPLATE--\n{{ locale_names('UNKNOWN')|length }}\n{{ locale_names()|length > 600 ? 'ok' : 'ko' }}\n{{ locale_names('fr')|length > 600 ? 'ok' : 'ko' }}\n{{ locale_names()['fr'] }}\n{{ locale_names('fr')['fr'] }}\n--DATA--\nreturn [];\n--EXPECT--\n0\nok\nok\nFrench\nfrançais\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/script_names.test",
    "content": "--TEST--\n\"script_names\" function\n--TEMPLATE--\n{{ script_names('UNKNOWN')|length }}\n{{ script_names()|length > 200 ? 'more than 200' : 'less than 200' }}\n{{ script_names('fr')|length > 200 ? 'more than 200' : 'less than 200' }}\n{{ script_names()['Marc'] }}\n{{ script_names('fr')['Marc'] }}\n--DATA--\nreturn [];\n--EXPECT--\n0\nmore than 200\nmore than 200\nMarchen\nMarchen\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/timezone_name.test",
    "content": "--TEST--\n\"timezone_name\" filter\n--TEMPLATE--\n{{ 'UNKNOWN'|timezone_name }}\n{{ null|timezone_name }}\n{{ 'Europe/Paris'|timezone_name }}\n{{ 'America/Los_Angeles'|timezone_name }}\n{{ 'America/Los_Angeles'|timezone_name('fr') }}\n--DATA--\nreturn [];\n--EXPECT--\nUNKNOWN\n\nCentral European Time (Paris)\nPacific Time (Los Angeles)\nheure du Pacifique nord-américain (Los Angeles)\n"
  },
  {
    "path": "extra/intl-extra/Tests/Fixtures/timezone_names.test",
    "content": "--TEST--\n\"timezone_names\" function\n--TEMPLATE--\n{{ timezone_names('UNKNOWN')|length }}\n{{ timezone_names()|length > 400 ? 'ok' : 'ko' }}\n{{ timezone_names('fr')|length > 400 ? 'ok' : 'ko' }}\n{{ timezone_names()['Europe/Paris'] }}\n{{ timezone_names('fr')['Europe/Paris'] }}\n--DATA--\nreturn [];\n--EXPECT--\n0\nok\nok\nCentral European Time (Paris)\nheure d’Europe centrale (Paris)\n"
  },
  {
    "path": "extra/intl-extra/Tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Intl\\Tests;\n\nuse Twig\\Extra\\Intl\\IntlExtension;\nuse Twig\\Test\\IntegrationTestCase;\n\nclass IntegrationTest extends IntegrationTestCase\n{\n    public function getExtensions()\n    {\n        return [\n            new IntlExtension(),\n        ];\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        return __DIR__.'/Fixtures/';\n    }\n}\n"
  },
  {
    "path": "extra/intl-extra/Tests/IntlExtensionTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Intl\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Extra\\Intl\\IntlExtension;\nuse Twig\\Loader\\ArrayLoader;\n\nclass IntlExtensionTest extends TestCase\n{\n    public function testFormatterWithoutProto()\n    {\n        $ext = new IntlExtension();\n        $env = new Environment(new ArrayLoader());\n\n        $this->assertSame('12.346', $ext->formatNumber('12.3456'));\n        $this->assertStringStartsWith(\n            'Feb 20, 2020, 1:37:00',\n            $ext->formatDateTime($env, new \\DateTime('2020-02-20T13:37:00+00:00'))\n        );\n    }\n\n    public function testFormatterWithoutProtoFallsBackToCoreExtensionTimezone()\n    {\n        $ext = new IntlExtension();\n        $env = new Environment(new ArrayLoader());\n        // EET is always +2 without changes for daylight saving time\n        // so it has a fixed difference to UTC\n        $env->getExtension(CoreExtension::class)->setTimezone('EET');\n\n        $this->assertStringStartsWith(\n            'Feb 20, 2020, 3:37:00',\n            $ext->formatDateTime($env, new \\DateTime('2020-02-20T13:37:00+00:00', new \\DateTimeZone('UTC')))\n        );\n    }\n\n    public function testFormatterWithoutProtoSkipTimezoneConverter()\n    {\n        $ext = new IntlExtension();\n        $env = new Environment(new ArrayLoader());\n        // EET is always +2 without changes for daylight saving time\n        // so it has a fixed difference to UTC\n        $env->getExtension(CoreExtension::class)->setTimezone('EET');\n\n        $this->assertStringStartsWith(\n            'Feb 20, 2020, 1:37:00',\n            $ext->formatDateTime($env, new \\DateTime('2020-02-20T13:37:00+00:00', new \\DateTimeZone('UTC')), 'medium', 'medium', '', false)\n        );\n    }\n\n    public function testFormatterProto()\n    {\n        $dateFormatterProto = new \\IntlDateFormatter('fr', \\IntlDateFormatter::FULL, \\IntlDateFormatter::FULL, new \\DateTimeZone('Europe/Paris'));\n        $numberFormatterProto = new \\NumberFormatter('fr', \\NumberFormatter::DECIMAL);\n        $numberFormatterProto->setTextAttribute(\\NumberFormatter::POSITIVE_PREFIX, '++');\n        $numberFormatterProto->setAttribute(\\NumberFormatter::FRACTION_DIGITS, 1);\n        $ext = new IntlExtension($dateFormatterProto, $numberFormatterProto);\n        $env = new Environment(new ArrayLoader());\n\n        $this->assertSame('++12,3', $ext->formatNumber('12.3456'));\n        $this->assertContains(\n            $ext->formatDateTime($env, new \\DateTime('2020-02-20T13:37:00+00:00', new \\DateTimeZone('Europe/Paris'))),\n            [\n                'jeudi 20 février 2020 à 13:37:00 heure normale d’Europe centrale',\n                'jeudi 20 février 2020 à 13:37:00 temps universel coordonné',\n            ]\n        );\n    }\n\n    public function testFormatterOverridenProto()\n    {\n        $dateFormatterProto = new \\IntlDateFormatter('fr', \\IntlDateFormatter::FULL, \\IntlDateFormatter::FULL, new \\DateTimeZone('Europe/Paris'));\n        $numberFormatterProto = new \\NumberFormatter('fr', \\NumberFormatter::DECIMAL);\n        $numberFormatterProto->setTextAttribute(\\NumberFormatter::POSITIVE_PREFIX, '++');\n        $numberFormatterProto->setAttribute(\\NumberFormatter::FRACTION_DIGITS, 1);\n        $ext = new IntlExtension($dateFormatterProto, $numberFormatterProto);\n        $env = new Environment(new ArrayLoader());\n\n        $this->assertSame(\n            'twelve point three',\n            $ext->formatNumber('12.3456', [], 'spellout', 'default', 'en_US')\n        );\n        $this->assertSame(\n            '2020-02-20 13:37:00',\n            $ext->formatDateTime($env, new \\DateTime('2020-02-20T13:37:00+00:00'), 'short', 'short', 'yyyy-MM-dd HH:mm:ss', 'UTC', 'gregorian', 'en_US')\n        );\n    }\n}\n"
  },
  {
    "path": "extra/intl-extra/composer.json",
    "content": "{\n    \"name\": \"twig/intl-extra\",\n    \"type\": \"library\",\n    \"description\": \"A Twig extension for Intl\",\n    \"keywords\": [\"twig\", \"intl\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"MIT\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"twig/twig\": \"^3.13|^4.0\",\n        \"symfony/intl\": \"^5.4|^6.4|^7.0|^8.0\"\n    },\n    \"require-dev\": {\n        \"symfony/phpunit-bridge\": \"^6.4|^7.0\"\n    },\n    \"autoload\": {\n        \"psr-4\" : { \"Twig\\\\Extra\\\\Intl\\\\\" : \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    }\n}\n"
  },
  {
    "path": "extra/intl-extra/phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\" backupGlobals=\"false\" colors=\"true\" bootstrap=\"vendor/autoload.php\" failOnRisky=\"true\" failOnWarning=\"true\">\n  <coverage>\n    <include>\n      <directory>./</directory>\n    </include>\n    <exclude>\n      <directory>./Tests</directory>\n      <directory>./vendor</directory>\n    </exclude>\n  </coverage>\n  <php>\n    <env name=\"LANG\" value=\"en_US.UTF-8\" force=\"true\"/>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n  </php>\n  <testsuites>\n    <testsuite name=\"Twig Intl Extension Test Suite\">\n      <directory>./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "extra/markdown-extra/.gitattributes",
    "content": "/Tests export-ignore\n/.git* export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": "extra/markdown-extra/.gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n"
  },
  {
    "path": "extra/markdown-extra/DefaultMarkdown.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown;\n\nuse League\\CommonMark\\CommonMarkConverter;\nuse Michelf\\MarkdownExtra;\n\nclass DefaultMarkdown implements MarkdownInterface\n{\n    private $converter;\n\n    public function __construct()\n    {\n        if (class_exists(CommonMarkConverter::class)) {\n            $this->converter = new LeagueMarkdown();\n        } elseif (class_exists(MarkdownExtra::class)) {\n            $this->converter = new MichelfMarkdown();\n        } elseif (class_exists(\\Parsedown::class)) {\n            $this->converter = new ErusevMarkdown();\n        } else {\n            throw new \\LogicException('You cannot use the \"markdown_to_html\" filter as no Markdown library is available; try running \"composer require league/commonmark\".');\n        }\n    }\n\n    public function convert(string $body): string\n    {\n        return $this->converter->convert($body);\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/ErusevMarkdown.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown;\n\nclass ErusevMarkdown implements MarkdownInterface\n{\n    private $converter;\n\n    public function __construct(?\\Parsedown $converter = null)\n    {\n        $this->converter = $converter ?: new \\Parsedown();\n    }\n\n    public function convert(string $body): string\n    {\n        return $this->converter->text($body);\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/LICENSE",
    "content": "Copyright (c) 2019-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "extra/markdown-extra/LeagueMarkdown.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown;\n\nuse League\\CommonMark\\CommonMarkConverter;\nuse League\\CommonMark\\MarkdownConverter;\n\nclass LeagueMarkdown implements MarkdownInterface\n{\n    private $converter;\n    private $legacySupport;\n\n    public function __construct(?MarkdownConverter $converter = null)\n    {\n        $this->converter = $converter ?: new CommonMarkConverter();\n        $this->legacySupport = !method_exists($this->converter, 'convert');\n    }\n\n    public function convert(string $body): string\n    {\n        if ($this->legacySupport) {\n            return $this->converter->convertToHtml($body);\n        }\n\n        return $this->converter->convert($body);\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/MarkdownExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown;\n\nuse League\\HTMLToMarkdown\\HtmlConverter;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\TwigFilter;\n\nfinal class MarkdownExtension extends AbstractExtension\n{\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('markdown_to_html', ['Twig\\\\Extra\\\\Markdown\\\\MarkdownRuntime', 'convert'], ['is_safe' => ['all']]),\n            new TwigFilter('html_to_markdown', [self::class, 'htmlToMarkdown'], ['is_safe' => ['all']]),\n        ];\n    }\n\n    /**\n     * @internal\n     */\n    public static function htmlToMarkdown(string $body, array $options = []): string\n    {\n        static $converters;\n\n        if (!class_exists(HtmlConverter::class)) {\n            throw new \\LogicException('You cannot use the \"html_to_markdown\" filter as league/html-to-markdown is not installed; try running \"composer require league/html-to-markdown\".');\n        }\n\n        $options += [\n            'hard_break' => true,\n            'strip_tags' => true,\n            'remove_nodes' => 'head style',\n        ];\n\n        if (!isset($converters[$key = serialize($options)])) {\n            $converters[$key] = new HtmlConverter($options);\n        }\n\n        return $converters[$key]->convert($body);\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/MarkdownInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown;\n\ninterface MarkdownInterface\n{\n    public function convert(string $body): string;\n}\n"
  },
  {
    "path": "extra/markdown-extra/MarkdownRuntime.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown;\n\nclass MarkdownRuntime\n{\n    private $converter;\n\n    public function __construct(MarkdownInterface $converter)\n    {\n        $this->converter = $converter;\n    }\n\n    public function convert(string $body): string\n    {\n        // remove indentation\n        if ($white = substr($body, 0, strspn($body, \" \\t\\r\\n\\0\\x0B\"))) {\n            $body = preg_replace(\"{^$white}m\", '', $body);\n        }\n\n        return $this->converter->convert($body);\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/MichelfMarkdown.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown;\n\nuse Michelf\\MarkdownExtra;\n\nclass MichelfMarkdown implements MarkdownInterface\n{\n    private $converter;\n\n    public function __construct(?MarkdownExtra $converter = null)\n    {\n        if (null === $converter) {\n            $converter = new MarkdownExtra();\n            $converter->hard_wrap = true;\n        }\n\n        $this->converter = $converter;\n    }\n\n    public function convert(string $body): string\n    {\n        return $this->converter->transform($body);\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/README.md",
    "content": "Twig Markdown Extension\n=======================\n\nThis package is a Twig extension that provides the following:\n\n * [`markdown_to_html`][1] filter: generates HTML from a Markdown block;\n\n * [`html_to_markdown`][2] filter: generates Markdown from an HTML block.\n\n[1]: https://twig.symfony.com/markdown_to_html\n[2]: https://twig.symfony.com/html_to_markdown\n"
  },
  {
    "path": "extra/markdown-extra/Resources/functions.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown;\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9.0\n */\nfunction html_to_markdown(string $body, array $options = []): string\n{\n    trigger_deprecation('twig/intl-extra', '3.9.0', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return MarkdownExtension::htmlToMarkdown($body, $options);\n}\n"
  },
  {
    "path": "extra/markdown-extra/Tests/Fixtures/html_to_markdown.test",
    "content": "--TEST--\n\"html_to_markdown\" filter\n--TEMPLATE--\n{% apply html_to_markdown %}\n    <html>\n        <h1>Hello</h1>\n        <p><b><span>Great!</span></b></p>\n    </html>\n{% endapply %}\n\n\n{% apply html_to_markdown({hard_break: false}) %}\n    <html>Great<br>Break</html>\n{% endapply %}\n\n\n{{ include('html')|html_to_markdown }}\n--TEMPLATE(html)--\n<html>\n    <h1>Hello</h1>\n    <p><b>Great!</b></p>\n</html>\n--DATA--\nreturn []\n--EXPECT--\nHello\n=====\n\n**Great!**\n\nGreat  \nBreak\n\nHello\n=====\n\n**Great!**\n"
  },
  {
    "path": "extra/markdown-extra/Tests/FunctionalTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Extra\\Markdown\\DefaultMarkdown;\nuse Twig\\Extra\\Markdown\\ErusevMarkdown;\nuse Twig\\Extra\\Markdown\\LeagueMarkdown;\nuse Twig\\Extra\\Markdown\\MarkdownExtension;\nuse Twig\\Extra\\Markdown\\MarkdownRuntime;\nuse Twig\\Extra\\Markdown\\MichelfMarkdown;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\RuntimeLoader\\RuntimeLoaderInterface;\n\nclass FunctionalTest extends TestCase\n{\n    /**\n     * @dataProvider getMarkdownTests\n     */\n    public function testMarkdown(string $template, string $expected)\n    {\n        foreach ([LeagueMarkdown::class, ErusevMarkdown::class, /* MichelfMarkdown::class, */ DefaultMarkdown::class] as $class) {\n            $twig = new Environment(new ArrayLoader([\n                'index' => $template,\n                'html' => <<<EOF\nHello\n=====\n\nGreat!\nEOF,\n            ]));\n            $twig->addExtension(new MarkdownExtension());\n            $twig->addRuntimeLoader(new class($class) implements RuntimeLoaderInterface {\n                private $class;\n\n                public function __construct(string $class)\n                {\n                    $this->class = $class;\n                }\n\n                public function load(string $c): ?object\n                {\n                    return MarkdownRuntime::class === $c ? new $c(new $this->class()) : null;\n                }\n            });\n            $this->assertMatchesRegularExpression('{'.$expected.'}m', trim($twig->render('index')));\n        }\n    }\n\n    public static function getMarkdownTests()\n    {\n        return [\n            [<<<EOF\n{% apply markdown_to_html %}\nHello\n=====\n\nGreat!\n{% endapply %}\nEOF, \"<h1>Hello</h1>\\n+<p>Great!</p>\"],\n            [<<<EOF\n{% apply markdown_to_html %}\n    Hello\n    =====\n\n    Great!\n{% endapply %}\nEOF, \"<h1>Hello</h1>\\n+<p>Great!</p>\"],\n            [\"{{ include('html')|markdown_to_html }}\", \"<h1>Hello</h1>\\n+<p>Great!</p>\"],\n        ];\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/Tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown\\Tests;\n\nuse Twig\\Extra\\Markdown\\MarkdownExtension;\nuse Twig\\Test\\IntegrationTestCase;\n\nclass IntegrationTest extends IntegrationTestCase\n{\n    public function getExtensions()\n    {\n        return [\n            new MarkdownExtension(),\n        ];\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        return __DIR__.'/Fixtures/';\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/Tests/LegacyFunctionsTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\Markdown\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\n\nuse function Twig\\Extra\\Markdown\\html_to_markdown;\n\nuse Twig\\Extra\\Markdown\\MarkdownExtension;\n\n/**\n * @group legacy\n */\nclass LegacyFunctionsTest extends TestCase\n{\n    public function testHtmlToMarkdown()\n    {\n        $this->assertSame(MarkdownExtension::htmlToMarkdown('<p>foo</p>'), html_to_markdown('<p>foo</p>'));\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/composer.json",
    "content": "{\n    \"name\": \"twig/markdown-extra\",\n    \"type\": \"library\",\n    \"description\": \"A Twig extension for Markdown\",\n    \"keywords\": [\"twig\", \"html\", \"markdown\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"MIT\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"symfony/deprecation-contracts\": \"^2.5|^3\",\n        \"twig/twig\": \"^3.13|^4.0\"\n    },\n    \"require-dev\": {\n        \"symfony/phpunit-bridge\": \"^6.4|^7.0\",\n        \"erusev/parsedown\": \"dev-master as 1.x-dev\",\n        \"league/commonmark\": \"^2.7\",\n        \"league/html-to-markdown\": \"^4.8|^5.0\",\n        \"michelf/php-markdown\": \"^1.8|^2.0\"\n    },\n    \"autoload\": {\n        \"files\": [ \"Resources/functions.php\" ],\n        \"psr-4\" : { \"Twig\\\\Extra\\\\Markdown\\\\\" : \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    }\n}\n"
  },
  {
    "path": "extra/markdown-extra/phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\" backupGlobals=\"false\" colors=\"true\" bootstrap=\"vendor/autoload.php\" failOnRisky=\"true\" failOnWarning=\"true\">\n  <coverage>\n    <include>\n      <directory>./</directory>\n    </include>\n    <exclude>\n      <directory>./Tests</directory>\n      <directory>./vendor</directory>\n    </exclude>\n  </coverage>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n  </php>\n  <testsuites>\n    <testsuite name=\"Twig Markdown Extension Test Suite\">\n      <directory>./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "extra/string-extra/.gitattributes",
    "content": "/Tests export-ignore\n/.git* export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": "extra/string-extra/.gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n"
  },
  {
    "path": "extra/string-extra/LICENSE",
    "content": "Copyright (c) 2019-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "extra/string-extra/README.md",
    "content": "String Extension\n================\n\nThis package is a Twig extension that provides integration with the Symfony\nString component. It provides the following filters:\n\n * [`u`][1]: Wraps a text in a `UnicodeString` object to give access to\n[methods of the class][2].\n\n * [`slug`][3]: Wraps the [`AsciiSlugger`][4]'s `slug` method.\n\n * [`singular`][5] and [`plural`][6]: Wraps the [`Inflector`][7] `singularize`\n   and `pluralize` methods.\n\n[1]: https://twig.symfony.com/u\n[2]: https://symfony.com/doc/current/components/string.html\n[3]: https://twig.symfony.com/slug\n[4]: https://symfony.com/doc/current/components/string.html#slugger\n[5]: https://twig.symfony.com/singular\n[6]: https://twig.symfony.com/plural\n[7]: https://symfony.com/doc/current/components/string.html#inflector\n"
  },
  {
    "path": "extra/string-extra/StringExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\String;\n\nuse Symfony\\Component\\String\\AbstractUnicodeString;\nuse Symfony\\Component\\String\\Inflector\\EnglishInflector;\nuse Symfony\\Component\\String\\Inflector\\FrenchInflector;\nuse Symfony\\Component\\String\\Inflector\\InflectorInterface;\nuse Symfony\\Component\\String\\Inflector\\SpanishInflector;\nuse Symfony\\Component\\String\\Slugger\\AsciiSlugger;\nuse Symfony\\Component\\String\\Slugger\\SluggerInterface;\nuse Symfony\\Component\\String\\UnicodeString;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\TwigFilter;\n\nfinal class StringExtension extends AbstractExtension\n{\n    private $slugger;\n    private $englishInflector;\n    private $spanishInflector;\n    private $frenchInflector;\n\n    public function __construct(?SluggerInterface $slugger = null)\n    {\n        $this->slugger = $slugger ?: new AsciiSlugger();\n    }\n\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('u', [$this, 'createUnicodeString']),\n            new TwigFilter('slug', [$this, 'createSlug']),\n            new TwigFilter('plural', [$this, 'plural']),\n            new TwigFilter('singular', [$this, 'singular']),\n        ];\n    }\n\n    public function createUnicodeString(?string $text): UnicodeString\n    {\n        return new UnicodeString($text ?? '');\n    }\n\n    public function createSlug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString\n    {\n        return $this->slugger->slug($string, $separator, $locale);\n    }\n\n    /**\n     * @return array|string\n     */\n    public function plural(string $value, string $locale = 'en', bool $all = false)\n    {\n        if ($all) {\n            return $this->getInflector($locale)->pluralize($value);\n        }\n\n        return $this->getInflector($locale)->pluralize($value)[0];\n    }\n\n    /**\n     * @return array|string\n     */\n    public function singular(string $value, string $locale = 'en', bool $all = false)\n    {\n        if ($all) {\n            return $this->getInflector($locale)->singularize($value);\n        }\n\n        return $this->getInflector($locale)->singularize($value)[0];\n    }\n\n    private function getInflector(string $locale): InflectorInterface\n    {\n        switch ($locale) {\n            case 'en':\n                return $this->englishInflector ?? $this->englishInflector = new EnglishInflector();\n            case 'es':\n                if (!class_exists(SpanishInflector::class)) {\n                    throw new RuntimeError('SpanishInflector is not available.');\n                }\n\n                return $this->spanishInflector ?? $this->spanishInflector = new SpanishInflector();\n            case 'fr':\n                return $this->frenchInflector ?? $this->frenchInflector = new FrenchInflector();\n            default:\n                throw new \\InvalidArgumentException(\\sprintf('Locale \"%s\" is not supported.', $locale));\n        }\n    }\n}\n"
  },
  {
    "path": "extra/string-extra/Tests/Fixtures/plural-invalid-language.test",
    "content": "--TEST--\n\"plural\" filter\n--TEMPLATE--\n{{ 'partition'|plural('it') }}\n\n--DATA--\nreturn []\n\n--EXCEPTION--\nTwig\\Error\\RuntimeError: An exception has been thrown during the rendering of a template (\"Locale \"it\" is not supported.\") in \"index.twig\" at line 2."
  },
  {
    "path": "extra/string-extra/Tests/Fixtures/plural.test",
    "content": "--TEST--\n\"plural\" filter\n--TEMPLATE--\n{{ 'partition'|plural('fr') }}\n{{ 'partition'|plural('fr', all=true)|join(',') }}\n{{ 'person'|plural('fr') }}\n{{ 'person'|plural('en', all=true)|join(',') }}\n\n--DATA--\nreturn []\n--EXPECT--\npartitions\npartitions\npersons\npersons,people\n"
  },
  {
    "path": "extra/string-extra/Tests/Fixtures/plural_es.test",
    "content": "--TEST--\n\"plural\" filter\n--CONDITION--\nclass_exists('Symfony\\Component\\String\\Inflector\\SpanishInflector')\n--TEMPLATE--\n{{ 'avión'|plural('es') }}\n{{ 'avión'|plural('es', all=true)|join(',') }}\n\n--DATA--\nreturn []\n--EXPECT--\naviones\naviones\n"
  },
  {
    "path": "extra/string-extra/Tests/Fixtures/singular-invalid-language.test",
    "content": "--TEST--\n\"singular\" filter\n--TEMPLATE--\n{{ 'partitions'|singular('it') }}\n\n--DATA--\nreturn []\n\n--EXCEPTION--\nTwig\\Error\\RuntimeError: An exception has been thrown during the rendering of a template (\"Locale \"it\" is not supported.\") in \"index.twig\" at line 2."
  },
  {
    "path": "extra/string-extra/Tests/Fixtures/singular.test",
    "content": "--TEST--\n\"singular\" filter\n--TEMPLATE--\n{{ 'partitions'|singular('fr') }}\n{{ 'partitions'|singular('fr', all=true)|join(',') }}\n{{ 'persons'|singular('fr') }}\n{{ 'persons'|singular('en', all=true)|join(',') }}\n{{ 'people'|singular('en') }}\n{{ 'people'|singular('en', all=true)|join(',') }}\n\n--DATA--\nreturn []\n--EXPECT--\npartition\npartition\nperson\nperson\nperson\nperson\n"
  },
  {
    "path": "extra/string-extra/Tests/Fixtures/singular_es.test",
    "content": "--TEST--\n\"singular\" filter\n--CONDITION--\nclass_exists('Symfony\\Component\\String\\Inflector\\SpanishInflector')\n--TEMPLATE--\n{{ 'personas'|singular('es') }}\n{{ 'personas'|singular('es', all=true)|join(',') }}\n\n--DATA--\nreturn []\n--EXPECT--\npersona\npersona\n"
  },
  {
    "path": "extra/string-extra/Tests/Fixtures/slug.test",
    "content": "--TEST--\n\"slug\" filter\n--TEMPLATE--\n{{ 'Wôrķšƥáçè ~~sèťtïñğš~~'|slug }}\n\n{{ 'Wôrķšƥáçè ~~sèťtïñğš~~'|slug('/') }}\n\n--DATA--\nreturn []\n--EXPECT--\nWorkspace-settings\n\nWorkspace/settings\n"
  },
  {
    "path": "extra/string-extra/Tests/Fixtures/string.test",
    "content": "--TEST--\n\"u\" filter\n--TEMPLATE--\n{{ 'Symfony String + Twig = <3'|u|raw }}\n\n{{ 'Symfony String + Twig = <3'|u.wordwrap(5).upper|raw }}\n\n{{ 'SymfonyStringWithTwig'|u.snake }}\n{{ 'symfony_string with twig'|u.camel.title }}\n\n{{ 'Lorem ipsum'|u.truncate(8, '...') }}\n\n--DATA--\nreturn []\n--EXPECT--\nSymfony String + Twig = <3\n\nSYMFONY\nSTRING\n+\nTWIG\n= <3\n\nsymfony_string_with_twig\nSymfonyStringWithTwig\n\nLorem...\n"
  },
  {
    "path": "extra/string-extra/Tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\String\\Tests;\n\nuse Twig\\Extra\\String\\StringExtension;\nuse Twig\\Test\\IntegrationTestCase;\n\nclass IntegrationTest extends IntegrationTestCase\n{\n    public function getExtensions()\n    {\n        return [\n            new StringExtension(),\n        ];\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        return __DIR__.'/Fixtures/';\n    }\n}\n"
  },
  {
    "path": "extra/string-extra/composer.json",
    "content": "{\n    \"name\": \"twig/string-extra\",\n    \"type\": \"library\",\n    \"description\": \"A Twig extension for Symfony String\",\n    \"keywords\": [\"twig\", \"html\", \"string\", \"unicode\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"MIT\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"symfony/string\": \"^5.4|^6.4|^7.0|^8.0\",\n        \"symfony/translation-contracts\": \"^1.1|^2|^3\",\n        \"twig/twig\": \"^3.13|^4.0\"\n    },\n    \"require-dev\": {\n        \"symfony/phpunit-bridge\": \"^6.4|^7.0\"\n    },\n    \"autoload\": {\n        \"psr-4\" : { \"Twig\\\\Extra\\\\String\\\\\" : \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    }\n}\n"
  },
  {
    "path": "extra/string-extra/phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\" backupGlobals=\"false\" colors=\"true\" bootstrap=\"vendor/autoload.php\" failOnRisky=\"true\" failOnWarning=\"true\">\n  <coverage>\n    <include>\n      <directory>./</directory>\n    </include>\n    <exclude>\n      <directory>./Tests</directory>\n      <directory>./vendor</directory>\n    </exclude>\n  </coverage>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n  </php>\n  <testsuites>\n    <testsuite name=\"Twig String Extension Test Suite\">\n      <directory>./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "extra/twig-extra-bundle/.gitattributes",
    "content": "/Tests export-ignore\n/.git* export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": "extra/twig-extra-bundle/.gitignore",
    "content": "vendor/\nvar/\ncomposer.lock\nphpunit.xml\n.phpunit.result.cache\n"
  },
  {
    "path": "extra/twig-extra-bundle/DependencyInjection/Compiler/MissingExtensionSuggestorPass.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle\\DependencyInjection\\Compiler;\n\nuse Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\DependencyInjection\\Reference;\n\nif (!method_exists(ContainerBuilder::class, 'getAutoconfiguredAttributes')) {\n    class MissingExtensionSuggestorPass implements CompilerPassInterface\n    {\n        public function process(ContainerBuilder $container): void\n        {\n            if (!$container->getParameter('kernel.debug')) {\n                return;\n            }\n            $twigDefinition = $container->getDefinition('twig');\n            $twigDefinition\n                ->addMethodCall('registerUndefinedFilterCallback', [[new Reference('twig.missing_extension_suggestor'), 'suggestFilter']])\n                ->addMethodCall('registerUndefinedFunctionCallback', [[new Reference('twig.missing_extension_suggestor'), 'suggestFunction']])\n                ->addMethodCall('registerUndefinedTokenParserCallback', [[new Reference('twig.missing_extension_suggestor'), 'suggestTag']])\n            ;\n        }\n    }\n} else {\n    class MissingExtensionSuggestorPass implements CompilerPassInterface\n    {\n        /** @return void */\n        public function process(ContainerBuilder $container)\n        {\n            if (!$container->getParameter('kernel.debug')) {\n                return;\n            }\n            $twigDefinition = $container->getDefinition('twig');\n            $twigDefinition\n                ->addMethodCall('registerUndefinedFilterCallback', [[new Reference('twig.missing_extension_suggestor'), 'suggestFilter']])\n                ->addMethodCall('registerUndefinedFunctionCallback', [[new Reference('twig.missing_extension_suggestor'), 'suggestFunction']])\n                ->addMethodCall('registerUndefinedTokenParserCallback', [[new Reference('twig.missing_extension_suggestor'), 'suggestTag']])\n            ;\n        }\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/DependencyInjection/Configuration.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle\\DependencyInjection;\n\nuse Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition;\nuse Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder;\nuse Symfony\\Component\\Config\\Definition\\ConfigurationInterface;\nuse Twig\\Extra\\TwigExtraBundle\\Extensions;\n\nclass Configuration implements ConfigurationInterface\n{\n    public function getConfigTreeBuilder(): TreeBuilder\n    {\n        $treeBuilder = new TreeBuilder('twig_extra');\n        $rootNode = $treeBuilder->getRootNode();\n\n        foreach (Extensions::getClasses() as $name => $class) {\n            $rootNode\n                ->children()\n                    ->arrayNode($name)\n                        ->{class_exists($class) ? 'canBeDisabled' : 'canBeEnabled'}()\n                    ->end()\n                ->end()\n            ;\n        }\n\n        $this->addCommonMarkConfiguration($rootNode);\n\n        return $treeBuilder;\n    }\n\n    /**\n     * Full configuration from {@link https://commonmark.thephpleague.com/2.7/configuration}.\n     */\n    private function addCommonMarkConfiguration(ArrayNodeDefinition $rootNode): void\n    {\n        $rootNode\n            ->children()\n                ->arrayNode('commonmark')\n                    ->ignoreExtraKeys(false)\n                    ->children()\n                        ->arrayNode('renderer')\n                            ->info('Array of options for rendering HTML.')\n                            ->children()\n                                ->scalarNode('block_separator')->end()\n                                ->scalarNode('inner_separator')->end()\n                                ->scalarNode('soft_break')->end()\n                            ->end()\n                        ->end()\n                        ->enumNode('html_input')\n                            ->info('How to handle HTML input.')\n                            ->values(['strip', 'allow', 'escape'])\n                            ->end()\n                        ->booleanNode('allow_unsafe_links')\n                            ->info('Remove risky link and image URLs by setting this to false.')\n                            ->defaultTrue()\n                            ->end()\n                        ->integerNode('max_nesting_level')\n                            ->info('The maximum nesting level for blocks.')\n                            ->defaultValue(\\PHP_INT_MAX)\n                            ->end()\n                        ->integerNode('max_delimiters_per_line')\n                            ->info('The maximum number of strong/emphasis delimiters per line.')\n                            ->defaultValue(\\PHP_INT_MAX)\n                            ->end()\n                        ->arrayNode('slug_normalizer')\n                            ->info('Array of options for configuring how URL-safe slugs are created.')\n                            ->children()\n                                ->variableNode('instance')->end()\n                                ->integerNode('max_length')->defaultValue(255)->end()\n                                ->variableNode('unique')->end()\n                            ->end()\n                        ->end()\n                        ->arrayNode('commonmark')\n                            ->info('Array of options for configuring the CommonMark core extension.')\n                            ->children()\n                                ->booleanNode('enable_em')->defaultTrue()->end()\n                                ->booleanNode('enable_strong')->defaultTrue()->end()\n                                ->booleanNode('use_asterisk')->defaultTrue()->end()\n                                ->booleanNode('use_underscore')->defaultTrue()->end()\n                                ->arrayNode('unordered_list_markers')\n                                    ->scalarPrototype()->end()\n                                    ->defaultValue([['-', '*', '+']])->end()\n                            ->end()\n                        ->end()\n                    ->end()\n                ->end()\n            ->end();\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle\\DependencyInjection;\n\nuse League\\CommonMark\\CommonMarkConverter;\nuse Symfony\\Component\\Config\\FileLocator;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\DependencyInjection\\Extension\\Extension;\nuse Symfony\\Component\\DependencyInjection\\Loader\\PhpFileLoader;\nuse Twig\\Extra\\TwigExtraBundle\\Extensions;\n\nif (!method_exists(ContainerBuilder::class, 'getAutoconfiguredAttributes')) {\n    /** @internal */\n    trait TwigExtraExtensionTrait\n    {\n        public function load(array $configs, ContainerBuilder $container): void\n        {\n            $this->doLoad($configs, $container);\n        }\n    }\n} else {\n    /** @internal */\n    trait TwigExtraExtensionTrait\n    {\n        /** @return void */\n        public function load(array $configs, ContainerBuilder $container)\n        {\n            $this->doLoad($configs, $container);\n        }\n    }\n}\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass TwigExtraExtension extends Extension\n{\n    use TwigExtraExtensionTrait;\n\n    private function doLoad(array $configs, ContainerBuilder $container): void\n    {\n        $loader = new PhpFileLoader($container, new FileLocator(\\dirname(__DIR__).'/Resources/config'));\n        $configuration = $this->getConfiguration($configs, $container);\n        $config = $this->processConfiguration($configuration, $configs);\n\n        if ($container->getParameter('kernel.debug')) {\n            $loader->load('suggestor.php');\n        }\n\n        foreach (array_keys(Extensions::getClasses()) as $extension) {\n            if ($this->isConfigEnabled($container, $config[$extension])) {\n                $loader->load($extension.'.php');\n\n                if ('markdown' === $extension && class_exists(CommonMarkConverter::class)) {\n                    $loader->load('markdown_league.php');\n\n                    if ($container->hasDefinition('twig.markdown.league_common_mark_converter_factory')) {\n                        $container\n                            ->getDefinition('twig.markdown.league_common_mark_converter_factory')\n                            ->setArgument('$config', $config['commonmark'] ?? []);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/Extensions.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle;\n\nuse Twig\\Extra\\Cache\\CacheExtension;\nuse Twig\\Extra\\CssInliner\\CssInlinerExtension;\nuse Twig\\Extra\\Html\\HtmlExtension;\nuse Twig\\Extra\\Inky\\InkyExtension;\nuse Twig\\Extra\\Intl\\IntlExtension;\nuse Twig\\Extra\\Markdown\\MarkdownExtension;\nuse Twig\\Extra\\String\\StringExtension;\n\nfinal class Extensions\n{\n    private const EXTENSIONS = [\n        'cache' => [\n            'name' => 'cache',\n            'class' => CacheExtension::class,\n            'class_name' => 'CacheExtension',\n            'package' => 'twig/cache-extra',\n            'filters' => [],\n            'functions' => [],\n            'tags' => ['cache'],\n        ],\n        'html' => [\n            'name' => 'html',\n            'class' => HtmlExtension::class,\n            'class_name' => 'HtmlExtension',\n            'package' => 'twig/html-extra',\n            'filters' => ['data_uri'],\n            'functions' => ['html_classes'],\n            'tags' => [],\n        ],\n        'markdown' => [\n            'name' => 'markdown',\n            'class' => MarkdownExtension::class,\n            'class_name' => 'MarkdownExtension',\n            'package' => 'twig/markdown-extra',\n            'filters' => ['html_to_markdown', 'markdown_to_html'],\n            'functions' => [],\n            'tags' => [],\n        ],\n        'intl' => [\n            'name' => 'intl',\n            'class' => IntlExtension::class,\n            'class_name' => 'IntlExtension',\n            'package' => 'twig/intl-extra',\n            'filters' => ['country_name', 'currency_name', 'currency_symbol', 'language_name', 'locale_name', 'timezone_name',\n                'format_currency', 'format_number', 'format_decimal_number', 'format_currency_number',\n                'format_percent_number', 'format_scientific_number', 'format_spellout_number', 'format_ordinal_number',\n                'format_duration_number', 'format_date', 'format_datetime', 'format_time',\n            ],\n            'functions' => ['country_timezones'],\n            'tags' => [],\n        ],\n        'cssinliner' => [\n            'name' => 'cssinliner',\n            'class' => CssInlinerExtension::class,\n            'class_name' => 'CssInlinerExtension',\n            'package' => 'twig/cssinliner-extra',\n            'filters' => ['inline_css'],\n            'functions' => [],\n            'tags' => [],\n        ],\n        'inky' => [\n            'name' => 'inky',\n            'class' => InkyExtension::class,\n            'class_name' => 'InkyExtension',\n            'package' => 'twig/inky-extra',\n            'filters' => ['inky_to_html'],\n            'functions' => [],\n            'tags' => [],\n        ],\n        'string' => [\n            'name' => 'string',\n            'class' => StringExtension::class,\n            'class_name' => 'StringExtension',\n            'package' => 'twig/string-extra',\n            'filters' => ['u'],\n            'functions' => [],\n            'tags' => [],\n        ],\n    ];\n\n    public static function getClasses(): array\n    {\n        return array_column(self::EXTENSIONS, 'class', 'name');\n    }\n\n    public static function getFilter(string $name): array\n    {\n        foreach (self::EXTENSIONS as $extension) {\n            if (\\in_array($name, $extension['filters'], true)) {\n                return [$extension['class_name'], $extension['package']];\n            }\n        }\n\n        return [];\n    }\n\n    public static function getFunction(string $name): array\n    {\n        foreach (self::EXTENSIONS as $extension) {\n            if (\\in_array($name, $extension['functions'], true)) {\n                return [$extension['class_name'], $extension['package']];\n            }\n        }\n\n        return [];\n    }\n\n    public static function getTag(string $name): array\n    {\n        foreach (self::EXTENSIONS as $extension) {\n            if (\\in_array($name, $extension['tags'], true)) {\n                return [$extension['class_name'], $extension['package']];\n            }\n        }\n\n        return [];\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/LICENSE",
    "content": "Copyright (c) 2019-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "extra/twig-extra-bundle/LeagueCommonMarkConverterFactory.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle;\n\nuse League\\CommonMark\\CommonMarkConverter;\nuse League\\CommonMark\\Extension\\ExtensionInterface;\n\n/**\n * @internal\n */\nfinal class LeagueCommonMarkConverterFactory\n{\n    private $extensions;\n    private $config;\n\n    /**\n     * @param ExtensionInterface[] $extensions\n     */\n    public function __construct(iterable $extensions, array $config = [])\n    {\n        $this->extensions = $extensions;\n        $this->config = $config;\n    }\n\n    public function __invoke(): CommonMarkConverter\n    {\n        $converter = new CommonMarkConverter($this->config);\n\n        foreach ($this->extensions as $extension) {\n            $converter->getEnvironment()->addExtension($extension);\n        }\n\n        return $converter;\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/MissingExtensionSuggestor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle;\n\nuse Twig\\Error\\SyntaxError;\n\nfinal class MissingExtensionSuggestor\n{\n    public function suggestFilter(string $name): bool\n    {\n        if ($filter = Extensions::getFilter($name)) {\n            throw new SyntaxError(\\sprintf('The \"%s\" filter is part of the %s, which is not installed/enabled; try running \"composer require %s\".', $name, $filter[0], $filter[1]));\n        }\n\n        return false;\n    }\n\n    public function suggestFunction(string $name): bool\n    {\n        if ($function = Extensions::getFunction($name)) {\n            throw new SyntaxError(\\sprintf('The \"%s\" function is part of the %s, which is not installed/enabled; try running \"composer require %s\".', $name, $function[0], $function[1]));\n        }\n\n        return false;\n    }\n\n    public function suggestTag(string $name): bool\n    {\n        if ($function = Extensions::getTag($name)) {\n            throw new SyntaxError(\\sprintf('The \"%s\" tag is part of the %s, which is not installed/enabled; try running \"composer require %s\".', $name, $function[0], $function[1]));\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/README.md",
    "content": "Twig Extra Bundle\n=================\n\nThis package is a Symfony bundle that allows to use all \"extra\" extensions\nwithout any configuration.\n\nInstallation\n------------\n\n    composer require twig/extra-bundle\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/cache.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse Psr\\Cache\\CacheItemPoolInterface;\nuse Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter;\nuse Symfony\\Contracts\\Cache\\CacheInterface;\nuse Symfony\\Contracts\\Cache\\TagAwareCacheInterface;\nuse Twig\\Extra\\Cache\\CacheExtension;\nuse Twig\\Extra\\Cache\\CacheRuntime;\n\nreturn static function (ContainerConfigurator $container) {\n    $service = \\function_exists('Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\service') ? 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\service' : 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ref';\n    $container->services()\n        ->set('twig.extension.cache', CacheExtension::class)\n            ->tag('twig.extension')\n\n        ->set('twig.runtime.cache', CacheRuntime::class)\n            ->args([\n                $service('twig.cache'),\n            ])\n            ->tag('twig.runtime')\n\n        ->set('twig.cache', TagAwareAdapter::class)\n            ->args([\n                $service('.twig.cache.inner'),\n            ])\n\n        ->set('.twig.cache.inner')\n            ->parent('cache.app')\n            ->tag('cache.pool', ['name' => 'twig.cache'])\n\n        ->alias(TagAwareCacheInterface::class.' $twigCache', 'twig.cache')\n        ->alias(CacheInterface::class.' $twigCache', '.twig.cache.inner')\n        ->alias(CacheItemPoolInterface::class.' $twigCache', '.twig.cache.inner')\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/cssinliner.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse Twig\\Extra\\CssInliner\\CssInlinerExtension;\n\nreturn static function (ContainerConfigurator $container) {\n    $container->services()\n        ->set('twig.extension.cssinliner', CssInlinerExtension::class)\n            ->tag('twig.extension')\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/html.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse Twig\\Extra\\Html\\HtmlExtension;\n\nreturn static function (ContainerConfigurator $container) {\n    $container->services()\n        ->set('twig.extension.html', HtmlExtension::class)\n            ->tag('twig.extension')\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/inky.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse Twig\\Extra\\Inky\\InkyExtension;\n\nreturn static function (ContainerConfigurator $container) {\n    $container->services()\n        ->set('twig.extension.inky', InkyExtension::class)\n            ->tag('twig.extension')\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/intl.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse Twig\\Extra\\Intl\\IntlExtension;\n\nreturn static function (ContainerConfigurator $container) {\n    $container->services()\n        ->set('twig.extension.intl', IntlExtension::class)\n            ->tag('twig.extension')\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/markdown.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse Twig\\Extra\\Markdown\\DefaultMarkdown;\nuse Twig\\Extra\\Markdown\\MarkdownExtension;\nuse Twig\\Extra\\Markdown\\MarkdownRuntime;\n\nreturn static function (ContainerConfigurator $container) {\n    $service = \\function_exists('Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\service') ? 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\service' : 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ref';\n    $container->services()\n        ->set('twig.extension.markdown', MarkdownExtension::class)\n            ->tag('twig.extension')\n\n        ->set('twig.runtime.markdown', MarkdownRuntime::class)\n            ->args([\n                $service('twig.markdown.default'),\n            ])\n            ->tag('twig.runtime')\n\n        ->set('twig.markdown.default', DefaultMarkdown::class)\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/markdown_league.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse League\\CommonMark\\CommonMarkConverter;\nuse Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument;\nuse Twig\\Extra\\Markdown\\LeagueMarkdown;\nuse Twig\\Extra\\TwigExtraBundle\\LeagueCommonMarkConverterFactory;\n\nreturn static function (ContainerConfigurator $container) {\n    $service = \\function_exists('Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\service') ? 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\service' : 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ref';\n    $container->services()\n        ->set('twig.markdown.league_common_mark_converter_factory', LeagueCommonMarkConverterFactory::class)\n        ->args([new TaggedIteratorArgument('twig.markdown.league_extension')])\n\n        ->set('twig.markdown.league_common_mark_converter', CommonMarkConverter::class)\n        ->factory($service('twig.markdown.league_common_mark_converter_factory'))\n\n        ->set('twig.markdown.default', LeagueMarkdown::class)\n        ->args([$service('twig.markdown.league_common_mark_converter')])\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/string.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse Twig\\Extra\\String\\StringExtension;\n\nreturn static function (ContainerConfigurator $container) {\n    $container->services()\n        ->set('twig.extension.string', StringExtension::class)\n            ->tag('twig.extension')\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Resources/config/suggestor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\nuse Twig\\Extra\\TwigExtraBundle\\MissingExtensionSuggestor;\n\nreturn static function (ContainerConfigurator $container) {\n    $container->services()\n        ->set('twig.missing_extension_suggestor', MissingExtensionSuggestor::class)\n    ;\n};\n"
  },
  {
    "path": "extra/twig-extra-bundle/Tests/DependencyInjection/TwigExtraExtensionTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle\\Tests\\DependencyInjection;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag;\nuse Twig\\Extra\\Markdown\\LeagueMarkdown;\nuse Twig\\Extra\\TwigExtraBundle\\DependencyInjection\\TwigExtraExtension;\nuse Twig\\Extra\\TwigExtraBundle\\Extensions;\n\nclass TwigExtraExtensionTest extends TestCase\n{\n    public function testDefaultConfiguration()\n    {\n        $container = new ContainerBuilder(new ParameterBag([\n            'kernel.debug' => false,\n        ]));\n        $container->registerExtension(new TwigExtraExtension());\n        $container->loadFromExtension('twig_extra', [\n            'commonmark' => [\n                'extra_key' => true,\n                'renderer' => [\n                    'block_separator' => \"\\n\",\n                    'inner_separator' => \"\\n\",\n                    'soft_break' => \"\\n\",\n                ],\n                'commonmark' => [\n                    'enable_em' => true,\n                    'enable_strong' => true,\n                    'use_asterisk' => true,\n                    'use_underscore' => true,\n                    'unordered_list_markers' => ['-', '*', '+'],\n                ],\n                'html_input' => 'escape',\n                'allow_unsafe_links' => false,\n                'max_nesting_level' => \\PHP_INT_MAX,\n                'slug_normalizer' => [\n                    'max_length' => 255,\n                ],\n            ],\n        ]);\n        $container->getCompilerPassConfig()->setOptimizationPasses([]);\n        $container->getCompilerPassConfig()->setRemovingPasses([]);\n        $container->getCompilerPassConfig()->setAfterRemovingPasses([]);\n        $container->compile();\n\n        foreach (Extensions::getClasses() as $name => $class) {\n            $this->assertEquals($class, $container->getDefinition('twig.extension.'.$name)->getClass());\n        }\n\n        $this->assertSame(LeagueMarkdown::class, $container->getDefinition('twig.markdown.default')->getClass());\n\n        $commonmarkConverterFactory = $container->getDefinition('twig.markdown.league_common_mark_converter')->getFactory();\n\n        $this->assertSame('twig.markdown.league_common_mark_converter_factory', (string) $commonmarkConverterFactory[0]);\n        $this->assertSame('__invoke', $commonmarkConverterFactory[1]);\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/Tests/Fixture/Kernel.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle\\Tests\\Fixture;\n\nuse League\\CommonMark\\Extension\\Strikethrough\\StrikethroughExtension;\nuse Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle;\nuse Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait;\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\NotificationAssertionsTrait;\nuse Symfony\\Bundle\\TwigBundle\\TwigBundle;\nuse Symfony\\Component\\Config\\Loader\\LoaderInterface;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\HttpKernel\\Kernel as BaseKernel;\nuse Twig\\Extra\\TwigExtraBundle\\TwigExtraBundle;\n\nclass Kernel extends BaseKernel\n{\n    use MicroKernelTrait;\n\n    public function registerBundles(): iterable\n    {\n        yield new FrameworkBundle();\n        yield new TwigBundle();\n        yield new TwigExtraBundle();\n    }\n\n    protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader): void\n    {\n        $config = [\n            'secret' => 'S3CRET',\n            'test' => true,\n            'router' => ['utf8' => true],\n            'http_method_override' => false,\n            'php_errors' => [\n                'log' => true,\n            ],\n        ];\n\n        // the \"handle_all_throwables\" option was introduced in FrameworkBundle 6.2 (and so was the NotificationAssertionsTrait)\n        if (trait_exists(NotificationAssertionsTrait::class)) {\n            $config['handle_all_throwables'] = true;\n        }\n\n        $c->loadFromExtension('framework', $config);\n        $c->loadFromExtension('twig', [\n            'default_path' => __DIR__.'/views',\n        ]);\n\n        $c->register(StrikethroughExtension::class)->addTag('twig.markdown.league_extension');\n    }\n\n    protected function configureRoutes($routes): void\n    {\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/Tests/Fixture/views/markdown_to_html.html.twig",
    "content": "{% apply markdown_to_html %}\n# Hello ~~World~~\n{% endapply %}\n"
  },
  {
    "path": "extra/twig-extra-bundle/Tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle\\Tests;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase;\n\nclass IntegrationTest extends KernelTestCase\n{\n    public function testCommonMarkRendering()\n    {\n        self::bootKernel();\n\n        $container = method_exists(self::class, 'getContainer') ? self::getContainer() : self::$container;\n        $rendered = $container->get('twig')->render('markdown_to_html.html.twig');\n\n        $this->assertStringContainsString('<h1>Hello <del>World</del></h1>', $rendered);\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/TwigExtraBundle.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extra\\TwigExtraBundle;\n\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\HttpKernel\\Bundle\\Bundle;\nuse Symfony\\Component\\HttpKernel\\KernelInterface;\nuse Twig\\Extra\\TwigExtraBundle\\DependencyInjection\\Compiler\\MissingExtensionSuggestorPass;\n\nif (method_exists(KernelInterface::class, 'getShareDir')) {\n    class TwigExtraBundle extends Bundle\n    {\n        public function build(ContainerBuilder $container): void\n        {\n            parent::build($container);\n\n            $container->addCompilerPass(new MissingExtensionSuggestorPass());\n        }\n    }\n} else {\n    class TwigExtraBundle extends Bundle\n    {\n        /** @return void */\n        public function build(ContainerBuilder $container)\n        {\n            parent::build($container);\n\n            $container->addCompilerPass(new MissingExtensionSuggestorPass());\n        }\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/composer.json",
    "content": "{\n    \"name\": \"twig/extra-bundle\",\n    \"type\": \"symfony-bundle\",\n    \"description\": \"A Symfony bundle for extra Twig extensions\",\n    \"keywords\": [\"twig\", \"extra\", \"bundle\"],\n    \"homepage\": \"https://twig.symfony.com\",\n    \"license\": \"MIT\",\n    \"minimum-stability\": \"dev\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\",\n            \"homepage\": \"http://fabien.potencier.org\",\n            \"role\": \"Lead Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1.0\",\n        \"symfony/framework-bundle\": \"^5.4|^6.4|^7.0|^8.0\",\n        \"symfony/twig-bundle\": \"^5.4|^6.4|^7.0|^8.0\",\n        \"twig/twig\": \"^3.2|^4.0\"\n    },\n    \"require-dev\": {\n        \"league/commonmark\": \"^2.7\",\n        \"symfony/phpunit-bridge\": \"^6.4|^7.0\",\n        \"twig/cache-extra\": \"^3.0\",\n        \"twig/cssinliner-extra\": \"^3.0\",\n        \"twig/html-extra\": \"^3.0\",\n        \"twig/inky-extra\": \"^3.0\",\n        \"twig/intl-extra\": \"^3.0\",\n        \"twig/markdown-extra\": \"^3.0\",\n        \"twig/string-extra\": \"^3.0\"\n    },\n    \"autoload\": {\n        \"psr-4\" : { \"Twig\\\\Extra\\\\TwigExtraBundle\\\\\" : \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    }\n}\n"
  },
  {
    "path": "extra/twig-extra-bundle/phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\" backupGlobals=\"false\" colors=\"true\" bootstrap=\"vendor/autoload.php\" failOnRisky=\"true\" failOnWarning=\"true\">\n  <coverage>\n    <include>\n      <directory>./</directory>\n    </include>\n    <exclude>\n      <directory>./Tests</directory>\n      <directory>./vendor</directory>\n    </exclude>\n  </coverage>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n    <server name=\"KERNEL_CLASS\" value=\"Twig\\Extra\\TwigExtraBundle\\Tests\\Fixture\\Kernel\"/>\n    <server name=\"SYMFONY_DEPRECATIONS_HELPER\" value=\"max[self]=0&amp;max[direct]=0\"/>\n  </php>\n  <testsuites>\n    <testsuite name=\"Twig Extra bundle Test Suite\">\n      <directory>./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "phpstan-baseline.neon",
    "content": "parameters:\n\tignoreErrors:\n\t\t- # The method is dynamically generated by the CheckSecurityNode\n\t\t\tmessage: '#^Call to an undefined method Twig\\\\Template\\:\\:checkSecurity\\(\\)\\.$#'\n\t\t\tidentifier: method.notFound\n\t\t\tcount: 1\n\t\t\tpath: src/Extension/CoreExtension.php\n\n\t\t- # 2 parameters will be required\n\t\t\tmessage: '#^Method Twig\\\\Node\\\\IncludeNode\\:\\:addGetTemplate\\(\\) invoked with 2 parameters, 1 required\\.$#'\n\t\t\tidentifier: arguments.count\n\t\t\tcount: 1\n\t\t\tpath: src/Node/IncludeNode.php\n\n\t\t- # int|string will be supported in 4.x\n\t\t\tmessage: '#^PHPDoc tag @param for parameter $name with type int|string is not subtype of native type string\\.$#'\n\t\t\tidentifier: parameter.phpDocType\n\t\t\tcount: 5\n\t\t\tpath: src/Node/Node.php\n\n\t\t- # Adding 0 to the string representation of a number is valid and what we want here\n\t\t\tmessage: '#^Binary operation \"\\+\" between 0 and string results in an error\\.$#'\n\t\t\tidentifier: binaryOp.invalid\n\t\t\tcount: 1\n\t\t\tpath: src/Lexer.php\n"
  },
  {
    "path": "phpstan.neon.dist",
    "content": "includes:\n    - phpstan-baseline.neon\n\nparameters:\n    level: 3\n    paths:\n        - src\n    excludePaths:\n        - src/Test\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" backupGlobals=\"false\" backupStaticAttributes=\"false\" colors=\"true\" convertErrorsToExceptions=\"true\" convertNoticesToExceptions=\"true\" convertWarningsToExceptions=\"true\" processIsolation=\"false\" stopOnFailure=\"false\" bootstrap=\"vendor/autoload.php\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.6/phpunit.xsd\">\n  <coverage/>\n  <testsuites>\n    <testsuite name=\"Twig Test Suite\">\n      <directory>./tests/</directory>\n    </testsuite>\n  </testsuites>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\" />\n  </php>\n  <listeners>\n    <listener class=\"Symfony\\Bridge\\PhpUnit\\SymfonyTestsListener\" />\n  </listeners>\n</phpunit>\n"
  },
  {
    "path": "splitsh.json",
    "content": "{\n    \"subtrees\": {\n        \"twig-extra-bundle\": \"extra/twig-extra-bundle\",\n        \"cache-extra\": \"extra/cache-extra\",\n        \"cssinliner-extra\": \"extra/cssinliner-extra\",\n        \"html-extra\": \"extra/html-extra\",\n        \"inky-extra\": \"extra/inky-extra\",\n        \"intl-extra\": \"extra/intl-extra\",\n        \"markdown-extra\": \"extra/markdown-extra\",\n        \"string-extra\": \"extra/string-extra\"\n    },\n    \"defaults\": {\n        \"git_constraint\": \"<1.8.2\"\n    }\n}\n"
  },
  {
    "path": "src/AbstractTwigCallable.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nabstract class AbstractTwigCallable implements TwigCallableInterface\n{\n    protected $options;\n\n    private $name;\n    private $dynamicName;\n    private $callable;\n    private $arguments;\n\n    public function __construct(string $name, $callable = null, array $options = [])\n    {\n        $this->name = $this->dynamicName = $name;\n        $this->callable = $callable;\n        $this->arguments = [];\n        $this->options = array_merge([\n            'needs_environment' => false,\n            'needs_context' => false,\n            'needs_charset' => false,\n            'is_variadic' => false,\n            'deprecation_info' => null,\n            'deprecated' => false,\n            'deprecating_package' => '',\n            'alternative' => null,\n        ], $options);\n\n        if ($this->options['deprecation_info'] && !$this->options['deprecation_info'] instanceof DeprecatedCallableInfo) {\n            throw new \\LogicException(\\sprintf('The \"deprecation_info\" option must be an instance of \"%s\".', DeprecatedCallableInfo::class));\n        }\n\n        if ($this->options['deprecated']) {\n            if ($this->options['deprecation_info']) {\n                throw new \\LogicException('When setting the \"deprecation_info\" option, you need to remove the obsolete deprecated options.');\n            }\n\n            trigger_deprecation('twig/twig', '3.15', 'Using the \"deprecated\", \"deprecating_package\", and \"alternative\" options is deprecated, pass a \"deprecation_info\" one instead.');\n\n            $this->options['deprecation_info'] = new DeprecatedCallableInfo(\n                $this->options['deprecating_package'],\n                $this->options['deprecated'],\n                null,\n                $this->options['alternative'],\n            );\n        }\n\n        if ($this->options['deprecation_info']) {\n            $this->options['deprecation_info']->setName($name);\n            $this->options['deprecation_info']->setType($this->getType());\n        }\n    }\n\n    public function __toString(): string\n    {\n        return \\sprintf('%s(%s)', static::class, $this->name);\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function getDynamicName(): string\n    {\n        return $this->dynamicName;\n    }\n\n    /**\n     * @return callable|array{class-string, string}|null\n     */\n    public function getCallable()\n    {\n        return $this->callable;\n    }\n\n    public function getNodeClass(): string\n    {\n        return $this->options['node_class'];\n    }\n\n    public function needsCharset(): bool\n    {\n        return $this->options['needs_charset'];\n    }\n\n    public function needsEnvironment(): bool\n    {\n        return $this->options['needs_environment'];\n    }\n\n    public function needsContext(): bool\n    {\n        return $this->options['needs_context'];\n    }\n\n    /**\n     * @return static\n     */\n    public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self\n    {\n        $new = clone $this;\n        $new->name = $name;\n        $new->dynamicName = $dynamicName;\n        $new->arguments = $arguments;\n\n        return $new;\n    }\n\n    /**\n     * @deprecated since Twig 3.12, use withDynamicArguments() instead\n     */\n    public function setArguments(array $arguments): void\n    {\n        trigger_deprecation('twig/twig', '3.12', 'The \"%s::setArguments()\" method is deprecated, use \"%s::withDynamicArguments()\" instead.', static::class, static::class);\n\n        $this->arguments = $arguments;\n    }\n\n    public function getArguments(): array\n    {\n        return $this->arguments;\n    }\n\n    public function isVariadic(): bool\n    {\n        return $this->options['is_variadic'];\n    }\n\n    public function isDeprecated(): bool\n    {\n        return (bool) $this->options['deprecation_info'];\n    }\n\n    public function triggerDeprecation(?string $file = null, ?int $line = null): void\n    {\n        $this->options['deprecation_info']->triggerDeprecation($file, $line);\n    }\n\n    /**\n     * @deprecated since Twig 3.15\n     */\n    public function getDeprecatingPackage(): string\n    {\n        trigger_deprecation('twig/twig', '3.15', 'The \"%s\" method is deprecated, use \"%s::triggerDeprecation()\" instead.', __METHOD__, static::class);\n\n        return $this->options['deprecating_package'];\n    }\n\n    /**\n     * @deprecated since Twig 3.15\n     */\n    public function getDeprecatedVersion(): string\n    {\n        trigger_deprecation('twig/twig', '3.15', 'The \"%s\" method is deprecated, use \"%s::triggerDeprecation()\" instead.', __METHOD__, static::class);\n\n        return \\is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];\n    }\n\n    /**\n     * @deprecated since Twig 3.15\n     */\n    public function getAlternative(): ?string\n    {\n        trigger_deprecation('twig/twig', '3.15', 'The \"%s\" method is deprecated, use \"%s::triggerDeprecation()\" instead.', __METHOD__, static::class);\n\n        return $this->options['alternative'];\n    }\n\n    public function getMinimalNumberOfRequiredArguments(): int\n    {\n        return ($this->options['needs_charset'] ? 1 : 0) + ($this->options['needs_environment'] ? 1 : 0) + ($this->options['needs_context'] ? 1 : 0) + \\count($this->arguments);\n    }\n}\n"
  },
  {
    "path": "src/Attribute/AsTwigFilter.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Attribute;\n\nuse Twig\\DeprecatedCallableInfo;\nuse Twig\\TwigFilter;\n\n/**\n * Registers a method as template filter.\n *\n * If the first argument of the method has Twig\\Environment type-hint, the filter will receive the current environment.\n * Additional arguments of the method come from the filter call.\n *\n *     #[AsTwigFilter(name: 'foo')]\n *     function fooFilter(Environment $env, $string, $arg1 = null, ...) { ... }\n *\n *    {{ 'string'|foo(arg1) }}\n *\n * @see TwigFilter\n */\n#[\\Attribute(\\Attribute::TARGET_METHOD | \\Attribute::IS_REPEATABLE)]\nfinal class AsTwigFilter\n{\n    /**\n     * @param non-empty-string            $name             The name of the filter in Twig\n     * @param bool|null                   $needsCharset     Whether the filter needs the charset passed as the first argument\n     * @param bool|null                   $needsEnvironment Whether the filter needs the environment passed as the first argument, or after the charset\n     * @param bool|null                   $needsContext     Whether the filter needs the context array passed as the first argument, or after the charset and the environment\n     * @param string[]|null               $isSafe           List of formats in which you want the raw output to be printed unescaped\n     * @param string|array|null           $isSafeCallback   Function called at compilation time to determine if the filter is safe\n     * @param string|null                 $preEscape        Some filters may need to work on input that is already escaped or safe\n     * @param string[]|null               $preservesSafety  Preserves the safety of the value that the filter is applied to\n     * @param DeprecatedCallableInfo|null $deprecationInfo  Information about the deprecation\n     */\n    public function __construct(\n        public string $name,\n        public ?bool $needsCharset = null,\n        public ?bool $needsEnvironment = null,\n        public ?bool $needsContext = null,\n        public ?array $isSafe = null,\n        public string|array|null $isSafeCallback = null,\n        public ?string $preEscape = null,\n        public ?array $preservesSafety = null,\n        public ?DeprecatedCallableInfo $deprecationInfo = null,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Attribute/AsTwigFunction.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Attribute;\n\nuse Twig\\DeprecatedCallableInfo;\nuse Twig\\TwigFunction;\n\n/**\n * Registers a method as template function.\n *\n * If the first argument of the method has Twig\\Environment type-hint, the function will receive the current environment.\n * Additional arguments of the method come from the function call.\n *\n *     #[AsTwigFunction(name: 'foo')]\n *     function fooFunction(Environment $env, string $string, $arg1 = null, ...) { ... }\n *\n *     {{ foo('string', arg1) }}\n *\n * @see TwigFunction\n */\n#[\\Attribute(\\Attribute::TARGET_METHOD | \\Attribute::IS_REPEATABLE)]\nfinal class AsTwigFunction\n{\n    /**\n     * @param non-empty-string            $name             The name of the function in Twig\n     * @param bool|null                   $needsCharset     Whether the function needs the charset passed as the first argument\n     * @param bool|null                   $needsEnvironment Whether the function needs the environment passed as the first argument, or after the charset\n     * @param bool|null                   $needsContext     Whether the function needs the context array passed as the first argument, or after the charset and the environment\n     * @param string[]|null               $isSafe           List of formats in which you want the raw output to be printed unescaped\n     * @param string|array|null           $isSafeCallback   Function called at compilation time to determine if the function is safe\n     * @param DeprecatedCallableInfo|null $deprecationInfo  Information about the deprecation\n     */\n    public function __construct(\n        public string $name,\n        public ?bool $needsCharset = null,\n        public ?bool $needsEnvironment = null,\n        public ?bool $needsContext = null,\n        public ?array $isSafe = null,\n        public string|array|null $isSafeCallback = null,\n        public ?DeprecatedCallableInfo $deprecationInfo = null,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Attribute/AsTwigTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Attribute;\n\nuse Twig\\DeprecatedCallableInfo;\nuse Twig\\TwigTest;\n\n/**\n * Registers a method as template test.\n *\n * The first argument is the value to test and the other arguments are the\n * arguments passed to the test in the template.\n *\n *     #[AsTwigTest(name: 'foo')]\n *     public function fooTest($value, $arg1 = null) { ... }\n *\n *     {% if value is foo(arg1) %}\n *\n * @see TwigTest\n */\n#[\\Attribute(\\Attribute::TARGET_METHOD | \\Attribute::IS_REPEATABLE)]\nfinal class AsTwigTest\n{\n    /**\n     * @param non-empty-string            $name             The name of the test in Twig\n     * @param bool|null                   $needsCharset     Whether the test needs the charset passed as the first argument\n     * @param bool|null                   $needsEnvironment Whether the test needs the environment passed as the first argument, or after the charset\n     * @param bool|null                   $needsContext     Whether the test needs the context array passed as the first argument, or after the charset and the environment\n     * @param DeprecatedCallableInfo|null $deprecationInfo  Information about the deprecation\n     */\n    public function __construct(\n        public string $name,\n        public ?bool $needsCharset = null,\n        public ?bool $needsEnvironment = null,\n        public ?bool $needsContext = null,\n        public ?DeprecatedCallableInfo $deprecationInfo = null,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Attribute/FirstClassTwigCallableReady.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Attribute;\n\n/**\n * Marks nodes that are ready to accept a TwigCallable instead of its name.\n */\n#[\\Attribute(\\Attribute::TARGET_METHOD)]\nfinal class FirstClassTwigCallableReady\n{\n}\n"
  },
  {
    "path": "src/Attribute/YieldReady.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Attribute;\n\n/**\n * Marks nodes that are ready for using \"yield\" instead of \"echo\" or \"print()\" for rendering.\n */\n#[\\Attribute(\\Attribute::TARGET_CLASS)]\nfinal class YieldReady\n{\n}\n"
  },
  {
    "path": "src/Cache/CacheInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Cache;\n\n/**\n * Interface implemented by cache classes.\n *\n * It is highly recommended to always store templates on the filesystem to\n * benefit from the PHP opcode cache. This interface is mostly useful if you\n * need to implement a custom strategy for storing templates on the filesystem.\n *\n * @author Andrew Tch <andrew@noop.lv>\n */\ninterface CacheInterface\n{\n    /**\n     * Generates a cache key for the given template class name.\n     */\n    public function generateKey(string $name, string $className): string;\n\n    /**\n     * Writes the compiled template to cache.\n     *\n     * @param string $content The template representation as a PHP class\n     */\n    public function write(string $key, string $content): void;\n\n    /**\n     * Loads a template from the cache.\n     */\n    public function load(string $key): void;\n\n    /**\n     * Returns the modification timestamp of a key.\n     */\n    public function getTimestamp(string $key): int;\n}\n"
  },
  {
    "path": "src/Cache/ChainCache.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Cache;\n\n/**\n * Chains several caches together.\n *\n * Cached items are fetched from the first cache having them in its data store.\n * They are saved and deleted in all adapters at once.\n *\n * @author Quentin Devos <quentin@devos.pm>\n */\nfinal class ChainCache implements CacheInterface, RemovableCacheInterface\n{\n    /**\n     * @param iterable<CacheInterface> $caches The ordered list of caches used to store and fetch cached items\n     */\n    public function __construct(\n        private iterable $caches,\n    ) {\n    }\n\n    public function generateKey(string $name, string $className): string\n    {\n        return $className.'#'.$name;\n    }\n\n    public function write(string $key, string $content): void\n    {\n        $splitKey = $this->splitKey($key);\n\n        foreach ($this->caches as $cache) {\n            $cache->write($cache->generateKey(...$splitKey), $content);\n        }\n    }\n\n    public function load(string $key): void\n    {\n        [$name, $className] = $this->splitKey($key);\n\n        foreach ($this->caches as $cache) {\n            $cache->load($cache->generateKey($name, $className));\n\n            if (class_exists($className, false)) {\n                break;\n            }\n        }\n    }\n\n    public function getTimestamp(string $key): int\n    {\n        $splitKey = $this->splitKey($key);\n\n        foreach ($this->caches as $cache) {\n            if (0 < $timestamp = $cache->getTimestamp($cache->generateKey(...$splitKey))) {\n                return $timestamp;\n            }\n        }\n\n        return 0;\n    }\n\n    public function remove(string $name, string $cls): void\n    {\n        foreach ($this->caches as $cache) {\n            if ($cache instanceof RemovableCacheInterface) {\n                $cache->remove($name, $cls);\n            }\n        }\n    }\n\n    /**\n     * @return string[]\n     */\n    private function splitKey(string $key): array\n    {\n        return array_reverse(explode('#', $key, 2));\n    }\n}\n"
  },
  {
    "path": "src/Cache/FilesystemCache.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Cache;\n\n/**\n * Implements a cache on the filesystem.\n *\n * @author Andrew Tch <andrew@noop.lv>\n */\nclass FilesystemCache implements CacheInterface, RemovableCacheInterface\n{\n    public const FORCE_BYTECODE_INVALIDATION = 1;\n\n    private $directory;\n    private $options;\n\n    public function __construct(string $directory, int $options = 0)\n    {\n        $this->directory = rtrim($directory, '\\/').'/';\n        $this->options = $options;\n    }\n\n    public function generateKey(string $name, string $className): string\n    {\n        $hash = hash(\\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className);\n\n        return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php';\n    }\n\n    public function load(string $key): void\n    {\n        if (is_file($key)) {\n            @include_once $key;\n        }\n    }\n\n    public function write(string $key, string $content): void\n    {\n        $dir = \\dirname($key);\n        if (!is_dir($dir)) {\n            if (false === @mkdir($dir, 0777, true)) {\n                clearstatcache(true, $dir);\n                if (!is_dir($dir)) {\n                    throw new \\RuntimeException(\\sprintf('Unable to create the cache directory (%s).', $dir));\n                }\n            }\n        } elseif (!is_writable($dir)) {\n            throw new \\RuntimeException(\\sprintf('Unable to write in the cache directory (%s).', $dir));\n        }\n\n        $tmpFile = tempnam($dir, basename($key));\n        if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) {\n            @chmod($key, 0666 & ~umask());\n\n            if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) {\n                // Compile cached file into bytecode cache\n                if (\\function_exists('opcache_invalidate') && filter_var(\\ini_get('opcache.enable'), \\FILTER_VALIDATE_BOOLEAN)) {\n                    @opcache_invalidate($key, true);\n                } elseif (\\function_exists('apc_compile_file')) {\n                    apc_compile_file($key);\n                }\n            }\n\n            return;\n        }\n\n        throw new \\RuntimeException(\\sprintf('Failed to write cache file \"%s\".', $key));\n    }\n\n    public function remove(string $name, string $cls): void\n    {\n        $key = $this->generateKey($name, $cls);\n        if (!@unlink($key) && file_exists($key)) {\n            throw new \\RuntimeException(\\sprintf('Failed to delete cache file \"%s\".', $key));\n        }\n    }\n\n    public function getTimestamp(string $key): int\n    {\n        if (!is_file($key)) {\n            return 0;\n        }\n\n        return (int) @filemtime($key);\n    }\n}\n"
  },
  {
    "path": "src/Cache/NullCache.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Cache;\n\n/**\n * Implements a no-cache strategy.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class NullCache implements CacheInterface, RemovableCacheInterface\n{\n    public function generateKey(string $name, string $className): string\n    {\n        return '';\n    }\n\n    public function write(string $key, string $content): void\n    {\n    }\n\n    public function load(string $key): void\n    {\n    }\n\n    public function getTimestamp(string $key): int\n    {\n        return 0;\n    }\n\n    public function remove(string $name, string $cls): void\n    {\n    }\n}\n"
  },
  {
    "path": "src/Cache/ReadOnlyFilesystemCache.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Cache;\n\n/**\n * Implements a cache on the filesystem that can only be read, not written to.\n *\n * @author Quentin Devos <quentin@devos.pm>\n */\nclass ReadOnlyFilesystemCache extends FilesystemCache\n{\n    public function write(string $key, string $content): void\n    {\n        // Do nothing with the content, it's a read-only filesystem.\n    }\n}\n"
  },
  {
    "path": "src/Cache/RemovableCacheInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Cache;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface RemovableCacheInterface\n{\n    public function remove(string $name, string $cls): void;\n}\n"
  },
  {
    "path": "src/Compiler.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Node\\Node;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass Compiler\n{\n    private $lastLine;\n    private $source;\n    private $indentation;\n    private $debugInfo = [];\n    private $sourceOffset;\n    private $sourceLine;\n    private $varNameSalt = 0;\n    private $didUseEcho = false;\n    private $didUseEchoStack = [];\n\n    public function __construct(\n        private Environment $env,\n    ) {\n    }\n\n    public function getEnvironment(): Environment\n    {\n        return $this->env;\n    }\n\n    public function getSource(): string\n    {\n        return $this->source;\n    }\n\n    /**\n     * @return $this\n     */\n    public function reset(int $indentation = 0)\n    {\n        $this->lastLine = null;\n        $this->source = '';\n        $this->debugInfo = [];\n        $this->sourceOffset = 0;\n        // source code starts at 1 (as we then increment it when we encounter new lines)\n        $this->sourceLine = 1;\n        $this->indentation = $indentation;\n        $this->varNameSalt = 0;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function compile(Node $node, int $indentation = 0)\n    {\n        $this->reset($indentation);\n        $this->didUseEchoStack[] = $this->didUseEcho;\n\n        try {\n            $this->didUseEcho = false;\n            $node->compile($this);\n\n            if ($this->didUseEcho) {\n                trigger_deprecation('twig/twig', '3.9', 'Using \"%s\" is deprecated, use \"yield\" instead in \"%s\", then flag the class with #[\\Twig\\Attribute\\YieldReady].', $this->didUseEcho, $node::class);\n            }\n\n            return $this;\n        } finally {\n            $this->didUseEcho = array_pop($this->didUseEchoStack);\n        }\n    }\n\n    /**\n     * @return $this\n     */\n    public function subcompile(Node $node, bool $raw = true)\n    {\n        if (!$raw) {\n            $this->source .= str_repeat(' ', $this->indentation * 4);\n        }\n\n        $this->didUseEchoStack[] = $this->didUseEcho;\n\n        try {\n            $this->didUseEcho = false;\n            $node->compile($this);\n\n            if ($this->didUseEcho) {\n                trigger_deprecation('twig/twig', '3.9', 'Using \"%s\" is deprecated, use \"yield\" instead in \"%s\", then flag the class with #[\\Twig\\Attribute\\YieldReady].', $this->didUseEcho, $node::class);\n            }\n\n            return $this;\n        } finally {\n            $this->didUseEcho = array_pop($this->didUseEchoStack);\n        }\n    }\n\n    /**\n     * Adds a raw string to the compiled code.\n     *\n     * @return $this\n     */\n    public function raw(string $string)\n    {\n        $this->checkForEcho($string);\n        $this->source .= $string;\n\n        return $this;\n    }\n\n    /**\n     * Writes a string to the compiled code by adding indentation.\n     *\n     * @return $this\n     */\n    public function write(...$strings)\n    {\n        foreach ($strings as $string) {\n            $this->checkForEcho($string);\n            $this->source .= str_repeat(' ', $this->indentation * 4).$string;\n        }\n\n        return $this;\n    }\n\n    /**\n     * Adds a quoted string to the compiled code.\n     *\n     * @return $this\n     */\n    public function string(string $value)\n    {\n        $this->source .= \\sprintf('\"%s\"', addcslashes($value, \"\\0\\t\\\"\\$\\\\\"));\n\n        return $this;\n    }\n\n    /**\n     * Returns a PHP representation of a given value.\n     *\n     * @return $this\n     */\n    public function repr($value)\n    {\n        if (\\is_int($value) || \\is_float($value)) {\n            if (false !== $locale = setlocale(\\LC_NUMERIC, '0')) {\n                setlocale(\\LC_NUMERIC, 'C');\n            }\n\n            $this->raw(var_export($value, true));\n\n            if (false !== $locale) {\n                setlocale(\\LC_NUMERIC, $locale);\n            }\n        } elseif (null === $value) {\n            $this->raw('null');\n        } elseif (\\is_bool($value)) {\n            $this->raw($value ? 'true' : 'false');\n        } elseif (\\is_array($value)) {\n            $this->raw('[');\n            $first = true;\n            foreach ($value as $key => $v) {\n                if (!$first) {\n                    $this->raw(', ');\n                }\n                $first = false;\n                $this->repr($key);\n                $this->raw(' => ');\n                $this->repr($v);\n            }\n            $this->raw(']');\n        } else {\n            $this->string($value);\n        }\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function addDebugInfo(Node $node)\n    {\n        if ($node->getTemplateLine() != $this->lastLine) {\n            $this->write(\\sprintf(\"// line %d\\n\", $node->getTemplateLine()));\n\n            $this->sourceLine += substr_count($this->source, \"\\n\", $this->sourceOffset);\n            $this->sourceOffset = \\strlen($this->source);\n            $this->debugInfo[$this->sourceLine] = $node->getTemplateLine();\n\n            $this->lastLine = $node->getTemplateLine();\n        }\n\n        return $this;\n    }\n\n    public function getDebugInfo(): array\n    {\n        ksort($this->debugInfo);\n\n        return $this->debugInfo;\n    }\n\n    /**\n     * @return $this\n     */\n    public function indent(int $step = 1)\n    {\n        $this->indentation += $step;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     *\n     * @throws \\LogicException When trying to outdent too much so the indentation would become negative\n     */\n    public function outdent(int $step = 1)\n    {\n        // can't outdent by more steps than the current indentation level\n        if ($this->indentation < $step) {\n            throw new \\LogicException('Unable to call outdent() as the indentation would become negative.');\n        }\n\n        $this->indentation -= $step;\n\n        return $this;\n    }\n\n    public function getVarName(): string\n    {\n        return \\sprintf('_v%d', $this->varNameSalt++);\n    }\n\n    private function checkForEcho(string $string): void\n    {\n        if ($this->didUseEcho) {\n            return;\n        }\n\n        $this->didUseEcho = preg_match('/^\\s*+(echo|print)\\b/', $string, $m) ? $m[1] : false;\n    }\n}\n"
  },
  {
    "path": "src/DeprecatedCallableInfo.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class DeprecatedCallableInfo\n{\n    private string $type;\n    private string $name;\n\n    public function __construct(\n        private string $package,\n        private string $version,\n        private ?string $altName = null,\n        private ?string $altPackage = null,\n        private ?string $altVersion = null,\n    ) {\n    }\n\n    public function setType(string $type): void\n    {\n        $this->type = $type;\n    }\n\n    public function setName(string $name): void\n    {\n        $this->name = $name;\n    }\n\n    public function triggerDeprecation(?string $file = null, ?int $line = null): void\n    {\n        $message = \\sprintf('Twig %s \"%s\" is deprecated', ucfirst($this->type), $this->name);\n\n        if ($this->altName) {\n            $message .= \\sprintf('; use \"%s\"', $this->altName);\n            if ($this->altPackage) {\n                $message .= \\sprintf(' from the \"%s\" package', $this->altPackage);\n            }\n            if ($this->altVersion) {\n                $message .= \\sprintf(' (available since version %s)', $this->altVersion);\n            }\n            $message .= ' instead';\n        }\n\n        if ($file) {\n            $message .= \\sprintf(' in %s', $file);\n            if ($line) {\n                $message .= \\sprintf(' at line %d', $line);\n            }\n        }\n\n        $message .= '.';\n\n        trigger_deprecation($this->package, $this->version, $message);\n    }\n}\n"
  },
  {
    "path": "src/Environment.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Cache\\CacheInterface;\nuse Twig\\Cache\\FilesystemCache;\nuse Twig\\Cache\\NullCache;\nuse Twig\\Cache\\RemovableCacheInterface;\nuse Twig\\Error\\Error;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\ExpressionParsers;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Extension\\EscaperExtension;\nuse Twig\\Extension\\ExtensionInterface;\nuse Twig\\Extension\\OptimizerExtension;\nuse Twig\\Extension\\YieldNotReadyExtension;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Loader\\ChainLoader;\nuse Twig\\Loader\\LoaderInterface;\nuse Twig\\Node\\ModuleNode;\nuse Twig\\Node\\Node;\nuse Twig\\NodeVisitor\\NodeVisitorInterface;\nuse Twig\\Runtime\\EscaperRuntime;\nuse Twig\\RuntimeLoader\\FactoryRuntimeLoader;\nuse Twig\\RuntimeLoader\\RuntimeLoaderInterface;\nuse Twig\\TokenParser\\TokenParserInterface;\n\n/**\n * Stores the Twig configuration and renders templates.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass Environment\n{\n    public const VERSION = '3.24.1-DEV';\n    public const VERSION_ID = 32401;\n    public const MAJOR_VERSION = 3;\n    public const MINOR_VERSION = 24;\n    public const RELEASE_VERSION = 1;\n    public const EXTRA_VERSION = 'DEV';\n\n    private $charset;\n    private $loader;\n    private $debug;\n    private $autoReload;\n    private $cache;\n    private $lexer;\n    private $parser;\n    private $compiler;\n    /** @var array<string, mixed> */\n    private $globals = [];\n    private $resolvedGlobals;\n    private $loadedTemplates;\n    private $strictVariables;\n    private $originalCache;\n    private $extensionSet;\n    private $runtimeLoaders = [];\n    private $runtimes = [];\n    private $optionsHash;\n    /** @var bool */\n    private $useYield;\n    private $defaultRuntimeLoader;\n    private array $hotCache = [];\n\n    /**\n     * Constructor.\n     *\n     * Available options:\n     *\n     *  * debug: When set to true, it automatically set \"auto_reload\" to true as\n     *           well (default to false).\n     *\n     *  * charset: The charset used by the templates (default to UTF-8).\n     *\n     *  * cache: An absolute path where to store the compiled templates,\n     *           a \\Twig\\Cache\\CacheInterface implementation,\n     *           or false to disable compilation cache (default).\n     *\n     *  * auto_reload: Whether to reload the template if the original source changed.\n     *                 If you don't provide the auto_reload option, it will be\n     *                 determined automatically based on the debug value.\n     *\n     *  * strict_variables: Whether to ignore invalid variables in templates\n     *                      (default to false).\n     *\n     *  * autoescape: Whether to enable auto-escaping (default to html):\n     *                  * false: disable auto-escaping\n     *                  * html, js: set the autoescaping to one of the supported strategies\n     *                  * name: set the autoescaping strategy based on the template name extension\n     *                  * PHP callback: a PHP callback that returns an escaping strategy based on the template \"name\"\n     *\n     *  * optimizations: A flag that indicates which optimizations to apply\n     *                   (default to -1 which means that all optimizations are enabled;\n     *                   set it to 0 to disable).\n     *\n     *  * use_yield: true: forces templates to exclusively use \"yield\" instead of \"echo\" (all extensions must be yield ready)\n     *               false (default): allows templates to use a mix of \"yield\" and \"echo\" calls to allow for a progressive migration\n     *               Switch to \"true\" when possible as this will be the only supported mode in Twig 4.0\n     */\n    public function __construct(LoaderInterface $loader, array $options = [])\n    {\n        $this->setLoader($loader);\n\n        $options = array_merge([\n            'debug' => false,\n            'charset' => 'UTF-8',\n            'strict_variables' => false,\n            'autoescape' => 'html',\n            'cache' => false,\n            'auto_reload' => null,\n            'optimizations' => -1,\n            'use_yield' => false,\n        ], $options);\n\n        $this->useYield = (bool) $options['use_yield'];\n        $this->debug = (bool) $options['debug'];\n        $this->setCharset($options['charset'] ?? 'UTF-8');\n        $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];\n        $this->strictVariables = (bool) $options['strict_variables'];\n        $this->setCache($options['cache']);\n        $this->extensionSet = new ExtensionSet();\n        $this->defaultRuntimeLoader = new FactoryRuntimeLoader([\n            EscaperRuntime::class => function () { return new EscaperRuntime($this->charset); },\n        ]);\n\n        $this->addExtension(new CoreExtension());\n        $escaperExt = new EscaperExtension($options['autoescape']);\n        $escaperExt->setEnvironment($this, false);\n        $this->addExtension($escaperExt);\n        if (\\PHP_VERSION_ID >= 80000) {\n            $this->addExtension(new YieldNotReadyExtension($this->useYield));\n        }\n        $this->addExtension(new OptimizerExtension($options['optimizations']));\n    }\n\n    /**\n     * @internal\n     */\n    public function useYield(): bool\n    {\n        return $this->useYield;\n    }\n\n    /**\n     * Enables debugging mode.\n     *\n     * @return void\n     */\n    public function enableDebug()\n    {\n        $this->debug = true;\n        $this->updateOptionsHash();\n    }\n\n    /**\n     * Disables debugging mode.\n     *\n     * @return void\n     */\n    public function disableDebug()\n    {\n        $this->debug = false;\n        $this->updateOptionsHash();\n    }\n\n    /**\n     * Checks if debug mode is enabled.\n     *\n     * @return bool true if debug mode is enabled, false otherwise\n     */\n    public function isDebug()\n    {\n        return $this->debug;\n    }\n\n    /**\n     * Enables the auto_reload option.\n     *\n     * @return void\n     */\n    public function enableAutoReload()\n    {\n        $this->autoReload = true;\n    }\n\n    /**\n     * Disables the auto_reload option.\n     *\n     * @return void\n     */\n    public function disableAutoReload()\n    {\n        $this->autoReload = false;\n    }\n\n    /**\n     * Checks if the auto_reload option is enabled.\n     *\n     * @return bool true if auto_reload is enabled, false otherwise\n     */\n    public function isAutoReload()\n    {\n        return $this->autoReload;\n    }\n\n    /**\n     * Enables the strict_variables option.\n     *\n     * @return void\n     */\n    public function enableStrictVariables()\n    {\n        $this->strictVariables = true;\n        $this->updateOptionsHash();\n    }\n\n    /**\n     * Disables the strict_variables option.\n     *\n     * @return void\n     */\n    public function disableStrictVariables()\n    {\n        $this->strictVariables = false;\n        $this->updateOptionsHash();\n    }\n\n    /**\n     * Checks if the strict_variables option is enabled.\n     *\n     * @return bool true if strict_variables is enabled, false otherwise\n     */\n    public function isStrictVariables()\n    {\n        return $this->strictVariables;\n    }\n\n    public function removeCache(string $name): void\n    {\n        $cls = $this->getTemplateClass($name);\n        $this->hotCache[$name] = $cls.'_'.bin2hex(random_bytes(16));\n\n        if ($this->cache instanceof RemovableCacheInterface) {\n            $this->cache->remove($name, $cls);\n        } else {\n            throw new \\LogicException(\\sprintf('The \"%s\" cache class does not support removing template cache as it does not implement the \"RemovableCacheInterface\" interface.', \\get_class($this->cache)));\n        }\n    }\n\n    /**\n     * Gets the current cache implementation.\n     *\n     * @param bool $original Whether to return the original cache option or the real cache instance\n     *\n     * @return CacheInterface|string|false A Twig\\Cache\\CacheInterface implementation,\n     *                                     an absolute path to the compiled templates,\n     *                                     or false to disable cache\n     */\n    public function getCache($original = true)\n    {\n        return $original ? $this->originalCache : $this->cache;\n    }\n\n    /**\n     * Sets the current cache implementation.\n     *\n     * @param CacheInterface|string|false $cache A Twig\\Cache\\CacheInterface implementation,\n     *                                           an absolute path to the compiled templates,\n     *                                           or false to disable cache\n     *\n     * @return void\n     */\n    public function setCache($cache)\n    {\n        if (\\is_string($cache)) {\n            $this->originalCache = $cache;\n            $this->cache = new FilesystemCache($cache, $this->autoReload ? FilesystemCache::FORCE_BYTECODE_INVALIDATION : 0);\n        } elseif (false === $cache) {\n            $this->originalCache = $cache;\n            $this->cache = new NullCache();\n        } elseif ($cache instanceof CacheInterface) {\n            $this->originalCache = $this->cache = $cache;\n        } else {\n            throw new \\LogicException('Cache can only be a string, false, or a \\Twig\\Cache\\CacheInterface implementation.');\n        }\n    }\n\n    /**\n     * Gets the template class associated with the given string.\n     *\n     * The generated template class is based on the following parameters:\n     *\n     *  * The cache key for the given template;\n     *  * The currently enabled extensions;\n     *  * PHP version;\n     *  * Twig version;\n     *  * Options with what environment was created.\n     *\n     * @param string   $name  The name for which to calculate the template class name\n     * @param int|null $index The index if it is an embedded template\n     *\n     * @internal\n     */\n    public function getTemplateClass(string $name, ?int $index = null): string\n    {\n        $key = ($this->hotCache[$name] ?? $this->getLoader()->getCacheKey($name)).$this->optionsHash;\n\n        return '__TwigTemplate_'.hash(\\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index);\n    }\n\n    /**\n     * Renders a template.\n     *\n     * @param string|TemplateWrapper $name The template name\n     *\n     * @throws LoaderError  When the template cannot be found\n     * @throws SyntaxError  When an error occurred during compilation\n     * @throws RuntimeError When an error occurred during rendering\n     */\n    public function render($name, array $context = []): string\n    {\n        return $this->load($name)->render($context);\n    }\n\n    /**\n     * Displays a template.\n     *\n     * @param string|TemplateWrapper $name The template name\n     *\n     * @throws LoaderError  When the template cannot be found\n     * @throws SyntaxError  When an error occurred during compilation\n     * @throws RuntimeError When an error occurred during rendering\n     */\n    public function display($name, array $context = []): void\n    {\n        $this->load($name)->display($context);\n    }\n\n    /**\n     * Loads a template.\n     *\n     * @param string|TemplateWrapper $name The template name\n     *\n     * @throws LoaderError  When the template cannot be found\n     * @throws RuntimeError When a previously generated cache is corrupted\n     * @throws SyntaxError  When an error occurred during compilation\n     */\n    public function load($name): TemplateWrapper\n    {\n        if ($name instanceof TemplateWrapper) {\n            return $name;\n        }\n        if ($name instanceof Template) {\n            trigger_deprecation('twig/twig', '3.9', 'Passing a \"%s\" instance to \"%s\" is deprecated.', self::class, __METHOD__);\n\n            return $name;\n        }\n\n        return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name));\n    }\n\n    /**\n     * Loads a template internal representation.\n     *\n     * This method is for internal use only and should never be called\n     * directly.\n     *\n     * @param string   $name  The template name\n     * @param int|null $index The index if it is an embedded template\n     *\n     * @throws LoaderError  When the template cannot be found\n     * @throws RuntimeError When a previously generated cache is corrupted\n     * @throws SyntaxError  When an error occurred during compilation\n     *\n     * @internal\n     */\n    public function loadTemplate(string $cls, string $name, ?int $index = null): Template\n    {\n        $mainCls = $cls;\n        if (null !== $index) {\n            $cls .= '___'.$index;\n        }\n\n        if (isset($this->loadedTemplates[$cls])) {\n            return $this->loadedTemplates[$cls];\n        }\n\n        if (!class_exists($cls, false)) {\n            $key = $this->cache->generateKey($name, $mainCls);\n\n            if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {\n                $this->cache->load($key);\n            }\n\n            if (!class_exists($cls, false)) {\n                $source = $this->getLoader()->getSourceContext($name);\n                $content = $this->compileSource($source);\n                if (!isset($this->hotCache[$name])) {\n                    $this->cache->write($key, $content);\n                    $this->cache->load($key);\n                }\n\n                if (!class_exists($mainCls, false)) {\n                    /* Last line of defense if either $this->bcWriteCacheFile was used,\n                     * $this->cache is implemented as a no-op or we have a race condition\n                     * where the cache was cleared between the above calls to write to and load from\n                     * the cache.\n                     */\n                    eval('?>'.$content);\n                }\n\n                if (!class_exists($cls, false)) {\n                    throw new RuntimeError(\\sprintf('Failed to load Twig template \"%s\", index \"%s\": cache might be corrupted.', $name, $index), -1, $source);\n                }\n            }\n        }\n\n        $this->extensionSet->initRuntime();\n\n        return $this->loadedTemplates[$cls] = new $cls($this);\n    }\n\n    /**\n     * Creates a template from source.\n     *\n     * This method should not be used as a generic way to load templates.\n     *\n     * @param string      $template The template source\n     * @param string|null $name     An optional name of the template to be used in error messages\n     *\n     * @throws LoaderError When the template cannot be found\n     * @throws SyntaxError When an error occurred during compilation\n     */\n    public function createTemplate(string $template, ?string $name = null): TemplateWrapper\n    {\n        $hash = hash(\\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $template, false);\n        if (null !== $name) {\n            $name = \\sprintf('%s (string template %s)', $name, $hash);\n        } else {\n            $name = \\sprintf('__string_template__%s', $hash);\n        }\n\n        $loader = new ChainLoader([\n            new ArrayLoader([$name => $template]),\n            $current = $this->getLoader(),\n        ]);\n\n        $this->setLoader($loader);\n        try {\n            return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name));\n        } finally {\n            $this->setLoader($current);\n        }\n    }\n\n    /**\n     * Returns true if the template is still fresh.\n     *\n     * Besides checking the loader for freshness information,\n     * this method also checks if the enabled extensions have\n     * not changed.\n     *\n     * @param int $time The last modification time of the cached template\n     */\n    public function isTemplateFresh(string $name, int $time): bool\n    {\n        return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time);\n    }\n\n    /**\n     * Tries to load a template consecutively from an array.\n     *\n     * Similar to load() but it also accepts instances of \\Twig\\TemplateWrapper\n     * and an array of templates where each is tried to be loaded.\n     *\n     * @param string|TemplateWrapper|array<string|TemplateWrapper> $names A template or an array of templates to try consecutively\n     *\n     * @throws LoaderError When none of the templates can be found\n     * @throws SyntaxError When an error occurred during compilation\n     */\n    public function resolveTemplate($names): TemplateWrapper\n    {\n        if (!\\is_array($names)) {\n            return $this->load($names);\n        }\n\n        $count = \\count($names);\n        foreach ($names as $name) {\n            if ($name instanceof Template) {\n                trigger_deprecation('twig/twig', '3.9', 'Passing a \"%s\" instance to \"%s\" is deprecated.', Template::class, __METHOD__);\n\n                return new TemplateWrapper($this, $name);\n            }\n            if ($name instanceof TemplateWrapper) {\n                return $name;\n            }\n\n            if (1 !== $count && !$this->getLoader()->exists($name)) {\n                continue;\n            }\n\n            return $this->load($name);\n        }\n\n        throw new LoaderError(\\sprintf('Unable to find one of the following templates: \"%s\".', implode('\", \"', $names)));\n    }\n\n    /**\n     * @return void\n     */\n    public function setLexer(Lexer $lexer)\n    {\n        $this->lexer = $lexer;\n    }\n\n    /**\n     * @throws SyntaxError When the code is syntactically wrong\n     */\n    public function tokenize(Source $source): TokenStream\n    {\n        if (null === $this->lexer) {\n            $this->lexer = new Lexer($this);\n        }\n\n        return $this->lexer->tokenize($source);\n    }\n\n    /**\n     * @return void\n     */\n    public function setParser(Parser $parser)\n    {\n        $this->parser = $parser;\n    }\n\n    /**\n     * Converts a token stream to a node tree.\n     *\n     * @throws SyntaxError When the token stream is syntactically or semantically wrong\n     */\n    public function parse(TokenStream $stream): ModuleNode\n    {\n        if (null === $this->parser) {\n            $this->parser = new Parser($this);\n        }\n\n        return $this->parser->parse($stream);\n    }\n\n    /**\n     * @return void\n     */\n    public function setCompiler(Compiler $compiler)\n    {\n        $this->compiler = $compiler;\n    }\n\n    /**\n     * Compiles a node and returns the PHP code.\n     */\n    public function compile(Node $node): string\n    {\n        if (null === $this->compiler) {\n            $this->compiler = new Compiler($this);\n        }\n\n        return $this->compiler->compile($node)->getSource();\n    }\n\n    /**\n     * Compiles a template source code.\n     *\n     * @throws SyntaxError When there was an error during tokenizing, parsing or compiling\n     */\n    public function compileSource(Source $source): string\n    {\n        try {\n            return $this->compile($this->parse($this->tokenize($source)));\n        } catch (Error $e) {\n            $e->setSourceContext($source);\n            throw $e;\n        } catch (\\Exception $e) {\n            throw new SyntaxError(\\sprintf('An exception has been thrown during the compilation of a template (\"%s\").', $e->getMessage()), -1, $source, $e);\n        }\n    }\n\n    /**\n     * @return void\n     */\n    public function setLoader(LoaderInterface $loader)\n    {\n        $this->loader = $loader;\n    }\n\n    public function getLoader(): LoaderInterface\n    {\n        return $this->loader;\n    }\n\n    /**\n     * @return void\n     */\n    public function setCharset(string $charset)\n    {\n        if ('UTF8' === $charset = strtoupper($charset ?: '')) {\n            // iconv on Windows requires \"UTF-8\" instead of \"UTF8\"\n            $charset = 'UTF-8';\n        }\n\n        $this->charset = $charset;\n    }\n\n    public function getCharset(): string\n    {\n        return $this->charset;\n    }\n\n    public function hasExtension(string $class): bool\n    {\n        return $this->extensionSet->hasExtension($class);\n    }\n\n    /**\n     * @return void\n     */\n    public function addRuntimeLoader(RuntimeLoaderInterface $loader)\n    {\n        $this->runtimeLoaders[] = $loader;\n    }\n\n    /**\n     * @template TExtension of ExtensionInterface\n     *\n     * @param class-string<TExtension> $class\n     *\n     * @return TExtension\n     */\n    public function getExtension(string $class): ExtensionInterface\n    {\n        return $this->extensionSet->getExtension($class);\n    }\n\n    /**\n     * Returns the runtime implementation of a Twig element (filter/function/tag/test).\n     *\n     * @template TRuntime of object\n     *\n     * @param class-string<TRuntime> $class A runtime class name\n     *\n     * @return TRuntime The runtime implementation\n     *\n     * @throws RuntimeError When the template cannot be found\n     */\n    public function getRuntime(string $class)\n    {\n        if (isset($this->runtimes[$class])) {\n            return $this->runtimes[$class];\n        }\n\n        foreach ($this->runtimeLoaders as $loader) {\n            if (null !== $runtime = $loader->load($class)) {\n                return $this->runtimes[$class] = $runtime;\n            }\n        }\n\n        if (null !== $runtime = $this->defaultRuntimeLoader->load($class)) {\n            return $this->runtimes[$class] = $runtime;\n        }\n\n        throw new RuntimeError(\\sprintf('Unable to load the \"%s\" runtime.', $class));\n    }\n\n    /**\n     * @return void\n     */\n    public function addExtension(ExtensionInterface $extension)\n    {\n        $this->extensionSet->addExtension($extension);\n        $this->updateOptionsHash();\n    }\n\n    /**\n     * @param ExtensionInterface[] $extensions An array of extensions\n     *\n     * @return void\n     */\n    public function setExtensions(array $extensions)\n    {\n        $this->extensionSet->setExtensions($extensions);\n        $this->updateOptionsHash();\n    }\n\n    /**\n     * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on)\n     */\n    public function getExtensions(): array\n    {\n        return $this->extensionSet->getExtensions();\n    }\n\n    /**\n     * @return void\n     */\n    public function addTokenParser(TokenParserInterface $parser)\n    {\n        $this->extensionSet->addTokenParser($parser);\n    }\n\n    /**\n     * @return TokenParserInterface[]\n     *\n     * @internal\n     */\n    public function getTokenParsers(): array\n    {\n        return $this->extensionSet->getTokenParsers();\n    }\n\n    /**\n     * @internal\n     */\n    public function getTokenParser(string $name): ?TokenParserInterface\n    {\n        return $this->extensionSet->getTokenParser($name);\n    }\n\n    /**\n     * @param callable(string): (TokenParserInterface|false) $callable\n     */\n    public function registerUndefinedTokenParserCallback(callable $callable): void\n    {\n        $this->extensionSet->registerUndefinedTokenParserCallback($callable);\n    }\n\n    /**\n     * @return void\n     */\n    public function addNodeVisitor(NodeVisitorInterface $visitor)\n    {\n        $this->extensionSet->addNodeVisitor($visitor);\n    }\n\n    /**\n     * @return NodeVisitorInterface[]\n     *\n     * @internal\n     */\n    public function getNodeVisitors(): array\n    {\n        return $this->extensionSet->getNodeVisitors();\n    }\n\n    /**\n     * @return void\n     */\n    public function addFilter(TwigFilter $filter)\n    {\n        $this->extensionSet->addFilter($filter);\n    }\n\n    /**\n     * @internal\n     */\n    public function getFilter(string $name): ?TwigFilter\n    {\n        return $this->extensionSet->getFilter($name);\n    }\n\n    /**\n     * @param callable(string): (TwigFilter|false) $callable\n     */\n    public function registerUndefinedFilterCallback(callable $callable): void\n    {\n        $this->extensionSet->registerUndefinedFilterCallback($callable);\n    }\n\n    /**\n     * Gets the registered Filters.\n     *\n     * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback.\n     *\n     * @return TwigFilter[]\n     *\n     * @see registerUndefinedFilterCallback\n     *\n     * @internal\n     */\n    public function getFilters(): array\n    {\n        return $this->extensionSet->getFilters();\n    }\n\n    /**\n     * @return void\n     */\n    public function addTest(TwigTest $test)\n    {\n        $this->extensionSet->addTest($test);\n    }\n\n    /**\n     * @return TwigTest[]\n     *\n     * @internal\n     */\n    public function getTests(): array\n    {\n        return $this->extensionSet->getTests();\n    }\n\n    /**\n     * @internal\n     */\n    public function getTest(string $name): ?TwigTest\n    {\n        return $this->extensionSet->getTest($name);\n    }\n\n    /**\n     * @param callable(string): (TwigTest|false) $callable\n     */\n    public function registerUndefinedTestCallback(callable $callable): void\n    {\n        $this->extensionSet->registerUndefinedTestCallback($callable);\n    }\n\n    /**\n     * @return void\n     */\n    public function addFunction(TwigFunction $function)\n    {\n        $this->extensionSet->addFunction($function);\n    }\n\n    /**\n     * @internal\n     */\n    public function getFunction(string $name): ?TwigFunction\n    {\n        return $this->extensionSet->getFunction($name);\n    }\n\n    /**\n     * @param callable(string): (TwigFunction|false) $callable\n     */\n    public function registerUndefinedFunctionCallback(callable $callable): void\n    {\n        $this->extensionSet->registerUndefinedFunctionCallback($callable);\n    }\n\n    /**\n     * Gets registered functions.\n     *\n     * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.\n     *\n     * @return TwigFunction[]\n     *\n     * @see registerUndefinedFunctionCallback\n     *\n     * @internal\n     */\n    public function getFunctions(): array\n    {\n        return $this->extensionSet->getFunctions();\n    }\n\n    /**\n     * Registers a Global.\n     *\n     * New globals can be added before compiling or rendering a template;\n     * but after, you can only update existing globals.\n     *\n     * @param mixed $value The global value\n     *\n     * @return void\n     */\n    public function addGlobal(string $name, $value)\n    {\n        if ($this->extensionSet->isInitialized() && !\\array_key_exists($name, $this->getGlobals())) {\n            throw new \\LogicException(\\sprintf('Unable to add global \"%s\" as the runtime or the extensions have already been initialized.', $name));\n        }\n\n        if (null !== $this->resolvedGlobals) {\n            $this->resolvedGlobals[$name] = $value;\n        } else {\n            $this->globals[$name] = $value;\n        }\n    }\n\n    /**\n     * @return array<string, mixed>\n     */\n    public function getGlobals(): array\n    {\n        if ($this->extensionSet->isInitialized()) {\n            if (null === $this->resolvedGlobals) {\n                $this->resolvedGlobals = array_merge($this->extensionSet->getGlobals(), $this->globals);\n            }\n\n            return $this->resolvedGlobals;\n        }\n\n        return array_merge($this->extensionSet->getGlobals(), $this->globals);\n    }\n\n    public function resetGlobals(): void\n    {\n        $this->resolvedGlobals = null;\n        $this->extensionSet->resetGlobals();\n    }\n\n    /**\n     * @deprecated since Twig 3.14\n     */\n    public function mergeGlobals(array $context): array\n    {\n        trigger_deprecation('twig/twig', '3.14', 'The \"%s\" method is deprecated.', __METHOD__);\n\n        return $context + $this->getGlobals();\n    }\n\n    /**\n     * @internal\n     */\n    public function getExpressionParsers(): ExpressionParsers\n    {\n        return $this->extensionSet->getExpressionParsers();\n    }\n\n    private function updateOptionsHash(): void\n    {\n        $this->optionsHash = implode(':', [\n            $this->extensionSet->getSignature(),\n            \\PHP_MAJOR_VERSION,\n            \\PHP_MINOR_VERSION,\n            self::VERSION,\n            (int) $this->debug,\n            (int) $this->strictVariables,\n            $this->useYield ? '1' : '0',\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Error/Error.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Error;\n\nuse Twig\\Source;\nuse Twig\\Template;\n\n/**\n * Twig base exception.\n *\n * This exception class and its children must only be used when\n * an error occurs during the loading of a template, when a syntax error\n * is detected in a template, or when rendering a template. Other\n * errors must use regular PHP exception classes (like when the template\n * cache directory is not writable for instance).\n *\n * To help debugging template issues, this class tracks the original template\n * name and line where the error occurred.\n *\n * Whenever possible, you must set these information (original template name\n * and line number) yourself by passing them to the constructor. If some or all\n * these information are not available from where you throw the exception, then\n * this class will guess them automatically.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass Error extends \\Exception\n{\n    private $lineno;\n    private $rawMessage;\n    private ?Source $source;\n    private string $phpFile;\n    private int $phpLine;\n\n    /**\n     * Constructor.\n     *\n     * By default, automatic guessing is enabled.\n     *\n     * @param string      $message The error message\n     * @param int         $lineno  The template line where the error occurred\n     * @param Source|null $source  The source context where the error occurred\n     */\n    public function __construct(string $message, int $lineno = -1, ?Source $source = null, ?\\Throwable $previous = null)\n    {\n        parent::__construct('', 0, $previous);\n\n        $this->phpFile = $this->getFile();\n        $this->phpLine = $this->getLine();\n        $this->lineno = $lineno;\n        $this->source = $source;\n        $this->rawMessage = $message;\n        $this->updateRepr();\n    }\n\n    public function getRawMessage(): string\n    {\n        return $this->rawMessage;\n    }\n\n    public function getTemplateLine(): int\n    {\n        return $this->lineno;\n    }\n\n    public function setTemplateLine(int $lineno): void\n    {\n        $this->lineno = $lineno;\n        $this->updateRepr();\n    }\n\n    public function getSourceContext(): ?Source\n    {\n        return $this->source;\n    }\n\n    public function setSourceContext(?Source $source = null): void\n    {\n        $this->source = $source;\n        $this->updateRepr();\n    }\n\n    public function guess(): void\n    {\n        if ($this->lineno > -1) {\n            return;\n        }\n\n        $this->guessTemplateInfo();\n        $this->updateRepr();\n    }\n\n    public function appendMessage($rawMessage): void\n    {\n        $this->rawMessage .= $rawMessage;\n        $this->updateRepr();\n    }\n\n    private function updateRepr(): void\n    {\n        if ($this->source && $this->source->getPath()) {\n            // we only update the file and the line together\n            $this->file = $this->source->getPath();\n            if ($this->lineno > 0) {\n                $this->line = $this->lineno;\n            } else {\n                $this->line = -1;\n            }\n        }\n\n        $this->message = $this->rawMessage;\n        $last = substr($this->message, -1);\n        if ($punctuation = '.' === $last || '?' === $last ? $last : '') {\n            $this->message = substr($this->message, 0, -1);\n        }\n        if ($this->source && $this->source->getName()) {\n            $this->message .= \\sprintf(' in \"%s\"', $this->source->getName());\n        }\n        if ($this->lineno > 0) {\n            $this->message .= \\sprintf(' at line %d', $this->lineno);\n        }\n        if ($punctuation) {\n            $this->message .= $punctuation;\n        }\n    }\n\n    private function guessTemplateInfo(): void\n    {\n        // $this->source is never null here (see guess() usage in Template)\n\n        $this->lineno = 0;\n        $template = null;\n        $backtrace = debug_backtrace(\\DEBUG_BACKTRACE_IGNORE_ARGS | \\DEBUG_BACKTRACE_PROVIDE_OBJECT);\n        foreach ($backtrace as $trace) {\n            if (isset($trace['object']) && $trace['object'] instanceof Template && $this->source->getName() === $trace['object']->getTemplateName()) {\n                $template = $trace['object'];\n\n                break;\n            }\n        }\n\n        if (null === $template) {\n            return; // Impossible to guess the info as the template was not found in the backtrace\n        }\n\n        $r = new \\ReflectionObject($template);\n        $file = $r->getFileName();\n\n        $exceptions = [$e = $this];\n        while ($e = $e->getPrevious()) {\n            $exceptions[] = $e;\n        }\n\n        while ($e = array_pop($exceptions)) {\n            $traces = $e->getTrace();\n            array_unshift($traces, ['file' => $e instanceof self ? $e->phpFile : $e->getFile(), 'line' => $e instanceof self ? $e->phpLine : $e->getLine()]);\n            while ($trace = array_shift($traces)) {\n                if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {\n                    continue;\n                }\n\n                foreach ($template->getDebugInfo() as $codeLine => $templateLine) {\n                    if ($codeLine <= $trace['line']) {\n                        // update template line\n                        $this->lineno = $templateLine;\n\n                        return;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Error/LoaderError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Error;\n\n/**\n * Exception thrown when an error occurs during template loading.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass LoaderError extends Error\n{\n}\n"
  },
  {
    "path": "src/Error/RuntimeError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Error;\n\n/**\n * Exception thrown when an error occurs at runtime.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass RuntimeError extends Error\n{\n}\n"
  },
  {
    "path": "src/Error/SyntaxError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Error;\n\n/**\n * \\Exception thrown when a syntax error occurs during lexing or parsing of a template.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass SyntaxError extends Error\n{\n    /**\n     * Tweaks the error message to include suggestions.\n     *\n     * @param string $name  The original name of the item that does not exist\n     * @param array  $items An array of possible items\n     */\n    public function addSuggestions(string $name, array $items): void\n    {\n        $alternatives = [];\n        foreach ($items as $item) {\n            $lev = levenshtein($name, $item);\n            if ($lev <= \\strlen($name) / 3 || str_contains($item, $name)) {\n                $alternatives[$item] = $lev;\n            }\n        }\n\n        if (!$alternatives) {\n            return;\n        }\n\n        asort($alternatives);\n\n        $this->appendMessage(\\sprintf(' Did you mean \"%s\"?', implode('\", \"', array_keys($alternatives))));\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/AbstractExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\nabstract class AbstractExpressionParser implements ExpressionParserInterface\n{\n    public function __toString(): string\n    {\n        return \\sprintf('%s(%s)', ExpressionParserType::getType($this)->value, $this->getName());\n    }\n\n    public function getPrecedenceChange(): ?PrecedenceChange\n    {\n        return null;\n    }\n\n    public function getAliases(): array\n    {\n        return [];\n    }\n\n    public function getOperatorTokens(): array\n    {\n        return [$this->getName(), ...$this->getAliases()];\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/ExpressionParserDescriptionInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\ninterface ExpressionParserDescriptionInterface\n{\n    public function getDescription(): string;\n}\n"
  },
  {
    "path": "src/ExpressionParser/ExpressionParserInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\n/**\n * @method list<string> getOperatorTokens() Returns the operator token strings that this expression parser handles.\n *                                          These are the strings that should be recognized as operator tokens by the Lexer,\n *                                          and used to look up the parser in the registry.\n *                                          For most parsers, this returns the name and aliases. Parsers that don't handle\n *                                          operator tokens (like LiteralExpressionParser) should return an empty array.\n *                                          This method will be added to the interface in Twig 4.0.\n */\ninterface ExpressionParserInterface\n{\n    public function __toString(): string;\n\n    public function getName(): string;\n\n    public function getPrecedence(): int;\n\n    public function getPrecedenceChange(): ?PrecedenceChange;\n\n    /**\n     * @return array<string>\n     */\n    public function getAliases(): array;\n}\n"
  },
  {
    "path": "src/ExpressionParser/ExpressionParserType.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\n/**\n * @internal\n */\nenum ExpressionParserType: string\n{\n    case Prefix = 'prefix';\n    case Infix = 'infix';\n\n    public static function getType(object $object): ExpressionParserType\n    {\n        if ($object instanceof PrefixExpressionParserInterface) {\n            return self::Prefix;\n        }\n        if ($object instanceof InfixExpressionParserInterface) {\n            return self::Infix;\n        }\n\n        throw new \\InvalidArgumentException(\\sprintf('Unsupported expression parser type: %s', $object::class));\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/ExpressionParsers.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\n/**\n * @template-implements \\IteratorAggregate<ExpressionParserInterface>\n *\n * @internal\n */\nfinal class ExpressionParsers implements \\IteratorAggregate\n{\n    /**\n     * @var array<class-string<ExpressionParserInterface>, array<string, ExpressionParserInterface>>\n     */\n    private array $parsersByName = [];\n\n    /**\n     * @var array<class-string<ExpressionParserInterface>, ExpressionParserInterface>\n     */\n    private array $parsersByClass = [];\n\n    /**\n     * @var \\WeakMap<ExpressionParserInterface, array<ExpressionParserInterface>>|null\n     */\n    private ?\\WeakMap $precedenceChanges = null;\n\n    /**\n     * @param array<ExpressionParserInterface> $parsers\n     */\n    public function __construct(array $parsers = [])\n    {\n        $this->add($parsers);\n    }\n\n    /**\n     * @param array<ExpressionParserInterface> $parsers\n     *\n     * @return $this\n     */\n    public function add(array $parsers): static\n    {\n        foreach ($parsers as $parser) {\n            if ($parser->getPrecedence() > 512 || $parser->getPrecedence() < 0) {\n                trigger_deprecation('twig/twig', '3.21', 'Precedence for \"%s\" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence());\n                // throw new \\InvalidArgumentException(\\sprintf('Precedence for \"%s\" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence()));\n            }\n            $interface = $parser instanceof PrefixExpressionParserInterface ? PrefixExpressionParserInterface::class : InfixExpressionParserInterface::class;\n            $this->parsersByClass[$parser::class] = $parser;\n            foreach (self::getOperatorTokensFor($parser) as $token) {\n                $this->parsersByName[$interface][$token] = $parser;\n            }\n        }\n\n        return $this;\n    }\n\n    /**\n     * @template T of ExpressionParserInterface\n     *\n     * @param class-string<T> $class\n     *\n     * @return T|null\n     */\n    public function getByClass(string $class): ?ExpressionParserInterface\n    {\n        return $this->parsersByClass[$class] ?? null;\n    }\n\n    /**\n     * @template T of ExpressionParserInterface\n     *\n     * @param class-string<T> $interface\n     *\n     * @return T|null\n     */\n    public function getByName(string $interface, string $name): ?ExpressionParserInterface\n    {\n        return $this->parsersByName[$interface][$name] ?? null;\n    }\n\n    public function getIterator(): \\Traversable\n    {\n        $seen = [];\n        foreach ($this->parsersByName as $parsers) {\n            foreach ($parsers as $parser) {\n                $id = spl_object_id($parser);\n                if (!isset($seen[$id])) {\n                    $seen[$id] = true;\n                    yield $parser;\n                }\n            }\n        }\n        foreach ($this->parsersByClass as $parser) {\n            $id = spl_object_id($parser);\n            if (!isset($seen[$id])) {\n                $seen[$id] = true;\n                yield $parser;\n            }\n        }\n    }\n\n    /**\n     * @internal\n     *\n     * @return \\WeakMap<ExpressionParserInterface, array<ExpressionParserInterface>>\n     */\n    public function getPrecedenceChanges(): \\WeakMap\n    {\n        if (null === $this->precedenceChanges) {\n            $this->precedenceChanges = new \\WeakMap();\n            foreach ($this as $ep) {\n                if (!$ep->getPrecedenceChange()) {\n                    continue;\n                }\n                $min = min($ep->getPrecedenceChange()->getNewPrecedence(), $ep->getPrecedence());\n                $max = max($ep->getPrecedenceChange()->getNewPrecedence(), $ep->getPrecedence());\n                foreach ($this as $e) {\n                    if ($e->getPrecedence() > $min && $e->getPrecedence() < $max) {\n                        if (!isset($this->precedenceChanges[$e])) {\n                            $this->precedenceChanges[$e] = [];\n                        }\n                        $this->precedenceChanges[$e][] = $ep;\n                    }\n                }\n            }\n        }\n\n        return $this->precedenceChanges;\n    }\n\n    /**\n     * @internal\n     *\n     * @return array<string>\n     */\n    public static function getOperatorTokensFor(ExpressionParserInterface $parser): array\n    {\n        if (method_exists($parser, 'getOperatorTokens')) {\n            return $parser->getOperatorTokens();\n        }\n\n        trigger_deprecation('twig/twig', '3.24', 'Not implementing the \"getOperatorTokens()\" method in \"%s\" is deprecated. This method will be part of the \"%s\" interface in 4.0.', $parser::class, ExpressionParserInterface::class);\n\n        return [$parser->getName(), ...$parser->getAliases()];\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/ArgumentsTrait.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\Binary\\SetBinary;\nuse Twig\\Node\\Expression\\Unary\\SpreadUnary;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Expression\\Variable\\LocalVariable;\nuse Twig\\Node\\Nodes;\nuse Twig\\Parser;\nuse Twig\\Token;\n\ntrait ArgumentsTrait\n{\n    private function parseCallableArguments(Parser $parser, int $line, bool $parseOpenParenthesis = true): ArrayExpression\n    {\n        $arguments = new ArrayExpression([], $line);\n        foreach ($this->parseNamedArguments($parser, $parseOpenParenthesis) as $k => $n) {\n            $arguments->addElement($n, new LocalVariable($k, $line));\n        }\n\n        return $arguments;\n    }\n\n    private function parseNamedArguments(Parser $parser, bool $parseOpenParenthesis = true): Nodes\n    {\n        $args = [];\n        $stream = $parser->getStream();\n        if ($parseOpenParenthesis) {\n            $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');\n        }\n        $hasSpread = false;\n        while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {\n            if ($args) {\n                $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');\n\n                // if the comma above was a trailing comma, early exit the argument parse loop\n                if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {\n                    break;\n                }\n            }\n\n            $value = $parser->parseExpression();\n            if ($value instanceof SpreadUnary) {\n                $hasSpread = true;\n            } elseif ($hasSpread) {\n                throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext());\n            }\n\n            $name = null;\n            if ($value instanceof SetBinary) {\n                $name = $value->getNode('left')->getAttribute('name');\n                $value = $value->getNode('right');\n            } elseif (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':'))) {\n                if (!$value instanceof ContextVariable) {\n                    throw new SyntaxError(\\sprintf('A parameter name must be a string, \"%s\" given.', $value::class), $token->getLine(), $stream->getSourceContext());\n                }\n                $name = $value->getAttribute('name');\n                $value = $parser->parseExpression();\n            }\n\n            if (null === $name) {\n                $args[] = $value;\n            } else {\n                $args[$name] = $value;\n            }\n        }\n        $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');\n\n        return new Nodes($args);\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/ArrowExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ArrowFunctionExpression;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class ArrowExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression\n    {\n        // As the expression of the arrow function is independent from the current precedence, we want a precedence of 0\n        return new ArrowFunctionExpression($parser->parseExpression(), $expr, $token->getLine());\n    }\n\n    public function getName(): string\n    {\n        return '=>';\n    }\n\n    public function getDescription(): string\n    {\n        return 'Arrow function (x => expr)';\n    }\n\n    public function getPrecedence(): int\n    {\n        return 250;\n    }\n\n    public function getAssociativity(): InfixAssociativity\n    {\n        return InfixAssociativity::Left;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/AssignmentExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\Binary\\AbstractBinary;\nuse Twig\\Node\\Expression\\Binary\\ObjectDestructuringSetBinary;\nuse Twig\\Node\\Expression\\Binary\\SequenceDestructuringSetBinary;\nuse Twig\\Node\\Expression\\Binary\\SetBinary;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nclass AssignmentExpressionParser extends BinaryOperatorExpressionParser\n{\n    public function __construct(\n        string $name,\n    ) {\n        parent::__construct(SetBinary::class, $name, 0, InfixAssociativity::Right);\n    }\n\n    /**\n     * @return AbstractBinary\n     */\n    public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression\n    {\n        if (!$left instanceof ContextVariable && !$left instanceof ArrayExpression) {\n            throw new SyntaxError(\\sprintf('Cannot assign to \"%s\", only variables can be assigned.', $left::class), $token->getLine(), $parser->getStream()->getSourceContext());\n        }\n        $right = $parser->parseExpression(InfixAssociativity::Left === $this->getAssociativity() ? $this->getPrecedence() + 1 : $this->getPrecedence());\n        $right = match ($this->getName()) {\n            '=' => $right,\n            default => throw new \\LogicException(\\sprintf('Unknown operator: %s.', $this->getName())),\n        };\n\n        if ($left instanceof ArrayExpression) {\n            if ($left->isSequence()) {\n                return new SequenceDestructuringSetBinary($left, $right, $token->getLine());\n            }\n\n            return new ObjectDestructuringSetBinary($left, $right, $token->getLine());\n        }\n\n        return new SetBinary($left, $right, $token->getLine());\n    }\n\n    public function getDescription(): string\n    {\n        return 'Assignment operator';\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/BinaryOperatorExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\ExpressionParser\\PrecedenceChange;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\Binary\\AbstractBinary;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nclass BinaryOperatorExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    public function __construct(\n        /** @var class-string<AbstractBinary> */\n        private string $nodeClass,\n        private string $name,\n        private int $precedence,\n        private InfixAssociativity $associativity = InfixAssociativity::Left,\n        private ?PrecedenceChange $precedenceChange = null,\n        private ?string $description = null,\n        private array $aliases = [],\n    ) {\n    }\n\n    /**\n     * @return AbstractBinary\n     */\n    public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression\n    {\n        $right = $parser->parseExpression(InfixAssociativity::Left === $this->getAssociativity() ? $this->getPrecedence() + 1 : $this->getPrecedence());\n\n        return new ($this->nodeClass)($left, $right, $token->getLine());\n    }\n\n    public function getAssociativity(): InfixAssociativity\n    {\n        return $this->associativity;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function getDescription(): string\n    {\n        return $this->description ?? '';\n    }\n\n    public function getPrecedence(): int\n    {\n        return $this->precedence;\n    }\n\n    public function getPrecedenceChange(): ?PrecedenceChange\n    {\n        return $this->precedenceChange;\n    }\n\n    public function getAliases(): array\n    {\n        return $this->aliases;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/ConditionalTernaryExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Ternary\\ConditionalTernary;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class ConditionalTernaryExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression\n    {\n        $then = $parser->parseExpression($this->getPrecedence());\n        if ($parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {\n            // Ternary operator (expr ? expr2 : expr3)\n            $else = $parser->parseExpression($this->getPrecedence());\n        } else {\n            // Ternary without else (expr ? expr2)\n            $else = new ConstantExpression('', $token->getLine());\n        }\n\n        return new ConditionalTernary($left, $then, $else, $token->getLine());\n    }\n\n    public function getName(): string\n    {\n        return '?';\n    }\n\n    public function getDescription(): string\n    {\n        return 'Conditional operator (a ? b : c)';\n    }\n\n    public function getPrecedence(): int\n    {\n        return 0;\n    }\n\n    public function getAssociativity(): InfixAssociativity\n    {\n        return InfixAssociativity::Left;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/DotExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\Lexer;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Expression\\MacroReferenceExpression;\nuse Twig\\Node\\Expression\\NameExpression;\nuse Twig\\Node\\Expression\\Variable\\TemplateVariable;\nuse Twig\\Parser;\nuse Twig\\Template;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class DotExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    use ArgumentsTrait;\n\n    public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression\n    {\n        $nullSafe = '?.' === $token->getValue();\n        $stream = $parser->getStream();\n        $token = $stream->getCurrent();\n        $lineno = $token->getLine();\n        $arguments = new ArrayExpression([], $lineno);\n        $type = Template::ANY_CALL;\n\n        if ($stream->nextIf(Token::OPERATOR_TYPE, '(')) {\n            $attribute = $parser->parseExpression();\n            $stream->expect(Token::PUNCTUATION_TYPE, ')');\n        } else {\n            $token = $stream->next();\n            if (\n                $token->test(Token::NAME_TYPE)\n                || $token->test(Token::NUMBER_TYPE)\n                || ($token->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue()))\n            ) {\n                $attribute = new ConstantExpression($token->getValue(), $token->getLine());\n            } else {\n                throw new SyntaxError(\\sprintf('Expected name or number, got value \"%s\" of type \"%s\".', $token->getValue(), $token->toEnglish()), $token->getLine(), $stream->getSourceContext());\n            }\n        }\n\n        if ($stream->test(Token::OPERATOR_TYPE, '(')) {\n            $type = Template::METHOD_CALL;\n            $arguments = $this->parseCallableArguments($parser, $token->getLine());\n        }\n\n        if (\n            $expr instanceof NameExpression\n            && (\n                null !== $parser->getImportedSymbol('template', $expr->getAttribute('name'))\n                || '_self' === $expr->getAttribute('name') && $attribute instanceof ConstantExpression\n            )\n        ) {\n            return new MacroReferenceExpression(new TemplateVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$attribute->getAttribute('value'), $arguments, $expr->getTemplateLine());\n        }\n\n        return new GetAttrExpression($expr, $attribute, $arguments, $type, $lineno, $nullSafe);\n    }\n\n    public function getName(): string\n    {\n        return '.';\n    }\n\n    public function getAliases(): array\n    {\n        return ['?.'];\n    }\n\n    public function getDescription(): string\n    {\n        return 'Get an attribute on a variable';\n    }\n\n    public function getPrecedence(): int\n    {\n        return 512;\n    }\n\n    public function getAssociativity(): InfixAssociativity\n    {\n        return InfixAssociativity::Left;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/FilterExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\ExpressionParser\\PrecedenceChange;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class FilterExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    use ArgumentsTrait;\n\n    private $readyNodes = [];\n\n    public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression\n    {\n        $stream = $parser->getStream();\n        $token = $stream->expect(Token::NAME_TYPE);\n        $line = $token->getLine();\n\n        if (!$stream->test(Token::OPERATOR_TYPE, '(')) {\n            $arguments = new EmptyNode();\n        } else {\n            $arguments = $this->parseNamedArguments($parser);\n        }\n\n        $filter = $parser->getFilter($token->getValue(), $line);\n\n        $ready = true;\n        if (!isset($this->readyNodes[$class = $filter->getNodeClass()])) {\n            $this->readyNodes[$class] = (bool) (new \\ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);\n        }\n\n        if (!$ready = $this->readyNodes[$class]) {\n            trigger_deprecation('twig/twig', '3.12', 'Twig node \"%s\" is not marked as ready for passing a \"TwigFilter\" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);\n        }\n\n        return new $class($expr, $ready ? $filter : new ConstantExpression($filter->getName(), $line), $arguments, $line);\n    }\n\n    public function getName(): string\n    {\n        return '|';\n    }\n\n    public function getDescription(): string\n    {\n        return 'Twig filter call';\n    }\n\n    public function getPrecedence(): int\n    {\n        return 512;\n    }\n\n    public function getPrecedenceChange(): ?PrecedenceChange\n    {\n        return new PrecedenceChange('twig/twig', '3.21', 300);\n    }\n\n    public function getAssociativity(): InfixAssociativity\n    {\n        return InfixAssociativity::Left;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/FunctionExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\MacroReferenceExpression;\nuse Twig\\Node\\Expression\\NameExpression;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class FunctionExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    use ArgumentsTrait;\n\n    private $readyNodes = [];\n\n    public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression\n    {\n        $line = $token->getLine();\n        if (!$expr instanceof NameExpression) {\n            throw new SyntaxError('Function name must be an identifier.', $line, $parser->getStream()->getSourceContext());\n        }\n\n        $name = $expr->getAttribute('name');\n\n        if (null !== $alias = $parser->getImportedSymbol('function', $name)) {\n            return new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], $this->parseCallableArguments($parser, $line, false), $line);\n        }\n\n        $args = $this->parseNamedArguments($parser, false);\n\n        $function = $parser->getFunction($name, $line);\n\n        if ($function->getParserCallable()) {\n            $fakeNode = new EmptyNode($line);\n            $fakeNode->setSourceContext($parser->getStream()->getSourceContext());\n\n            return ($function->getParserCallable())($parser, $fakeNode, $args, $line);\n        }\n\n        if (!isset($this->readyNodes[$class = $function->getNodeClass()])) {\n            $this->readyNodes[$class] = (bool) (new \\ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);\n        }\n\n        if (!$ready = $this->readyNodes[$class]) {\n            trigger_deprecation('twig/twig', '3.12', 'Twig node \"%s\" is not marked as ready for passing a \"TwigFunction\" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);\n        }\n\n        return new $class($ready ? $function : $function->getName(), $args, $line);\n    }\n\n    public function getName(): string\n    {\n        return '(';\n    }\n\n    public function getDescription(): string\n    {\n        return 'Twig function call';\n    }\n\n    public function getPrecedence(): int\n    {\n        return 512;\n    }\n\n    public function getAssociativity(): InfixAssociativity\n    {\n        return InfixAssociativity::Left;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/IsExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\MacroReferenceExpression;\nuse Twig\\Node\\Expression\\NameExpression;\nuse Twig\\Node\\Nodes;\nuse Twig\\Parser;\nuse Twig\\Token;\nuse Twig\\TwigTest;\n\n/**\n * @internal\n */\nclass IsExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    use ArgumentsTrait;\n\n    private $readyNodes = [];\n\n    public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression\n    {\n        $stream = $parser->getStream();\n        $test = $parser->getTest($token->getLine());\n\n        $arguments = null;\n        if ($stream->test(Token::OPERATOR_TYPE, '(')) {\n            $arguments = $this->parseNamedArguments($parser);\n        } elseif ($test->hasOneMandatoryArgument()) {\n            $arguments = new Nodes([0 => $parser->parseExpression($this->getPrecedence())]);\n        }\n\n        if ('defined' === $test->getName() && $expr instanceof NameExpression && null !== $alias = $parser->getImportedSymbol('function', $expr->getAttribute('name'))) {\n            $expr = new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], new ArrayExpression([], $expr->getTemplateLine()), $expr->getTemplateLine());\n        }\n\n        $ready = $test instanceof TwigTest;\n        if (!isset($this->readyNodes[$class = $test->getNodeClass()])) {\n            $this->readyNodes[$class] = (bool) (new \\ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);\n        }\n\n        if (!$ready = $this->readyNodes[$class]) {\n            trigger_deprecation('twig/twig', '3.12', 'Twig node \"%s\" is not marked as ready for passing a \"TwigTest\" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);\n        }\n\n        return new $class($expr, $ready ? $test : $test->getName(), $arguments, $stream->getCurrent()->getLine());\n    }\n\n    public function getPrecedence(): int\n    {\n        return 100;\n    }\n\n    public function getName(): string\n    {\n        return 'is';\n    }\n\n    public function getDescription(): string\n    {\n        return 'Twig tests';\n    }\n\n    public function getAssociativity(): InfixAssociativity\n    {\n        return InfixAssociativity::Left;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/IsNotExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\Unary\\NotUnary;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class IsNotExpressionParser extends IsExpressionParser\n{\n    public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression\n    {\n        return new NotUnary(parent::parse($parser, $expr, $token), $token->getLine());\n    }\n\n    public function getName(): string\n    {\n        return 'is not';\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Infix/SquareBracketExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Infix;\n\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Nodes;\nuse Twig\\Parser;\nuse Twig\\Template;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class SquareBracketExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression\n    {\n        $stream = $parser->getStream();\n        $lineno = $token->getLine();\n        $arguments = new ArrayExpression([], $lineno);\n\n        // slice?\n        $slice = false;\n        if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {\n            $slice = true;\n            $attribute = new ConstantExpression(0, $token->getLine());\n        } else {\n            $attribute = $parser->parseExpression();\n        }\n\n        if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) {\n            $slice = true;\n        }\n\n        if ($slice) {\n            if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {\n                $length = new ConstantExpression(null, $token->getLine());\n            } else {\n                $length = $parser->parseExpression();\n            }\n\n            $filter = $parser->getFilter('slice', $token->getLine());\n            $arguments = new Nodes([$attribute, $length]);\n            $filter = new ($filter->getNodeClass())($expr, $filter, $arguments, $token->getLine());\n\n            $stream->expect(Token::PUNCTUATION_TYPE, ']');\n\n            return $filter;\n        }\n\n        $stream->expect(Token::PUNCTUATION_TYPE, ']');\n\n        return new GetAttrExpression($expr, $attribute, $arguments, Template::ARRAY_CALL, $lineno);\n    }\n\n    public function getName(): string\n    {\n        return '[';\n    }\n\n    public function getDescription(): string\n    {\n        return 'Array access';\n    }\n\n    public function getPrecedence(): int\n    {\n        return 512;\n    }\n\n    public function getAssociativity(): InfixAssociativity\n    {\n        return InfixAssociativity::Left;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/InfixAssociativity.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\nenum InfixAssociativity\n{\n    case Left;\n    case Right;\n}\n"
  },
  {
    "path": "src/ExpressionParser/InfixExpressionParserInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Parser;\nuse Twig\\Token;\n\ninterface InfixExpressionParserInterface extends ExpressionParserInterface\n{\n    /**\n     * @throws SyntaxError\n     */\n    public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression;\n\n    public function getAssociativity(): InfixAssociativity;\n}\n"
  },
  {
    "path": "src/ExpressionParser/PrecedenceChange.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\n/**\n * Represents a precedence change.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass PrecedenceChange\n{\n    public function __construct(\n        private string $package,\n        private string $version,\n        private int $newPrecedence,\n    ) {\n    }\n\n    public function getPackage(): string\n    {\n        return $this->package;\n    }\n\n    public function getVersion(): string\n    {\n        return $this->version;\n    }\n\n    public function getNewPrecedence(): int\n    {\n        return $this->newPrecedence;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Prefix/GroupingExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Prefix;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\PrefixExpressionParserInterface;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ListExpression;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class GroupingExpressionParser extends AbstractExpressionParser implements PrefixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    public function parse(Parser $parser, Token $token): AbstractExpression\n    {\n        $stream = $parser->getStream();\n        $expr = $parser->parseExpression($this->getPrecedence());\n\n        if ($stream->nextIf(Token::PUNCTUATION_TYPE, ')')) {\n            if (!$stream->test(Token::OPERATOR_TYPE, '=>')) {\n                return $expr->setExplicitParentheses();\n            }\n\n            return new ListExpression([self::toAssignContextVariable($expr)], $token->getLine());\n        }\n\n        // determine if we are parsing an arrow function arguments\n        if (!$stream->test(Token::PUNCTUATION_TYPE, ',')) {\n            $stream->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');\n        }\n\n        $names = [$expr];\n        while (true) {\n            if ($stream->nextIf(Token::PUNCTUATION_TYPE, ')')) {\n                break;\n            }\n            $stream->expect(Token::PUNCTUATION_TYPE, ',');\n            $token = $stream->expect(Token::NAME_TYPE);\n            $names[] = new ContextVariable($token->getValue(), $token->getLine());\n        }\n\n        if (!$stream->test(Token::OPERATOR_TYPE, '=>')) {\n            throw new SyntaxError('A list of variables must be followed by an arrow.', $stream->getCurrent()->getLine(), $stream->getSourceContext());\n        }\n\n        return new ListExpression(array_map(self::toAssignContextVariable(...), $names), $token->getLine());\n    }\n\n    private static function toAssignContextVariable(AbstractExpression $expr): AssignContextVariable\n    {\n        if (!$expr instanceof ContextVariable) {\n            throw new SyntaxError('A list must only contain variables.', $expr->getTemplateLine(), $expr->getSourceContext());\n        }\n\n        return $expr instanceof AssignContextVariable ? $expr : new AssignContextVariable($expr->getAttribute('name'), $expr->getTemplateLine());\n    }\n\n    public function getName(): string\n    {\n        return '(';\n    }\n\n    public function getDescription(): string\n    {\n        return 'Explicit group expression (a)';\n    }\n\n    public function getPrecedence(): int\n    {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Prefix/LiteralExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Prefix;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\PrefixExpressionParserInterface;\nuse Twig\\Lexer;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\Binary\\ConcatBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\EmptyExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class LiteralExpressionParser extends AbstractExpressionParser implements PrefixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    public function parse(Parser $parser, Token $token): AbstractExpression\n    {\n        $stream = $parser->getStream();\n        switch (true) {\n            case $token->test(Token::NAME_TYPE):\n                $stream->next();\n                switch ($token->getValue()) {\n                    case 'true':\n                    case 'TRUE':\n                        return new ConstantExpression(true, $token->getLine());\n\n                    case 'false':\n                    case 'FALSE':\n                        return new ConstantExpression(false, $token->getLine());\n\n                    case 'none':\n                    case 'NONE':\n                    case 'null':\n                    case 'NULL':\n                        return new ConstantExpression(null, $token->getLine());\n\n                    default:\n                        return new ContextVariable($token->getValue(), $token->getLine());\n                }\n\n                // no break\n            case $token->test(Token::NUMBER_TYPE):\n                $stream->next();\n\n                return new ConstantExpression($token->getValue(), $token->getLine());\n\n            case $token->test(Token::STRING_TYPE):\n            case $token->test(Token::INTERPOLATION_START_TYPE):\n                return $this->parseStringExpression($parser);\n\n            case $token->test(Token::PUNCTUATION_TYPE):\n                // In 4.0, we should always return the node or throw an error for default\n                if ($node = match ($token->getValue()) {\n                    '{' => $this->parseMappingExpression($parser),\n                    default => null,\n                }) {\n                    return $node;\n                }\n\n                // no break\n            case $token->test(Token::OPERATOR_TYPE):\n                if ('[' === $token->getValue()) {\n                    return $this->parseSequenceExpression($parser);\n                }\n\n                if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {\n                    // in this context, string operators are variable names\n                    $stream->next();\n\n                    return new ContextVariable($token->getValue(), $token->getLine());\n                }\n\n                // no break\n            default:\n                throw new SyntaxError(\\sprintf('Unexpected token \"%s\" of value \"%s\".', $token->toEnglish(), $token->getValue()), $token->getLine(), $stream->getSourceContext());\n        }\n    }\n\n    public function getName(): string\n    {\n        return 'literal';\n    }\n\n    public function getOperatorTokens(): array\n    {\n        return [];\n    }\n\n    public function getDescription(): string\n    {\n        return 'A literal value (boolean, string, number, sequence, mapping, ...)';\n    }\n\n    public function getPrecedence(): int\n    {\n        // not used\n        return 0;\n    }\n\n    private function parseStringExpression(Parser $parser)\n    {\n        $stream = $parser->getStream();\n\n        $nodes = [];\n        // a string cannot be followed by another string in a single expression\n        $nextCanBeString = true;\n        while (true) {\n            if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) {\n                $nodes[] = new ConstantExpression($token->getValue(), $token->getLine());\n                $nextCanBeString = false;\n            } elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) {\n                $nodes[] = $parser->parseExpression();\n                $stream->expect(Token::INTERPOLATION_END_TYPE);\n                $nextCanBeString = true;\n            } else {\n                break;\n            }\n        }\n\n        $expr = array_shift($nodes);\n        foreach ($nodes as $node) {\n            $expr = new ConcatBinary($expr, $node, $node->getTemplateLine());\n        }\n\n        return $expr;\n    }\n\n    private function parseSequenceExpression(Parser $parser)\n    {\n        $stream = $parser->getStream();\n        $stream->expect(Token::OPERATOR_TYPE, '[', 'A sequence element was expected');\n\n        $node = new ArrayExpression([], $stream->getCurrent()->getLine());\n        $first = true;\n        while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) {\n            if (!$first) {\n                $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A sequence element must be followed by a comma');\n\n                // trailing ,?\n                if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {\n                    break;\n                }\n            }\n            $first = false;\n\n            // Check for empty slots (comma with no expression)\n            if ($stream->test(Token::PUNCTUATION_TYPE, ',')) {\n                $node->addElement(new EmptyExpression($stream->getCurrent()->getLine()));\n            } else {\n                $node->addElement($parser->parseExpression());\n            }\n        }\n        $stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed');\n\n        return $node;\n    }\n\n    private function parseMappingExpression(Parser $parser)\n    {\n        $stream = $parser->getStream();\n        $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected');\n\n        $node = new ArrayExpression([], $stream->getCurrent()->getLine());\n        $first = true;\n        while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) {\n            if (!$first) {\n                $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A mapping value must be followed by a comma');\n\n                // trailing ,?\n                if ($stream->test(Token::PUNCTUATION_TYPE, '}')) {\n                    break;\n                }\n            }\n            $first = false;\n\n            if ($stream->test(Token::OPERATOR_TYPE, '...')) {\n                $node->addElement($parser->parseExpression());\n\n                continue;\n            }\n\n            // a mapping key can be:\n            //\n            //  * a number -- 12\n            //  * a string -- 'a'\n            //  * a name, which is equivalent to a string -- a\n            //  * an expression, which must be enclosed in parentheses -- (1 + 2)\n            if ($token = $stream->nextIf(Token::NAME_TYPE)) {\n                $key = new ConstantExpression($token->getValue(), $token->getLine());\n\n                // {a} is a shortcut for {a:a}\n                if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {\n                    $value = new ContextVariable($key->getAttribute('value'), $key->getTemplateLine());\n                    $node->addElement($value, $key);\n                    continue;\n                }\n            } elseif (($token = $stream->nextIf(Token::STRING_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) {\n                $key = new ConstantExpression($token->getValue(), $token->getLine());\n            } elseif ($stream->test(Token::OPERATOR_TYPE, '(')) {\n                $key = $parser->parseExpression();\n            } else {\n                $current = $stream->getCurrent();\n\n                throw new SyntaxError(\\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token \"%s\" of value \"%s\".', $current->toEnglish(), $current->getValue()), $current->getLine(), $stream->getSourceContext());\n            }\n\n            $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A mapping key must be followed by a colon (:)');\n            $value = $parser->parseExpression();\n\n            $node->addElement($value, $key);\n        }\n        $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed');\n\n        return $node;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/Prefix/UnaryOperatorExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser\\Prefix;\n\nuse Twig\\ExpressionParser\\AbstractExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserDescriptionInterface;\nuse Twig\\ExpressionParser\\PrecedenceChange;\nuse Twig\\ExpressionParser\\PrefixExpressionParserInterface;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\Unary\\AbstractUnary;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class UnaryOperatorExpressionParser extends AbstractExpressionParser implements PrefixExpressionParserInterface, ExpressionParserDescriptionInterface\n{\n    public function __construct(\n        /** @var class-string<AbstractUnary> */\n        private string $nodeClass,\n        private string $name,\n        private int $precedence,\n        private ?PrecedenceChange $precedenceChange = null,\n        private ?string $description = null,\n        private array $aliases = [],\n        private ?int $operandPrecedence = null,\n    ) {\n    }\n\n    /**\n     * @return AbstractUnary\n     */\n    public function parse(Parser $parser, Token $token): AbstractExpression\n    {\n        return new ($this->nodeClass)($parser->parseExpression($this->operandPrecedence ?? $this->precedence), $token->getLine());\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function getDescription(): string\n    {\n        return $this->description ?? '';\n    }\n\n    public function getPrecedence(): int\n    {\n        return $this->precedence;\n    }\n\n    public function getPrecedenceChange(): ?PrecedenceChange\n    {\n        return $this->precedenceChange;\n    }\n\n    public function getAliases(): array\n    {\n        return $this->aliases;\n    }\n}\n"
  },
  {
    "path": "src/ExpressionParser/PrefixExpressionParserInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\ExpressionParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Parser;\nuse Twig\\Token;\n\ninterface PrefixExpressionParserInterface extends ExpressionParserInterface\n{\n    /**\n     * @throws SyntaxError\n     */\n    public function parse(Parser $parser, Token $token): AbstractExpression;\n}\n"
  },
  {
    "path": "src/ExpressionParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\Infix\\DotExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\FilterExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\SquareBracketExpressionParser;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Unary\\NegUnary;\nuse Twig\\Node\\Expression\\Unary\\PosUnary;\nuse Twig\\Node\\Expression\\Unary\\SpreadUnary;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\n\n/**\n * Parses expressions.\n *\n * This parser implements a \"Precedence climbing\" algorithm.\n *\n * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm\n * @see https://en.wikipedia.org/wiki/Operator-precedence_parser\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @deprecated since Twig 3.21\n */\nclass ExpressionParser\n{\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public const OPERATOR_LEFT = 1;\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public const OPERATOR_RIGHT = 2;\n\n    public function __construct(\n        private Parser $parser,\n        private Environment $env,\n    ) {\n        trigger_deprecation('twig/twig', '3.21', 'Class \"%s\" is deprecated, use \"Parser::parseExpression()\" instead.', __CLASS__);\n    }\n\n    public function parseExpression($precedence = 0)\n    {\n        if (\\func_num_args() > 1) {\n            trigger_deprecation('twig/twig', '3.15', 'Passing a second argument ($allowArrow) to \"%s()\" is deprecated.', __METHOD__);\n        }\n\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated, use \"Parser::parseExpression()\" instead.', __METHOD__);\n\n        return $this->parser->parseExpression((int) $precedence);\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parsePrimaryExpression()\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        return $this->parseExpression();\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parseStringExpression()\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        return $this->parseExpression();\n    }\n\n    /**\n     * @deprecated since Twig 3.11, use parseExpression() instead\n     */\n    public function parseArrayExpression()\n    {\n        trigger_deprecation('twig/twig', '3.11', 'Calling \"%s()\" is deprecated, use \"parseExpression()\" instead.', __METHOD__);\n\n        return $this->parseExpression();\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parseSequenceExpression()\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        return $this->parseExpression();\n    }\n\n    /**\n     * @deprecated since Twig 3.11, use parseExpression() instead\n     */\n    public function parseHashExpression()\n    {\n        trigger_deprecation('twig/twig', '3.11', 'Calling \"%s()\" is deprecated, use \"parseExpression()\" instead.', __METHOD__);\n\n        return $this->parseExpression();\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parseMappingExpression()\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        return $this->parseExpression();\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parsePostfixExpression($node)\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        while (true) {\n            $token = $this->parser->getCurrentToken();\n            if ($token->test(Token::PUNCTUATION_TYPE)) {\n                if ('.' == $token->getValue() || '[' == $token->getValue()) {\n                    $node = $this->parseSubscriptExpression($node);\n                } elseif ('|' == $token->getValue()) {\n                    $node = $this->parseFilterExpression($node);\n                } else {\n                    break;\n                }\n            } else {\n                break;\n            }\n        }\n\n        return $node;\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parseSubscriptExpression($node)\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        $parsers = new \\ReflectionProperty($this->parser, 'parsers');\n\n        if ('.' === $this->parser->getStream()->next()->getValue()) {\n            return $parsers->getValue($this->parser)->getByClass(DotExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken());\n        }\n\n        return $parsers->getValue($this->parser)->getByClass(SquareBracketExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken());\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parseFilterExpression($node)\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        $this->parser->getStream()->next();\n\n        return $this->parseFilterExpressionRaw($node);\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parseFilterExpressionRaw($node)\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        $parsers = new \\ReflectionProperty($this->parser, 'parsers');\n\n        $op = $parsers->getValue($this->parser)->getByClass(FilterExpressionParser::class);\n        while (true) {\n            $node = $op->parse($this->parser, $node, $this->parser->getCurrentToken());\n            if (!$this->parser->getStream()->test(Token::OPERATOR_TYPE, '|')) {\n                break;\n            }\n            $this->parser->getStream()->next();\n        }\n\n        return $node;\n    }\n\n    /**\n     * Parses arguments.\n     *\n     * @return Node\n     *\n     * @throws SyntaxError\n     *\n     * @deprecated since Twig 3.19 Use Twig\\ExpressionParser\\Infix\\ArgumentsTrait::parseNamedArguments() instead\n     */\n    public function parseArguments()\n    {\n        trigger_deprecation('twig/twig', '3.19', \\sprintf('The \"%s()\" method is deprecated, use \"Twig\\ExpressionParser\\Infix\\ArgumentsTrait::parseNamedArguments()\" instead.', __METHOD__));\n\n        $parsePrimaryExpression = new \\ReflectionMethod($this->parser, 'parsePrimaryExpression');\n\n        $namedArguments = false;\n        $definition = false;\n        if (\\func_num_args() > 1) {\n            $definition = func_get_arg(1);\n        }\n        if (\\func_num_args() > 0) {\n            trigger_deprecation('twig/twig', '3.15', 'Passing arguments to \"%s()\" is deprecated.', __METHOD__);\n            $namedArguments = func_get_arg(0);\n        }\n\n        $args = [];\n        $stream = $this->parser->getStream();\n\n        $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');\n        $hasSpread = false;\n        while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {\n            if ($args) {\n                $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');\n\n                // if the comma above was a trailing comma, early exit the argument parse loop\n                if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {\n                    break;\n                }\n            }\n\n            if ($definition) {\n                $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');\n                $value = new ContextVariable($token->getValue(), $this->parser->getCurrentToken()->getLine());\n            } else {\n                if ($stream->nextIf(Token::SPREAD_TYPE)) {\n                    $hasSpread = true;\n                    $value = new SpreadUnary($this->parseExpression(), $stream->getCurrent()->getLine());\n                } elseif ($hasSpread) {\n                    throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext());\n                } else {\n                    $value = $this->parseExpression();\n                }\n            }\n\n            $name = null;\n            if ($namedArguments && (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || (!$definition && $token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':')))) {\n                if (!$value instanceof ContextVariable) {\n                    throw new SyntaxError(\\sprintf('A parameter name must be a string, \"%s\" given.', $value::class), $token->getLine(), $stream->getSourceContext());\n                }\n                $name = $value->getAttribute('name');\n\n                if ($definition) {\n                    $value = $parsePrimaryExpression->invoke($this->parser);\n\n                    if (!$this->checkConstantExpression($value)) {\n                        throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());\n                    }\n                } else {\n                    $value = $this->parseExpression();\n                }\n            }\n\n            if ($definition) {\n                if (null === $name) {\n                    $name = $value->getAttribute('name');\n                    $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());\n                    $value->setAttribute('is_implicit', true);\n                }\n                $args[$name] = $value;\n            } else {\n                if (null === $name) {\n                    $args[] = $value;\n                } else {\n                    $args[$name] = $value;\n                }\n            }\n        }\n        $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');\n\n        return new Nodes($args);\n    }\n\n    /**\n     * @deprecated since Twig 3.21, use \"AbstractTokenParser::parseAssignmentExpression()\" instead\n     */\n    public function parseAssignmentExpression()\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated, use \"AbstractTokenParser::parseAssignmentExpression()\" instead.', __METHOD__);\n\n        $stream = $this->parser->getStream();\n        $targets = [];\n        while (true) {\n            $token = $this->parser->getCurrentToken();\n            if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {\n                // in this context, string operators are variable names\n                $this->parser->getStream()->next();\n            } else {\n                $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to');\n            }\n            $targets[] = new AssignContextVariable($token->getValue(), $token->getLine());\n\n            if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {\n                break;\n            }\n        }\n\n        return new Nodes($targets);\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function parseMultitargetExpression()\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        $targets = [];\n        while (true) {\n            $targets[] = $this->parseExpression();\n            if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) {\n                break;\n            }\n        }\n\n        return new Nodes($targets);\n    }\n\n    // checks that the node only contains \"constant\" elements\n    // to be removed in 4.0\n    private function checkConstantExpression(Node $node): bool\n    {\n        if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression\n            || $node instanceof NegUnary || $node instanceof PosUnary\n        )) {\n            return false;\n        }\n\n        foreach ($node as $n) {\n            if (!$this->checkConstantExpression($n)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * @deprecated since Twig 3.19 Use Twig\\ExpressionParser\\Infix\\ArgumentsTrait::parseNamedArguments() instead\n     */\n    public function parseOnlyArguments()\n    {\n        trigger_deprecation('twig/twig', '3.19', \\sprintf('The \"%s()\" method is deprecated, use \"Twig\\ExpressionParser\\Infix\\ArgumentsTrait::parseNamedArguments()\" instead.', __METHOD__));\n\n        return $this->parseArguments();\n    }\n}\n"
  },
  {
    "path": "src/Extension/AbstractExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nabstract class AbstractExtension implements LastModifiedExtensionInterface\n{\n    public function getTokenParsers()\n    {\n        return [];\n    }\n\n    public function getNodeVisitors()\n    {\n        return [];\n    }\n\n    public function getFilters()\n    {\n        return [];\n    }\n\n    public function getTests()\n    {\n        return [];\n    }\n\n    public function getFunctions()\n    {\n        return [];\n    }\n\n    public function getOperators()\n    {\n        return [[], []];\n    }\n\n    public function getExpressionParsers(): array\n    {\n        return [];\n    }\n\n    public function getLastModified(): int\n    {\n        $filename = (new \\ReflectionClass($this))->getFileName();\n        if (!is_file($filename)) {\n            return 0;\n        }\n\n        $lastModified = filemtime($filename);\n\n        // Track modifications of the runtime class if it exists and follows the naming convention\n        if (str_ends_with($filename, 'Extension.php') && is_file($filename = substr($filename, 0, -13).'Runtime.php')) {\n            $lastModified = max($lastModified, filemtime($filename));\n        }\n\n        return $lastModified;\n    }\n}\n"
  },
  {
    "path": "src/Extension/AttributeExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\Attribute\\AsTwigFilter;\nuse Twig\\Attribute\\AsTwigFunction;\nuse Twig\\Attribute\\AsTwigTest;\nuse Twig\\Environment;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\n/**\n * Define Twig filters, functions, and tests with PHP attributes.\n *\n * @author Jérôme Tamarelle <jerome@tamarelle.net>\n */\nfinal class AttributeExtension extends AbstractExtension\n{\n    private array $filters;\n    private array $functions;\n    private array $tests;\n\n    /**\n     * Use a runtime class using PHP attributes to define filters, functions, and tests.\n     *\n     * @param class-string $class\n     */\n    public function __construct(private string $class)\n    {\n    }\n\n    /**\n     * @return class-string\n     */\n    public function getClass(): string\n    {\n        return $this->class;\n    }\n\n    public function getFilters(): array\n    {\n        if (!isset($this->filters)) {\n            $this->initFromAttributes();\n        }\n\n        return $this->filters;\n    }\n\n    public function getFunctions(): array\n    {\n        if (!isset($this->functions)) {\n            $this->initFromAttributes();\n        }\n\n        return $this->functions;\n    }\n\n    public function getTests(): array\n    {\n        if (!isset($this->tests)) {\n            $this->initFromAttributes();\n        }\n\n        return $this->tests;\n    }\n\n    public function getLastModified(): int\n    {\n        return max(\n            filemtime(__FILE__),\n            is_file($filename = (new \\ReflectionClass($this->getClass()))->getFileName()) ? filemtime($filename) : 0,\n        );\n    }\n\n    private function initFromAttributes(): void\n    {\n        $filters = $functions = $tests = [];\n        $reflectionClass = new \\ReflectionClass($this->getClass());\n        foreach ($reflectionClass->getMethods() as $method) {\n            foreach ($method->getAttributes(AsTwigFilter::class) as $reflectionAttribute) {\n                /** @var AsTwigFilter $attribute */\n                $attribute = $reflectionAttribute->newInstance();\n\n                $callable = new TwigFilter($attribute->name, [$reflectionClass->name, $method->getName()], [\n                    'needs_context' => $attribute->needsContext ?? false,\n                    'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method),\n                    'needs_charset' => $attribute->needsCharset ?? false,\n                    'is_variadic' => $method->isVariadic(),\n                    'is_safe' => $attribute->isSafe,\n                    'is_safe_callback' => $attribute->isSafeCallback,\n                    'pre_escape' => $attribute->preEscape,\n                    'preserves_safety' => $attribute->preservesSafety,\n                    'deprecation_info' => $attribute->deprecationInfo,\n                ]);\n\n                if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) {\n                    throw new \\LogicException(\\sprintf('\"%s::%s()\" needs at least %d arguments to be used AsTwigFilter, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));\n                }\n\n                $filters[$attribute->name] = $callable;\n            }\n\n            foreach ($method->getAttributes(AsTwigFunction::class) as $reflectionAttribute) {\n                /** @var AsTwigFunction $attribute */\n                $attribute = $reflectionAttribute->newInstance();\n\n                $callable = new TwigFunction($attribute->name, [$reflectionClass->name, $method->getName()], [\n                    'needs_context' => $attribute->needsContext ?? false,\n                    'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method),\n                    'needs_charset' => $attribute->needsCharset ?? false,\n                    'is_variadic' => $method->isVariadic(),\n                    'is_safe' => $attribute->isSafe,\n                    'is_safe_callback' => $attribute->isSafeCallback,\n                    'deprecation_info' => $attribute->deprecationInfo,\n                ]);\n\n                if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) {\n                    throw new \\LogicException(\\sprintf('\"%s::%s()\" needs at least %d arguments to be used AsTwigFunction, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));\n                }\n\n                $functions[$attribute->name] = $callable;\n            }\n\n            foreach ($method->getAttributes(AsTwigTest::class) as $reflectionAttribute) {\n                /** @var AsTwigTest $attribute */\n                $attribute = $reflectionAttribute->newInstance();\n\n                $callable = new TwigTest($attribute->name, [$reflectionClass->name, $method->getName()], [\n                    'needs_context' => $attribute->needsContext ?? false,\n                    'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method),\n                    'needs_charset' => $attribute->needsCharset ?? false,\n                    'is_variadic' => $method->isVariadic(),\n                    'deprecation_info' => $attribute->deprecationInfo,\n                ]);\n\n                if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) {\n                    throw new \\LogicException(\\sprintf('\"%s::%s()\" needs at least %d arguments to be used AsTwigTest, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));\n                }\n\n                $tests[$attribute->name] = $callable;\n            }\n        }\n\n        // Assign all at the end to avoid inconsistent state in case of exception\n        $this->filters = array_values($filters);\n        $this->functions = array_values($functions);\n        $this->tests = array_values($tests);\n    }\n\n    /**\n     * Detect if the first argument of the method is the environment.\n     */\n    private function needsEnvironment(\\ReflectionFunctionAbstract $function): bool\n    {\n        if (!$parameters = $function->getParameters()) {\n            return false;\n        }\n\n        return $parameters[0]->getType() instanceof \\ReflectionNamedType\n            && Environment::class === $parameters[0]->getType()->getName()\n            && !$parameters[0]->isVariadic();\n    }\n}\n"
  },
  {
    "path": "src/Extension/CoreExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\DeprecatedCallableInfo;\nuse Twig\\Environment;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\Infix\\ArrowExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\AssignmentExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\BinaryOperatorExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\ConditionalTernaryExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\DotExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\FilterExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\FunctionExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\IsExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\IsNotExpressionParser;\nuse Twig\\ExpressionParser\\Infix\\SquareBracketExpressionParser;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\PrecedenceChange;\nuse Twig\\ExpressionParser\\Prefix\\GroupingExpressionParser;\nuse Twig\\ExpressionParser\\Prefix\\LiteralExpressionParser;\nuse Twig\\ExpressionParser\\Prefix\\UnaryOperatorExpressionParser;\nuse Twig\\Markup;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\Binary\\AddBinary;\nuse Twig\\Node\\Expression\\Binary\\AndBinary;\nuse Twig\\Node\\Expression\\Binary\\BitwiseAndBinary;\nuse Twig\\Node\\Expression\\Binary\\BitwiseOrBinary;\nuse Twig\\Node\\Expression\\Binary\\BitwiseXorBinary;\nuse Twig\\Node\\Expression\\Binary\\ConcatBinary;\nuse Twig\\Node\\Expression\\Binary\\DivBinary;\nuse Twig\\Node\\Expression\\Binary\\ElvisBinary;\nuse Twig\\Node\\Expression\\Binary\\EndsWithBinary;\nuse Twig\\Node\\Expression\\Binary\\EqualBinary;\nuse Twig\\Node\\Expression\\Binary\\FloorDivBinary;\nuse Twig\\Node\\Expression\\Binary\\GreaterBinary;\nuse Twig\\Node\\Expression\\Binary\\GreaterEqualBinary;\nuse Twig\\Node\\Expression\\Binary\\HasEveryBinary;\nuse Twig\\Node\\Expression\\Binary\\HasSomeBinary;\nuse Twig\\Node\\Expression\\Binary\\InBinary;\nuse Twig\\Node\\Expression\\Binary\\LessBinary;\nuse Twig\\Node\\Expression\\Binary\\LessEqualBinary;\nuse Twig\\Node\\Expression\\Binary\\MatchesBinary;\nuse Twig\\Node\\Expression\\Binary\\ModBinary;\nuse Twig\\Node\\Expression\\Binary\\MulBinary;\nuse Twig\\Node\\Expression\\Binary\\NotEqualBinary;\nuse Twig\\Node\\Expression\\Binary\\NotInBinary;\nuse Twig\\Node\\Expression\\Binary\\NotSameAsBinary;\nuse Twig\\Node\\Expression\\Binary\\NullCoalesceBinary;\nuse Twig\\Node\\Expression\\Binary\\OrBinary;\nuse Twig\\Node\\Expression\\Binary\\PowerBinary;\nuse Twig\\Node\\Expression\\Binary\\RangeBinary;\nuse Twig\\Node\\Expression\\Binary\\SameAsBinary;\nuse Twig\\Node\\Expression\\Binary\\SpaceshipBinary;\nuse Twig\\Node\\Expression\\Binary\\StartsWithBinary;\nuse Twig\\Node\\Expression\\Binary\\SubBinary;\nuse Twig\\Node\\Expression\\Binary\\XorBinary;\nuse Twig\\Node\\Expression\\BlockReferenceExpression;\nuse Twig\\Node\\Expression\\Filter\\DefaultFilter;\nuse Twig\\Node\\Expression\\FunctionNode\\EnumCasesFunction;\nuse Twig\\Node\\Expression\\FunctionNode\\EnumFunction;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Expression\\ParentExpression;\nuse Twig\\Node\\Expression\\Test\\ConstantTest;\nuse Twig\\Node\\Expression\\Test\\DefinedTest;\nuse Twig\\Node\\Expression\\Test\\DivisiblebyTest;\nuse Twig\\Node\\Expression\\Test\\EvenTest;\nuse Twig\\Node\\Expression\\Test\\NullTest;\nuse Twig\\Node\\Expression\\Test\\OddTest;\nuse Twig\\Node\\Expression\\Test\\SameasTest;\nuse Twig\\Node\\Expression\\Test\\TrueTest;\nuse Twig\\Node\\Expression\\Unary\\NegUnary;\nuse Twig\\Node\\Expression\\Unary\\NotUnary;\nuse Twig\\Node\\Expression\\Unary\\PosUnary;\nuse Twig\\Node\\Expression\\Unary\\SpreadUnary;\nuse Twig\\Node\\Node;\nuse Twig\\Parser;\nuse Twig\\Sandbox\\SecurityNotAllowedMethodError;\nuse Twig\\Sandbox\\SecurityNotAllowedPropertyError;\nuse Twig\\Source;\nuse Twig\\Template;\nuse Twig\\TemplateWrapper;\nuse Twig\\TokenParser\\ApplyTokenParser;\nuse Twig\\TokenParser\\BlockTokenParser;\nuse Twig\\TokenParser\\DeprecatedTokenParser;\nuse Twig\\TokenParser\\DoTokenParser;\nuse Twig\\TokenParser\\EmbedTokenParser;\nuse Twig\\TokenParser\\ExtendsTokenParser;\nuse Twig\\TokenParser\\FlushTokenParser;\nuse Twig\\TokenParser\\ForTokenParser;\nuse Twig\\TokenParser\\FromTokenParser;\nuse Twig\\TokenParser\\GuardTokenParser;\nuse Twig\\TokenParser\\IfTokenParser;\nuse Twig\\TokenParser\\ImportTokenParser;\nuse Twig\\TokenParser\\IncludeTokenParser;\nuse Twig\\TokenParser\\MacroTokenParser;\nuse Twig\\TokenParser\\SetTokenParser;\nuse Twig\\TokenParser\\TypesTokenParser;\nuse Twig\\TokenParser\\UseTokenParser;\nuse Twig\\TokenParser\\WithTokenParser;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\nuse Twig\\Util\\CallableArgumentsExtractor;\n\nfinal class CoreExtension extends AbstractExtension\n{\n    public const ARRAY_LIKE_CLASSES = [\n        'ArrayIterator',\n        'ArrayObject',\n        'CachingIterator',\n        'RecursiveArrayIterator',\n        'RecursiveCachingIterator',\n        'SplDoublyLinkedList',\n        'SplFixedArray',\n        'SplObjectStorage',\n        'SplQueue',\n        'SplStack',\n        'WeakMap',\n    ];\n\n    private const DEFAULT_TRIM_CHARS = \" \\t\\n\\r\\0\\x0B\";\n\n    private $dateFormats = ['F j, Y H:i', '%d days'];\n    private $numberFormat = [0, '.', ','];\n    private $timezone;\n\n    /**\n     * Sets the default format to be used by the date filter.\n     *\n     * @param string|null $format             The default date format string\n     * @param string|null $dateIntervalFormat The default date interval format string\n     */\n    public function setDateFormat($format = null, $dateIntervalFormat = null)\n    {\n        if (null !== $format) {\n            $this->dateFormats[0] = $format;\n        }\n\n        if (null !== $dateIntervalFormat) {\n            $this->dateFormats[1] = $dateIntervalFormat;\n        }\n    }\n\n    /**\n     * Gets the default format to be used by the date filter.\n     *\n     * @return array The default date format string and the default date interval format string\n     */\n    public function getDateFormat()\n    {\n        return $this->dateFormats;\n    }\n\n    /**\n     * Sets the default timezone to be used by the date filter.\n     *\n     * @param \\DateTimeZone|string $timezone The default timezone string or a \\DateTimeZone object\n     */\n    public function setTimezone($timezone)\n    {\n        $this->timezone = $timezone instanceof \\DateTimeZone ? $timezone : new \\DateTimeZone($timezone);\n    }\n\n    /**\n     * Gets the default timezone to be used by the date filter.\n     *\n     * @return \\DateTimeZone The default timezone currently in use\n     */\n    public function getTimezone()\n    {\n        if (null === $this->timezone) {\n            $this->timezone = new \\DateTimeZone(date_default_timezone_get());\n        }\n\n        return $this->timezone;\n    }\n\n    /**\n     * Sets the default format to be used by the number_format filter.\n     *\n     * @param int    $decimal      the number of decimal places to use\n     * @param string $decimalPoint the character(s) to use for the decimal point\n     * @param string $thousandSep  the character(s) to use for the thousands separator\n     */\n    public function setNumberFormat($decimal, $decimalPoint, $thousandSep)\n    {\n        $this->numberFormat = [$decimal, $decimalPoint, $thousandSep];\n    }\n\n    /**\n     * Get the default format used by the number_format filter.\n     *\n     * @return array The arguments for number_format()\n     */\n    public function getNumberFormat()\n    {\n        return $this->numberFormat;\n    }\n\n    public function getTokenParsers(): array\n    {\n        return [\n            new ApplyTokenParser(),\n            new ForTokenParser(),\n            new IfTokenParser(),\n            new ExtendsTokenParser(),\n            new IncludeTokenParser(),\n            new BlockTokenParser(),\n            new UseTokenParser(),\n            new MacroTokenParser(),\n            new ImportTokenParser(),\n            new FromTokenParser(),\n            new SetTokenParser(),\n            new TypesTokenParser(),\n            new FlushTokenParser(),\n            new DoTokenParser(),\n            new EmbedTokenParser(),\n            new WithTokenParser(),\n            new DeprecatedTokenParser(),\n            new GuardTokenParser(),\n        ];\n    }\n\n    public function getFilters(): array\n    {\n        return [\n            // formatting filters\n            new TwigFilter('date', [$this, 'formatDate']),\n            new TwigFilter('date_modify', [$this, 'modifyDate']),\n            new TwigFilter('format', [self::class, 'sprintf']),\n            new TwigFilter('replace', [self::class, 'replace']),\n            new TwigFilter('number_format', [$this, 'formatNumber']),\n            new TwigFilter('abs', 'abs'),\n            new TwigFilter('round', [self::class, 'round']),\n\n            // encoding\n            new TwigFilter('url_encode', [self::class, 'urlencode']),\n            new TwigFilter('json_encode', 'json_encode'),\n            new TwigFilter('convert_encoding', [self::class, 'convertEncoding']),\n\n            // string filters\n            new TwigFilter('title', [self::class, 'titleCase'], ['needs_charset' => true]),\n            new TwigFilter('capitalize', [self::class, 'capitalize'], ['needs_charset' => true]),\n            new TwigFilter('upper', [self::class, 'upper'], ['needs_charset' => true]),\n            new TwigFilter('lower', [self::class, 'lower'], ['needs_charset' => true]),\n            new TwigFilter('striptags', [self::class, 'striptags']),\n            new TwigFilter('trim', [self::class, 'trim']),\n            new TwigFilter('nl2br', [self::class, 'nl2br'], ['pre_escape' => 'html', 'is_safe' => ['html']]),\n            new TwigFilter('spaceless', [self::class, 'spaceless'], ['is_safe' => ['html'], 'deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.12')]),\n\n            // array helpers\n            new TwigFilter('join', [self::class, 'join']),\n            new TwigFilter('split', [self::class, 'split'], ['needs_charset' => true]),\n            new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true]),\n            new TwigFilter('merge', [self::class, 'merge']),\n            new TwigFilter('batch', [self::class, 'batch']),\n            new TwigFilter('column', [self::class, 'column']),\n            new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true]),\n            new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true]),\n            new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true]),\n            new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true]),\n\n            // string/array filters\n            new TwigFilter('reverse', [self::class, 'reverse'], ['needs_charset' => true]),\n            new TwigFilter('shuffle', [self::class, 'shuffle'], ['needs_charset' => true]),\n            new TwigFilter('length', [self::class, 'length'], ['needs_charset' => true]),\n            new TwigFilter('slice', [self::class, 'slice'], ['needs_charset' => true]),\n            new TwigFilter('first', [self::class, 'first'], ['needs_charset' => true]),\n            new TwigFilter('last', [self::class, 'last'], ['needs_charset' => true]),\n\n            // iteration and runtime\n            new TwigFilter('default', [self::class, 'default'], ['node_class' => DefaultFilter::class]),\n            new TwigFilter('keys', [self::class, 'keys']),\n            new TwigFilter('invoke', [self::class, 'invoke']),\n        ];\n    }\n\n    public function getFunctions(): array\n    {\n        return [\n            new TwigFunction('parent', null, ['parser_callable' => [self::class, 'parseParentFunction']]),\n            new TwigFunction('block', null, ['parser_callable' => [self::class, 'parseBlockFunction']]),\n            new TwigFunction('attribute', null, ['parser_callable' => [self::class, 'parseAttributeFunction']]),\n            new TwigFunction('max', 'max'),\n            new TwigFunction('min', 'min'),\n            new TwigFunction('range', 'range'),\n            new TwigFunction('constant', [self::class, 'constant']),\n            new TwigFunction('cycle', [self::class, 'cycle']),\n            new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]),\n            new TwigFunction('date', [$this, 'convertDate']),\n            new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]),\n            new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true, 'is_safe' => ['all']]),\n            new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]),\n            new TwigFunction('enum', [self::class, 'enum'], ['node_class' => EnumFunction::class]),\n        ];\n    }\n\n    public function getTests(): array\n    {\n        return [\n            new TwigTest('even', null, ['node_class' => EvenTest::class]),\n            new TwigTest('odd', null, ['node_class' => OddTest::class]),\n            new TwigTest('defined', null, ['node_class' => DefinedTest::class]),\n            new TwigTest('same as', null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]),\n            new TwigTest('none', null, ['node_class' => NullTest::class]),\n            new TwigTest('null', null, ['node_class' => NullTest::class]),\n            new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]),\n            new TwigTest('constant', null, ['node_class' => ConstantTest::class]),\n            new TwigTest('empty', [self::class, 'testEmpty']),\n            new TwigTest('iterable', 'is_iterable'),\n            new TwigTest('sequence', [self::class, 'testSequence']),\n            new TwigTest('mapping', [self::class, 'testMapping']),\n            new TwigTest('true', null, ['node_class' => TrueTest::class]),\n        ];\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return [];\n    }\n\n    public function getExpressionParsers(): array\n    {\n        return [\n            // unary operators\n            new UnaryOperatorExpressionParser(NotUnary::class, 'not', 50, new PrecedenceChange('twig/twig', '3.15', 70)),\n            new UnaryOperatorExpressionParser(SpreadUnary::class, '...', 512, description: 'Spread operator', operandPrecedence: 0),\n            new UnaryOperatorExpressionParser(NegUnary::class, '-', 500),\n            new UnaryOperatorExpressionParser(PosUnary::class, '+', 500),\n\n            // binary operators\n            new BinaryOperatorExpressionParser(ElvisBinary::class, '?:', 5, InfixAssociativity::Right, description: 'Elvis operator (a ?: b)', aliases: ['? :']),\n            new BinaryOperatorExpressionParser(NullCoalesceBinary::class, '??', 300, InfixAssociativity::Right, new PrecedenceChange('twig/twig', '3.15', 5), description: 'Null coalescing operator (a ?? b)'),\n            new BinaryOperatorExpressionParser(OrBinary::class, 'or', 10),\n            new BinaryOperatorExpressionParser(XorBinary::class, 'xor', 12),\n            new BinaryOperatorExpressionParser(AndBinary::class, 'and', 15),\n            new BinaryOperatorExpressionParser(BitwiseOrBinary::class, 'b-or', 16),\n            new BinaryOperatorExpressionParser(BitwiseXorBinary::class, 'b-xor', 17),\n            new BinaryOperatorExpressionParser(BitwiseAndBinary::class, 'b-and', 18),\n            new BinaryOperatorExpressionParser(EqualBinary::class, '==', 20),\n            new BinaryOperatorExpressionParser(NotEqualBinary::class, '!=', 20),\n            new BinaryOperatorExpressionParser(SpaceshipBinary::class, '<=>', 20),\n            new BinaryOperatorExpressionParser(LessBinary::class, '<', 20),\n            new BinaryOperatorExpressionParser(GreaterBinary::class, '>', 20),\n            new BinaryOperatorExpressionParser(GreaterEqualBinary::class, '>=', 20),\n            new BinaryOperatorExpressionParser(LessEqualBinary::class, '<=', 20),\n            new BinaryOperatorExpressionParser(NotInBinary::class, 'not in', 20),\n            new BinaryOperatorExpressionParser(InBinary::class, 'in', 20),\n            new BinaryOperatorExpressionParser(MatchesBinary::class, 'matches', 20),\n            new BinaryOperatorExpressionParser(StartsWithBinary::class, 'starts with', 20),\n            new BinaryOperatorExpressionParser(EndsWithBinary::class, 'ends with', 20),\n            new BinaryOperatorExpressionParser(HasSomeBinary::class, 'has some', 20),\n            new BinaryOperatorExpressionParser(HasEveryBinary::class, 'has every', 20),\n            new BinaryOperatorExpressionParser(SameAsBinary::class, '===', 20),\n            new BinaryOperatorExpressionParser(NotSameAsBinary::class, '!==', 20),\n            new BinaryOperatorExpressionParser(RangeBinary::class, '..', 25),\n            new BinaryOperatorExpressionParser(AddBinary::class, '+', 30),\n            new BinaryOperatorExpressionParser(SubBinary::class, '-', 30),\n            new BinaryOperatorExpressionParser(ConcatBinary::class, '~', 40, precedenceChange: new PrecedenceChange('twig/twig', '3.15', 27)),\n            new BinaryOperatorExpressionParser(MulBinary::class, '*', 60),\n            new BinaryOperatorExpressionParser(DivBinary::class, '/', 60),\n            new BinaryOperatorExpressionParser(FloorDivBinary::class, '//', 60, description: 'Floor division'),\n            new BinaryOperatorExpressionParser(ModBinary::class, '%', 60),\n            new BinaryOperatorExpressionParser(PowerBinary::class, '**', 200, InfixAssociativity::Right, description: 'Exponentiation operator'),\n\n            // ternary operator\n            new ConditionalTernaryExpressionParser(),\n\n            // assignment operator\n            new AssignmentExpressionParser('='),\n\n            // Twig callables\n            new IsExpressionParser(),\n            new IsNotExpressionParser(),\n            new FilterExpressionParser(),\n            new FunctionExpressionParser(),\n\n            // get attribute operators\n            new DotExpressionParser(),\n            new SquareBracketExpressionParser(),\n\n            // group expression\n            new GroupingExpressionParser(),\n\n            // arrow function\n            new ArrowExpressionParser(),\n\n            // all literals\n            new LiteralExpressionParser(),\n        ];\n    }\n\n    /**\n     * Cycles over a sequence.\n     *\n     * @param array|\\ArrayAccess $values   A non-empty sequence of values\n     * @param int<0, max>        $position The position of the value to return in the cycle\n     *\n     * @return mixed The value at the given position in the sequence, wrapping around as needed\n     *\n     * @internal\n     */\n    public static function cycle($values, $position): mixed\n    {\n        if (!\\is_array($values)) {\n            if (!$values instanceof \\ArrayAccess) {\n                throw new RuntimeError('The \"cycle\" function expects an array or \"ArrayAccess\" as first argument.');\n            }\n\n            if (!is_countable($values)) {\n                // To be uncommented in 4.0\n                // throw new RuntimeError('The \"cycle\" function expects a countable sequence as first argument.');\n\n                trigger_deprecation('twig/twig', '3.12', 'Passing a non-countable sequence of values to \"%s()\" is deprecated.', __METHOD__);\n\n                $values = self::toArray($values, false);\n            }\n        }\n\n        if (!$count = \\count($values)) {\n            throw new RuntimeError('The \"cycle\" function expects a non-empty sequence.');\n        }\n\n        return $values[$position % $count];\n    }\n\n    /**\n     * Returns a random value depending on the supplied parameter type:\n     * - a random item from a \\Traversable or array\n     * - a random character from a string\n     * - a random integer between 0 and the integer parameter.\n     *\n     * @param \\Traversable|array|int|float|string $values The values to pick a random item from\n     * @param int|null                            $max    Maximum value used when $values is an int\n     *\n     * @return mixed A random value from the given sequence\n     *\n     * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is)\n     *\n     * @internal\n     */\n    public static function random(string $charset, $values = null, $max = null)\n    {\n        if (null === $values) {\n            return null === $max ? mt_rand() : mt_rand(0, (int) $max);\n        }\n\n        if (\\is_int($values) || \\is_float($values)) {\n            if (null === $max) {\n                if ($values < 0) {\n                    $max = 0;\n                    $min = $values;\n                } else {\n                    $max = $values;\n                    $min = 0;\n                }\n            } else {\n                $min = $values;\n            }\n\n            return mt_rand((int) $min, (int) $max);\n        }\n\n        if (\\is_string($values)) {\n            if ('' === $values) {\n                return '';\n            }\n\n            if ('UTF-8' !== $charset) {\n                $values = self::convertEncoding($values, 'UTF-8', $charset);\n            }\n\n            // unicode version of str_split()\n            // split at all positions, but not after the start and not before the end\n            $values = preg_split('/(?<!^)(?!$)/u', $values);\n\n            if ('UTF-8' !== $charset) {\n                foreach ($values as $i => $value) {\n                    $values[$i] = self::convertEncoding($value, $charset, 'UTF-8');\n                }\n            }\n        }\n\n        if (!is_iterable($values)) {\n            return $values;\n        }\n\n        $values = self::toArray($values);\n\n        if (0 === \\count($values)) {\n            throw new RuntimeError('The \"random\" function cannot pick from an empty sequence or mapping.');\n        }\n\n        return $values[array_rand($values, 1)];\n    }\n\n    /**\n     * Formats a date.\n     *\n     *   {{ post.published_at|date(\"m/d/Y\") }}\n     *\n     * @param \\DateTimeInterface|\\DateInterval|string|int|null $date     A date, a timestamp or null to use the current time\n     * @param string|null                                      $format   The target format, null to use the default\n     * @param \\DateTimeZone|string|false|null                  $timezone The target timezone, null to use the default, false to leave unchanged\n     */\n    public function formatDate($date, $format = null, $timezone = null): string\n    {\n        if (null === $format) {\n            $formats = $this->getDateFormat();\n            $format = $date instanceof \\DateInterval ? $formats[1] : $formats[0];\n        }\n\n        if ($date instanceof \\DateInterval) {\n            return $date->format($format);\n        }\n\n        return $this->convertDate($date, $timezone)->format($format);\n    }\n\n    /**\n     * Returns a new date object modified.\n     *\n     *   {{ post.published_at|date_modify(\"-1day\")|date(\"m/d/Y\") }}\n     *\n     * @param \\DateTimeInterface|string|int|null $date     A date, a timestamp or null to use the current time\n     * @param string                             $modifier A modifier string\n     *\n     * @return \\DateTime|\\DateTimeImmutable\n     *\n     * @internal\n     */\n    public function modifyDate($date, $modifier)\n    {\n        return $this->convertDate($date, false)->modify($modifier);\n    }\n\n    /**\n     * Returns a formatted string.\n     *\n     * @param string|null $format\n     *\n     * @internal\n     */\n    public static function sprintf($format, ...$values): string\n    {\n        return \\sprintf($format ?? '', ...$values);\n    }\n\n    /**\n     * @internal\n     */\n    public static function dateConverter(Environment $env, $date, $format = null, $timezone = null): string\n    {\n        return $env->getExtension(self::class)->formatDate($date, $format, $timezone);\n    }\n\n    /**\n     * Converts an input to a \\DateTime instance.\n     *\n     *    {% if date(user.created_at) < date('+2days') %}\n     *      {# do something #}\n     *    {% endif %}\n     *\n     * @param \\DateTimeInterface|string|int|null $date     A date, a timestamp or null to use the current time\n     * @param \\DateTimeZone|string|false|null    $timezone The target timezone, null to use the default, false to leave unchanged\n     *\n     * @return \\DateTime|\\DateTimeImmutable\n     */\n    public function convertDate($date = null, $timezone = null)\n    {\n        // determine the timezone\n        if (false !== $timezone) {\n            if (null === $timezone) {\n                $timezone = $this->getTimezone();\n            } elseif (!$timezone instanceof \\DateTimeZone) {\n                $timezone = new \\DateTimeZone($timezone);\n            }\n        }\n\n        // immutable dates\n        if ($date instanceof \\DateTimeImmutable) {\n            return false !== $timezone ? $date->setTimezone($timezone) : $date;\n        }\n\n        if ($date instanceof \\DateTime) {\n            $date = clone $date;\n            if (false !== $timezone) {\n                $date->setTimezone($timezone);\n            }\n\n            return $date;\n        }\n\n        if (null === $date || 'now' === $date) {\n            if (null === $date) {\n                $date = 'now';\n            }\n\n            return new \\DateTime($date, false !== $timezone ? $timezone : $this->getTimezone());\n        }\n\n        $asString = (string) $date;\n        if (ctype_digit($asString) || ('' !== $asString && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {\n            $date = new \\DateTime('@'.$date);\n        } else {\n            $date = new \\DateTime($date);\n        }\n\n        if (false !== $timezone) {\n            $date->setTimezone($timezone);\n        }\n\n        return $date;\n    }\n\n    /**\n     * Replaces strings within a string.\n     *\n     * @param string|null        $str  String to replace in\n     * @param array|\\Traversable $from Replace values\n     *\n     * @internal\n     */\n    public static function replace($str, $from): string\n    {\n        if (!is_iterable($from)) {\n            throw new RuntimeError(\\sprintf('The \"replace\" filter expects a sequence or a mapping, got \"%s\".', get_debug_type($from)));\n        }\n\n        return strtr($str ?? '', self::toArray($from));\n    }\n\n    /**\n     * Rounds a number.\n     *\n     * @param int|float|string|null   $value     The value to round\n     * @param int|float               $precision The rounding precision\n     * @param 'common'|'ceil'|'floor' $method    The method to use for rounding\n     *\n     * @return float The rounded number\n     *\n     * @internal\n     */\n    public static function round($value, $precision = 0, $method = 'common')\n    {\n        $value = (float) $value;\n\n        if ('common' === $method) {\n            return round($value, $precision);\n        }\n\n        if ('ceil' !== $method && 'floor' !== $method) {\n            throw new RuntimeError('The \"round\" filter only supports the \"common\", \"ceil\", and \"floor\" methods.');\n        }\n\n        return $method($value * 10 ** $precision) / 10 ** $precision;\n    }\n\n    /**\n     * Formats a number.\n     *\n     * All of the formatting options can be left null, in that case the defaults will\n     * be used. Supplying any of the parameters will override the defaults set in the\n     * environment object.\n     *\n     * @param mixed       $number       A float/int/string of the number to format\n     * @param int|null    $decimal      the number of decimal points to display\n     * @param string|null $decimalPoint the character(s) to use for the decimal point\n     * @param string|null $thousandSep  the character(s) to use for the thousands separator\n     */\n    public function formatNumber($number, $decimal = null, $decimalPoint = null, $thousandSep = null): string\n    {\n        $defaults = $this->getNumberFormat();\n        if (null === $decimal) {\n            $decimal = $defaults[0];\n        }\n\n        if (null === $decimalPoint) {\n            $decimalPoint = $defaults[1];\n        }\n\n        if (null === $thousandSep) {\n            $thousandSep = $defaults[2];\n        }\n\n        return number_format((float) $number, $decimal, $decimalPoint, $thousandSep);\n    }\n\n    /**\n     * URL encodes (RFC 3986) a string as a path segment or an array as a query string.\n     *\n     * @param string|array|null $url A URL or an array of query parameters\n     *\n     * @internal\n     */\n    public static function urlencode($url): string\n    {\n        if (\\is_array($url)) {\n            return http_build_query($url, '', '&', \\PHP_QUERY_RFC3986);\n        }\n\n        return rawurlencode($url ?? '');\n    }\n\n    /**\n     * Merges any number of arrays or Traversable objects.\n     *\n     *  {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}\n     *\n     *  {% set items = items|merge({ 'peugeot': 'car' }, { 'banana': 'fruit' }) %}\n     *\n     *  {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'banana': 'fruit' } #}\n     *\n     * @param array|\\Traversable ...$arrays Any number of arrays or Traversable objects to merge\n     *\n     * @internal\n     */\n    public static function merge(...$arrays): array\n    {\n        $result = [];\n\n        foreach ($arrays as $argNumber => $array) {\n            if (!is_iterable($array)) {\n                throw new RuntimeError(\\sprintf('The \"merge\" filter expects a sequence or a mapping, got \"%s\" for argument %d.', get_debug_type($array), $argNumber + 1));\n            }\n\n            $result = array_merge($result, self::toArray($array));\n        }\n\n        return $result;\n    }\n\n    /**\n     * Slices a variable.\n     *\n     * @param mixed $item         A variable\n     * @param int   $start        Start of the slice\n     * @param int   $length       Size of the slice\n     * @param bool  $preserveKeys Whether to preserve key or not (when the input is an array)\n     *\n     * @return mixed The sliced variable\n     *\n     * @internal\n     */\n    public static function slice(string $charset, $item, $start, $length = null, $preserveKeys = false)\n    {\n        if ($item instanceof \\Traversable) {\n            while ($item instanceof \\IteratorAggregate) {\n                $item = $item->getIterator();\n            }\n\n            if ($start >= 0 && $length >= 0 && $item instanceof \\Iterator) {\n                try {\n                    return iterator_to_array(new \\LimitIterator($item, $start, $length ?? -1), $preserveKeys);\n                } catch (\\OutOfBoundsException $e) {\n                    return [];\n                }\n            }\n\n            $item = iterator_to_array($item, $preserveKeys);\n        }\n\n        if (\\is_array($item)) {\n            return \\array_slice($item, $start, $length, $preserveKeys);\n        }\n\n        return mb_substr((string) $item, $start, $length, $charset);\n    }\n\n    /**\n     * Returns the first element of the item.\n     *\n     * @param mixed $item A variable\n     *\n     * @return mixed The first element of the item\n     *\n     * @internal\n     */\n    public static function first(string $charset, $item)\n    {\n        $elements = self::slice($charset, $item, 0, 1, false);\n\n        return \\is_string($elements) ? $elements : current($elements);\n    }\n\n    /**\n     * Returns the last element of the item.\n     *\n     * @param mixed $item A variable\n     *\n     * @return mixed The last element of the item\n     *\n     * @internal\n     */\n    public static function last(string $charset, $item)\n    {\n        $elements = self::slice($charset, $item, -1, 1, false);\n\n        return \\is_string($elements) ? $elements : current($elements);\n    }\n\n    /**\n     * Joins the values to a string.\n     *\n     * The separators between elements are empty strings per default, you can define them with the optional parameters.\n     *\n     *  {{ [1, 2, 3]|join(', ', ' and ') }}\n     *  {# returns 1, 2 and 3 #}\n     *\n     *  {{ [1, 2, 3]|join('|') }}\n     *  {# returns 1|2|3 #}\n     *\n     *  {{ [1, 2, 3]|join }}\n     *  {# returns 123 #}\n     *\n     * @param iterable|array|string|float|int|bool|null $value An array\n     * @param string                                    $glue  The separator\n     * @param string|null                               $and   The separator for the last pair\n     *\n     * @internal\n     */\n    public static function join($value, $glue = '', $and = null): string\n    {\n        if (!is_iterable($value)) {\n            $value = (array) $value;\n        }\n\n        $value = self::toArray($value, false);\n\n        if (0 === \\count($value)) {\n            return '';\n        }\n\n        if (null === $and || $and === $glue) {\n            return implode($glue, $value);\n        }\n\n        if (1 === \\count($value)) {\n            return $value[0];\n        }\n\n        return implode($glue, \\array_slice($value, 0, -1)).$and.$value[\\count($value) - 1];\n    }\n\n    /**\n     * Splits the string into an array.\n     *\n     *  {{ \"one,two,three\"|split(',') }}\n     *  {# returns [one, two, three] #}\n     *\n     *  {{ \"one,two,three,four,five\"|split(',', 3) }}\n     *  {# returns [one, two, \"three,four,five\"] #}\n     *\n     *  {{ \"123\"|split('') }}\n     *  {# returns [1, 2, 3] #}\n     *\n     *  {{ \"aabbcc\"|split('', 2) }}\n     *  {# returns [aa, bb, cc] #}\n     *\n     * @param string|null $value     A string\n     * @param string      $delimiter The delimiter\n     * @param int|null    $limit     The limit\n     *\n     * @internal\n     */\n    public static function split(string $charset, $value, $delimiter, $limit = null): array\n    {\n        $value = $value ?? '';\n\n        if ('' !== $delimiter) {\n            return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);\n        }\n\n        if ($limit <= 1) {\n            return preg_split('/(?<!^)(?!$)/u', $value);\n        }\n\n        $length = mb_strlen($value, $charset);\n        if ($length < $limit) {\n            return [$value];\n        }\n\n        $r = [];\n        for ($i = 0; $i < $length; $i += $limit) {\n            $r[] = mb_substr($value, $i, $limit, $charset);\n        }\n\n        return $r;\n    }\n\n    /**\n     * @internal\n     */\n    public static function default($value, $default = '')\n    {\n        if (self::testEmpty($value)) {\n            return $default;\n        }\n\n        return $value;\n    }\n\n    /**\n     * Returns the keys for the given array.\n     *\n     * It is useful when you want to iterate over the keys of an array:\n     *\n     *  {% for key in array|keys %}\n     *      {# ... #}\n     *  {% endfor %}\n     *\n     * @internal\n     */\n    public static function keys($array): array\n    {\n        if ($array instanceof \\Traversable) {\n            while ($array instanceof \\IteratorAggregate) {\n                $array = $array->getIterator();\n            }\n\n            $keys = [];\n            if ($array instanceof \\Iterator) {\n                $array->rewind();\n                while ($array->valid()) {\n                    $keys[] = $array->key();\n                    $array->next();\n                }\n\n                return $keys;\n            }\n\n            foreach ($array as $key => $item) {\n                $keys[] = $key;\n            }\n\n            return $keys;\n        }\n\n        if (!\\is_array($array)) {\n            return [];\n        }\n\n        return array_keys($array);\n    }\n\n    /**\n     * Invokes a callable.\n     *\n     * @internal\n     */\n    public static function invoke(\\Closure $arrow, ...$arguments): mixed\n    {\n        return $arrow(...$arguments);\n    }\n\n    /**\n     * Reverses a variable.\n     *\n     * @param array|\\Traversable|string|null $item         An array, a \\Traversable instance, or a string\n     * @param bool                           $preserveKeys Whether to preserve key or not\n     *\n     * @return mixed The reversed input\n     *\n     * @internal\n     */\n    public static function reverse(string $charset, $item, $preserveKeys = false)\n    {\n        if ($item instanceof \\Traversable) {\n            return array_reverse(iterator_to_array($item), $preserveKeys);\n        }\n\n        if (\\is_array($item)) {\n            return array_reverse($item, $preserveKeys);\n        }\n\n        $string = (string) $item;\n\n        if ('UTF-8' !== $charset) {\n            $string = self::convertEncoding($string, 'UTF-8', $charset);\n        }\n\n        preg_match_all('/./us', $string, $matches);\n\n        $string = implode('', array_reverse($matches[0]));\n\n        if ('UTF-8' !== $charset) {\n            $string = self::convertEncoding($string, $charset, 'UTF-8');\n        }\n\n        return $string;\n    }\n\n    /**\n     * Shuffles an array, a \\Traversable instance, or a string.\n     * The function does not preserve keys.\n     *\n     * @param array|\\Traversable|string|null $item\n     *\n     * @internal\n     */\n    public static function shuffle(string $charset, $item)\n    {\n        if (\\is_string($item)) {\n            if ('UTF-8' !== $charset) {\n                $item = self::convertEncoding($item, 'UTF-8', $charset);\n            }\n\n            $item = preg_split('/(?<!^)(?!$)/u', $item, -1);\n            shuffle($item);\n            $item = implode('', $item);\n\n            if ('UTF-8' !== $charset) {\n                $item = self::convertEncoding($item, $charset, 'UTF-8');\n            }\n\n            return $item;\n        }\n\n        if (is_iterable($item)) {\n            $item = self::toArray($item, false);\n            shuffle($item);\n        }\n\n        return $item;\n    }\n\n    /**\n     * Sorts an array.\n     *\n     * @param array|\\Traversable $array\n     * @param ?\\Closure          $arrow\n     *\n     * @internal\n     */\n    public static function sort(Environment $env, $array, $arrow = null): array\n    {\n        if ($array instanceof \\Traversable) {\n            $array = iterator_to_array($array);\n        } elseif (!\\is_array($array)) {\n            throw new RuntimeError(\\sprintf('The \"sort\" filter expects a sequence or a mapping, got \"%s\".', get_debug_type($array)));\n        }\n\n        if (null !== $arrow) {\n            self::checkArrow($env, $arrow, 'sort', 'filter');\n\n            uasort($array, $arrow);\n        } else {\n            asort($array);\n        }\n\n        return $array;\n    }\n\n    /**\n     * @internal\n     */\n    public static function inFilter($value, $compare)\n    {\n        if ($value instanceof Markup) {\n            $value = (string) $value;\n        }\n        if ($compare instanceof Markup) {\n            $compare = (string) $compare;\n        }\n\n        if (\\is_string($compare)) {\n            if (\\is_string($value) || \\is_int($value) || \\is_float($value)) {\n                return '' === $value || str_contains($compare, (string) $value);\n            }\n\n            return false;\n        }\n\n        if (!is_iterable($compare)) {\n            return false;\n        }\n\n        if (\\is_object($value) || \\is_resource($value)) {\n            if (!\\is_array($compare)) {\n                foreach ($compare as $item) {\n                    if ($item === $value) {\n                        return true;\n                    }\n                }\n\n                return false;\n            }\n\n            return \\in_array($value, $compare, true);\n        }\n\n        foreach ($compare as $item) {\n            if (0 === self::compare($value, $item)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Compares two values using a more strict version of the PHP non-strict comparison operator.\n     *\n     * @see https://wiki.php.net/rfc/string_to_number_comparison\n     * @see https://wiki.php.net/rfc/trailing_whitespace_numerics\n     *\n     * @internal\n     */\n    public static function compare($a, $b)\n    {\n        // int <=> string\n        if (\\is_int($a) && \\is_string($b)) {\n            $bTrim = trim($b, \" \\t\\n\\r\\v\\f\");\n            if (!is_numeric($bTrim)) {\n                return (string) $a <=> $b;\n            }\n            if ((int) $bTrim == $bTrim) {\n                return $a <=> (int) $bTrim;\n            }\n\n            return (float) $a <=> (float) $bTrim;\n        }\n        if (\\is_string($a) && \\is_int($b)) {\n            $aTrim = trim($a, \" \\t\\n\\r\\v\\f\");\n            if (!is_numeric($aTrim)) {\n                return $a <=> (string) $b;\n            }\n            if ((int) $aTrim == $aTrim) {\n                return (int) $aTrim <=> $b;\n            }\n\n            return (float) $aTrim <=> (float) $b;\n        }\n\n        // float <=> string\n        if (\\is_float($a) && \\is_string($b)) {\n            if (is_nan($a)) {\n                return 1;\n            }\n            $bTrim = trim($b, \" \\t\\n\\r\\v\\f\");\n            if (!is_numeric($bTrim)) {\n                return (string) $a <=> $b;\n            }\n\n            return $a <=> (float) $bTrim;\n        }\n        if (\\is_string($a) && \\is_float($b)) {\n            if (is_nan($b)) {\n                return 1;\n            }\n            $aTrim = trim($a, \" \\t\\n\\r\\v\\f\");\n            if (!is_numeric($aTrim)) {\n                return $a <=> (string) $b;\n            }\n\n            return (float) $aTrim <=> $b;\n        }\n\n        // fallback to <=>\n        return $a <=> $b;\n    }\n\n    /**\n     * @throws RuntimeError When an invalid pattern is used\n     *\n     * @internal\n     */\n    public static function matches(string $regexp, ?string $str): int\n    {\n        set_error_handler(static function ($t, $m) use ($regexp) {\n            throw new RuntimeError(\\sprintf('Regexp \"%s\" passed to \"matches\" is not valid', $regexp).substr($m, 12));\n        });\n        try {\n            return preg_match($regexp, $str ?? '');\n        } finally {\n            restore_error_handler();\n        }\n    }\n\n    /**\n     * Returns a trimmed string.\n     *\n     * @param string|\\Stringable|null $string\n     * @param string|null             $characterMask\n     * @param string                  $side          left, right, or both\n     *\n     * @throws RuntimeError When an invalid trimming side is used\n     *\n     * @internal\n     */\n    public static function trim($string, $characterMask = null, $side = 'both'): string|\\Stringable\n    {\n        if (null === $characterMask) {\n            $characterMask = self::DEFAULT_TRIM_CHARS;\n        }\n\n        $trimmed = match ($side) {\n            'both' => trim($string ?? '', $characterMask),\n            'left' => ltrim($string ?? '', $characterMask),\n            'right' => rtrim($string ?? '', $characterMask),\n            default => throw new RuntimeError('Trimming side must be \"left\", \"right\" or \"both\".'),\n        };\n\n        // trimming a safe string with the default character mask always returns a safe string (independently of the context)\n        return $string instanceof Markup && self::DEFAULT_TRIM_CHARS === $characterMask ? new Markup($trimmed, $string->getCharset()) : $trimmed;\n    }\n\n    /**\n     * Inserts HTML line breaks before all newlines in a string.\n     *\n     * @param string|null $string\n     *\n     * @internal\n     */\n    public static function nl2br($string): string\n    {\n        return nl2br($string ?? '');\n    }\n\n    /**\n     * Removes whitespaces between HTML tags.\n     *\n     * @param string|null $content\n     *\n     * @internal\n     */\n    public static function spaceless($content): string\n    {\n        return trim(preg_replace('/>\\s+</', '><', $content ?? ''));\n    }\n\n    /**\n     * @param string|null $string\n     * @param string      $to\n     * @param string      $from\n     *\n     * @internal\n     */\n    public static function convertEncoding($string, $to, $from): string\n    {\n        if (!\\function_exists('iconv')) {\n            throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');\n        }\n\n        return iconv($from, $to, $string ?? '');\n    }\n\n    /**\n     * Returns the length of a variable.\n     *\n     * @param mixed $thing A variable\n     *\n     * @internal\n     */\n    public static function length(string $charset, $thing): int\n    {\n        if (null === $thing) {\n            return 0;\n        }\n\n        if (\\is_scalar($thing)) {\n            return mb_strlen($thing, $charset);\n        }\n\n        if ($thing instanceof \\Countable || \\is_array($thing) || $thing instanceof \\SimpleXMLElement) {\n            return \\count($thing);\n        }\n\n        if ($thing instanceof \\Traversable) {\n            return iterator_count($thing);\n        }\n\n        if ($thing instanceof \\Stringable) {\n            return mb_strlen((string) $thing, $charset);\n        }\n\n        return 1;\n    }\n\n    /**\n     * Converts a string to uppercase.\n     *\n     * @param string|null $string A string\n     *\n     * @internal\n     */\n    public static function upper(string $charset, $string): string\n    {\n        return mb_strtoupper($string ?? '', $charset);\n    }\n\n    /**\n     * Converts a string to lowercase.\n     *\n     * @param string|null $string A string\n     *\n     * @internal\n     */\n    public static function lower(string $charset, $string): string\n    {\n        return mb_strtolower($string ?? '', $charset);\n    }\n\n    /**\n     * Strips HTML and PHP tags from a string.\n     *\n     * @param string|null          $string\n     * @param string[]|string|null $allowable_tags\n     *\n     * @internal\n     */\n    public static function striptags($string, $allowable_tags = null): string\n    {\n        return strip_tags($string ?? '', $allowable_tags);\n    }\n\n    /**\n     * Returns a titlecased string.\n     *\n     * @param string|null $string A string\n     *\n     * @internal\n     */\n    public static function titleCase(string $charset, $string): string\n    {\n        return mb_convert_case($string ?? '', \\MB_CASE_TITLE, $charset);\n    }\n\n    /**\n     * Returns a capitalized string.\n     *\n     * @param string|null $string A string\n     *\n     * @internal\n     */\n    public static function capitalize(string $charset, $string): string\n    {\n        return mb_strtoupper(mb_substr($string ?? '', 0, 1, $charset), $charset).mb_strtolower(mb_substr($string ?? '', 1, null, $charset), $charset);\n    }\n\n    /**\n     * @internal\n     *\n     * to be removed in 4.0\n     */\n    public static function callMacro(Template $template, string $method, array $args, int $lineno, array $context, Source $source)\n    {\n        if (!method_exists($template, $method)) {\n            $parent = $template;\n            while ($parent = $parent->getParent($context)) {\n                if (method_exists($parent, $method)) {\n                    return $parent->$method(...$args);\n                }\n            }\n\n            throw new RuntimeError(\\sprintf('Macro \"%s\" is not defined in template \"%s\".', substr($method, \\strlen('macro_')), $template->getTemplateName()), $lineno, $source);\n        }\n\n        return $template->$method(...$args);\n    }\n\n    /**\n     * @template TSequence\n     *\n     * @param TSequence $seq\n     *\n     * @return ($seq is iterable ? TSequence : array{})\n     *\n     * @internal\n     */\n    public static function ensureTraversable($seq)\n    {\n        if (is_iterable($seq)) {\n            return $seq;\n        }\n\n        return [];\n    }\n\n    /**\n     * @internal\n     */\n    public static function toArray($seq, $preserveKeys = true)\n    {\n        if ($seq instanceof \\Traversable) {\n            return iterator_to_array($seq, $preserveKeys);\n        }\n\n        if (!\\is_array($seq)) {\n            return $seq;\n        }\n\n        return $preserveKeys ? $seq : array_values($seq);\n    }\n\n    /**\n     * Checks if a variable is empty.\n     *\n     *    {# evaluates to true if the foo variable is null, false, or the empty string #}\n     *    {% if foo is empty %}\n     *        {# ... #}\n     *    {% endif %}\n     *\n     * @param mixed $value A variable\n     *\n     * @internal\n     */\n    public static function testEmpty($value): bool\n    {\n        if ($value instanceof \\Countable) {\n            return 0 === \\count($value);\n        }\n\n        if ($value instanceof \\Traversable) {\n            return !iterator_count($value);\n        }\n\n        if ($value instanceof \\Stringable) {\n            return '' === (string) $value;\n        }\n\n        return '' === $value || false === $value || null === $value || [] === $value;\n    }\n\n    /**\n     * Checks if a variable is a sequence.\n     *\n     *    {# evaluates to true if the foo variable is a sequence #}\n     *    {% if foo is sequence %}\n     *        {# ... #}\n     *    {% endif %}\n     *\n     * @internal\n     */\n    public static function testSequence($value): bool\n    {\n        if ($value instanceof \\ArrayObject) {\n            $value = $value->getArrayCopy();\n        }\n\n        if ($value instanceof \\Traversable) {\n            $value = iterator_to_array($value);\n        }\n\n        return \\is_array($value) && array_is_list($value);\n    }\n\n    /**\n     * Checks if a variable is a mapping.\n     *\n     *    {# evaluates to true if the foo variable is a mapping #}\n     *    {% if foo is mapping %}\n     *        {# ... #}\n     *    {% endif %}\n     *\n     * @internal\n     */\n    public static function testMapping($value): bool\n    {\n        if ($value instanceof \\ArrayObject) {\n            $value = $value->getArrayCopy();\n        }\n\n        if ($value instanceof \\Traversable) {\n            $value = iterator_to_array($value);\n        }\n\n        return (\\is_array($value) && !array_is_list($value)) || \\is_object($value);\n    }\n\n    /**\n     * Renders a template.\n     *\n     * @param array                        $context\n     * @param string|array|TemplateWrapper $template      The template to render or an array of templates to try consecutively\n     * @param array                        $variables     The variables to pass to the template\n     * @param bool                         $withContext\n     * @param bool                         $ignoreMissing Whether to ignore missing templates or not\n     * @param bool                         $sandboxed     Whether to sandbox the template or not\n     *\n     * @internal\n     */\n    public static function include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false): string\n    {\n        $alreadySandboxed = false;\n        $sandbox = null;\n        if ($withContext) {\n            $variables = array_merge($context, $variables);\n        }\n\n        if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) {\n            $sandbox = $env->getExtension(SandboxExtension::class);\n            if (!$alreadySandboxed = $sandbox->isSandboxed()) {\n                $sandbox->enableSandbox();\n            }\n        }\n\n        try {\n            $loaded = null;\n            try {\n                $loaded = $env->resolveTemplate($template);\n            } catch (LoaderError $e) {\n                if (!$ignoreMissing) {\n                    throw $e;\n                }\n\n                return '';\n            }\n\n            if ($isSandboxed) {\n                $loaded->unwrap()->checkSecurity();\n            }\n\n            return $loaded->render($variables);\n        } finally {\n            if ($isSandboxed && !$alreadySandboxed) {\n                $sandbox->disableSandbox();\n            }\n        }\n    }\n\n    /**\n     * Returns a template content without rendering it.\n     *\n     * @param string $name          The template name\n     * @param bool   $ignoreMissing Whether to ignore missing templates or not\n     *\n     * @internal\n     */\n    public static function source(Environment $env, $name, $ignoreMissing = false): string\n    {\n        $loader = $env->getLoader();\n        try {\n            return $loader->getSourceContext($name)->getCode();\n        } catch (LoaderError $e) {\n            if (!$ignoreMissing) {\n                throw $e;\n            }\n\n            return '';\n        }\n    }\n\n    /**\n     * Returns the list of cases of the enum.\n     *\n     * @template T of \\UnitEnum\n     *\n     * @param class-string<T> $enum\n     *\n     * @return list<T>\n     *\n     * @internal\n     */\n    public static function enumCases(string $enum): array\n    {\n        if (!enum_exists($enum)) {\n            throw new RuntimeError(\\sprintf('Enum \"%s\" does not exist.', $enum));\n        }\n\n        return $enum::cases();\n    }\n\n    /**\n     * Provides the ability to access enums by their class names.\n     *\n     * @template T of \\UnitEnum\n     *\n     * @param class-string<T> $enum\n     *\n     * @return T\n     *\n     * @internal\n     */\n    public static function enum(string $enum): \\UnitEnum\n    {\n        if (!enum_exists($enum)) {\n            throw new RuntimeError(\\sprintf('\"%s\" is not an enum.', $enum));\n        }\n\n        if (!$cases = $enum::cases()) {\n            throw new RuntimeError(\\sprintf('\"%s\" is an empty enum.', $enum));\n        }\n\n        return $cases[0];\n    }\n\n    /**\n     * Provides the ability to get constants from instances as well as class/global constants.\n     *\n     * @param string      $constant     The name of the constant\n     * @param object|null $object       The object to get the constant from\n     * @param bool        $checkDefined Whether to check if the constant is defined or not\n     *\n     * @return mixed Class constants can return many types like scalars, arrays, and\n     *               objects depending on the PHP version (\\BackedEnum, \\UnitEnum, etc.)\n     *               When $checkDefined is true, returns true when the constant is defined, false otherwise\n     *\n     * @internal\n     */\n    public static function constant($constant, $object = null, bool $checkDefined = false)\n    {\n        if (null !== $object) {\n            if ('class' === $constant) {\n                return $checkDefined ? true : $object::class;\n            }\n\n            $constant = $object::class.'::'.$constant;\n        }\n\n        if (!\\defined($constant)) {\n            if ($checkDefined) {\n                return false;\n            }\n\n            if ('::class' === strtolower(substr($constant, -7))) {\n                throw new RuntimeError(\\sprintf('You cannot use the Twig function \"constant\" to access \"%s\". You could provide an object and call constant(\"class\", $object) or use the class name directly as a string.', $constant));\n            }\n\n            throw new RuntimeError(\\sprintf('Constant \"%s\" is undefined.', $constant));\n        }\n\n        return $checkDefined ? true : \\constant($constant);\n    }\n\n    /**\n     * Batches item.\n     *\n     * @param array $items An array of items\n     * @param int   $size  The size of the batch\n     * @param mixed $fill  A value used to fill missing items\n     *\n     * @internal\n     */\n    public static function batch($items, $size, $fill = null, $preserveKeys = true): array\n    {\n        if (!is_iterable($items)) {\n            throw new RuntimeError(\\sprintf('The \"batch\" filter expects a sequence or a mapping, got \"%s\".', get_debug_type($items)));\n        }\n\n        $size = (int) ceil($size);\n\n        $result = array_chunk(self::toArray($items, $preserveKeys), $size, $preserveKeys);\n\n        if (null !== $fill && $result) {\n            $last = \\count($result) - 1;\n            if ($fillCount = $size - \\count($result[$last])) {\n                for ($i = 0; $i < $fillCount; ++$i) {\n                    $result[$last][] = $fill;\n                }\n            }\n        }\n\n        return $result;\n    }\n\n    /**\n     * Returns the attribute value for a given array/object.\n     *\n     * @param mixed  $object            The object or array from where to get the item\n     * @param mixed  $item              The item to get from the array or object\n     * @param array  $arguments         An array of arguments to pass if the item is an object method\n     * @param string $type              The type of attribute (@see \\Twig\\Template constants)\n     * @param bool   $isDefinedTest     Whether this is only a defined check\n     * @param bool   $ignoreStrictCheck Whether to ignore the strict attribute check or not\n     * @param int    $lineno            The template line where the attribute was called\n     *\n     * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true\n     *\n     * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false\n     *\n     * @internal\n     */\n    public static function getAttribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1)\n    {\n        $propertyNotAllowedError = null;\n\n        // array\n        if (Template::METHOD_CALL !== $type) {\n            $arrayItem = \\is_bool($item) || \\is_float($item) ? (int) $item : $item;\n\n            if ($sandboxed && $object instanceof \\ArrayAccess && !\\in_array($object::class, self::ARRAY_LIKE_CLASSES, true)) {\n                try {\n                    $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $arrayItem, $lineno, $source);\n                } catch (SecurityNotAllowedPropertyError $propertyNotAllowedError) {\n                    goto methodCheck;\n                }\n            }\n\n            if (match (true) {\n                \\is_array($object) => \\array_key_exists($arrayItem = (string) $arrayItem, $object),\n                $object instanceof \\ArrayAccess => $object->offsetExists($arrayItem),\n                default => false,\n            }) {\n                if ($isDefinedTest) {\n                    return true;\n                }\n\n                return $object[$arrayItem];\n            }\n\n            if (Template::ARRAY_CALL === $type || !\\is_object($object)) {\n                if ($isDefinedTest) {\n                    return false;\n                }\n\n                if ($ignoreStrictCheck || !$env->isStrictVariables()) {\n                    return;\n                }\n\n                if ($object instanceof \\ArrayAccess) {\n                    if (\\is_object($arrayItem) || \\is_array($arrayItem)) {\n                        $message = \\sprintf('Key of type \"%s\" does not exist in ArrayAccess-able object of class \"%s\".', get_debug_type($arrayItem), get_debug_type($object));\n                    } else {\n                        $message = \\sprintf('Key \"%s\" does not exist in ArrayAccess-able object of class \"%s\".', $arrayItem, get_debug_type($object));\n                    }\n                } elseif (\\is_object($object)) {\n                    $message = \\sprintf('Impossible to access a key \"%s\" on an object of class \"%s\" that does not implement ArrayAccess interface.', $item, get_debug_type($object));\n                } elseif (\\is_array($object)) {\n                    if (!$object) {\n                        $message = \\sprintf('Key \"%s\" does not exist as the sequence/mapping is empty.', $arrayItem);\n                    } else {\n                        $message = \\sprintf('Key \"%s\" for sequence/mapping with keys \"%s\" does not exist.', $arrayItem, implode(', ', array_keys($object)));\n                    }\n                } elseif (Template::ARRAY_CALL === $type) {\n                    if (null === $object) {\n                        $message = \\sprintf('Impossible to access a key (\"%s\") on a null variable.', $item);\n                    } else {\n                        $message = \\sprintf('Impossible to access a key (\"%s\") on a %s variable (\"%s\").', $item, get_debug_type($object), $object);\n                    }\n                } elseif (null === $object) {\n                    $message = \\sprintf('Impossible to access an attribute (\"%s\") on a null variable.', $item);\n                } else {\n                    $message = \\sprintf('Impossible to access an attribute (\"%s\") on a %s variable (\"%s\").', $item, get_debug_type($object), $object);\n                }\n\n                throw new RuntimeError($message, $lineno, $source);\n            }\n        }\n\n        $item = (string) $item;\n\n        if (!\\is_object($object)) {\n            if ($isDefinedTest) {\n                return false;\n            }\n\n            if ($ignoreStrictCheck || !$env->isStrictVariables()) {\n                return;\n            }\n\n            if (null === $object) {\n                $message = \\sprintf('Impossible to invoke a method (\"%s\") on a null variable.', $item);\n            } elseif (\\is_array($object)) {\n                $message = \\sprintf('Impossible to invoke a method (\"%s\") on a sequence/mapping.', $item);\n            } else {\n                $message = \\sprintf('Impossible to invoke a method (\"%s\") on a %s variable (\"%s\").', $item, get_debug_type($object), $object);\n            }\n\n            throw new RuntimeError($message, $lineno, $source);\n        }\n\n        if ($object instanceof Template) {\n            throw new RuntimeError('Accessing \\Twig\\Template attributes is forbidden.', $lineno, $source);\n        }\n\n        // object property\n        if (Template::METHOD_CALL !== $type) {\n            if ($sandboxed) {\n                try {\n                    $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source);\n                } catch (SecurityNotAllowedPropertyError $propertyNotAllowedError) {\n                    goto methodCheck;\n                }\n            }\n\n            static $propertyCheckers = [];\n\n            if ($object instanceof \\Closure && '__invoke' === $item) {\n                return $isDefinedTest ? true : $object();\n            }\n\n            if (isset($object->$item)\n                || ($propertyCheckers[$object::class][$item] ??= self::getPropertyChecker($object::class, $item))($object, $item)\n            ) {\n                if ($isDefinedTest) {\n                    return true;\n                }\n\n                return $object->$item;\n            }\n\n            if ($object instanceof \\DateTimeInterface && \\in_array($item, ['date', 'timezone', 'timezone_type'], true)) {\n                if ($isDefinedTest) {\n                    return true;\n                }\n\n                return ((array) $object)[$item];\n            }\n\n            if (\\defined($object::class.'::'.$item)) {\n                if ($isDefinedTest) {\n                    return true;\n                }\n\n                return \\constant($object::class.'::'.$item);\n            }\n        }\n\n        methodCheck:\n\n        static $cache = [];\n\n        $class = $object::class;\n\n        // object method\n        // precedence: getXxx() > isXxx() > hasXxx()\n        if (!isset($cache[$class])) {\n            $methods = get_class_methods($object);\n            if ($object instanceof \\Closure) {\n                $methods[] = '__invoke';\n            }\n            sort($methods);\n            $lcMethods = array_map('strtolower', $methods);\n            $classCache = [];\n            foreach ($methods as $i => $method) {\n                $classCache[$method] = $method;\n                $classCache[$lcName = $lcMethods[$i]] = $method;\n\n                if ('g' === $lcName[0] && str_starts_with($lcName, 'get')) {\n                    $name = substr($method, 3);\n                    $lcName = substr($lcName, 3);\n                } elseif ('i' === $lcName[0] && str_starts_with($lcName, 'is')) {\n                    $name = substr($method, 2);\n                    $lcName = substr($lcName, 2);\n                } elseif ('h' === $lcName[0] && str_starts_with($lcName, 'has')) {\n                    $name = substr($method, 3);\n                    $lcName = substr($lcName, 3);\n                    if (\\in_array('is'.$lcName, $lcMethods, true)) {\n                        continue;\n                    }\n                } else {\n                    continue;\n                }\n\n                // skip get() and is() methods (in which case, $name is empty)\n                if ($name) {\n                    if (!isset($classCache[$name])) {\n                        $classCache[$name] = $method;\n                    }\n\n                    if (!isset($classCache[$lcName])) {\n                        $classCache[$lcName] = $method;\n                    }\n                }\n            }\n            $cache[$class] = $classCache;\n        }\n\n        $call = false;\n        if (isset($cache[$class][$item])) {\n            $method = $cache[$class][$item];\n        } elseif (isset($cache[$class][$lcItem = strtolower($item)])) {\n            $method = $cache[$class][$lcItem];\n        } elseif (isset($cache[$class]['__call'])) {\n            $method = $item;\n            $call = true;\n        } else {\n            if ($isDefinedTest) {\n                return false;\n            }\n\n            if ($propertyNotAllowedError) {\n                throw $propertyNotAllowedError;\n            }\n\n            if ($ignoreStrictCheck || !$env->isStrictVariables()) {\n                return;\n            }\n\n            throw new RuntimeError(\\sprintf('Neither the property \"%1$s\" nor one of the methods \"%1$s()\", \"get%1$s()\", \"is%1$s()\", \"has%1$s()\" or \"__call()\" exist and have public access in class \"%2$s\".', $item, $class), $lineno, $source);\n        }\n\n        if ($sandboxed) {\n            try {\n                $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source);\n            } catch (SecurityNotAllowedMethodError $e) {\n                if ($isDefinedTest) {\n                    return false;\n                }\n\n                if ($propertyNotAllowedError) {\n                    throw $propertyNotAllowedError;\n                }\n\n                throw $e;\n            }\n        }\n\n        if ($isDefinedTest) {\n            return true;\n        }\n\n        // Some objects throw exceptions when they have __call, and the method we try\n        // to call is not supported. If ignoreStrictCheck is true, we should return null.\n        try {\n            $ret = $object->$method(...$arguments);\n        } catch (\\BadMethodCallException $e) {\n            if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) {\n                return;\n            }\n            throw $e;\n        }\n\n        return $ret;\n    }\n\n    /**\n     * Returns the values from a single column in the input array.\n     *\n     * <pre>\n     *  {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}\n     *\n     *  {% set fruits = items|column('fruit') %}\n     *\n     *  {# fruits now contains ['apple', 'orange'] #}\n     * </pre>\n     *\n     * @param array|\\Traversable $array An array\n     * @param int|string         $name  The column name\n     * @param int|string|null    $index The column to use as the index/keys for the returned array\n     *\n     * @return array The array of values\n     *\n     * @internal\n     */\n    public static function column($array, $name, $index = null): array\n    {\n        if (!is_iterable($array)) {\n            throw new RuntimeError(\\sprintf('The \"column\" filter expects a sequence or a mapping, got \"%s\".', get_debug_type($array)));\n        }\n\n        if ($array instanceof \\Traversable) {\n            $array = iterator_to_array($array);\n        }\n\n        return array_column($array, $name, $index);\n    }\n\n    /**\n     * @param \\Closure $arrow\n     *\n     * @internal\n     */\n    public static function filter(Environment $env, $array, $arrow)\n    {\n        if (!is_iterable($array)) {\n            throw new RuntimeError(\\sprintf('The \"filter\" filter expects a sequence/mapping or \"Traversable\", got \"%s\".', get_debug_type($array)));\n        }\n\n        self::checkArrow($env, $arrow, 'filter', 'filter');\n\n        if (\\is_array($array)) {\n            return array_filter($array, $arrow, \\ARRAY_FILTER_USE_BOTH);\n        }\n\n        // the IteratorIterator wrapping is needed as some internal PHP classes are \\Traversable but do not implement \\Iterator\n        return new \\CallbackFilterIterator(new \\IteratorIterator($array), $arrow);\n    }\n\n    /**\n     * @param \\Closure $arrow\n     *\n     * @internal\n     */\n    public static function find(Environment $env, $array, $arrow)\n    {\n        if (!is_iterable($array)) {\n            throw new RuntimeError(\\sprintf('The \"find\" filter expects a sequence or a mapping, got \"%s\".', get_debug_type($array)));\n        }\n\n        self::checkArrow($env, $arrow, 'find', 'filter');\n\n        foreach ($array as $k => $v) {\n            if ($arrow($v, $k)) {\n                return $v;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @param \\Closure $arrow\n     *\n     * @internal\n     */\n    public static function map(Environment $env, $array, $arrow)\n    {\n        if (!is_iterable($array)) {\n            throw new RuntimeError(\\sprintf('The \"map\" filter expects a sequence or a mapping, got \"%s\".', get_debug_type($array)));\n        }\n\n        self::checkArrow($env, $arrow, 'map', 'filter');\n\n        $r = [];\n        foreach ($array as $k => $v) {\n            $r[$k] = $arrow($v, $k);\n        }\n\n        return $r;\n    }\n\n    /**\n     * @param \\Closure $arrow\n     *\n     * @internal\n     */\n    public static function reduce(Environment $env, $array, $arrow, $initial = null)\n    {\n        if (!is_iterable($array)) {\n            throw new RuntimeError(\\sprintf('The \"reduce\" filter expects a sequence or a mapping, got \"%s\".', get_debug_type($array)));\n        }\n\n        self::checkArrow($env, $arrow, 'reduce', 'filter');\n\n        $accumulator = $initial;\n        foreach ($array as $key => $value) {\n            $accumulator = $arrow($accumulator, $value, $key);\n        }\n\n        return $accumulator;\n    }\n\n    /**\n     * @param \\Closure $arrow\n     *\n     * @internal\n     */\n    public static function arraySome(Environment $env, $array, $arrow)\n    {\n        if (!is_iterable($array)) {\n            throw new RuntimeError(\\sprintf('The \"has some\" test expects a sequence or a mapping, got \"%s\".', get_debug_type($array)));\n        }\n\n        self::checkArrow($env, $arrow, 'has some', 'operator');\n\n        foreach ($array as $k => $v) {\n            if ($arrow($v, $k)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @param \\Closure $arrow\n     *\n     * @internal\n     */\n    public static function arrayEvery(Environment $env, $array, $arrow)\n    {\n        if (!is_iterable($array)) {\n            throw new RuntimeError(\\sprintf('The \"has every\" test expects a sequence or a mapping, got \"%s\".', get_debug_type($array)));\n        }\n\n        self::checkArrow($env, $arrow, 'has every', 'operator');\n\n        foreach ($array as $k => $v) {\n            if (!$arrow($v, $k)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * @internal\n     */\n    public static function checkArrow(Environment $env, $arrow, $thing, $type)\n    {\n        if ($arrow instanceof \\Closure) {\n            return;\n        }\n\n        if ($env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) {\n            throw new RuntimeError(\\sprintf('The callable passed to the \"%s\" %s must be a Closure in sandbox mode.', $thing, $type));\n        }\n\n        trigger_deprecation('twig/twig', '3.15', 'Passing a callable that is not a PHP \\Closure as an argument to the \"%s\" %s is deprecated.', $thing, $type);\n    }\n\n    /**\n     * @internal to be removed in Twig 4\n     */\n    public static function captureOutput(iterable $body): string\n    {\n        $level = ob_get_level();\n        ob_start();\n\n        try {\n            foreach ($body as $data) {\n                echo $data;\n            }\n        } catch (\\Throwable $e) {\n            while (ob_get_level() > $level) {\n                ob_end_clean();\n            }\n\n            throw $e;\n        }\n\n        return ob_get_clean();\n    }\n\n    /**\n     * @internal\n     */\n    public static function parseParentFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression\n    {\n        if (!$blockName = $parser->peekBlockStack()) {\n            throw new SyntaxError('Calling the \"parent\" function outside of a block is forbidden.', $line, $parser->getStream()->getSourceContext());\n        }\n\n        if (!$parser->hasInheritance()) {\n            throw new SyntaxError('Calling the \"parent\" function on a template that does not call \"extends\" or \"use\" is forbidden.', $line, $parser->getStream()->getSourceContext());\n        }\n\n        return new ParentExpression($blockName, $line);\n    }\n\n    /**\n     * @internal\n     */\n    public static function parseBlockFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression\n    {\n        $fakeFunction = new TwigFunction('block', static fn ($name, $template = null) => null);\n        $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args);\n\n        return new BlockReferenceExpression($args[0], $args[1] ?? null, $line);\n    }\n\n    /**\n     * @internal\n     */\n    public static function parseAttributeFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression\n    {\n        $fakeFunction = new TwigFunction('attribute', static fn ($variable, $attribute, $arguments = null) => null);\n        $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args);\n\n        /*\n        Deprecation to uncomment sometimes during the lifetime of the 4.x branch\n        $src = $parser->getStream()->getSourceContext();\n        $dep = new DeprecatedCallableInfo('twig/twig', '3.15', 'The \"attribute\" function is deprecated, use the \".\" notation instead.');\n        $dep->setName('attribute');\n        $dep->setType('function');\n        $dep->triggerDeprecation($src->getPath() ?: $src->getName(), $line);\n        */\n\n        return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line);\n    }\n\n    private static function getPropertyChecker(string $class, string $property): \\Closure\n    {\n        static $classReflectors = [];\n\n        $class = $classReflectors[$class] ??= new \\ReflectionClass($class);\n\n        if (!$class->hasProperty($property)) {\n            static $propertyExists;\n\n            return $propertyExists ??= \\Closure::fromCallable('property_exists');\n        }\n\n        $property = $class->getProperty($property);\n\n        if (!$property->isPublic() || $property->isStatic()) {\n            static $false;\n\n            return $false ??= static fn () => false;\n        }\n\n        return static fn ($object) => $property->isInitialized($object);\n    }\n}\n"
  },
  {
    "path": "src/Extension/DebugExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\Environment;\nuse Twig\\Template;\nuse Twig\\TemplateWrapper;\nuse Twig\\TwigFunction;\n\nfinal class DebugExtension extends AbstractExtension\n{\n    public function getFunctions(): array\n    {\n        // dump is safe if var_dump is overridden by xdebug\n        $isDumpOutputHtmlSafe = \\extension_loaded('xdebug')\n            // Xdebug overloads var_dump in develop mode when html_errors is enabled\n            && str_contains(\\ini_get('xdebug.mode'), 'develop')\n            && (false === \\ini_get('html_errors') || \\ini_get('html_errors'))\n            || 'cli' === \\PHP_SAPI\n        ;\n\n        return [\n            new TwigFunction('dump', [self::class, 'dump'], ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),\n        ];\n    }\n\n    /**\n     * @internal\n     */\n    public static function dump(Environment $env, $context, ...$vars)\n    {\n        if (!$env->isDebug()) {\n            return;\n        }\n\n        ob_start();\n\n        if (!$vars) {\n            $vars = [];\n            foreach ($context as $key => $value) {\n                if (!$value instanceof Template && !$value instanceof TemplateWrapper) {\n                    $vars[$key] = $value;\n                }\n            }\n\n            var_dump($vars);\n        } else {\n            var_dump(...$vars);\n        }\n\n        return ob_get_clean();\n    }\n}\n"
  },
  {
    "path": "src/Extension/EscaperExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\Environment;\nuse Twig\\FileExtensionEscapingStrategy;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Filter\\RawFilter;\nuse Twig\\Node\\Node;\nuse Twig\\NodeVisitor\\EscaperNodeVisitor;\nuse Twig\\Runtime\\EscaperRuntime;\nuse Twig\\TokenParser\\AutoEscapeTokenParser;\nuse Twig\\TwigFilter;\n\nfinal class EscaperExtension extends AbstractExtension\n{\n    private $environment;\n    private $escapers = [];\n    private $escaper;\n    private $defaultStrategy;\n\n    /**\n     * @param string|false|callable $defaultStrategy An escaping strategy\n     *\n     * @see setDefaultStrategy()\n     */\n    public function __construct($defaultStrategy = 'html')\n    {\n        $this->setDefaultStrategy($defaultStrategy);\n    }\n\n    public function getTokenParsers(): array\n    {\n        return [new AutoEscapeTokenParser()];\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return [new EscaperNodeVisitor()];\n    }\n\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('escape', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]),\n            new TwigFilter('e', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]),\n            new TwigFilter('raw', null, ['is_safe' => ['all'], 'node_class' => RawFilter::class]),\n        ];\n    }\n\n    public function getLastModified(): int\n    {\n        return max(\n            parent::getLastModified(),\n            filemtime((new \\ReflectionClass(EscaperRuntime::class))->getFileName()),\n        );\n    }\n\n    /**\n     * @deprecated since Twig 3.10\n     */\n    public function setEnvironment(Environment $environment): void\n    {\n        $triggerDeprecation = \\func_num_args() > 1 ? func_get_arg(1) : true;\n        if ($triggerDeprecation) {\n            trigger_deprecation('twig/twig', '3.10', 'The \"%s()\" method is deprecated and not needed if you are using methods from \"Twig\\Runtime\\EscaperRuntime\".', __METHOD__);\n        }\n\n        $this->environment = $environment;\n        $this->escaper = $environment->getRuntime(EscaperRuntime::class);\n    }\n\n    /**\n     * @return void\n     *\n     * @deprecated since Twig 3.10\n     */\n    public function setEscaperRuntime(EscaperRuntime $escaper)\n    {\n        trigger_deprecation('twig/twig', '3.10', 'The \"%s()\" method is deprecated and not needed if you are using methods from \"Twig\\Runtime\\EscaperRuntime\".', __METHOD__);\n\n        $this->escaper = $escaper;\n    }\n\n    /**\n     * Sets the default strategy to use when not defined by the user.\n     *\n     * The strategy can be a valid PHP callback that takes the template\n     * name as an argument and returns the strategy to use.\n     *\n     * @param string|false|callable(string $templateName): string $defaultStrategy An escaping strategy\n     */\n    public function setDefaultStrategy($defaultStrategy): void\n    {\n        if ('name' === $defaultStrategy) {\n            $defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess'];\n        }\n\n        $this->defaultStrategy = $defaultStrategy;\n    }\n\n    /**\n     * Gets the default strategy to use when not defined by the user.\n     *\n     * @param string $name The template name\n     *\n     * @return string|false The default strategy to use for the template\n     */\n    public function getDefaultStrategy(string $name)\n    {\n        // disable string callables to avoid calling a function named html or js,\n        // or any other upcoming escaping strategy\n        if (!\\is_string($this->defaultStrategy) && false !== $this->defaultStrategy) {\n            return \\call_user_func($this->defaultStrategy, $name);\n        }\n\n        return $this->defaultStrategy;\n    }\n\n    /**\n     * Defines a new escaper to be used via the escape filter.\n     *\n     * @param string                                        $strategy The strategy name that should be used as a strategy in the escape call\n     * @param callable(Environment, string, string): string $callable A valid PHP callable\n     *\n     * @return void\n     *\n     * @deprecated since Twig 3.10\n     */\n    public function setEscaper($strategy, callable $callable)\n    {\n        trigger_deprecation('twig/twig', '3.10', 'The \"%s()\" method is deprecated, use the \"Twig\\Runtime\\EscaperRuntime::setEscaper()\" method instead (be warned that Environment is not passed anymore to the callable).', __METHOD__);\n\n        if (!isset($this->environment)) {\n            throw new \\LogicException(\\sprintf('You must call \"setEnvironment()\" before calling \"%s()\".', __METHOD__));\n        }\n\n        $this->escapers[$strategy] = $callable;\n        $callable = function ($string, $charset) use ($callable) {\n            return $callable($this->environment, $string, $charset);\n        };\n\n        $this->escaper->setEscaper($strategy, $callable);\n    }\n\n    /**\n     * Gets all defined escapers.\n     *\n     * @return array<string, callable(Environment, string, string): string> An array of escapers\n     *\n     * @deprecated since Twig 3.10\n     */\n    public function getEscapers()\n    {\n        trigger_deprecation('twig/twig', '3.10', 'The \"%s()\" method is deprecated, use the \"Twig\\Runtime\\EscaperRuntime::getEscaper()\" method instead.', __METHOD__);\n\n        return $this->escapers;\n    }\n\n    /**\n     * @return void\n     *\n     * @deprecated since Twig 3.10\n     */\n    public function setSafeClasses(array $safeClasses = [])\n    {\n        trigger_deprecation('twig/twig', '3.10', 'The \"%s()\" method is deprecated, use the \"Twig\\Runtime\\EscaperRuntime::setSafeClasses()\" method instead.', __METHOD__);\n\n        if (!isset($this->escaper)) {\n            throw new \\LogicException(\\sprintf('You must call \"setEnvironment()\" before calling \"%s()\".', __METHOD__));\n        }\n\n        $this->escaper->setSafeClasses($safeClasses);\n    }\n\n    /**\n     * @return void\n     *\n     * @deprecated since Twig 3.10\n     */\n    public function addSafeClass(string $class, array $strategies)\n    {\n        trigger_deprecation('twig/twig', '3.10', 'The \"%s()\" method is deprecated, use the \"Twig\\Runtime\\EscaperRuntime::addSafeClass()\" method instead.', __METHOD__);\n\n        if (!isset($this->escaper)) {\n            throw new \\LogicException(\\sprintf('You must call \"setEnvironment()\" before calling \"%s()\".', __METHOD__));\n        }\n\n        $this->escaper->addSafeClass($class, $strategies);\n    }\n\n    /**\n     * @internal\n     *\n     * @return array<string>\n     */\n    public static function escapeFilterIsSafe(Node $filterArgs)\n    {\n        foreach ($filterArgs as $arg) {\n            if ($arg instanceof ConstantExpression) {\n                return [$arg->getAttribute('value')];\n            }\n\n            return [];\n        }\n\n        return ['html'];\n    }\n}\n"
  },
  {
    "path": "src/Extension/ExtensionInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\ExpressionParser;\nuse Twig\\ExpressionParser\\ExpressionParserInterface;\nuse Twig\\ExpressionParser\\PrecedenceChange;\nuse Twig\\Node\\Expression\\Binary\\AbstractBinary;\nuse Twig\\Node\\Expression\\Unary\\AbstractUnary;\nuse Twig\\NodeVisitor\\NodeVisitorInterface;\nuse Twig\\TokenParser\\TokenParserInterface;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\n/**\n * Interface implemented by extension classes.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @method array<ExpressionParserInterface> getExpressionParsers()\n */\ninterface ExtensionInterface\n{\n    /**\n     * Returns the token parser instances to add to the existing list.\n     *\n     * @return TokenParserInterface[]\n     */\n    public function getTokenParsers();\n\n    /**\n     * Returns the node visitor instances to add to the existing list.\n     *\n     * @return NodeVisitorInterface[]\n     */\n    public function getNodeVisitors();\n\n    /**\n     * Returns a list of filters to add to the existing list.\n     *\n     * @return TwigFilter[]\n     */\n    public function getFilters();\n\n    /**\n     * Returns a list of tests to add to the existing list.\n     *\n     * @return TwigTest[]\n     */\n    public function getTests();\n\n    /**\n     * Returns a list of functions to add to the existing list.\n     *\n     * @return TwigFunction[]\n     */\n    public function getFunctions();\n\n    /**\n     * Returns a list of operators to add to the existing list.\n     *\n     * @return array<array>\n     *\n     * @psalm-return array{\n     *     array<string, array{precedence: int, precedence_change?: PrecedenceChange, class: class-string<AbstractUnary>}>,\n     *     array<string, array{precedence: int, precedence_change?: PrecedenceChange, class?: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>\n     * }\n     */\n    public function getOperators();\n}\n"
  },
  {
    "path": "src/Extension/GlobalsInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\n/**\n * Allows Twig extensions to add globals to the context.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface GlobalsInterface\n{\n    /**\n     * @return array<string, mixed>\n     */\n    public function getGlobals(): array;\n}\n"
  },
  {
    "path": "src/Extension/LastModifiedExtensionInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\ninterface LastModifiedExtensionInterface extends ExtensionInterface\n{\n    /**\n     * Returns the last modification time of the extension for cache invalidation.\n     *\n     * This timestamp should be the last time the source code of the extension class\n     * and all its dependencies were modified (including the Runtime class).\n     */\n    public function getLastModified(): int;\n}\n"
  },
  {
    "path": "src/Extension/OptimizerExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\NodeVisitor\\OptimizerNodeVisitor;\n\nfinal class OptimizerExtension extends AbstractExtension\n{\n    public function __construct(\n        private int $optimizers = -1,\n    ) {\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return [new OptimizerNodeVisitor($this->optimizers)];\n    }\n}\n"
  },
  {
    "path": "src/Extension/ProfilerExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\Profiler\\NodeVisitor\\ProfilerNodeVisitor;\nuse Twig\\Profiler\\Profile;\n\nclass ProfilerExtension extends AbstractExtension\n{\n    private $actives = [];\n\n    public function __construct(Profile $profile)\n    {\n        $this->actives[] = $profile;\n    }\n\n    /**\n     * @return void\n     */\n    public function enter(Profile $profile)\n    {\n        $this->actives[0]->addProfile($profile);\n        array_unshift($this->actives, $profile);\n    }\n\n    /**\n     * @return void\n     */\n    public function leave(Profile $profile)\n    {\n        $profile->leave();\n        array_shift($this->actives);\n\n        if (1 === \\count($this->actives)) {\n            $this->actives[0]->leave();\n        }\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return [new ProfilerNodeVisitor(static::class)];\n    }\n}\n"
  },
  {
    "path": "src/Extension/RuntimeExtensionInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\n/**\n * @author Grégoire Pineau <lyrixx@lyrixx.info>\n */\ninterface RuntimeExtensionInterface\n{\n}\n"
  },
  {
    "path": "src/Extension/SandboxExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\NodeVisitor\\SandboxNodeVisitor;\nuse Twig\\Sandbox\\SecurityNotAllowedMethodError;\nuse Twig\\Sandbox\\SecurityNotAllowedPropertyError;\nuse Twig\\Sandbox\\SecurityPolicyInterface;\nuse Twig\\Sandbox\\SourcePolicyInterface;\nuse Twig\\Source;\nuse Twig\\TokenParser\\SandboxTokenParser;\n\nfinal class SandboxExtension extends AbstractExtension\n{\n    private $sandboxedGlobally;\n    private $sandboxed;\n    private $policy;\n    private $sourcePolicy;\n\n    public function __construct(SecurityPolicyInterface $policy, $sandboxed = false, ?SourcePolicyInterface $sourcePolicy = null)\n    {\n        $this->policy = $policy;\n        $this->sandboxedGlobally = $sandboxed;\n        $this->sourcePolicy = $sourcePolicy;\n    }\n\n    public function getTokenParsers(): array\n    {\n        return [new SandboxTokenParser()];\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return [new SandboxNodeVisitor()];\n    }\n\n    public function enableSandbox(): void\n    {\n        $this->sandboxed = true;\n    }\n\n    public function disableSandbox(): void\n    {\n        $this->sandboxed = false;\n    }\n\n    public function isSandboxed(?Source $source = null): bool\n    {\n        return $this->sandboxedGlobally || $this->sandboxed || $this->isSourceSandboxed($source);\n    }\n\n    public function isSandboxedGlobally(): bool\n    {\n        return $this->sandboxedGlobally;\n    }\n\n    private function isSourceSandboxed(?Source $source): bool\n    {\n        if (null === $source || null === $this->sourcePolicy) {\n            return false;\n        }\n\n        return $this->sourcePolicy->enableSandbox($source);\n    }\n\n    public function setSecurityPolicy(SecurityPolicyInterface $policy): void\n    {\n        $this->policy = $policy;\n    }\n\n    public function getSecurityPolicy(): SecurityPolicyInterface\n    {\n        return $this->policy;\n    }\n\n    public function checkSecurity($tags, $filters, $functions, ?Source $source = null): void\n    {\n        if ($this->isSandboxed($source)) {\n            $this->policy->checkSecurity($tags, $filters, $functions);\n        }\n    }\n\n    public function checkMethodAllowed($obj, $method, int $lineno = -1, ?Source $source = null): void\n    {\n        if ($this->isSandboxed($source)) {\n            try {\n                $this->policy->checkMethodAllowed($obj, $method);\n            } catch (SecurityNotAllowedMethodError $e) {\n                $e->setSourceContext($source);\n                $e->setTemplateLine($lineno);\n\n                throw $e;\n            }\n        }\n    }\n\n    public function checkPropertyAllowed($obj, $property, int $lineno = -1, ?Source $source = null): void\n    {\n        if ($this->isSandboxed($source)) {\n            try {\n                $this->policy->checkPropertyAllowed($obj, $property);\n            } catch (SecurityNotAllowedPropertyError $e) {\n                $e->setSourceContext($source);\n                $e->setTemplateLine($lineno);\n\n                throw $e;\n            }\n        }\n    }\n\n    /**\n     * @throws SecurityNotAllowedMethodError\n     */\n    public function ensureToStringAllowed($obj, int $lineno = -1, ?Source $source = null)\n    {\n        if (\\is_array($obj)) {\n            $this->ensureToStringAllowedForArray($obj, $lineno, $source);\n\n            return $obj;\n        }\n\n        if ($obj instanceof \\Stringable && $this->isSandboxed($source)) {\n            try {\n                $this->policy->checkMethodAllowed($obj, '__toString');\n            } catch (SecurityNotAllowedMethodError $e) {\n                $e->setSourceContext($source);\n                $e->setTemplateLine($lineno);\n\n                throw $e;\n            }\n        }\n\n        return $obj;\n    }\n\n    private function ensureToStringAllowedForArray(array $obj, int $lineno, ?Source $source, array &$stack = []): void\n    {\n        foreach ($obj as $k => $v) {\n            if (!$v) {\n                continue;\n            }\n\n            if (!\\is_array($v)) {\n                $this->ensureToStringAllowed($v, $lineno, $source);\n                continue;\n            }\n\n            if ($r = \\ReflectionReference::fromArrayElement($obj, $k)) {\n                if (isset($stack[$r->getId()])) {\n                    continue;\n                }\n\n                $stack[$r->getId()] = true;\n            }\n\n            $this->ensureToStringAllowedForArray($v, $lineno, $source, $stack);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Extension/StagingExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\NodeVisitor\\NodeVisitorInterface;\nuse Twig\\TokenParser\\TokenParserInterface;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\n/**\n * Used by \\Twig\\Environment as a staging area.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nfinal class StagingExtension extends AbstractExtension\n{\n    private $functions = [];\n    private $filters = [];\n    private $visitors = [];\n    private $tokenParsers = [];\n    private $tests = [];\n\n    public function addFunction(TwigFunction $function): void\n    {\n        if (isset($this->functions[$function->getName()])) {\n            throw new \\LogicException(\\sprintf('Function \"%s\" is already registered.', $function->getName()));\n        }\n\n        $this->functions[$function->getName()] = $function;\n    }\n\n    public function getFunctions(): array\n    {\n        return $this->functions;\n    }\n\n    public function addFilter(TwigFilter $filter): void\n    {\n        if (isset($this->filters[$filter->getName()])) {\n            throw new \\LogicException(\\sprintf('Filter \"%s\" is already registered.', $filter->getName()));\n        }\n\n        $this->filters[$filter->getName()] = $filter;\n    }\n\n    public function getFilters(): array\n    {\n        return $this->filters;\n    }\n\n    public function addNodeVisitor(NodeVisitorInterface $visitor): void\n    {\n        $this->visitors[] = $visitor;\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return $this->visitors;\n    }\n\n    public function addTokenParser(TokenParserInterface $parser): void\n    {\n        if (isset($this->tokenParsers[$parser->getTag()])) {\n            throw new \\LogicException(\\sprintf('Tag \"%s\" is already registered.', $parser->getTag()));\n        }\n\n        $this->tokenParsers[$parser->getTag()] = $parser;\n    }\n\n    public function getTokenParsers(): array\n    {\n        return $this->tokenParsers;\n    }\n\n    public function addTest(TwigTest $test): void\n    {\n        if (isset($this->tests[$test->getName()])) {\n            throw new \\LogicException(\\sprintf('Test \"%s\" is already registered.', $test->getName()));\n        }\n\n        $this->tests[$test->getName()] = $test;\n    }\n\n    public function getTests(): array\n    {\n        return $this->tests;\n    }\n}\n"
  },
  {
    "path": "src/Extension/StringLoaderExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\Environment;\nuse Twig\\TemplateWrapper;\nuse Twig\\TwigFunction;\n\nfinal class StringLoaderExtension extends AbstractExtension\n{\n    public function getFunctions(): array\n    {\n        return [\n            new TwigFunction('template_from_string', [self::class, 'templateFromString'], ['needs_environment' => true]),\n        ];\n    }\n\n    /**\n     * Loads a template from a string.\n     *\n     *     {{ include(template_from_string(\"Hello {{ name }}\")) }}\n     *\n     * @param string|null $name An optional name of the template to be used in error messages\n     *\n     * @internal\n     */\n    public static function templateFromString(Environment $env, string|\\Stringable $template, ?string $name = null): TemplateWrapper\n    {\n        return $env->createTemplate((string) $template, $name);\n    }\n}\n"
  },
  {
    "path": "src/Extension/YieldNotReadyExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Extension;\n\nuse Twig\\NodeVisitor\\YieldNotReadyNodeVisitor;\n\n/**\n * @internal to be removed in Twig 4\n */\nfinal class YieldNotReadyExtension extends AbstractExtension\n{\n    public function __construct(\n        private bool $useYield,\n    ) {\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return [new YieldNotReadyNodeVisitor($this->useYield)];\n    }\n}\n"
  },
  {
    "path": "src/ExtensionSet.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Error\\RuntimeError;\nuse Twig\\ExpressionParser\\ExpressionParsers;\nuse Twig\\ExpressionParser\\Infix\\BinaryOperatorExpressionParser;\nuse Twig\\ExpressionParser\\InfixAssociativity;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\ExpressionParser\\PrecedenceChange;\nuse Twig\\ExpressionParser\\Prefix\\UnaryOperatorExpressionParser;\nuse Twig\\Extension\\AttributeExtension;\nuse Twig\\Extension\\ExtensionInterface;\nuse Twig\\Extension\\GlobalsInterface;\nuse Twig\\Extension\\LastModifiedExtensionInterface;\nuse Twig\\Extension\\StagingExtension;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\NodeVisitor\\NodeVisitorInterface;\nuse Twig\\TokenParser\\TokenParserInterface;\n\n// Help opcache.preload discover always-needed symbols\n// @see https://github.com/php/php-src/issues/10131\nclass_exists(BinaryOperatorExpressionParser::class);\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nfinal class ExtensionSet\n{\n    private $extensions;\n    private $initialized = false;\n    private $runtimeInitialized = false;\n    private $staging;\n    private $parsers;\n    private $visitors;\n    /** @var array<string, TwigFilter> */\n    private $filters;\n    /** @var array<string, TwigFilter> */\n    private $dynamicFilters;\n    /** @var array<string, TwigTest> */\n    private $tests;\n    /** @var array<string, TwigTest> */\n    private $dynamicTests;\n    /** @var array<string, TwigFunction> */\n    private $functions;\n    /** @var array<string, TwigFunction> */\n    private $dynamicFunctions;\n    private ExpressionParsers $expressionParsers;\n    /** @var array<string, mixed>|null */\n    private $globals;\n    /** @var array<callable(string): (TwigFunction|false)> */\n    private $functionCallbacks = [];\n    /** @var array<callable(string): (TwigFilter|false)> */\n    private $filterCallbacks = [];\n    /** @var array<callable(string): (TwigTest|false)> */\n    private $testCallbacks = [];\n    /** @var array<callable(string): (TokenParserInterface|false)> */\n    private $parserCallbacks = [];\n    private $lastModified = 0;\n\n    public function __construct()\n    {\n        $this->staging = new StagingExtension();\n    }\n\n    /**\n     * @return void\n     */\n    public function initRuntime()\n    {\n        $this->runtimeInitialized = true;\n    }\n\n    public function hasExtension(string $class): bool\n    {\n        return isset($this->extensions[ltrim($class, '\\\\')]);\n    }\n\n    public function getExtension(string $class): ExtensionInterface\n    {\n        $class = ltrim($class, '\\\\');\n\n        if (!isset($this->extensions[$class])) {\n            throw new RuntimeError(\\sprintf('The \"%s\" extension is not enabled.', $class));\n        }\n\n        return $this->extensions[$class];\n    }\n\n    /**\n     * @param ExtensionInterface[] $extensions\n     */\n    public function setExtensions(array $extensions): void\n    {\n        foreach ($extensions as $extension) {\n            $this->addExtension($extension);\n        }\n    }\n\n    /**\n     * @return ExtensionInterface[]\n     */\n    public function getExtensions(): array\n    {\n        return $this->extensions;\n    }\n\n    public function getSignature(): string\n    {\n        return json_encode(array_keys($this->extensions));\n    }\n\n    public function isInitialized(): bool\n    {\n        return $this->initialized || $this->runtimeInitialized;\n    }\n\n    public function getLastModified(): int\n    {\n        if (0 !== $this->lastModified) {\n            return $this->lastModified;\n        }\n\n        $lastModified = 0;\n        foreach ($this->extensions as $extension) {\n            if ($extension instanceof LastModifiedExtensionInterface) {\n                $lastModified = max($extension->getLastModified(), $lastModified);\n            } else {\n                $r = new \\ReflectionObject($extension);\n                if (is_file($r->getFileName())) {\n                    $lastModified = max(filemtime($r->getFileName()), $lastModified);\n                }\n            }\n        }\n\n        return $this->lastModified = $lastModified;\n    }\n\n    public function addExtension(ExtensionInterface $extension): void\n    {\n        if ($extension instanceof AttributeExtension) {\n            $class = $extension->getClass();\n        } else {\n            $class = $extension::class;\n        }\n\n        if ($this->initialized) {\n            throw new \\LogicException(\\sprintf('Unable to register extension \"%s\" as extensions have already been initialized.', $class));\n        }\n\n        if (isset($this->extensions[$class])) {\n            throw new \\LogicException(\\sprintf('Unable to register extension \"%s\" as it is already registered.', $class));\n        }\n\n        $this->extensions[$class] = $extension;\n    }\n\n    public function addFunction(TwigFunction $function): void\n    {\n        if ($this->initialized) {\n            throw new \\LogicException(\\sprintf('Unable to add function \"%s\" as extensions have already been initialized.', $function->getName()));\n        }\n\n        $this->staging->addFunction($function);\n    }\n\n    /**\n     * @return TwigFunction[]\n     */\n    public function getFunctions(): array\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        return $this->functions;\n    }\n\n    public function getFunction(string $name): ?TwigFunction\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        if (isset($this->functions[$name])) {\n            return $this->functions[$name];\n        }\n\n        foreach ($this->dynamicFunctions as $pattern => $function) {\n            if (preg_match($pattern, $name, $matches)) {\n                array_shift($matches);\n\n                return $function->withDynamicArguments($name, $function->getName(), $matches);\n            }\n        }\n\n        foreach ($this->functionCallbacks as $callback) {\n            if (false !== $function = $callback($name)) {\n                return $function;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @param callable(string): (TwigFunction|false) $callable\n     */\n    public function registerUndefinedFunctionCallback(callable $callable): void\n    {\n        $this->functionCallbacks[] = $callable;\n    }\n\n    public function addFilter(TwigFilter $filter): void\n    {\n        if ($this->initialized) {\n            throw new \\LogicException(\\sprintf('Unable to add filter \"%s\" as extensions have already been initialized.', $filter->getName()));\n        }\n\n        $this->staging->addFilter($filter);\n    }\n\n    /**\n     * @return TwigFilter[]\n     */\n    public function getFilters(): array\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        return $this->filters;\n    }\n\n    public function getFilter(string $name): ?TwigFilter\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        if (isset($this->filters[$name])) {\n            return $this->filters[$name];\n        }\n\n        foreach ($this->dynamicFilters as $pattern => $filter) {\n            if (preg_match($pattern, $name, $matches)) {\n                array_shift($matches);\n\n                return $filter->withDynamicArguments($name, $filter->getName(), $matches);\n            }\n        }\n\n        foreach ($this->filterCallbacks as $callback) {\n            if (false !== $filter = $callback($name)) {\n                return $filter;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @param callable(string): (TwigFilter|false) $callable\n     */\n    public function registerUndefinedFilterCallback(callable $callable): void\n    {\n        $this->filterCallbacks[] = $callable;\n    }\n\n    public function addNodeVisitor(NodeVisitorInterface $visitor): void\n    {\n        if ($this->initialized) {\n            throw new \\LogicException('Unable to add a node visitor as extensions have already been initialized.');\n        }\n\n        $this->staging->addNodeVisitor($visitor);\n    }\n\n    /**\n     * @return NodeVisitorInterface[]\n     */\n    public function getNodeVisitors(): array\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        return $this->visitors;\n    }\n\n    public function addTokenParser(TokenParserInterface $parser): void\n    {\n        if ($this->initialized) {\n            throw new \\LogicException('Unable to add a token parser as extensions have already been initialized.');\n        }\n\n        $this->staging->addTokenParser($parser);\n    }\n\n    /**\n     * @return TokenParserInterface[]\n     */\n    public function getTokenParsers(): array\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        return $this->parsers;\n    }\n\n    public function getTokenParser(string $name): ?TokenParserInterface\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        if (isset($this->parsers[$name])) {\n            return $this->parsers[$name];\n        }\n\n        foreach ($this->parserCallbacks as $callback) {\n            if (false !== $parser = $callback($name)) {\n                return $parser;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @param callable(string): (TokenParserInterface|false) $callable\n     */\n    public function registerUndefinedTokenParserCallback(callable $callable): void\n    {\n        $this->parserCallbacks[] = $callable;\n    }\n\n    /**\n     * @return array<string, mixed>\n     */\n    public function getGlobals(): array\n    {\n        if (null !== $this->globals) {\n            return $this->globals;\n        }\n\n        $globals = [];\n        foreach ($this->extensions as $extension) {\n            if (!$extension instanceof GlobalsInterface) {\n                continue;\n            }\n\n            $globals = array_merge($globals, $extension->getGlobals());\n        }\n\n        if ($this->initialized) {\n            $this->globals = $globals;\n        }\n\n        return $globals;\n    }\n\n    public function resetGlobals(): void\n    {\n        $this->globals = null;\n    }\n\n    public function addTest(TwigTest $test): void\n    {\n        if ($this->initialized) {\n            throw new \\LogicException(\\sprintf('Unable to add test \"%s\" as extensions have already been initialized.', $test->getName()));\n        }\n\n        $this->staging->addTest($test);\n    }\n\n    /**\n     * @return TwigTest[]\n     */\n    public function getTests(): array\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        return $this->tests;\n    }\n\n    public function getTest(string $name): ?TwigTest\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        if (isset($this->tests[$name])) {\n            return $this->tests[$name];\n        }\n\n        foreach ($this->dynamicTests as $pattern => $test) {\n            if (preg_match($pattern, $name, $matches)) {\n                array_shift($matches);\n\n                return $test->withDynamicArguments($name, $test->getName(), $matches);\n            }\n        }\n\n        foreach ($this->testCallbacks as $callback) {\n            if (false !== $test = $callback($name)) {\n                return $test;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @param callable(string): (TwigTest|false) $callable\n     */\n    public function registerUndefinedTestCallback(callable $callable): void\n    {\n        $this->testCallbacks[] = $callable;\n    }\n\n    public function getExpressionParsers(): ExpressionParsers\n    {\n        if (!$this->initialized) {\n            $this->initExtensions();\n        }\n\n        return $this->expressionParsers;\n    }\n\n    private function initExtensions(): void\n    {\n        $this->parsers = [];\n        $this->filters = [];\n        $this->functions = [];\n        $this->tests = [];\n        $this->dynamicFilters = [];\n        $this->dynamicFunctions = [];\n        $this->dynamicTests = [];\n        $this->visitors = [];\n        $this->expressionParsers = new ExpressionParsers();\n\n        foreach ($this->extensions as $extension) {\n            $this->initExtension($extension);\n        }\n        $this->initExtension($this->staging);\n        // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception\n        $this->initialized = true;\n    }\n\n    private function initExtension(ExtensionInterface $extension): void\n    {\n        // filters\n        foreach ($extension->getFilters() as $filter) {\n            $this->filters[$name = $filter->getName()] = $filter;\n            if (str_contains($name, '*')) {\n                $this->dynamicFilters['#^'.str_replace('\\\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $filter;\n            }\n        }\n\n        // functions\n        foreach ($extension->getFunctions() as $function) {\n            $this->functions[$name = $function->getName()] = $function;\n            if (str_contains($name, '*')) {\n                $this->dynamicFunctions['#^'.str_replace('\\\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $function;\n            }\n        }\n\n        // tests\n        foreach ($extension->getTests() as $test) {\n            $this->tests[$name = $test->getName()] = $test;\n            if (str_contains($name, '*')) {\n                $this->dynamicTests['#^'.str_replace('\\\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $test;\n            }\n        }\n\n        // token parsers\n        foreach ($extension->getTokenParsers() as $parser) {\n            if (!$parser instanceof TokenParserInterface) {\n                throw new \\LogicException('getTokenParsers() must return an array of \\Twig\\TokenParser\\TokenParserInterface.');\n            }\n\n            $this->parsers[$parser->getTag()] = $parser;\n        }\n\n        // node visitors\n        foreach ($extension->getNodeVisitors() as $visitor) {\n            $this->visitors[] = $visitor;\n        }\n\n        // expression parsers\n        if (method_exists($extension, 'getExpressionParsers')) {\n            $this->expressionParsers->add($extension->getExpressionParsers());\n        }\n\n        $operators = $extension->getOperators();\n        if (!\\is_array($operators)) {\n            throw new \\InvalidArgumentException(\\sprintf('\"%s::getOperators()\" must return an array with operators, got \"%s\".', $extension::class, get_debug_type($operators).(\\is_resource($operators) ? '' : '#'.$operators)));\n        }\n\n        if (2 !== \\count($operators)) {\n            throw new \\InvalidArgumentException(\\sprintf('\"%s::getOperators()\" must return an array of 2 elements, got %d.', $extension::class, \\count($operators)));\n        }\n\n        $expressionParsers = [];\n        foreach ($operators[0] as $operator => $op) {\n            $expressionParsers[] = new UnaryOperatorExpressionParser($op['class'], $operator, $op['precedence'], $op['precedence_change'] ?? null, '', $op['aliases'] ?? []);\n        }\n        foreach ($operators[1] as $operator => $op) {\n            $op['associativity'] = match ($op['associativity']) {\n                1 => InfixAssociativity::Left,\n                2 => InfixAssociativity::Right,\n                default => throw new \\InvalidArgumentException(\\sprintf('Invalid associativity \"%s\" for operator \"%s\".', $op['associativity'], $operator)),\n            };\n\n            if (isset($op['callable'])) {\n                $expressionParsers[] = $this->convertInfixExpressionParser($op['class'], $operator, $op['precedence'], $op['associativity'], $op['precedence_change'] ?? null, $op['aliases'] ?? [], $op['callable']);\n            } else {\n                $expressionParsers[] = new BinaryOperatorExpressionParser($op['class'], $operator, $op['precedence'], $op['associativity'], $op['precedence_change'] ?? null, '', $op['aliases'] ?? []);\n            }\n        }\n\n        if (\\count($expressionParsers)) {\n            trigger_deprecation('twig/twig', '3.21', \\sprintf('Extension \"%s\" uses the old signature for \"getOperators()\", please implement \"getExpressionParsers()\" instead.', $extension::class));\n\n            $this->expressionParsers->add($expressionParsers);\n        }\n    }\n\n    private function convertInfixExpressionParser(string $nodeClass, string $operator, int $precedence, InfixAssociativity $associativity, ?PrecedenceChange $precedenceChange, array $aliases, callable $callable): InfixExpressionParserInterface\n    {\n        trigger_deprecation('twig/twig', '3.21', \\sprintf('Using a non-ExpressionParserInterface object to define the \"%s\" binary operator is deprecated.', $operator));\n\n        return new class($nodeClass, $operator, $precedence, $associativity, $precedenceChange, $aliases, $callable) extends BinaryOperatorExpressionParser {\n            public function __construct(\n                string $nodeClass,\n                string $operator,\n                int $precedence,\n                InfixAssociativity $associativity = InfixAssociativity::Left,\n                ?PrecedenceChange $precedenceChange = null,\n                array $aliases = [],\n                private $callable = null,\n            ) {\n                parent::__construct($nodeClass, $operator, $precedence, $associativity, $precedenceChange, $aliases);\n            }\n\n            public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression\n            {\n                return ($this->callable)($parser, $expr);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "src/FileExtensionEscapingStrategy.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\n/**\n * Default autoescaping strategy based on file names.\n *\n * This strategy sets the HTML as the default autoescaping strategy,\n * but changes it based on the template name.\n *\n * Note that there is no runtime performance impact as the\n * default autoescaping strategy is set at compilation time.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass FileExtensionEscapingStrategy\n{\n    /**\n     * Guesses the best autoescaping strategy based on the file name.\n     *\n     * @param string $name The template name\n     *\n     * @return string|false The escaping strategy name to use or false to disable\n     */\n    public static function guess(string $name)\n    {\n        if (\\in_array(substr($name, -1), ['/', '\\\\'], true)) {\n            return 'html'; // return html for directories\n        }\n\n        if (str_ends_with($name, '.twig')) {\n            $name = substr($name, 0, -5);\n        }\n\n        $extension = pathinfo($name, \\PATHINFO_EXTENSION);\n\n        switch ($extension) {\n            case 'js':\n            case 'json':\n                return 'js';\n\n            case 'css':\n                return 'css';\n\n            case 'txt':\n                return false;\n\n            default:\n                return 'html';\n        }\n    }\n}\n"
  },
  {
    "path": "src/Lexer.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\ExpressionParsers;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass Lexer\n{\n    private $isInitialized = false;\n\n    private $tokens;\n    private $code;\n    private $cursor;\n    private $lineno;\n    private $end;\n    private $state;\n    private $states;\n    private $brackets;\n    private $env;\n    private $source;\n    private $options;\n    private $regexes;\n    private $position;\n    private $positions;\n    private $currentVarBlockLine;\n    private array $openingBrackets = ['{', '(', '['];\n    private array $closingBrackets = ['}', ')', ']'];\n\n    public const STATE_DATA = 0;\n    public const STATE_BLOCK = 1;\n    public const STATE_VAR = 2;\n    public const STATE_STRING = 3;\n    public const STATE_INTERPOLATION = 4;\n\n    public const REGEX_NAME = '/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/A';\n    public const REGEX_STRING = '/\"([^#\"\\\\\\\\]*(?:\\\\\\\\.[^#\"\\\\\\\\]*)*)\"|\\'([^\\'\\\\\\\\]*(?:\\\\\\\\.[^\\'\\\\\\\\]*)*)\\'/As';\n\n    public const REGEX_NUMBER = '/(?(DEFINE)\n        (?<LNUM>[0-9]+(_[0-9]+)*)               # Integers (with underscores)   123_456\n        (?<FRAC>\\.(?&LNUM))                     # Fractional part               .456\n        (?<EXPONENT>[eE][+-]?(?&LNUM))          # Exponent part                 E+10\n        (?<DNUM>(?&LNUM)(?:(?&FRAC))?)          # Decimal number                123_456.456\n    )(?:(?&DNUM)(?:(?&EXPONENT))?)              #                               123_456.456E+10\n    /Ax';\n\n    public const REGEX_DQ_STRING_DELIM = '/\"/A';\n    public const REGEX_DQ_STRING_PART = '/[^#\"\\\\\\\\]*(?:(?:\\\\\\\\.|#(?!\\{))[^#\"\\\\\\\\]*)*/As';\n    public const REGEX_INLINE_COMMENT = '/#[^\\n]*/A';\n    public const PUNCTUATION = '()[]{}?:.,|';\n\n    private const SPECIAL_CHARS = [\n        'f' => \"\\f\",\n        'n' => \"\\n\",\n        'r' => \"\\r\",\n        't' => \"\\t\",\n        'v' => \"\\v\",\n    ];\n\n    public function __construct(Environment $env, array $options = [])\n    {\n        $this->env = $env;\n\n        $this->options = array_merge([\n            'tag_comment' => ['{#', '#}'],\n            'tag_block' => ['{%', '%}'],\n            'tag_variable' => ['{{', '}}'],\n            'whitespace_trim' => '-',\n            'whitespace_line_trim' => '~',\n            'whitespace_line_chars' => ' \\t\\0\\x0B',\n            'interpolation' => ['#{', '}'],\n        ], $options);\n    }\n\n    private function initialize(): void\n    {\n        if ($this->isInitialized) {\n            return;\n        }\n\n        // when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default\n        $this->regexes = [\n            // }}\n            'lex_var' => '{\n                \\s*\n                (?:'.\n                    preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '#').'\\s*'. // -}}\\s*\n                    '|'.\n                    preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \\t\\0\\x0B]*\n                    '|'.\n                    preg_quote($this->options['tag_variable'][1], '#'). // }}\n                ')\n            }Ax',\n\n            // %}\n            'lex_block' => '{\n                \\s*\n                (?:'.\n                    preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\\s*\\n?'. // -%}\\s*\\n?\n                    '|'.\n                    preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \\t\\0\\x0B]*\n                    '|'.\n                    preg_quote($this->options['tag_block'][1], '#').'\\n?'. // %}\\n?\n                ')\n            }Ax',\n\n            // {% endverbatim %}\n            'lex_raw_data' => '{'.\n                preg_quote($this->options['tag_block'][0], '#'). // {%\n                '('.\n                    $this->options['whitespace_trim']. // -\n                    '|'.\n                    $this->options['whitespace_line_trim']. // ~\n                ')?\\s*endverbatim\\s*'.\n                '(?:'.\n                    preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\\s*'. // -%}\n                    '|'.\n                    preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \\t\\0\\x0B]*\n                    '|'.\n                    preg_quote($this->options['tag_block'][1], '#'). // %}\n                ')\n            }sx',\n\n            'operator' => $this->getOperatorRegex(),\n\n            // #}\n            'lex_comment' => '{\n                (?:'.\n                    preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\\s*\\n?'. // -#}\\s*\\n?\n                    '|'.\n                    preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \\t\\0\\x0B]*\n                    '|'.\n                    preg_quote($this->options['tag_comment'][1], '#').'\\n?'. // #}\\n?\n                ')\n            }sx',\n\n            // verbatim %}\n            'lex_block_raw' => '{\n                \\s*verbatim\\s*\n                (?:'.\n                    preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\\s*'. // -%}\\s*\n                    '|'.\n                    preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \\t\\0\\x0B]*\n                    '|'.\n                    preg_quote($this->options['tag_block'][1], '#'). // %}\n                ')\n            }Asx',\n\n            'lex_block_line' => '{\\s*line\\s+(\\d+)\\s*'.preg_quote($this->options['tag_block'][1], '#').'}As',\n\n            // {{ or {% or {#\n            'lex_tokens_start' => '{\n                ('.\n                    preg_quote($this->options['tag_variable'][0], '#'). // {{\n                    '|'.\n                    preg_quote($this->options['tag_block'][0], '#'). // {%\n                    '|'.\n                    preg_quote($this->options['tag_comment'][0], '#'). // {#\n                ')('.\n                    preg_quote($this->options['whitespace_trim'], '#'). // -\n                    '|'.\n                    preg_quote($this->options['whitespace_line_trim'], '#'). // ~\n                ')?\n            }sx',\n            'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0], '#').'\\s*}A',\n            'interpolation_end' => '{\\s*'.preg_quote($this->options['interpolation'][1], '#').'}A',\n        ];\n\n        $this->isInitialized = true;\n    }\n\n    public function tokenize(Source $source): TokenStream\n    {\n        $this->initialize();\n\n        $this->source = $source;\n        $this->code = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $source->getCode());\n        $this->cursor = 0;\n        $this->lineno = 1;\n        $this->end = \\strlen($this->code);\n        $this->tokens = [];\n        $this->state = self::STATE_DATA;\n        $this->states = [];\n        $this->brackets = [];\n        $this->position = -1;\n\n        // find all token starts in one go\n        preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \\PREG_OFFSET_CAPTURE);\n        $this->positions = $matches;\n\n        while ($this->cursor < $this->end) {\n            // dispatch to the lexing functions depending\n            // on the current state\n            switch ($this->state) {\n                case self::STATE_DATA:\n                    $this->lexData();\n                    break;\n\n                case self::STATE_BLOCK:\n                    $this->lexBlock();\n                    break;\n\n                case self::STATE_VAR:\n                    $this->lexVar();\n                    break;\n\n                case self::STATE_STRING:\n                    $this->lexString();\n                    break;\n\n                case self::STATE_INTERPOLATION:\n                    $this->lexInterpolation();\n                    break;\n            }\n        }\n\n        $this->pushToken(Token::EOF_TYPE);\n\n        if ($this->brackets) {\n            [$expect, $lineno] = array_pop($this->brackets);\n            throw new SyntaxError(\\sprintf('Unclosed \"%s\".', $expect), $lineno, $this->source);\n        }\n\n        return new TokenStream($this->tokens, $this->source);\n    }\n\n    private function lexData(): void\n    {\n        // if no matches are left we return the rest of the template as simple text token\n        if ($this->position == \\count($this->positions[0]) - 1) {\n            $this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor));\n            $this->cursor = $this->end;\n\n            return;\n        }\n\n        // Find the first token after the current cursor\n        $position = $this->positions[0][++$this->position];\n        while ($position[1] < $this->cursor) {\n            if ($this->position == \\count($this->positions[0]) - 1) {\n                return;\n            }\n            $position = $this->positions[0][++$this->position];\n        }\n\n        // push the template text first\n        $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor);\n\n        // trim?\n        if (isset($this->positions[2][$this->position][0])) {\n            if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) {\n                // whitespace_trim detected ({%-, {{- or {#-)\n                $text = rtrim($text);\n            } elseif ($this->options['whitespace_line_trim'] === $this->positions[2][$this->position][0]) {\n                // whitespace_line_trim detected ({%~, {{~ or {#~)\n                // don't trim \\r and \\n\n                $text = rtrim($text, \" \\t\\0\\x0B\");\n            }\n        }\n        $this->pushToken(Token::TEXT_TYPE, $text);\n        $this->moveCursor($textContent.$position[0]);\n\n        switch ($this->positions[1][$this->position][0]) {\n            case $this->options['tag_comment'][0]:\n                $this->lexComment();\n                break;\n\n            case $this->options['tag_block'][0]:\n                // raw data?\n                if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) {\n                    $this->moveCursor($match[0]);\n                    $this->lexRawData();\n                // {% line \\d+ %}\n                } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) {\n                    $this->moveCursor($match[0]);\n                    $this->lineno = (int) $match[1];\n                } else {\n                    $this->pushToken(Token::BLOCK_START_TYPE);\n                    $this->pushState(self::STATE_BLOCK);\n                    $this->currentVarBlockLine = $this->lineno;\n                }\n                break;\n\n            case $this->options['tag_variable'][0]:\n                $this->pushToken(Token::VAR_START_TYPE);\n                $this->pushState(self::STATE_VAR);\n                $this->currentVarBlockLine = $this->lineno;\n                break;\n        }\n    }\n\n    private function lexBlock(): void\n    {\n        if (!$this->brackets && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) {\n            $this->pushToken(Token::BLOCK_END_TYPE);\n            $this->moveCursor($match[0]);\n            $this->popState();\n        } else {\n            $this->lexExpression();\n        }\n    }\n\n    private function lexVar(): void\n    {\n        if (!$this->brackets && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) {\n            $this->pushToken(Token::VAR_END_TYPE);\n            $this->moveCursor($match[0]);\n            $this->popState();\n        } else {\n            $this->lexExpression();\n        }\n    }\n\n    private function lexExpression(): void\n    {\n        // whitespace\n        if (preg_match('/\\s+/A', $this->code, $match, 0, $this->cursor)) {\n            $this->moveCursor($match[0]);\n\n            if ($this->cursor >= $this->end) {\n                throw new SyntaxError(\\sprintf('Unclosed \"%s\".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source);\n            }\n        }\n\n        // operators\n        if (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {\n            $operator = preg_replace('/\\s+/', ' ', $match[0]);\n            if (\\in_array($operator, $this->openingBrackets, true)) {\n                $this->checkBrackets($operator);\n            }\n            $this->pushToken(Token::OPERATOR_TYPE, $operator);\n            $this->moveCursor($match[0]);\n        }\n        // names\n        elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {\n            $this->pushToken(Token::NAME_TYPE, $match[0]);\n            $this->moveCursor($match[0]);\n        }\n        // numbers\n        elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) {\n            $this->pushToken(Token::NUMBER_TYPE, 0 + str_replace('_', '', $match[0]));\n            $this->moveCursor($match[0]);\n        }\n        // punctuation\n        elseif (str_contains(self::PUNCTUATION, $this->code[$this->cursor])) {\n            $this->checkBrackets($this->code[$this->cursor]);\n            $this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);\n            ++$this->cursor;\n        }\n        // strings\n        elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {\n            $this->pushToken(Token::STRING_TYPE, $this->stripcslashes(substr($match[0], 1, -1), substr($match[0], 0, 1)));\n            $this->moveCursor($match[0]);\n        }\n        // opening double quoted string\n        elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {\n            $this->brackets[] = ['\"', $this->lineno];\n            $this->pushState(self::STATE_STRING);\n            $this->moveCursor($match[0]);\n        }\n        // inline comment\n        elseif (preg_match(self::REGEX_INLINE_COMMENT, $this->code, $match, 0, $this->cursor)) {\n            $this->moveCursor($match[0]);\n        }\n        // unlexable\n        else {\n            throw new SyntaxError(\\sprintf('Unexpected character \"%s\".', $this->code[$this->cursor]), $this->lineno, $this->source);\n        }\n    }\n\n    private function stripcslashes(string $str, string $quoteType): string\n    {\n        $result = '';\n        $length = \\strlen($str);\n\n        $i = 0;\n        while ($i < $length) {\n            if (false === $pos = strpos($str, '\\\\', $i)) {\n                $result .= substr($str, $i);\n                break;\n            }\n\n            $result .= substr($str, $i, $pos - $i);\n            $i = $pos + 1;\n\n            if ($i >= $length) {\n                $result .= '\\\\';\n                break;\n            }\n\n            $nextChar = $str[$i];\n\n            if (isset(self::SPECIAL_CHARS[$nextChar])) {\n                $result .= self::SPECIAL_CHARS[$nextChar];\n            } elseif ('\\\\' === $nextChar) {\n                $result .= $nextChar;\n            } elseif (\"'\" === $nextChar || '\"' === $nextChar) {\n                if ($nextChar !== $quoteType) {\n                    trigger_deprecation('twig/twig', '3.12', 'Character \"%s\" should not be escaped; the \"\\\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra \"\\\" character at position %d in \"%s\" at line %d.', $nextChar, $i + 1, $this->source->getName(), $this->lineno);\n                }\n                $result .= $nextChar;\n            } elseif ('#' === $nextChar && $i + 1 < $length && '{' === $str[$i + 1]) {\n                $result .= '#{';\n                ++$i;\n            } elseif ('x' === $nextChar && $i + 1 < $length && ctype_xdigit($str[$i + 1])) {\n                $hex = $str[++$i];\n                if ($i + 1 < $length && ctype_xdigit($str[$i + 1])) {\n                    $hex .= $str[++$i];\n                }\n                $result .= \\chr(hexdec($hex));\n            } elseif (ctype_digit($nextChar) && $nextChar < '8') {\n                $octal = $nextChar;\n                while ($i + 1 < $length && ctype_digit($str[$i + 1]) && $str[$i + 1] < '8' && \\strlen($octal) < 3) {\n                    $octal .= $str[++$i];\n                }\n                $result .= \\chr(octdec($octal));\n            } else {\n                trigger_deprecation('twig/twig', '3.12', 'Character \"%s\" should not be escaped; the \"\\\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra \"\\\" character at position %d in \"%s\" at line %d.', $nextChar, $i + 1, $this->source->getName(), $this->lineno);\n                $result .= $nextChar;\n            }\n\n            ++$i;\n        }\n\n        return $result;\n    }\n\n    private function lexRawData(): void\n    {\n        if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \\PREG_OFFSET_CAPTURE, $this->cursor)) {\n            throw new SyntaxError('Unexpected end of file: Unclosed \"verbatim\" block.', $this->lineno, $this->source);\n        }\n\n        $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);\n        $this->moveCursor($text.$match[0][0]);\n\n        // trim?\n        if (isset($match[1][0])) {\n            if ($this->options['whitespace_trim'] === $match[1][0]) {\n                // whitespace_trim detected ({%-, {{- or {#-)\n                $text = rtrim($text);\n            } else {\n                // whitespace_line_trim detected ({%~, {{~ or {#~)\n                // don't trim \\r and \\n\n                $text = rtrim($text, \" \\t\\0\\x0B\");\n            }\n        }\n\n        $this->pushToken(Token::TEXT_TYPE, $text);\n    }\n\n    private function lexComment(): void\n    {\n        if (!preg_match($this->regexes['lex_comment'], $this->code, $match, \\PREG_OFFSET_CAPTURE, $this->cursor)) {\n            throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source);\n        }\n\n        $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]);\n    }\n\n    private function lexString(): void\n    {\n        if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {\n            $this->brackets[] = [$this->options['interpolation'][0], $this->lineno];\n            $this->pushToken(Token::INTERPOLATION_START_TYPE);\n            $this->moveCursor($match[0]);\n            $this->pushState(self::STATE_INTERPOLATION);\n        } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && '' !== $match[0]) {\n            $this->pushToken(Token::STRING_TYPE, $this->stripcslashes($match[0], '\"'));\n            $this->moveCursor($match[0]);\n        } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {\n            [$expect, $lineno] = array_pop($this->brackets);\n            if ('\"' != $this->code[$this->cursor]) {\n                throw new SyntaxError(\\sprintf('Unclosed \"%s\".', $expect), $lineno, $this->source);\n            }\n\n            $this->popState();\n            ++$this->cursor;\n        } else {\n            // unlexable\n            throw new SyntaxError(\\sprintf('Unexpected character \"%s\".', $this->code[$this->cursor]), $this->lineno, $this->source);\n        }\n    }\n\n    private function lexInterpolation(): void\n    {\n        $bracket = end($this->brackets);\n        if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) {\n            array_pop($this->brackets);\n            $this->pushToken(Token::INTERPOLATION_END_TYPE);\n            $this->moveCursor($match[0]);\n            $this->popState();\n        } else {\n            $this->lexExpression();\n        }\n    }\n\n    private function pushToken($type, $value = ''): void\n    {\n        // do not push empty text tokens\n        if (Token::TEXT_TYPE === $type && '' === $value) {\n            return;\n        }\n\n        $this->tokens[] = new Token($type, $value, $this->lineno);\n    }\n\n    private function moveCursor($text): void\n    {\n        $this->cursor += \\strlen($text);\n        $this->lineno += substr_count($text, \"\\n\");\n    }\n\n    private function getOperatorRegex(): string\n    {\n        $expressionParsers = [];\n        foreach ($this->env->getExpressionParsers() as $expressionParser) {\n            $expressionParsers = array_merge($expressionParsers, ExpressionParsers::getOperatorTokensFor($expressionParser));\n        }\n\n        $expressionParsers = array_combine($expressionParsers, array_map('strlen', $expressionParsers));\n        arsort($expressionParsers);\n\n        $regex = [];\n        foreach ($expressionParsers as $expressionParser => $length) {\n            // an operator that ends with a character must be followed by\n            // a whitespace, a parenthesis, an opening map [ or sequence {\n            $r = preg_quote($expressionParser, '/');\n            if (ctype_alpha($expressionParser[$length - 1])) {\n                $r .= '(?=[\\s()\\[{])';\n            }\n\n            // an operator that begins with a character must not have a dot or pipe before\n            if (ctype_alpha($expressionParser[0])) {\n                $r = '(?<![\\.\\|]\\s|.[\\.\\|])'.$r;\n            }\n\n            // an operator with a space can be any amount of whitespaces\n            $r = preg_replace('/\\s+/', '\\s+', $r);\n\n            $regex[] = $r;\n        }\n\n        return '/'.implode('|', $regex).'/A';\n    }\n\n    private function pushState($state): void\n    {\n        $this->states[] = $this->state;\n        $this->state = $state;\n    }\n\n    private function popState(): void\n    {\n        if (0 === \\count($this->states)) {\n            throw new \\LogicException('Cannot pop state without a previous state.');\n        }\n\n        $this->state = array_pop($this->states);\n    }\n\n    private function checkBrackets(string $code): void\n    {\n        // opening bracket\n        if (\\in_array($code, $this->openingBrackets, true)) {\n            $this->brackets[] = [$code, $this->lineno];\n        } elseif (\\in_array($code, $this->closingBrackets, true)) {\n            // closing bracket\n            if (!$this->brackets) {\n                throw new SyntaxError(\\sprintf('Unexpected \"%s\".', $code), $this->lineno, $this->source);\n            }\n\n            [$expect, $lineno] = array_pop($this->brackets);\n            if ($code !== str_replace($this->openingBrackets, $this->closingBrackets, $expect)) {\n                throw new SyntaxError(\\sprintf('Unclosed \"%s\".', $expect), $lineno, $this->source);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Loader/ArrayLoader.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Loader;\n\nuse Twig\\Error\\LoaderError;\nuse Twig\\Source;\n\n/**\n * Loads a template from an array.\n *\n * When using this loader with a cache mechanism, you should know that a new cache\n * key is generated each time a template content \"changes\" (the cache key being the\n * source code of the template). If you don't want to see your cache grows out of\n * control, you need to take care of clearing the old cache file by yourself.\n *\n * This loader should only be used for unit testing.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class ArrayLoader implements LoaderInterface\n{\n    /**\n     * @param array $templates An array of templates (keys are the names, and values are the source code)\n     */\n    public function __construct(\n        private array $templates = [],\n    ) {\n    }\n\n    public function setTemplate(string $name, string $template): void\n    {\n        $this->templates[$name] = $template;\n    }\n\n    public function getSourceContext(string $name): Source\n    {\n        if (!isset($this->templates[$name])) {\n            throw new LoaderError(\\sprintf('Template \"%s\" is not defined.', $name));\n        }\n\n        return new Source($this->templates[$name], $name);\n    }\n\n    public function exists(string $name): bool\n    {\n        return isset($this->templates[$name]);\n    }\n\n    public function getCacheKey(string $name): string\n    {\n        if (!isset($this->templates[$name])) {\n            throw new LoaderError(\\sprintf('Template \"%s\" is not defined.', $name));\n        }\n\n        return $name.':'.$this->templates[$name];\n    }\n\n    public function isFresh(string $name, int $time): bool\n    {\n        if (!isset($this->templates[$name])) {\n            throw new LoaderError(\\sprintf('Template \"%s\" is not defined.', $name));\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Loader/ChainLoader.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Loader;\n\nuse Twig\\Error\\LoaderError;\nuse Twig\\Source;\n\n/**\n * Loads templates from other loaders.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class ChainLoader implements LoaderInterface\n{\n    /**\n     * @var array<string, bool>\n     */\n    private $hasSourceCache = [];\n\n    /**\n     * @param iterable<LoaderInterface> $loaders\n     */\n    public function __construct(\n        private iterable $loaders = [],\n    ) {\n    }\n\n    public function addLoader(LoaderInterface $loader): void\n    {\n        $current = $this->loaders;\n\n        $this->loaders = (static function () use ($current, $loader): \\Generator {\n            yield from $current;\n            yield $loader;\n        })();\n\n        $this->hasSourceCache = [];\n    }\n\n    /**\n     * @return LoaderInterface[]\n     */\n    public function getLoaders(): array\n    {\n        if (!\\is_array($this->loaders)) {\n            $this->loaders = iterator_to_array($this->loaders, false);\n        }\n\n        return $this->loaders;\n    }\n\n    public function getSourceContext(string $name): Source\n    {\n        $exceptions = [];\n\n        foreach ($this->getLoaders() as $loader) {\n            if (!$loader->exists($name)) {\n                continue;\n            }\n\n            try {\n                return $loader->getSourceContext($name);\n            } catch (LoaderError $e) {\n                $exceptions[] = $e->getMessage();\n            }\n        }\n\n        throw new LoaderError(\\sprintf('Template \"%s\" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));\n    }\n\n    public function exists(string $name): bool\n    {\n        if (isset($this->hasSourceCache[$name])) {\n            return $this->hasSourceCache[$name];\n        }\n\n        foreach ($this->getLoaders() as $loader) {\n            if ($loader->exists($name)) {\n                return $this->hasSourceCache[$name] = true;\n            }\n        }\n\n        return $this->hasSourceCache[$name] = false;\n    }\n\n    public function getCacheKey(string $name): string\n    {\n        $exceptions = [];\n\n        foreach ($this->getLoaders() as $loader) {\n            if (!$loader->exists($name)) {\n                continue;\n            }\n\n            try {\n                return $loader->getCacheKey($name);\n            } catch (LoaderError $e) {\n                $exceptions[] = $loader::class.': '.$e->getMessage();\n            }\n        }\n\n        throw new LoaderError(\\sprintf('Template \"%s\" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));\n    }\n\n    public function isFresh(string $name, int $time): bool\n    {\n        $exceptions = [];\n\n        foreach ($this->getLoaders() as $loader) {\n            if (!$loader->exists($name)) {\n                continue;\n            }\n\n            try {\n                return $loader->isFresh($name, $time);\n            } catch (LoaderError $e) {\n                $exceptions[] = $loader::class.': '.$e->getMessage();\n            }\n        }\n\n        throw new LoaderError(\\sprintf('Template \"%s\" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));\n    }\n}\n"
  },
  {
    "path": "src/Loader/FilesystemLoader.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Loader;\n\nuse Twig\\Error\\LoaderError;\nuse Twig\\Source;\n\n/**\n * Loads template from the filesystem.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass FilesystemLoader implements LoaderInterface\n{\n    /** Identifier of the main namespace. */\n    public const MAIN_NAMESPACE = '__main__';\n\n    /**\n     * @var array<string, list<string>>\n     */\n    protected $paths = [];\n    protected $cache = [];\n    protected $errorCache = [];\n\n    private $rootPath;\n\n    /**\n     * @param string|string[] $paths    A path or an array of paths where to look for templates\n     * @param string|null     $rootPath The root path common to all relative paths (null for getcwd())\n     */\n    public function __construct($paths = [], ?string $rootPath = null)\n    {\n        $this->rootPath = ($rootPath ?? getcwd()).\\DIRECTORY_SEPARATOR;\n        if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) {\n            $this->rootPath = $realPath.\\DIRECTORY_SEPARATOR;\n        }\n\n        if ($paths) {\n            $this->setPaths($paths);\n        }\n    }\n\n    /**\n     * Returns the paths to the templates.\n     *\n     * @return list<string>\n     */\n    public function getPaths(string $namespace = self::MAIN_NAMESPACE): array\n    {\n        return $this->paths[$namespace] ?? [];\n    }\n\n    /**\n     * Returns the path namespaces.\n     *\n     * The main namespace is always defined.\n     *\n     * @return list<string>\n     */\n    public function getNamespaces(): array\n    {\n        return array_keys($this->paths);\n    }\n\n    /**\n     * @param string|string[] $paths A path or an array of paths where to look for templates\n     */\n    public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE): void\n    {\n        if (!\\is_array($paths)) {\n            $paths = [$paths];\n        }\n\n        $this->paths[$namespace] = [];\n        foreach ($paths as $path) {\n            $this->addPath($path, $namespace);\n        }\n    }\n\n    /**\n     * @throws LoaderError\n     */\n    public function addPath(string $path, string $namespace = self::MAIN_NAMESPACE): void\n    {\n        // invalidate the cache\n        $this->cache = $this->errorCache = [];\n\n        $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;\n        if (!is_dir($checkPath)) {\n            throw new LoaderError(\\sprintf('The \"%s\" directory does not exist (\"%s\").', $path, $checkPath));\n        }\n\n        $this->paths[$namespace][] = rtrim($path, '/\\\\');\n    }\n\n    /**\n     * @throws LoaderError\n     */\n    public function prependPath(string $path, string $namespace = self::MAIN_NAMESPACE): void\n    {\n        // invalidate the cache\n        $this->cache = $this->errorCache = [];\n\n        $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;\n        if (!is_dir($checkPath)) {\n            throw new LoaderError(\\sprintf('The \"%s\" directory does not exist (\"%s\").', $path, $checkPath));\n        }\n\n        $path = rtrim($path, '/\\\\');\n\n        if (!isset($this->paths[$namespace])) {\n            $this->paths[$namespace][] = $path;\n        } else {\n            array_unshift($this->paths[$namespace], $path);\n        }\n    }\n\n    public function getSourceContext(string $name): Source\n    {\n        if (null === $path = $this->findTemplate($name)) {\n            return new Source('', $name, '');\n        }\n\n        return new Source(file_get_contents($path), $name, $path);\n    }\n\n    public function getCacheKey(string $name): string\n    {\n        if (null === $path = $this->findTemplate($name)) {\n            return '';\n        }\n        $len = \\strlen($this->rootPath);\n        if (0 === strncmp($this->rootPath, $path, $len)) {\n            return substr($path, $len);\n        }\n\n        return $path;\n    }\n\n    /**\n     * @return bool\n     */\n    public function exists(string $name)\n    {\n        $name = $this->normalizeName($name);\n\n        if (isset($this->cache[$name])) {\n            return true;\n        }\n\n        return null !== $this->findTemplate($name, false);\n    }\n\n    public function isFresh(string $name, int $time): bool\n    {\n        // false support to be removed in 3.0\n        if (null === $path = $this->findTemplate($name)) {\n            return false;\n        }\n\n        return filemtime($path) < $time;\n    }\n\n    /**\n     * @return string|null\n     */\n    protected function findTemplate(string $name, bool $throw = true)\n    {\n        $name = $this->normalizeName($name);\n\n        if (isset($this->cache[$name])) {\n            return $this->cache[$name];\n        }\n\n        if (isset($this->errorCache[$name])) {\n            if (!$throw) {\n                return null;\n            }\n\n            throw new LoaderError($this->errorCache[$name]);\n        }\n\n        try {\n            [$namespace, $shortname] = $this->parseName($name);\n\n            $this->validateName($shortname);\n        } catch (LoaderError $e) {\n            if (!$throw) {\n                return null;\n            }\n\n            throw $e;\n        }\n\n        if (!isset($this->paths[$namespace])) {\n            $this->errorCache[$name] = \\sprintf('There are no registered paths for namespace \"%s\".', $namespace);\n\n            if (!$throw) {\n                return null;\n            }\n\n            throw new LoaderError($this->errorCache[$name]);\n        }\n\n        foreach ($this->paths[$namespace] as $path) {\n            if (!$this->isAbsolutePath($path)) {\n                $path = $this->rootPath.$path;\n            }\n\n            if (is_file($path.'/'.$shortname)) {\n                if (false !== $realpath = realpath($path.'/'.$shortname)) {\n                    return $this->cache[$name] = $realpath;\n                }\n\n                return $this->cache[$name] = $path.'/'.$shortname;\n            }\n        }\n\n        $this->errorCache[$name] = \\sprintf('Unable to find template \"%s\" (looked into: %s).', $name, implode(', ', $this->paths[$namespace]));\n\n        if (!$throw) {\n            return null;\n        }\n\n        throw new LoaderError($this->errorCache[$name]);\n    }\n\n    private function normalizeName(string $name): string\n    {\n        return preg_replace('#/{2,}#', '/', str_replace('\\\\', '/', $name));\n    }\n\n    private function parseName(string $name, string $default = self::MAIN_NAMESPACE): array\n    {\n        if (isset($name[0]) && '@' == $name[0]) {\n            if (false === $pos = strpos($name, '/')) {\n                throw new LoaderError(\\sprintf('Malformed namespaced template name \"%s\" (expecting \"@namespace/template_name\").', $name));\n            }\n\n            $namespace = substr($name, 1, $pos - 1);\n            $shortname = substr($name, $pos + 1);\n\n            return [$namespace, $shortname];\n        }\n\n        return [$default, $name];\n    }\n\n    private function validateName(string $name): void\n    {\n        if (str_contains($name, \"\\0\")) {\n            throw new LoaderError('A template name cannot contain NUL bytes.');\n        }\n\n        $name = ltrim($name, '/');\n        $parts = explode('/', $name);\n        $level = 0;\n        foreach ($parts as $part) {\n            if ('..' === $part) {\n                --$level;\n            } elseif ('.' !== $part) {\n                ++$level;\n            }\n\n            if ($level < 0) {\n                throw new LoaderError(\\sprintf('Looks like you try to load a template outside configured directories (%s).', $name));\n            }\n        }\n    }\n\n    private function isAbsolutePath(string $file): bool\n    {\n        return strspn($file, '/\\\\', 0, 1)\n            || (\\strlen($file) > 3 && ctype_alpha($file[0])\n                && ':' === $file[1]\n                && strspn($file, '/\\\\', 2, 1)\n            )\n            || null !== parse_url($file, \\PHP_URL_SCHEME)\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Loader/LoaderInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Loader;\n\nuse Twig\\Error\\LoaderError;\nuse Twig\\Source;\n\n/**\n * Interface all loaders must implement.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface LoaderInterface\n{\n    /**\n     * Returns the source context for a given template logical name.\n     *\n     * @throws LoaderError When $name is not found\n     */\n    public function getSourceContext(string $name): Source;\n\n    /**\n     * Gets the cache key to use for the cache for a given template name.\n     *\n     * @throws LoaderError When $name is not found\n     */\n    public function getCacheKey(string $name): string;\n\n    /**\n     * @param int $time Timestamp of the last modification time of the cached template\n     *\n     * @throws LoaderError When $name is not found\n     */\n    public function isFresh(string $name, int $time): bool;\n\n    /**\n     * @return bool\n     */\n    public function exists(string $name);\n}\n"
  },
  {
    "path": "src/Markup.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\n/**\n * Marks a content as safe.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass Markup implements \\Countable, \\JsonSerializable, \\Stringable\n{\n    private $content;\n    private ?string $charset;\n\n    public function __construct($content, $charset)\n    {\n        $this->content = (string) $content;\n        $this->charset = $charset;\n    }\n\n    public function __toString(): string\n    {\n        return $this->content;\n    }\n\n    public function getCharset(): string\n    {\n        return $this->charset;\n    }\n\n    /**\n     * @return int\n     */\n    #[\\ReturnTypeWillChange]\n    public function count()\n    {\n        return mb_strlen($this->content, $this->charset);\n    }\n\n    /**\n     * @return mixed\n     */\n    #[\\ReturnTypeWillChange]\n    public function jsonSerialize()\n    {\n        return $this->content;\n    }\n}\n"
  },
  {
    "path": "src/Node/AutoEscapeNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents an autoescape node.\n *\n * The value is the escaping strategy (can be html, js, ...)\n *\n * The true value is equivalent to html.\n *\n * If autoescaping is disabled, then the value is false.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass AutoEscapeNode extends Node\n{\n    public function __construct($value, Node $body, int $lineno)\n    {\n        parent::__construct(['body' => $body], ['value' => $value], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->subcompile($this->getNode('body'));\n    }\n}\n"
  },
  {
    "path": "src/Node/BlockNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents a block node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass BlockNode extends Node\n{\n    public function __construct(string $name, Node $body, int $lineno)\n    {\n        parent::__construct(['body' => $body], ['name' => $name], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->write(\"/**\\n\")\n            ->write(\" * @return iterable<null|scalar|\\Stringable>\\n\")\n            ->write(\" */\\n\")\n            ->write(\\sprintf(\"public function block_%s(array \\$context, array \\$blocks = []): iterable\\n\", $this->getAttribute('name')), \"{\\n\")\n            ->indent()\n            ->write(\"\\$macros = \\$this->macros;\\n\")\n        ;\n\n        $compiler\n            ->subcompile($this->getNode('body'))\n            ->write(\"yield from [];\\n\")\n            ->outdent()\n            ->write(\"}\\n\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/BlockReferenceNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents a block call node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass BlockReferenceNode extends Node implements NodeOutputInterface\n{\n    public function __construct(string $name, int $lineno)\n    {\n        parent::__construct([], ['name' => $name], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->write(\\sprintf(\"yield from \\$this->unwrap()->yieldBlock('%s', \\$context, \\$blocks);\\n\", $this->getAttribute('name')))\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/BodyNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\n\n/**\n * Represents a body node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass BodyNode extends Node\n{\n}\n"
  },
  {
    "path": "src/Node/CaptureNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents a node for which we need to capture the output.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass CaptureNode extends Node\n{\n    public function __construct(Node $body, int $lineno)\n    {\n        parent::__construct(['body' => $body], ['raw' => false], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $useYield = $compiler->getEnvironment()->useYield();\n\n        if (!$this->getAttribute('raw')) {\n            $compiler->raw(\"('' === \\$tmp = \");\n        }\n        $compiler\n            ->raw($useYield ? \"implode('', iterator_to_array(\" : '\\\\Twig\\\\Extension\\\\CoreExtension::captureOutput(')\n            ->raw(\"(function () use (&\\$context, \\$macros, \\$blocks) {\\n\")\n            ->indent()\n            ->subcompile($this->getNode('body'))\n            ->write(\"yield from [];\\n\")\n            ->outdent()\n            ->write('})()')\n        ;\n        if ($useYield) {\n            $compiler->raw(', false))');\n        } else {\n            $compiler->raw(')');\n        }\n        if (!$this->getAttribute('raw')) {\n            $compiler->raw(\") ? '' : new Markup(\\$tmp, \\$this->env->getCharset());\");\n        } else {\n            $compiler->raw(';');\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/CheckSecurityCallNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass CheckSecurityCallNode extends Node\n{\n    /**\n     * @return void\n     */\n    public function compile(Compiler $compiler)\n    {\n        $compiler\n            ->write(\"\\$this->sandbox = \\$this->extensions[SandboxExtension::class];\\n\")\n            ->write(\"\\$this->checkSecurity();\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/CheckSecurityNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass CheckSecurityNode extends Node\n{\n    private $usedFilters;\n    private $usedTags;\n    private $usedFunctions;\n\n    /**\n     * @param array<string, int> $usedFilters\n     * @param array<string, int> $usedTags\n     * @param array<string, int> $usedFunctions\n     */\n    public function __construct(array $usedFilters, array $usedTags, array $usedFunctions)\n    {\n        $this->usedFilters = $usedFilters;\n        $this->usedTags = $usedTags;\n        $this->usedFunctions = $usedFunctions;\n\n        parent::__construct();\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->write(\"\\n\")\n            ->write(\"public function checkSecurity()\\n\")\n            ->write(\"{\\n\")\n            ->indent()\n            ->write('static $tags = ')->repr(array_filter($this->usedTags))->raw(\";\\n\")\n            ->write('static $filters = ')->repr(array_filter($this->usedFilters))->raw(\";\\n\")\n            ->write('static $functions = ')->repr(array_filter($this->usedFunctions))->raw(\";\\n\\n\")\n            ->write(\"try {\\n\")\n            ->indent()\n            ->write(\"\\$this->sandbox->checkSecurity(\\n\")\n            ->indent()\n            ->write(!$this->usedTags ? \"[],\\n\" : \"['\".implode(\"', '\", array_keys($this->usedTags)).\"'],\\n\")\n            ->write(!$this->usedFilters ? \"[],\\n\" : \"['\".implode(\"', '\", array_keys($this->usedFilters)).\"'],\\n\")\n            ->write(!$this->usedFunctions ? \"[],\\n\" : \"['\".implode(\"', '\", array_keys($this->usedFunctions)).\"'],\\n\")\n            ->write(\"\\$this->source\\n\")\n            ->outdent()\n            ->write(\");\\n\")\n            ->outdent()\n            ->write(\"} catch (SecurityError \\$e) {\\n\")\n            ->indent()\n            ->write(\"\\$e->setSourceContext(\\$this->source);\\n\\n\")\n            ->write(\"if (\\$e instanceof SecurityNotAllowedTagError && isset(\\$tags[\\$e->getTagName()])) {\\n\")\n            ->indent()\n            ->write(\"\\$e->setTemplateLine(\\$tags[\\$e->getTagName()]);\\n\")\n            ->outdent()\n            ->write(\"} elseif (\\$e instanceof SecurityNotAllowedFilterError && isset(\\$filters[\\$e->getFilterName()])) {\\n\")\n            ->indent()\n            ->write(\"\\$e->setTemplateLine(\\$filters[\\$e->getFilterName()]);\\n\")\n            ->outdent()\n            ->write(\"} elseif (\\$e instanceof SecurityNotAllowedFunctionError && isset(\\$functions[\\$e->getFunctionName()])) {\\n\")\n            ->indent()\n            ->write(\"\\$e->setTemplateLine(\\$functions[\\$e->getFunctionName()]);\\n\")\n            ->outdent()\n            ->write(\"}\\n\\n\")\n            ->write(\"throw \\$e;\\n\")\n            ->outdent()\n            ->write(\"}\\n\\n\")\n            ->outdent()\n            ->write(\"}\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/CheckToStringNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\n\n/**\n * Checks if casting an expression to __toString() is allowed by the sandbox.\n *\n * For instance, when there is a simple Print statement, like {{ article }},\n * and if the sandbox is enabled, we need to check that the __toString()\n * method is allowed if 'article' is an object. The same goes for {{ article|upper }}\n * or {{ random(article) }}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass CheckToStringNode extends AbstractExpression\n{\n    public function __construct(AbstractExpression $expr)\n    {\n        parent::__construct(['expr' => $expr], [], $expr->getTemplateLine());\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $expr = $this->getNode('expr');\n        $compiler\n            ->raw('$this->sandbox->ensureToStringAllowed(')\n            ->subcompile($expr)\n            ->raw(', ')\n            ->repr($expr->getTemplateLine())\n            ->raw(', $this->source)')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/DeprecatedNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\n\n/**\n * Represents a deprecated node.\n *\n * @author Yonel Ceruto <yonelceruto@gmail.com>\n */\n#[YieldReady]\nclass DeprecatedNode extends Node\n{\n    public function __construct(AbstractExpression $expr, int $lineno)\n    {\n        parent::__construct(['expr' => $expr], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n\n        $expr = $this->getNode('expr');\n\n        if (!$expr instanceof ConstantExpression) {\n            $varName = $compiler->getVarName();\n            $compiler\n                ->write(\\sprintf('$%s = ', $varName))\n                ->subcompile($expr)\n                ->raw(\";\\n\")\n            ;\n        }\n\n        $compiler->write('trigger_deprecation(');\n        if ($this->hasNode('package')) {\n            $compiler->subcompile($this->getNode('package'));\n        } else {\n            $compiler->raw(\"''\");\n        }\n        $compiler->raw(', ');\n        if ($this->hasNode('version')) {\n            $compiler->subcompile($this->getNode('version'));\n        } else {\n            $compiler->raw(\"''\");\n        }\n        $compiler->raw(', ');\n\n        if ($expr instanceof ConstantExpression) {\n            $compiler->subcompile($expr);\n        } else {\n            $compiler->write(\\sprintf('$%s', $varName));\n        }\n\n        $compiler\n            ->raw('.')\n            ->string(\\sprintf(' in \"%s\" at line %d.', $this->getTemplateName(), $this->getTemplateLine()))\n            ->raw(\");\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/DoNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\n\n/**\n * Represents a do node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass DoNode extends Node\n{\n    public function __construct(AbstractExpression $expr, int $lineno)\n    {\n        parent::__construct(['expr' => $expr], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->write('')\n            ->subcompile($this->getNode('expr'))\n            ->raw(\";\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/EmbedNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\n\n/**\n * Represents an embed node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass EmbedNode extends IncludeNode\n{\n    // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)\n    public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno)\n    {\n        parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno);\n\n        $this->setAttribute('name', $name);\n        $this->setAttribute('index', $index);\n    }\n\n    protected function addGetTemplate(Compiler $compiler, string $template = ''): void\n    {\n        $compiler\n            ->raw('$this->load(')\n            ->string($this->getAttribute('name'))\n            ->raw(', ')\n            ->repr($this->getTemplateLine())\n            ->raw(', ')\n            ->repr($this->getAttribute('index'))\n            ->raw(')')\n        ;\n        if ($this->getAttribute('ignore_missing')) {\n            $compiler\n                ->raw(\";\\n\")\n                ->write(\\sprintf(\"\\$%s->getParent(\\$context);\\n\", $template))\n            ;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/EmptyNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\n\n/**\n * Represents an empty node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nfinal class EmptyNode extends Node\n{\n    public function __construct(int $lineno = 0)\n    {\n        parent::__construct([], [], $lineno);\n    }\n\n    public function setNode(string $name, Node $node): void\n    {\n        throw new \\LogicException('EmptyNode cannot have children.');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/AbstractExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Node\\Node;\n\n/**\n * Abstract class for all nodes that represents an expression.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nabstract class AbstractExpression extends Node\n{\n    public function isGenerator(): bool\n    {\n        return $this->hasAttribute('is_generator') && $this->getAttribute('is_generator');\n    }\n\n    /**\n     * @return static\n     */\n    public function setExplicitParentheses(): self\n    {\n        $this->setAttribute('with_parentheses', true);\n\n        return $this;\n    }\n\n    public function hasExplicitParentheses(): bool\n    {\n        return $this->hasAttribute('with_parentheses') && $this->getAttribute('with_parentheses');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/ArrayExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\Unary\\SpreadUnary;\nuse Twig\\Node\\Expression\\Unary\\StringCastUnary;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\n\nclass ArrayExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnArrayInterface\n{\n    use SupportDefinedTestTrait;\n\n    private $index;\n\n    public function __construct(array $elements, int $lineno)\n    {\n        parent::__construct($elements, [], $lineno);\n\n        $this->index = -1;\n        foreach ($this->getKeyValuePairs() as $pair) {\n            if ($pair['key'] instanceof ConstantExpression && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) {\n                $this->index = $pair['key']->getAttribute('value');\n            }\n        }\n    }\n\n    public function getKeyValuePairs(): array\n    {\n        $pairs = [];\n        foreach (array_chunk($this->nodes, 2) as $pair) {\n            $pairs[] = [\n                'key' => $pair[0],\n                'value' => $pair[1],\n            ];\n        }\n\n        return $pairs;\n    }\n\n    public function hasElement(AbstractExpression $key): bool\n    {\n        foreach ($this->getKeyValuePairs() as $pair) {\n            // we compare the string representation of the keys\n            // to avoid comparing the line numbers which are not relevant here.\n            if ((string) $key === (string) $pair['key']) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Checks if the array is a sequence (keys are sequential integers starting from 0).\n     *\n     * @internal\n     */\n    public function isSequence(): bool\n    {\n        foreach ($this->getKeyValuePairs() as $i => $pair) {\n            $key = $pair['key'];\n            if ($key instanceof TempNameExpression) {\n                $keyValue = $key->getAttribute('name');\n            } elseif ($key instanceof ConstantExpression) {\n                $keyValue = $key->getAttribute('value');\n            } else {\n                return false;\n            }\n\n            if ($keyValue !== $i) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void\n    {\n        if (null === $key) {\n            $key = new ConstantExpression(++$this->index, $value->getTemplateLine());\n        }\n\n        array_push($this->nodes, $key, $value);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if ($this->definedTest) {\n            $compiler->repr(true);\n\n            return;\n        }\n\n        // Check for empty expressions which are only allowed in destructuring\n        foreach ($this->getKeyValuePairs() as $pair) {\n            if ($pair['value'] instanceof EmptyExpression) {\n                throw new SyntaxError('Empty array elements are only allowed in destructuring assignments.', $pair['value']->getTemplateLine(), $this->getSourceContext());\n            }\n        }\n\n        $compiler->raw('[');\n        $isSequence = true;\n        foreach ($this->getKeyValuePairs() as $i => $pair) {\n            if (0 !== $i) {\n                $compiler->raw(', ');\n            }\n\n            $key = null;\n            if ($pair['key'] instanceof ContextVariable) {\n                $pair['key'] = new StringCastUnary($pair['key'], $pair['key']->getTemplateLine());\n            } elseif ($pair['key'] instanceof TempNameExpression) {\n                $key = $pair['key']->getAttribute('name');\n                $pair['key'] = new ConstantExpression($key, $pair['key']->getTemplateLine());\n            } elseif ($pair['key'] instanceof ConstantExpression) {\n                $key = $pair['key']->getAttribute('value');\n            }\n\n            if ($key !== $i) {\n                $isSequence = false;\n            }\n\n            if (!$isSequence && !$pair['value'] instanceof SpreadUnary) {\n                $compiler\n                    ->subcompile($pair['key'])\n                    ->raw(' => ')\n                ;\n            }\n\n            $compiler->subcompile($pair['value']);\n        }\n        $compiler->raw(']');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/ArrowFunctionExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\n\n/**\n * Represents an arrow function.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass ArrowFunctionExpression extends AbstractExpression\n{\n    public function __construct(AbstractExpression $expr, Node $names, $lineno)\n    {\n        if ($names instanceof ContextVariable) {\n            $names = new ListExpression([new AssignContextVariable($names->getAttribute('name'), $names->getTemplateLine())], $lineno);\n        }\n\n        if (!$names instanceof ListExpression) {\n            throw new SyntaxError('The arrow function argument must be a list of variables or a single variable.', $names->getTemplateLine(), $names->getSourceContext());\n        }\n\n        parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->raw('function (')\n            ->subcompile($this->getNode('names'))\n            ->raw(') use ($context, $macros) { ')\n        ;\n        foreach ($this->getNode('names') as $name) {\n            $compiler\n                ->raw('$context[\"')\n                ->raw($name->getAttribute('name'))\n                ->raw('\"] = $__')\n                ->raw($name->getAttribute('name'))\n                ->raw('__; ')\n            ;\n        }\n        $compiler\n            ->raw('return ')\n            ->subcompile($this->getNode('expr'))\n            ->raw('; }')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/AssignNameExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\n\nclass AssignNameExpression extends ContextVariable\n{\n    public function __construct(string $name, int $lineno)\n    {\n        if (self::class === static::class) {\n            trigger_deprecation('twig/twig', '3.15', 'The \"%s\" class is deprecated, use \"%s\" instead.', self::class, AssignContextVariable::class);\n        }\n\n        // All names supported by ExpressionParser::parsePrimaryExpression() should be excluded\n        if (\\in_array(strtolower($name), ['true', 'false', 'none', 'null'], true)) {\n            throw new SyntaxError(\\sprintf('You cannot assign a value to \"%s\".', $name), $lineno);\n        }\n\n        parent::__construct($name, $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('$context[')\n            ->string($this->getAttribute('name'))\n            ->raw(']')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/AbstractBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Node;\n\nabstract class AbstractBinary extends AbstractExpression implements BinaryInterface\n{\n    /**\n     * @param AbstractExpression $left\n     * @param AbstractExpression $right\n     */\n    public function __construct(Node $left, Node $right, int $lineno)\n    {\n        if (!$left instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"left\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $left::class);\n        }\n        if (!$right instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"right\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $right::class);\n        }\n\n        parent::__construct(['left' => $left, 'right' => $right], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('(')\n            ->subcompile($this->getNode('left'))\n            ->raw(' ')\n        ;\n        $this->operator($compiler);\n        $compiler\n            ->raw(' ')\n            ->subcompile($this->getNode('right'))\n            ->raw(')')\n        ;\n    }\n\n    abstract public function operator(Compiler $compiler): Compiler;\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/AddBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass AddBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('+');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/AndBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass AndBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('&&');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/BinaryInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Node\\Expression\\AbstractExpression;\n\n/**\n * @internal\n */\ninterface BinaryInterface\n{\n    public function __construct(AbstractExpression $left, AbstractExpression $right, int $lineno);\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/BitwiseAndBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass BitwiseAndBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('&');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/BitwiseOrBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass BitwiseOrBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('|');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/BitwiseXorBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass BitwiseXorBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('^');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/ConcatBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnStringInterface;\n\nclass ConcatBinary extends AbstractBinary implements ReturnStringInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('.');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/DivBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass DivBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('/');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/ElvisBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\OperatorEscapeInterface;\nuse Twig\\Node\\Node;\n\nfinal class ElvisBinary extends AbstractBinary implements OperatorEscapeInterface\n{\n    /**\n     * @param AbstractExpression $left\n     * @param AbstractExpression $right\n     */\n    public function __construct(Node $left, Node $right, int $lineno)\n    {\n        parent::__construct($left, $right, $lineno);\n\n        $this->setNode('test', clone $left);\n        $left->setAttribute('always_defined', true);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('((')\n            ->subcompile($this->getNode('test'))\n            ->raw(') ? (')\n            ->subcompile($this->getNode('left'))\n            ->raw(') : (')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('?:');\n    }\n\n    public function getOperandNamesToEscape(): array\n    {\n        return ['left', 'right'];\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/EndsWithBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass EndsWithBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        $left = $compiler->getVarName();\n        $right = $compiler->getVarName();\n        $compiler\n            ->raw(\\sprintf('(is_string($%s = ', $left))\n            ->subcompile($this->getNode('left'))\n            ->raw(\\sprintf(') && is_string($%s = ', $right))\n            ->subcompile($this->getNode('right'))\n            ->raw(\\sprintf(') && str_ends_with($%1$s, $%2$s))', $left, $right))\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/EqualBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass EqualBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        if (\\PHP_VERSION_ID >= 80000) {\n            parent::compile($compiler);\n\n            return;\n        }\n\n        $compiler\n            ->raw('(0 === CoreExtension::compare(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('==');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/FloorDivBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass FloorDivBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->raw('(int) floor(');\n        parent::compile($compiler);\n        $compiler->raw(')');\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('/');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/GreaterBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass GreaterBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        if (\\PHP_VERSION_ID >= 80000) {\n            parent::compile($compiler);\n\n            return;\n        }\n\n        $compiler\n            ->raw('(1 === CoreExtension::compare(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('>');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/GreaterEqualBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass GreaterEqualBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        if (\\PHP_VERSION_ID >= 80000) {\n            parent::compile($compiler);\n\n            return;\n        }\n\n        $compiler\n            ->raw('(0 <= CoreExtension::compare(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('>=');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/HasEveryBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass HasEveryBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('CoreExtension::arrayEvery($this->env, ')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw(')')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/HasSomeBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass HasSomeBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('CoreExtension::arraySome($this->env, ')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw(')')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/InBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass InBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('CoreExtension::inFilter(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw(')')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('in');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/LessBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass LessBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        if (\\PHP_VERSION_ID >= 80000) {\n            parent::compile($compiler);\n\n            return;\n        }\n\n        $compiler\n            ->raw('(-1 === CoreExtension::compare(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('<');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/LessEqualBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass LessEqualBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        if (\\PHP_VERSION_ID >= 80000) {\n            parent::compile($compiler);\n\n            return;\n        }\n\n        $compiler\n            ->raw('(0 >= CoreExtension::compare(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('<=');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/MatchesBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\nuse Twig\\Node\\Node;\n\nclass MatchesBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function __construct(Node $left, Node $right, int $lineno)\n    {\n        if (!$left instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.24', 'Passing a \"%s\" instance to \"%s()\" first argument is deprecated, pass an \"AbstractExpression\" instance instead.', $left::class, __METHOD__);\n        }\n        if (!$right instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.24', 'Passing a \"%s\" instance to \"%s()\" second argument is deprecated, pass an \"AbstractExpression\" instance instead.', $right::class, __METHOD__);\n        }\n\n        if ($right instanceof ConstantExpression) {\n            $regexp = $right->getAttribute('value');\n            set_error_handler(static fn ($t, $m) => throw new SyntaxError(\\sprintf('Regexp \"%s\" passed to \"matches\" is not valid: %s.', $regexp, substr($m, 14)), $lineno));\n            try {\n                preg_match($regexp, '');\n            } finally {\n                restore_error_handler();\n            }\n        }\n\n        parent::__construct($left, $right, $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('CoreExtension::matches(')\n            ->subcompile($this->getNode('right'))\n            ->raw(', ')\n            ->subcompile($this->getNode('left'))\n            ->raw(')')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/ModBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass ModBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('%');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/MulBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass MulBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('*');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/NotEqualBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass NotEqualBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        if (\\PHP_VERSION_ID >= 80000) {\n            parent::compile($compiler);\n\n            return;\n        }\n\n        $compiler\n            ->raw('(0 !== CoreExtension::compare(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('!=');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/NotInBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass NotInBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('!CoreExtension::inFilter(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw(')')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('not in');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/NotSameAsBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass NotSameAsBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('!==');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/NullCoalesceBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\BlockReferenceExpression;\nuse Twig\\Node\\Expression\\OperatorEscapeInterface;\nuse Twig\\Node\\Expression\\Test\\DefinedTest;\nuse Twig\\Node\\Expression\\Test\\NullTest;\nuse Twig\\Node\\Expression\\Unary\\NotUnary;\nuse Twig\\Node\\Node;\nuse Twig\\TwigTest;\n\nfinal class NullCoalesceBinary extends AbstractBinary implements OperatorEscapeInterface\n{\n    /**\n     * @param AbstractExpression $left\n     * @param AbstractExpression $right\n     */\n    public function __construct(Node $left, Node $right, int $lineno)\n    {\n        parent::__construct($left, $right, $lineno);\n\n        $test = new DefinedTest(clone $left, new TwigTest('defined'), new EmptyNode(), $left->getTemplateLine());\n        // for \"block()\", we don't need the null test as the return value is always a string\n        if (!$left instanceof BlockReferenceExpression) {\n            $test = new AndBinary(\n                $test,\n                new NotUnary(new NullTest($left, new TwigTest('null'), new EmptyNode(), $left->getTemplateLine()), $left->getTemplateLine()),\n                $left->getTemplateLine(),\n            );\n        }\n\n        $left->setAttribute('always_defined', true);\n        $this->setNode('test', $test);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('((')\n            ->subcompile($this->getNode('test'))\n            ->raw(') ? (')\n            ->subcompile($this->getNode('left'))\n            ->raw(') : (')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('??');\n    }\n\n    public function getOperandNamesToEscape(): array\n    {\n        return ['left', 'right'];\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/ObjectDestructuringSetBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\n\n/**\n * @internal\n */\nclass ObjectDestructuringSetBinary extends AbstractBinary\n{\n    /** @var list<array{property: string, variable: string}> */\n    private array $mappings = [];\n\n    /**\n     * @param ArrayExpression    $left  The array expression containing object/mapping destructuring properties\n     * @param AbstractExpression $right The expression providing values for assignment\n     */\n    public function __construct(Node $left, Node $right, int $lineno)\n    {\n        if (!$left instanceof ArrayExpression) {\n            throw new \\LogicException('Left side must be ArrayExpression for object/mapping destructuring.');\n        }\n        foreach ($left->getKeyValuePairs() as $pair) {\n            if (!$pair['value'] instanceof ContextVariable) {\n                throw new SyntaxError(\\sprintf('Cannot assign to \"%s\", only variables can be assigned in object/mapping destructuring.', $pair['value']::class), $lineno);\n            }\n\n            $this->mappings[] = [\n                'property' => $pair['key']->getAttribute('value'),\n                'variable' => $pair['value']->getAttribute('name'),\n            ];\n        }\n\n        parent::__construct($left, $right, $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n        $compiler->raw('[');\n        foreach ($this->mappings as $i => $mapping) {\n            if ($i) {\n                $compiler->raw(', ');\n            }\n            $compiler->raw('$context[')->repr($mapping['variable'])->raw(']');\n        }\n        $compiler->raw('] = [');\n        foreach ($this->mappings as $i => $mapping) {\n            if ($i) {\n                $compiler->raw(', ');\n            }\n            $compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ')->subcompile($this->getNode('right'))->raw(', ')->repr($mapping['property'])->raw(', [], \\\\Twig\\\\Template::ANY_CALL, false, false, false, ')->repr($this->getNode('right')->getTemplateLine())->raw(')');\n        }\n        $compiler->raw(']');\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('=');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/OrBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass OrBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('||');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/PowerBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass PowerBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('**');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/RangeBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnArrayInterface;\n\nclass RangeBinary extends AbstractBinary implements ReturnArrayInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('range(')\n            ->subcompile($this->getNode('left'))\n            ->raw(', ')\n            ->subcompile($this->getNode('right'))\n            ->raw(')')\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('..');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/SameAsBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass SameAsBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('===');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/SequenceDestructuringSetBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\EmptyExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\n\n/**\n * @internal\n */\nclass SequenceDestructuringSetBinary extends AbstractBinary\n{\n    private array $variables = [];\n\n    /**\n     * @param ArrayExpression    $left  The array expression containing variables to assign to\n     * @param AbstractExpression $right The expression providing values for assignment\n     */\n    public function __construct(Node $left, Node $right, int $lineno)\n    {\n        foreach ($left->getKeyValuePairs() as $pair) {\n            if ($pair['value'] instanceof EmptyExpression) {\n                $this->variables[] = null;\n            } elseif ($pair['value'] instanceof ContextVariable) {\n                $this->variables[] = $pair['value']->getAttribute('name');\n            } else {\n                throw new SyntaxError(\\sprintf('Cannot assign to \"%s\", only variables can be assigned in sequence destructuring.', $pair['value']::class), $lineno);\n            }\n        }\n\n        parent::__construct($left, $right, $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n        $compiler->raw('[');\n        foreach ($this->variables as $i => $name) {\n            if ($i) {\n                $compiler->raw(', ');\n            }\n            if (null !== $name) {\n                $compiler->raw('$context[')->repr($name)->raw(']');\n            }\n        }\n        $compiler->raw('] = array_pad(')->subcompile($this->getNode('right'))->raw(', ')->repr(\\count($this->variables))->raw(', null)');\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('=');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/SetBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass SetBinary extends AbstractBinary\n{\n    /**\n     * @param ContextVariable    $left\n     * @param AbstractExpression $right\n     */\n    public function __construct(Node $left, Node $right, int $lineno)\n    {\n        $name = $left->getAttribute('name');\n        if (!\\is_string($name)) {\n            throw new \\LogicException('The \"name\" attribute must be a string.');\n        }\n        $left = new AssignContextVariable($name, $left->getTemplateLine());\n\n        parent::__construct($left, $right, $lineno);\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('=');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/SpaceshipBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass SpaceshipBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('<=>');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/StartsWithBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass StartsWithBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function compile(Compiler $compiler): void\n    {\n        $left = $compiler->getVarName();\n        $right = $compiler->getVarName();\n        $compiler\n            ->raw(\\sprintf('(is_string($%s = ', $left))\n            ->subcompile($this->getNode('left'))\n            ->raw(\\sprintf(') && is_string($%s = ', $right))\n            ->subcompile($this->getNode('right'))\n            ->raw(\\sprintf(') && str_starts_with($%1$s, $%2$s))', $left, $right))\n        ;\n    }\n\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/SubBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnNumberInterface;\n\nclass SubBinary extends AbstractBinary implements ReturnNumberInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('-');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Binary/XorBinary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Binary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnBoolInterface;\n\nclass XorBinary extends AbstractBinary implements ReturnBoolInterface\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('xor');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/BlockReferenceExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Node;\n\n/**\n * Represents a block call node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass BlockReferenceExpression extends AbstractExpression implements SupportDefinedTestInterface\n{\n    use SupportDefinedTestDeprecationTrait;\n    use SupportDefinedTestTrait;\n\n    /**\n     * @param AbstractExpression $name\n     */\n    public function __construct(Node $name, ?Node $template, int $lineno)\n    {\n        if (!$name instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"node\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $name::class);\n        }\n\n        $nodes = ['name' => $name];\n        if (null !== $template) {\n            $nodes['template'] = $template;\n        }\n\n        parent::__construct($nodes, ['output' => false], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if ($this->definedTest) {\n            $this->compileTemplateCall($compiler, 'hasBlock');\n        } else {\n            if ($this->getAttribute('output')) {\n                $compiler->addDebugInfo($this);\n\n                $compiler->write('yield from ');\n                $this\n                    ->compileTemplateCall($compiler, 'yieldBlock')\n                    ->raw(\";\\n\");\n            } else {\n                $this->compileTemplateCall($compiler, 'renderBlock');\n            }\n        }\n    }\n\n    private function compileTemplateCall(Compiler $compiler, string $method): Compiler\n    {\n        if (!$this->hasNode('template')) {\n            $compiler->write('$this');\n        } else {\n            $compiler\n                ->write('$this->load(')\n                ->subcompile($this->getNode('template'))\n                ->raw(', ')\n                ->repr($this->getTemplateLine())\n                ->raw(')')\n            ;\n        }\n\n        $compiler->raw(\\sprintf('->unwrap()->%s', $method));\n\n        return $this->compileBlockArguments($compiler);\n    }\n\n    private function compileBlockArguments(Compiler $compiler): Compiler\n    {\n        $compiler\n            ->raw('(')\n            ->subcompile($this->getNode('name'))\n            ->raw(', $context');\n\n        if (!$this->hasNode('template')) {\n            $compiler->raw(', $blocks');\n        }\n\n        return $compiler->raw(')');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/CallExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Extension\\ExtensionInterface;\nuse Twig\\Node\\Node;\nuse Twig\\TwigCallableInterface;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\nuse Twig\\Util\\CallableArgumentsExtractor;\nuse Twig\\Util\\ReflectionCallable;\n\nabstract class CallExpression extends AbstractExpression\n{\n    private $reflector;\n\n    /**\n     * @return void\n     */\n    protected function compileCallable(Compiler $compiler)\n    {\n        $twigCallable = $this->getTwigCallable();\n        $callable = $twigCallable->getCallable();\n\n        if (\\is_string($callable) && !str_contains($callable, '::')) {\n            $compiler->raw($callable);\n        } else {\n            $rc = $this->reflectCallable($twigCallable);\n            $r = $rc->getReflector();\n            $callable = $rc->getCallable();\n\n            if (\\is_string($callable)) {\n                $compiler->raw($callable);\n            } elseif (\\is_array($callable) && \\is_string($callable[0])) {\n                if (!$r instanceof \\ReflectionMethod || $r->isStatic()) {\n                    $compiler->raw(\\sprintf('%s::%s', $callable[0], $callable[1]));\n                } else {\n                    $compiler->raw(\\sprintf('$this->env->getRuntime(\\'%s\\')->%s', $callable[0], $callable[1]));\n                }\n            } elseif (\\is_array($callable) && $callable[0] instanceof ExtensionInterface) {\n                $class = \\get_class($callable[0]);\n                if (!$compiler->getEnvironment()->hasExtension($class)) {\n                    // Compile a non-optimized call to trigger a \\Twig\\Error\\RuntimeError, which cannot be a compile-time error\n                    $compiler->raw(\\sprintf('$this->env->getExtension(\\'%s\\')', $class));\n                } else {\n                    $compiler->raw(\\sprintf('$this->extensions[\\'%s\\']', ltrim($class, '\\\\')));\n                }\n\n                $compiler->raw(\\sprintf('->%s', $callable[1]));\n            } else {\n                $compiler->raw(\\sprintf('$this->env->get%s(\\'%s\\')->getCallable()', ucfirst($this->getAttribute('type')), $twigCallable->getDynamicName()));\n            }\n        }\n\n        $this->compileArguments($compiler);\n    }\n\n    protected function compileArguments(Compiler $compiler, $isArray = false): void\n    {\n        if (\\func_num_args() >= 2) {\n            trigger_deprecation('twig/twig', '3.11', 'Passing a second argument to \"%s()\" is deprecated.', __METHOD__);\n        }\n\n        $compiler->raw($isArray ? '[' : '(');\n\n        $first = true;\n\n        $twigCallable = $this->getAttribute('twig_callable');\n\n        if ($twigCallable->needsCharset()) {\n            $compiler->raw('$this->env->getCharset()');\n            $first = false;\n        }\n\n        if ($twigCallable->needsEnvironment()) {\n            if (!$first) {\n                $compiler->raw(', ');\n            }\n            $compiler->raw('$this->env');\n            $first = false;\n        }\n\n        if ($twigCallable->needsContext()) {\n            if (!$first) {\n                $compiler->raw(', ');\n            }\n            $compiler->raw('$context');\n            $first = false;\n        }\n\n        foreach ($twigCallable->getArguments() as $argument) {\n            if (!$first) {\n                $compiler->raw(', ');\n            }\n            $compiler->string($argument);\n            $first = false;\n        }\n\n        if ($this->hasNode('node')) {\n            if (!$first) {\n                $compiler->raw(', ');\n            }\n            $compiler->subcompile($this->getNode('node'));\n            $first = false;\n        }\n\n        if ($this->hasNode('arguments')) {\n            $arguments = (new CallableArgumentsExtractor($this, $this->getTwigCallable()))->extractArguments($this->getNode('arguments'));\n            foreach ($arguments as $node) {\n                if (!$first) {\n                    $compiler->raw(', ');\n                }\n                $compiler->subcompile($node);\n                $first = false;\n            }\n        }\n\n        $compiler->raw($isArray ? ']' : ')');\n    }\n\n    /**\n     * @deprecated since Twig 3.12, use Twig\\Util\\CallableArgumentsExtractor::getArguments() instead\n     */\n    protected function getArguments($callable, $arguments)\n    {\n        trigger_deprecation('twig/twig', '3.12', 'The \"%s()\" method is deprecated, use Twig\\Util\\CallableArgumentsExtractor::getArguments() instead.', __METHOD__);\n\n        $callType = $this->getAttribute('type');\n        $callName = $this->getAttribute('name');\n\n        $parameters = [];\n        $named = false;\n        foreach ($arguments as $name => $node) {\n            if (!\\is_int($name)) {\n                $named = true;\n                $name = $this->normalizeName($name);\n            } elseif ($named) {\n                throw new SyntaxError(\\sprintf('Positional arguments cannot be used after named arguments for %s \"%s\".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());\n            }\n\n            $parameters[$name] = $node;\n        }\n\n        $isVariadic = $this->getAttribute('twig_callable')->isVariadic();\n        if (!$named && !$isVariadic) {\n            return $parameters;\n        }\n\n        if (!$callable) {\n            if ($named) {\n                $message = \\sprintf('Named arguments are not supported for %s \"%s\".', $callType, $callName);\n            } else {\n                $message = \\sprintf('Arbitrary positional arguments are not supported for %s \"%s\".', $callType, $callName);\n            }\n\n            throw new \\LogicException($message);\n        }\n\n        [$callableParameters, $isPhpVariadic] = $this->getCallableParameters($callable, $isVariadic);\n        $arguments = [];\n        $names = [];\n        $missingArguments = [];\n        $optionalArguments = [];\n        $pos = 0;\n        foreach ($callableParameters as $callableParameter) {\n            $name = $this->normalizeName($callableParameter->name);\n            if (\\PHP_VERSION_ID >= 80000 && 'range' === $callable) {\n                if ('start' === $name) {\n                    $name = 'low';\n                } elseif ('end' === $name) {\n                    $name = 'high';\n                }\n            }\n\n            $names[] = $name;\n\n            if (\\array_key_exists($name, $parameters)) {\n                if (\\array_key_exists($pos, $parameters)) {\n                    throw new SyntaxError(\\sprintf('Argument \"%s\" is defined twice for %s \"%s\".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());\n                }\n\n                if (\\count($missingArguments)) {\n                    throw new SyntaxError(\\sprintf(\n                        'Argument \"%s\" could not be assigned for %s \"%s(%s)\" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s \"%s\".',\n                        $name, $callType, $callName, implode(', ', $names), \\count($missingArguments) > 1 ? 's' : '', implode('\", \"', $missingArguments)\n                    ), $this->getTemplateLine(), $this->getSourceContext());\n                }\n\n                $arguments = array_merge($arguments, $optionalArguments);\n                $arguments[] = $parameters[$name];\n                unset($parameters[$name]);\n                $optionalArguments = [];\n            } elseif (\\array_key_exists($pos, $parameters)) {\n                $arguments = array_merge($arguments, $optionalArguments);\n                $arguments[] = $parameters[$pos];\n                unset($parameters[$pos]);\n                $optionalArguments = [];\n                ++$pos;\n            } elseif ($callableParameter->isDefaultValueAvailable()) {\n                $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1);\n            } elseif ($callableParameter->isOptional()) {\n                if (!$parameters) {\n                    break;\n                }\n                $missingArguments[] = $name;\n            } else {\n                throw new SyntaxError(\\sprintf('Value for argument \"%s\" is required for %s \"%s\".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());\n            }\n        }\n\n        if ($isVariadic) {\n            $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1);\n            foreach ($parameters as $key => $value) {\n                if (\\is_int($key)) {\n                    $arbitraryArguments->addElement($value);\n                } else {\n                    $arbitraryArguments->addElement($value, new ConstantExpression($key, -1));\n                }\n                unset($parameters[$key]);\n            }\n\n            if ($arbitraryArguments->count()) {\n                $arguments = array_merge($arguments, $optionalArguments);\n                $arguments[] = $arbitraryArguments;\n            }\n        }\n\n        if ($parameters) {\n            $unknownParameter = null;\n            foreach ($parameters as $parameter) {\n                if ($parameter instanceof Node) {\n                    $unknownParameter = $parameter;\n                    break;\n                }\n            }\n\n            throw new SyntaxError(\n                \\sprintf(\n                    'Unknown argument%s \"%s\" for %s \"%s(%s)\".',\n                    \\count($parameters) > 1 ? 's' : '', implode('\", \"', array_keys($parameters)), $callType, $callName, implode(', ', $names)\n                ),\n                $unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(),\n                $unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext()\n            );\n        }\n\n        return $arguments;\n    }\n\n    /**\n     * @deprecated since Twig 3.12\n     */\n    protected function normalizeName(string $name): string\n    {\n        trigger_deprecation('twig/twig', '3.12', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\\d])([A-Z])/'], ['\\\\1_\\\\2', '\\\\1_\\\\2'], $name));\n    }\n\n    // To be removed in 4.0\n    private function getCallableParameters($callable, bool $isVariadic): array\n    {\n        $twigCallable = $this->getAttribute('twig_callable');\n        $rc = $this->reflectCallable($twigCallable);\n        $r = $rc->getReflector();\n        $callableName = $rc->getName();\n\n        $parameters = $r->getParameters();\n        if ($this->hasNode('node')) {\n            array_shift($parameters);\n        }\n        if ($twigCallable->needsCharset()) {\n            array_shift($parameters);\n        }\n        if ($twigCallable->needsEnvironment()) {\n            array_shift($parameters);\n        }\n        if ($twigCallable->needsContext()) {\n            array_shift($parameters);\n        }\n        foreach ($twigCallable->getArguments() as $argument) {\n            array_shift($parameters);\n        }\n\n        $isPhpVariadic = false;\n        if ($isVariadic) {\n            $argument = end($parameters);\n            $isArray = $argument && $argument->hasType() && $argument->getType() instanceof \\ReflectionNamedType && 'array' === $argument->getType()->getName();\n            if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {\n                array_pop($parameters);\n            } elseif ($argument && $argument->isVariadic()) {\n                array_pop($parameters);\n                $isPhpVariadic = true;\n            } else {\n                throw new \\LogicException(\\sprintf('The last parameter of \"%s\" for %s \"%s\" must be an array with default value, eg. \"array $arg = []\".', $callableName, $this->getAttribute('type'), $twigCallable->getName()));\n            }\n        }\n\n        return [$parameters, $isPhpVariadic];\n    }\n\n    private function reflectCallable(TwigCallableInterface $callable): ReflectionCallable\n    {\n        if (!$this->reflector) {\n            $this->reflector = new ReflectionCallable($callable);\n        }\n\n        return $this->reflector;\n    }\n\n    /**\n     * Overrides the Twig callable based on attributes (as potentially, attributes changed between the creation and the compilation of the node).\n     *\n     * To be removed in 4.0 and replace by $this->getAttribute('twig_callable').\n     */\n    private function getTwigCallable(): TwigCallableInterface\n    {\n        $current = $this->getAttribute('twig_callable');\n\n        $this->setAttribute('twig_callable', match ($this->getAttribute('type')) {\n            'test' => (new TwigTest(\n                $this->getAttribute('name'),\n                $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(),\n                [\n                    'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(),\n                ],\n            ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()),\n            'function' => (new TwigFunction(\n                $this->hasAttribute('name') ? $this->getAttribute('name') : $current->getName(),\n                $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(),\n                [\n                    'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(),\n                    'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(),\n                    'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(),\n                    'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(),\n                ],\n            ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()),\n            'filter' => (new TwigFilter(\n                $this->getAttribute('name'),\n                $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(),\n                [\n                    'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(),\n                    'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(),\n                    'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(),\n                    'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(),\n                ],\n            ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()),\n        });\n\n        return $this->getAttribute('twig_callable');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/ConditionalExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\Ternary\\ConditionalTernary;\n\nclass ConditionalExpression extends AbstractExpression implements OperatorEscapeInterface\n{\n    public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno)\n    {\n        trigger_deprecation('twig/twig', '3.17', \\sprintf('\"%s\" is deprecated; use \"%s\" instead.', __CLASS__, ConditionalTernary::class));\n\n        parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        // Ternary with no then uses Elvis operator\n        if ($this->getNode('expr1') === $this->getNode('expr2')) {\n            $compiler\n                ->raw('((')\n                ->subcompile($this->getNode('expr1'))\n                ->raw(') ?: (')\n                ->subcompile($this->getNode('expr3'))\n                ->raw('))');\n        } else {\n            $compiler\n                ->raw('((')\n                ->subcompile($this->getNode('expr1'))\n                ->raw(') ? (')\n                ->subcompile($this->getNode('expr2'))\n                ->raw(') : (')\n                ->subcompile($this->getNode('expr3'))\n                ->raw('))');\n        }\n    }\n\n    public function getOperandNamesToEscape(): array\n    {\n        return ['expr2', 'expr3'];\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/ConstantExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\n\n/**\n * @final\n */\nclass ConstantExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnPrimitiveTypeInterface\n{\n    use SupportDefinedTestTrait;\n\n    public function __construct($value, int $lineno)\n    {\n        parent::__construct([], ['value' => $value], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->repr($this->definedTest ? true : $this->getAttribute('value'));\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/EmptyExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\n\n/**\n * Represents an empty slot in an array.\n *\n * This is currently only used in destructuring contexts.\n *\n * @internal\n */\nfinal class EmptyExpression extends AbstractExpression\n{\n    public function __construct(int $lineno)\n    {\n        parent::__construct([], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Filter/DefaultFilter.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Filter;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\Compiler;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FilterExpression;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Expression\\Ternary\\ConditionalTernary;\nuse Twig\\Node\\Expression\\Test\\DefinedTest;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\nuse Twig\\TwigFilter;\nuse Twig\\TwigTest;\n\n/**\n * Returns the value or the default value when it is undefined or empty.\n *\n *  {{ var.foo|default('foo item on var is not defined') }}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass DefaultFilter extends FilterExpression\n{\n    /**\n     * @param AbstractExpression $node\n     */\n    #[FirstClassTwigCallableReady]\n    public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno)\n    {\n        if (!$node instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"node\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $node::class);\n        }\n\n        if ($filter instanceof TwigFilter) {\n            $name = $filter->getName();\n            $default = new FilterExpression($node, $filter, $arguments, $node->getTemplateLine());\n        } else {\n            $name = $filter->getAttribute('value');\n            $default = new FilterExpression($node, new TwigFilter('default', [CoreExtension::class, 'default']), $arguments, $node->getTemplateLine());\n        }\n\n        if ('default' === $name && ($node instanceof ContextVariable || $node instanceof GetAttrExpression)) {\n            $test = new DefinedTest(clone $node, new TwigTest('defined'), new EmptyNode(), $node->getTemplateLine());\n            $false = \\count($arguments) ? $arguments->getNode('0') : new ConstantExpression('', $node->getTemplateLine());\n\n            $node = new ConditionalTernary($test, $default, $false, $node->getTemplateLine());\n        } else {\n            $node = $default;\n        }\n\n        parent::__construct($node, $filter, $arguments, $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->subcompile($this->getNode('node'));\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Filter/RawFilter.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Filter;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FilterExpression;\nuse Twig\\Node\\Node;\nuse Twig\\TwigFilter;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass RawFilter extends FilterExpression\n{\n    /**\n     * @param AbstractExpression $node\n     */\n    #[FirstClassTwigCallableReady]\n    public function __construct(Node $node, TwigFilter|ConstantExpression|null $filter = null, ?Node $arguments = null, int $lineno = 0)\n    {\n        if (!$node instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"node\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $node::class);\n        }\n\n        parent::__construct($node, $filter ?: new TwigFilter('raw', null, ['is_safe' => ['all']]), $arguments ?: new EmptyNode(), $lineno ?: $node->getTemplateLine());\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->subcompile($this->getNode('node'));\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/FilterExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\NameDeprecation;\nuse Twig\\Node\\Node;\nuse Twig\\TwigFilter;\n\nclass FilterExpression extends CallExpression\n{\n    /**\n     * @param AbstractExpression $node\n     */\n    #[FirstClassTwigCallableReady]\n    public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno)\n    {\n        if (!$node instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"node\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $node::class);\n        }\n\n        if ($filter instanceof TwigFilter) {\n            $name = $filter->getName();\n            $filterName = new ConstantExpression($name, $lineno);\n        } else {\n            $name = $filter->getAttribute('value');\n            $filterName = $filter;\n            trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of \"TwigFilter\" when creating a \"%s\" filter of type \"%s\" is deprecated.', $name, static::class);\n        }\n\n        parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], ['name' => $name, 'type' => 'filter'], $lineno);\n\n        if ($filter instanceof TwigFilter) {\n            $this->setAttribute('twig_callable', $filter);\n        }\n\n        $this->deprecateNode('filter', new NameDeprecation('twig/twig', '3.12'));\n\n        $this->deprecateAttribute('needs_charset', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('needs_environment', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('needs_context', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12'));\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $name = $this->getNode('filter', false)->getAttribute('value');\n        if ($name !== $this->getAttribute('name')) {\n            trigger_deprecation('twig/twig', '3.11', 'Changing the value of a \"filter\" node in a NodeVisitor class is not supported anymore.');\n            $this->removeAttribute('twig_callable');\n        }\n        if ('raw' === $name) {\n            trigger_deprecation('twig/twig', '3.11', 'Creating the \"raw\" filter via \"FilterExpression\" is deprecated; use \"RawFilter\" instead.');\n\n            $compiler->subcompile($this->getNode('node'));\n\n            return;\n        }\n\n        if (!$this->hasAttribute('twig_callable')) {\n            $this->setAttribute('twig_callable', $compiler->getEnvironment()->getFilter($name));\n        }\n\n        $this->compileCallable($compiler);\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/FunctionExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\NameDeprecation;\nuse Twig\\Node\\Node;\nuse Twig\\TwigFunction;\n\nclass FunctionExpression extends CallExpression implements SupportDefinedTestInterface\n{\n    use SupportDefinedTestDeprecationTrait;\n    use SupportDefinedTestTrait;\n\n    #[FirstClassTwigCallableReady]\n    public function __construct(TwigFunction|string $function, Node $arguments, int $lineno)\n    {\n        if ($function instanceof TwigFunction) {\n            $name = $function->getName();\n        } else {\n            $name = $function;\n            trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of \"TwigFunction\" when creating a \"%s\" function of type \"%s\" is deprecated.', $name, static::class);\n        }\n\n        parent::__construct(['arguments' => $arguments], ['name' => $name, 'type' => 'function'], $lineno);\n\n        if ($function instanceof TwigFunction) {\n            $this->setAttribute('twig_callable', $function);\n        }\n\n        $this->deprecateAttribute('needs_charset', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('needs_environment', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('needs_context', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12'));\n    }\n\n    public function enableDefinedTest(): void\n    {\n        if ('constant' === $this->getAttribute('name')) {\n            $this->definedTest = true;\n        }\n    }\n\n    /**\n     * @return void\n     */\n    public function compile(Compiler $compiler)\n    {\n        $name = $this->getAttribute('name');\n        if ($this->hasAttribute('twig_callable')) {\n            $name = $this->getAttribute('twig_callable')->getName();\n            if ($name !== $this->getAttribute('name')) {\n                trigger_deprecation('twig/twig', '3.12', 'Changing the value of a \"function\" node in a NodeVisitor class is not supported anymore.');\n                $this->removeAttribute('twig_callable');\n            }\n        }\n\n        if (!$this->hasAttribute('twig_callable')) {\n            $this->setAttribute('twig_callable', $compiler->getEnvironment()->getFunction($name));\n        }\n\n        if ('constant' === $name && $this->isDefinedTestEnabled()) {\n            $this->getNode('arguments')->setNode('checkDefined', new ConstantExpression(true, $this->getTemplateLine()));\n        }\n\n        $this->compileCallable($compiler);\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/FunctionNode/EnumCasesFunction.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\FunctionNode;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\n\nclass EnumCasesFunction extends FunctionExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $arguments = $this->getNode('arguments');\n        if ($arguments->hasNode('enum')) {\n            $firstArgument = $arguments->getNode('enum');\n        } elseif ($arguments->hasNode('0')) {\n            $firstArgument = $arguments->getNode('0');\n        } else {\n            $firstArgument = null;\n        }\n\n        if (!$firstArgument instanceof ConstantExpression || 1 !== \\count($arguments)) {\n            parent::compile($compiler);\n\n            return;\n        }\n\n        $value = $firstArgument->getAttribute('value');\n\n        if (!\\is_string($value)) {\n            throw new SyntaxError('The first argument of the \"enum_cases\" function must be a string.', $this->getTemplateLine(), $this->getSourceContext());\n        }\n\n        if (!enum_exists($value)) {\n            throw new SyntaxError(\\sprintf('The first argument of the \"enum_cases\" function must be the name of an enum, \"%s\" given.', $value), $this->getTemplateLine(), $this->getSourceContext());\n        }\n\n        $compiler->raw(\\sprintf('%s::cases()', $value));\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/FunctionNode/EnumFunction.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\FunctionNode;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\n\nclass EnumFunction extends FunctionExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $arguments = $this->getNode('arguments');\n        if ($arguments->hasNode('enum')) {\n            $firstArgument = $arguments->getNode('enum');\n        } elseif ($arguments->hasNode('0')) {\n            $firstArgument = $arguments->getNode('0');\n        } else {\n            $firstArgument = null;\n        }\n\n        if (!$firstArgument instanceof ConstantExpression || 1 !== \\count($arguments)) {\n            parent::compile($compiler);\n\n            return;\n        }\n\n        $value = $firstArgument->getAttribute('value');\n\n        if (!\\is_string($value)) {\n            throw new SyntaxError('The first argument of the \"enum\" function must be a string.', $this->getTemplateLine(), $this->getSourceContext());\n        }\n\n        if (!enum_exists($value)) {\n            throw new SyntaxError(\\sprintf('The first argument of the \"enum\" function must be the name of an enum, \"%s\" given.', $value), $this->getTemplateLine(), $this->getSourceContext());\n        }\n\n        if (!$cases = $value::cases()) {\n            throw new SyntaxError(\\sprintf('The first argument of the \"enum\" function must be a non-empty enum, \"%s\" given.', $value), $this->getTemplateLine(), $this->getSourceContext());\n        }\n\n        $compiler->raw(\\sprintf('%s::%s', $value, $cases[0]->name));\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/GetAttrExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Template;\n\nclass GetAttrExpression extends AbstractExpression implements SupportDefinedTestInterface\n{\n    use SupportDefinedTestDeprecationTrait;\n    use SupportDefinedTestTrait;\n\n    /**\n     * @param ArrayExpression|NameExpression|null $arguments\n     */\n    public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno, bool $nullSafe = false)\n    {\n        $nodes = ['node' => $node, 'attribute' => $attribute];\n        if (null !== $arguments) {\n            $nodes['arguments'] = $arguments;\n        }\n\n        if ($arguments && !$arguments instanceof ArrayExpression && !$arguments instanceof ContextVariable) {\n            trigger_deprecation('twig/twig', '3.15', \\sprintf('Not passing a \"%s\" instance as the \"arguments\" argument of the \"%s\" constructor is deprecated (\"%s\" given).', ArrayExpression::class, static::class, $arguments::class));\n        }\n\n        parent::__construct($nodes, ['type' => $type, 'ignore_strict_check' => false, 'optimizable' => !$nullSafe, 'null_safe' => $nullSafe, 'is_short_circuited' => false, 'var_name' => null], $lineno);\n    }\n\n    public function enableDefinedTest(): void\n    {\n        $this->definedTest = true;\n        $this->changeIgnoreStrictCheck($this);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $env = $compiler->getEnvironment();\n        $arrayAccessSandbox = false;\n        $nullSafe = $this->getAttribute('null_safe');\n\n        // optimize array calls\n        if (\n            $this->getAttribute('optimizable')\n            && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check'))\n            && !$this->definedTest\n            && Template::ARRAY_CALL === $this->getAttribute('type')\n        ) {\n            $var = '$'.$compiler->getVarName();\n            $compiler\n                ->raw('(('.$var.' = ')\n                ->subcompile($this->getNode('node'))\n                ->raw(') && is_array(')\n                ->raw($var);\n\n            if (!$env->hasExtension(SandboxExtension::class)) {\n                $compiler\n                    ->raw(') || ')\n                    ->raw($var)\n                    ->raw(' instanceof ArrayAccess ? (')\n                    ->raw($var)\n                    ->raw('[')\n                    ->subcompile($this->getNode('attribute'))\n                    ->raw('] ?? null) : null)')\n                ;\n\n                return;\n            }\n\n            $arrayAccessSandbox = true;\n\n            $compiler\n                ->raw(') || ')\n                ->raw($var)\n                ->raw(' instanceof ArrayAccess && in_array(')\n                ->raw($var.'::class')\n                ->raw(', CoreExtension::ARRAY_LIKE_CLASSES, true) ? (')\n                ->raw($var)\n                ->raw('[')\n                ->subcompile($this->getNode('attribute'))\n                ->raw('] ?? null) : ')\n            ;\n        }\n\n        if ($this->getAttribute('ignore_strict_check')) {\n            $this->getNode('node')->setAttribute('ignore_strict_check', true);\n        }\n\n        if (null === $nullSafeNode = $nullSafe ? $this : null) {\n            $node = $this->getNode('node');\n            while ($node instanceof self) {\n                if ($node->getAttribute('null_safe')) {\n                    $nullSafeNode = $node;\n                    break;\n                }\n                $node = $node->getNode('node');\n            }\n        }\n\n        $isShortCircuited = false;\n        if (null !== $nullSafeNode && !$nullSafeNode->isShortCircuited()) {\n            $compiler\n                ->raw('((null === ('.$nullSafeNode->getVarName($compiler).' = ')\n                ->subcompile($nullSafeNode->getNode('node'))\n                ->raw(')) ? null : ');\n\n            $nullSafeNode->markAsShortCircuited();\n            $isShortCircuited = true;\n        }\n\n        $compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ');\n\n        if ($nullSafe) {\n            $compiler->raw($this->getVarName($compiler));\n        } else {\n            $compiler->subcompile($this->getNode('node'));\n        }\n\n        $compiler\n            ->raw(', ')\n            ->subcompile($this->getNode('attribute'))\n        ;\n\n        if ($this->hasNode('arguments')) {\n            $compiler->raw(', ')->subcompile($this->getNode('arguments'));\n        } else {\n            $compiler->raw(', []');\n        }\n\n        $compiler->raw(', ')\n            ->repr($this->getAttribute('type'))\n            ->raw(', ')->repr($this->definedTest)\n            ->raw(', ')->repr($this->getAttribute('ignore_strict_check'))\n            ->raw(', ')->repr($env->hasExtension(SandboxExtension::class))\n            ->raw(', ')->repr($this->getNode('node')->getTemplateLine())\n            ->raw(')')\n        ;\n\n        if ($arrayAccessSandbox) {\n            $compiler->raw(')');\n        }\n\n        if ($isShortCircuited) {\n            $compiler->raw(')');\n        }\n    }\n\n    private function changeIgnoreStrictCheck(self $node): void\n    {\n        $node->setAttribute('optimizable', false);\n        $node->setAttribute('ignore_strict_check', true);\n\n        if ($node->getNode('node') instanceof self) {\n            $this->changeIgnoreStrictCheck($node->getNode('node'));\n        }\n    }\n\n    private function markAsShortCircuited(): void\n    {\n        $this->setAttribute('is_short_circuited', true);\n    }\n\n    private function isShortCircuited(): bool\n    {\n        return $this->getAttribute('is_short_circuited');\n    }\n\n    private function getVarName(Compiler $compiler): string\n    {\n        if (null === $this->getAttribute('var_name')) {\n            $this->setAttribute('var_name', $compiler->getVarName());\n        }\n\n        return '$'.$this->getAttribute('var_name');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/InlinePrint.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Node;\n\n/**\n * @internal\n */\nfinal class InlinePrint extends AbstractExpression\n{\n    /**\n     * @param AbstractExpression $node\n     */\n    public function __construct(Node $node, int $lineno)\n    {\n        trigger_deprecation('twig/twig', '3.16', \\sprintf('The \"%s\" class is deprecated with no replacement.', static::class));\n\n        parent::__construct(['node' => $node], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('yield ')\n            ->subcompile($this->getNode('node'))\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/ListExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\n\nclass ListExpression extends AbstractExpression\n{\n    /**\n     * @param array<AssignContextVariable> $items\n     */\n    public function __construct(array $items, int $lineno)\n    {\n        parent::__construct($items, [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        foreach ($this as $i => $name) {\n            if ($i) {\n                $compiler->raw(', ');\n            }\n\n            $compiler\n                ->raw('$__')\n                ->raw($name->getAttribute('name'))\n                ->raw('__')\n            ;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/MacroReferenceExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\Variable\\TemplateVariable;\n\n/**\n * Represents a macro call node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass MacroReferenceExpression extends AbstractExpression implements SupportDefinedTestInterface\n{\n    use SupportDefinedTestDeprecationTrait;\n    use SupportDefinedTestTrait;\n\n    public function __construct(TemplateVariable $template, string $name, AbstractExpression $arguments, int $lineno)\n    {\n        parent::__construct(['template' => $template, 'arguments' => $arguments], ['name' => $name], $lineno);\n    }\n\n    public function __clone()\n    {\n        // The template node must not be deep-cloned because its name is\n        // lazily generated during compilation and must stay in sync with\n        // the AssignTemplateVariable that populates the $macros array.\n        $template = $this->nodes['template'];\n        parent::__clone();\n        $this->nodes['template'] = $template;\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if ($this->definedTest) {\n            $compiler\n                ->subcompile($this->getNode('template'))\n                ->raw('->hasMacro(')\n                ->repr($this->getAttribute('name'))\n                ->raw(', $context')\n                ->raw(')')\n            ;\n\n            return;\n        }\n\n        $compiler\n            ->subcompile($this->getNode('template'))\n            ->raw('->getTemplateForMacro(')\n            ->repr($this->getAttribute('name'))\n            ->raw(', $context, ')\n            ->repr($this->getTemplateLine())\n            ->raw(', $this->getSourceContext())')\n            ->raw(\\sprintf('->%s', $this->getAttribute('name')))\n            ->raw('(...')\n            ->subcompile($this->getNode('arguments'))\n            ->raw(')')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/MethodCallExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\n\nclass MethodCallExpression extends AbstractExpression implements SupportDefinedTestInterface\n{\n    use SupportDefinedTestDeprecationTrait;\n    use SupportDefinedTestTrait;\n\n    public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno)\n    {\n        trigger_deprecation('twig/twig', '3.15', 'The \"%s\" class is deprecated, use \"%s\" instead.', __CLASS__, MacroReferenceExpression::class);\n\n        parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false], $lineno);\n\n        if ($node instanceof ContextVariable) {\n            $node->setAttribute('always_defined', true);\n        }\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if ($this->definedTest) {\n            $compiler\n                ->raw('method_exists($macros[')\n                ->repr($this->getNode('node')->getAttribute('name'))\n                ->raw('], ')\n                ->repr($this->getAttribute('method'))\n                ->raw(')')\n            ;\n\n            return;\n        }\n\n        $compiler\n            ->raw('CoreExtension::callMacro($macros[')\n            ->repr($this->getNode('node')->getAttribute('name'))\n            ->raw('], ')\n            ->repr($this->getAttribute('method'))\n            ->raw(', ')\n            ->subcompile($this->getNode('arguments'))\n            ->raw(', ')\n            ->repr($this->getTemplateLine())\n            ->raw(', $context, $this->getSourceContext())');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/NameExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\n\nclass NameExpression extends AbstractExpression implements SupportDefinedTestInterface\n{\n    use SupportDefinedTestDeprecationTrait;\n    use SupportDefinedTestTrait;\n\n    private $specialVars = [\n        '_self' => '$this->getTemplateName()',\n        '_context' => '$context',\n        '_charset' => '$this->env->getCharset()',\n    ];\n\n    public function __construct(string $name, int $lineno)\n    {\n        if (self::class === static::class) {\n            trigger_deprecation('twig/twig', '3.15', 'The \"%s\" class is deprecated, use \"%s\" instead.', self::class, ContextVariable::class);\n        }\n\n        parent::__construct([], ['name' => $name, 'ignore_strict_check' => false, 'always_defined' => false], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $name = $this->getAttribute('name');\n\n        $compiler->addDebugInfo($this);\n\n        if ($this->definedTest) {\n            if (isset($this->specialVars[$name]) || $this->getAttribute('always_defined')) {\n                $compiler->repr(true);\n            } elseif (\\PHP_VERSION_ID >= 70400) {\n                $compiler\n                    ->raw('array_key_exists(')\n                    ->string($name)\n                    ->raw(', $context)')\n                ;\n            } else {\n                $compiler\n                    ->raw('(isset($context[')\n                    ->string($name)\n                    ->raw(']) || array_key_exists(')\n                    ->string($name)\n                    ->raw(', $context))')\n                ;\n            }\n        } elseif (isset($this->specialVars[$name])) {\n            $compiler->raw($this->specialVars[$name]);\n        } elseif ($this->getAttribute('always_defined')) {\n            $compiler\n                ->raw('$context[')\n                ->string($name)\n                ->raw(']')\n            ;\n        } else {\n            if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {\n                $compiler\n                    ->raw('($context[')\n                    ->string($name)\n                    ->raw('] ?? null)')\n                ;\n            } else {\n                $compiler\n                    ->raw('(isset($context[')\n                    ->string($name)\n                    ->raw(']) || array_key_exists(')\n                    ->string($name)\n                    ->raw(', $context) ? $context[')\n                    ->string($name)\n                    ->raw('] : (function () { throw new RuntimeError(\\'Variable ')\n                    ->string($name)\n                    ->raw(' does not exist.\\', ')\n                    ->repr($this->lineno)\n                    ->raw(', $this->source); })()')\n                    ->raw(')')\n                ;\n            }\n        }\n    }\n\n    /**\n     * @deprecated since Twig 3.11 (to be removed in 4.0)\n     */\n    public function isSpecial()\n    {\n        trigger_deprecation('twig/twig', '3.11', 'The \"%s()\" method is deprecated and will be removed in Twig 4.0.', __METHOD__);\n\n        return isset($this->specialVars[$this->getAttribute('name')]);\n    }\n\n    /**\n     * @deprecated since Twig 3.11 (to be removed in 4.0)\n     */\n    public function isSimple()\n    {\n        trigger_deprecation('twig/twig', '3.11', 'The \"%s()\" method is deprecated and will be removed in Twig 4.0.', __METHOD__);\n\n        return !isset($this->specialVars[$this->getAttribute('name')]) && !$this->definedTest;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/NullCoalesceExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\Binary\\AndBinary;\nuse Twig\\Node\\Expression\\Binary\\NullCoalesceBinary;\nuse Twig\\Node\\Expression\\Test\\DefinedTest;\nuse Twig\\Node\\Expression\\Test\\NullTest;\nuse Twig\\Node\\Expression\\Unary\\NotUnary;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\nuse Twig\\TwigTest;\n\nclass NullCoalesceExpression extends ConditionalExpression\n{\n    /**\n     * @param AbstractExpression $left\n     * @param AbstractExpression $right\n     */\n    public function __construct(Node $left, Node $right, int $lineno)\n    {\n        trigger_deprecation('twig/twig', '3.17', \\sprintf('\"%s\" is deprecated; use \"%s\" instead.', __CLASS__, NullCoalesceBinary::class));\n\n        if (!$left instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"left\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $left::class);\n        }\n        if (!$right instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"right\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $right::class);\n        }\n\n        $test = new DefinedTest(clone $left, new TwigTest('defined'), new EmptyNode(), $left->getTemplateLine());\n        // for \"block()\", we don't need the null test as the return value is always a string\n        if (!$left instanceof BlockReferenceExpression) {\n            $test = new AndBinary(\n                $test,\n                new NotUnary(new NullTest($left, new TwigTest('null'), new EmptyNode(), $left->getTemplateLine()), $left->getTemplateLine()),\n                $left->getTemplateLine()\n            );\n        }\n\n        parent::__construct($test, $left, $right, $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        /*\n         * This optimizes only one case. PHP 7 also supports more complex expressions\n         * that can return null. So, for instance, if log is defined, log(\"foo\") ?? \"...\" works,\n         * but log($a[\"foo\"]) ?? \"...\" does not if $a[\"foo\"] is not defined. More advanced\n         * cases might be implemented as an optimizer node visitor, but has not been done\n         * as benefits are probably not worth the added complexity.\n         */\n        if ($this->getNode('expr2') instanceof ContextVariable) {\n            $this->getNode('expr2')->setAttribute('always_defined', true);\n            $compiler\n                ->raw('((')\n                ->subcompile($this->getNode('expr2'))\n                ->raw(') ?? (')\n                ->subcompile($this->getNode('expr3'))\n                ->raw('))')\n            ;\n        } else {\n            parent::compile($compiler);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/OperatorEscapeInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\n/**\n * Interface implemented by n-ary operators for n > 1.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface OperatorEscapeInterface\n{\n    /**\n     * @return string[]\n     */\n    public function getOperandNamesToEscape(): array;\n}\n"
  },
  {
    "path": "src/Node/Expression/ParentExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\n\n/**\n * Represents a parent node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass ParentExpression extends AbstractExpression\n{\n    public function __construct(string $name, int $lineno)\n    {\n        parent::__construct([], ['output' => false, 'name' => $name], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if ($this->getAttribute('output')) {\n            $compiler\n                ->addDebugInfo($this)\n                ->write('yield from $this->yieldParentBlock(')\n                ->string($this->getAttribute('name'))\n                ->raw(\", \\$context, \\$blocks);\\n\")\n            ;\n        } else {\n            $compiler\n                ->raw('$this->renderParentBlock(')\n                ->string($this->getAttribute('name'))\n                ->raw(', $context, $blocks)')\n            ;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/ReturnArrayInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\ninterface ReturnArrayInterface extends ReturnPrimitiveTypeInterface\n{\n}\n"
  },
  {
    "path": "src/Node/Expression/ReturnBoolInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\ninterface ReturnBoolInterface extends ReturnPrimitiveTypeInterface\n{\n}\n"
  },
  {
    "path": "src/Node/Expression/ReturnNumberInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\ninterface ReturnNumberInterface extends ReturnPrimitiveTypeInterface\n{\n}\n"
  },
  {
    "path": "src/Node/Expression/ReturnPrimitiveTypeInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\ninterface ReturnPrimitiveTypeInterface\n{\n}\n"
  },
  {
    "path": "src/Node/Expression/ReturnStringInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\ninterface ReturnStringInterface extends ReturnPrimitiveTypeInterface\n{\n}\n"
  },
  {
    "path": "src/Node/Expression/SupportDefinedTestDeprecationTrait.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\n/**\n * @internal\n *\n * To be removed in 4.0\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ntrait SupportDefinedTestDeprecationTrait\n{\n    public function getAttribute($name, $default = null)\n    {\n        if ('is_defined_test' === $name) {\n            trigger_deprecation('twig/twig', '3.21', 'The \"is_defined_test\" attribute is deprecated, call \"isDefinedTestEnabled()\" instead.');\n\n            return $this->isDefinedTestEnabled();\n        }\n\n        return parent::getAttribute($name, $default);\n    }\n\n    public function setAttribute(string $name, $value): void\n    {\n        if ('is_defined_test' === $name) {\n            trigger_deprecation('twig/twig', '3.21', 'The \"is_defined_test\" attribute is deprecated, call \"enableDefinedTest()\" instead.');\n\n            $this->definedTest = (bool) $value;\n        } else {\n            parent::setAttribute($name, $value);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/SupportDefinedTestInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\n/**\n * Interface implemented by expressions that support the defined test.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface SupportDefinedTestInterface\n{\n    public function enableDefinedTest(): void;\n\n    public function isDefinedTestEnabled(): bool;\n}\n"
  },
  {
    "path": "src/Node/Expression/SupportDefinedTestTrait.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\ntrait SupportDefinedTestTrait\n{\n    private bool $definedTest = false;\n\n    public function enableDefinedTest(): void\n    {\n        $this->definedTest = true;\n    }\n\n    public function isDefinedTestEnabled(): bool\n    {\n        return $this->definedTest;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/TempNameExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\n\nclass TempNameExpression extends AbstractExpression\n{\n    public const RESERVED_NAMES = ['varargs', 'context', 'macros', 'blocks', 'this'];\n\n    public function __construct(string|int|null $name, int $lineno)\n    {\n        // All names supported by ExpressionParser::parsePrimaryExpression() should be excluded\n        if ($name && \\in_array(strtolower($name), ['true', 'false', 'none', 'null'], true)) {\n            throw new SyntaxError(\\sprintf('You cannot assign a value to \"%s\".', $name), $lineno);\n        }\n\n        if (self::class === static::class) {\n            trigger_deprecation('twig/twig', '3.15', 'The \"%s\" class is deprecated.', self::class);\n        }\n\n        if (null !== $name && (\\is_int($name) || ctype_digit($name))) {\n            $name = (int) $name;\n        } elseif (\\in_array($name, self::RESERVED_NAMES, true)) {\n            $name = \"\\u{035C}\".$name;\n        }\n\n        parent::__construct([], ['name' => $name], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if (null === $this->getAttribute('name')) {\n            $this->setAttribute('name', $compiler->getVarName());\n        }\n\n        $compiler->raw('$'.$this->getAttribute('name'));\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Ternary/ConditionalTernary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Ternary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\OperatorEscapeInterface;\nuse Twig\\Node\\Expression\\ReturnPrimitiveTypeInterface;\nuse Twig\\Node\\Expression\\Test\\TrueTest;\nuse Twig\\TwigTest;\n\nfinal class ConditionalTernary extends AbstractExpression implements OperatorEscapeInterface\n{\n    public function __construct(AbstractExpression $test, AbstractExpression $left, AbstractExpression $right, int $lineno)\n    {\n        if (!$test instanceof ReturnPrimitiveTypeInterface) {\n            $test = new TrueTest($test, new TwigTest('true'), null, $test->getTemplateLine());\n        }\n\n        parent::__construct(['test' => $test, 'left' => $left, 'right' => $right], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('((')\n            ->subcompile($this->getNode('test'))\n            ->raw(') ? (')\n            ->subcompile($this->getNode('left'))\n            ->raw(') : (')\n            ->subcompile($this->getNode('right'))\n            ->raw('))')\n        ;\n    }\n\n    public function getOperandNamesToEscape(): array\n    {\n        return ['left', 'right'];\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Test/ConstantTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Test;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\TestExpression;\n\n/**\n * Checks if a variable is the exact same value as a constant.\n *\n *    {% if post.status is constant('Post::PUBLISHED') %}\n *      the status attribute is exactly the same as Post::PUBLISHED\n *    {% endif %}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass ConstantTest extends TestExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('(')\n            ->subcompile($this->getNode('node'))\n            ->raw(' === constant(')\n        ;\n\n        if ($this->getNode('arguments')->hasNode('1')) {\n            $compiler\n                ->raw('get_class(')\n                ->subcompile($this->getNode('arguments')->getNode('1'))\n                ->raw(').\"::\".')\n            ;\n        }\n\n        $compiler\n            ->subcompile($this->getNode('arguments')->getNode('0'))\n            ->raw('))')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Test/DefinedTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Test;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\SupportDefinedTestInterface;\nuse Twig\\Node\\Expression\\TestExpression;\nuse Twig\\Node\\Node;\nuse Twig\\TwigTest;\n\n/**\n * Checks if a variable is defined in the current context.\n *\n *    {# defined works with variable names and variable attributes #}\n *    {% if foo is defined %}\n *        {# ... #}\n *    {% endif %}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass DefinedTest extends TestExpression\n{\n    /**\n     * @param AbstractExpression $node\n     */\n    #[FirstClassTwigCallableReady]\n    public function __construct(Node $node, TwigTest|string $name, ?Node $arguments, int $lineno)\n    {\n        if (!$node instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"node\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $node::class);\n        }\n\n        if (!$node instanceof SupportDefinedTestInterface) {\n            throw new SyntaxError('The \"defined\" test only works with simple variables.', $lineno);\n        }\n\n        $node->enableDefinedTest();\n\n        if (\\is_string($name) && 'defined' !== $name) {\n            trigger_deprecation('twig/twig', '3.12', 'Creating a \"DefinedTest\" instance with a test name that is not \"defined\" is deprecated.');\n        }\n\n        parent::__construct($node, $name, $arguments, $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->subcompile($this->getNode('node'));\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Test/DivisiblebyTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Test;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\TestExpression;\n\n/**\n * Checks if a variable is divisible by a number.\n *\n *  {% if loop.index is divisible by(3) %}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass DivisiblebyTest extends TestExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('(0 == ')\n            ->subcompile($this->getNode('node'))\n            ->raw(' % ')\n            ->subcompile($this->getNode('arguments')->getNode('0'))\n            ->raw(')')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Test/EvenTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Test;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\TestExpression;\n\n/**\n * Checks if a number is even.\n *\n *  {{ var is even }}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass EvenTest extends TestExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('(')\n            ->subcompile($this->getNode('node'))\n            ->raw(' % 2 == 0')\n            ->raw(')')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Test/NullTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Test;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\TestExpression;\n\n/**\n * Checks that an expression is null.\n *\n *  {{ var is none }}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass NullTest extends TestExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('(null === ')\n            ->subcompile($this->getNode('node'))\n            ->raw(')')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Test/OddTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Test;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\TestExpression;\n\n/**\n * Checks if a number is odd.\n *\n *  {{ var is odd }}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass OddTest extends TestExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('(')\n            ->subcompile($this->getNode('node'))\n            ->raw(' % 2 != 0')\n            ->raw(')')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Test/SameasTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Test;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\TestExpression;\n\n/**\n * Checks if a variable is the same as another one (=== in PHP).\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass SameasTest extends TestExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('(')\n            ->subcompile($this->getNode('node'))\n            ->raw(' === ')\n            ->subcompile($this->getNode('arguments')->getNode('0'))\n            ->raw(')')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Test/TrueTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Test;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\TestExpression;\n\n/**\n * Checks that an expression is true.\n *\n *  {{ var is true }}\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass TrueTest extends TestExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->raw('(($tmp = ')\n            ->subcompile($this->getNode('node'))\n            ->raw(') && $tmp instanceof Markup ? (string) $tmp : $tmp)')\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/TestExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\NameDeprecation;\nuse Twig\\Node\\Node;\nuse Twig\\TwigTest;\n\nclass TestExpression extends CallExpression implements ReturnBoolInterface\n{\n    #[FirstClassTwigCallableReady]\n    /**\n     * @param AbstractExpression $node\n     */\n    public function __construct(Node $node, string|TwigTest $test, ?Node $arguments, int $lineno)\n    {\n        if (!$node instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance to the \"node\" argument of \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $node::class);\n        }\n\n        $nodes = ['node' => $node];\n        if (null !== $arguments) {\n            $nodes['arguments'] = $arguments;\n        }\n\n        if ($test instanceof TwigTest) {\n            $name = $test->getName();\n        } else {\n            $name = $test;\n            trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of \"TwigTest\" when creating a \"%s\" test of type \"%s\" is deprecated.', $name, static::class);\n        }\n\n        parent::__construct($nodes, ['name' => $name, 'type' => 'test'], $lineno);\n\n        if ($test instanceof TwigTest) {\n            $this->setAttribute('twig_callable', $test);\n        }\n\n        $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12'));\n        $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12'));\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $name = $this->getAttribute('name');\n        if ($this->hasAttribute('twig_callable')) {\n            $name = $this->getAttribute('twig_callable')->getName();\n            if ($name !== $this->getAttribute('name')) {\n                trigger_deprecation('twig/twig', '3.12', 'Changing the value of a \"test\" node in a NodeVisitor class is not supported anymore.');\n                $this->removeAttribute('twig_callable');\n            }\n        }\n\n        if (!$this->hasAttribute('twig_callable')) {\n            $this->setAttribute('twig_callable', $compiler->getEnvironment()->getTest($this->getAttribute('name')));\n        }\n\n        $this->compileCallable($compiler);\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Unary/AbstractUnary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Unary;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Node;\n\nabstract class AbstractUnary extends AbstractExpression implements UnaryInterface\n{\n    /**\n     * @param AbstractExpression $node\n     */\n    public function __construct(Node $node, int $lineno)\n    {\n        if (!$node instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance argument to \"%s\" is deprecated (\"%s\" given).', AbstractExpression::class, static::class, $node::class);\n        }\n\n        parent::__construct(['node' => $node], ['with_parentheses' => false], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if ($this->hasExplicitParentheses()) {\n            $compiler->raw('(');\n        } else {\n            $compiler->raw(' ');\n        }\n        $this->operator($compiler);\n        $compiler->subcompile($this->getNode('node'));\n        if ($this->hasExplicitParentheses()) {\n            $compiler->raw(')');\n        }\n    }\n\n    abstract public function operator(Compiler $compiler): Compiler;\n}\n"
  },
  {
    "path": "src/Node/Expression/Unary/NegUnary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Unary;\n\nuse Twig\\Compiler;\n\nclass NegUnary extends AbstractUnary\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('-');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Unary/NotUnary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Unary;\n\nuse Twig\\Compiler;\n\nclass NotUnary extends AbstractUnary\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('!');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Unary/PosUnary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Unary;\n\nuse Twig\\Compiler;\n\nclass PosUnary extends AbstractUnary\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('+');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Unary/SpreadUnary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Unary;\n\nuse Twig\\Compiler;\n\nfinal class SpreadUnary extends AbstractUnary\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('...');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Unary/StringCastUnary.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Unary;\n\nuse Twig\\Compiler;\n\nfinal class StringCastUnary extends AbstractUnary\n{\n    public function operator(Compiler $compiler): Compiler\n    {\n        return $compiler->raw('(string)');\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Unary/UnaryInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Unary;\n\nuse Twig\\Node\\Expression\\AbstractExpression;\n\n/**\n * @internal\n */\ninterface UnaryInterface\n{\n    public function __construct(AbstractExpression $node, int $lineno);\n}\n"
  },
  {
    "path": "src/Node/Expression/Variable/AssignContextVariable.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Variable;\n\nuse Twig\\Node\\Expression\\AssignNameExpression;\n\nfinal class AssignContextVariable extends AssignNameExpression\n{\n}\n"
  },
  {
    "path": "src/Node/Expression/Variable/AssignTemplateVariable.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Variable;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\n\nfinal class AssignTemplateVariable extends AbstractExpression\n{\n    public function __construct(TemplateVariable $var, bool $global = true)\n    {\n        parent::__construct(['var' => $var], ['global' => $global], $var->getTemplateLine());\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        /** @var TemplateVariable $var */\n        $var = $this->nodes['var'];\n\n        $compiler\n            ->addDebugInfo($this)\n            ->write('$macros[')\n            ->string($var->getName($compiler))\n            ->raw('] = ')\n        ;\n\n        if ($this->getAttribute('global')) {\n            $compiler\n                ->raw('$this->macros[')\n                ->string($var->getName($compiler))\n                ->raw('] = ')\n            ;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/Variable/ContextVariable.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Variable;\n\nuse Twig\\Node\\Expression\\NameExpression;\n\nclass ContextVariable extends NameExpression\n{\n}\n"
  },
  {
    "path": "src/Node/Expression/Variable/LocalVariable.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Variable;\n\nuse Twig\\Node\\Expression\\TempNameExpression;\n\nfinal class LocalVariable extends TempNameExpression\n{\n}\n"
  },
  {
    "path": "src/Node/Expression/Variable/TemplateVariable.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression\\Variable;\n\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\TempNameExpression;\n\nclass TemplateVariable extends TempNameExpression\n{\n    public function getName(Compiler $compiler): string\n    {\n        if (null === $this->getAttribute('name')) {\n            $this->setAttribute('name', $compiler->getVarName());\n        }\n\n        return $this->getAttribute('name');\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $name = $this->getName($compiler);\n\n        if ('_self' === $name) {\n            $compiler->raw('$this');\n        } else {\n            $compiler\n                ->raw('$macros[')\n                ->string($name)\n                ->raw(']')\n            ;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/Expression/VariadicExpression.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node\\Expression;\n\nuse Twig\\Compiler;\n\nclass VariadicExpression extends ArrayExpression\n{\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->raw('...');\n\n        parent::compile($compiler);\n    }\n}\n"
  },
  {
    "path": "src/Node/FlushNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents a flush node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass FlushNode extends Node\n{\n    public function __construct(int $lineno)\n    {\n        parent::__construct([], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n\n        if ($compiler->getEnvironment()->useYield()) {\n            $compiler->write(\"yield '';\\n\");\n        }\n\n        $compiler->write(\"flush();\\n\");\n    }\n}\n"
  },
  {
    "path": "src/Node/ForElseNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents an else node in a for loop.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass ForElseNode extends Node\n{\n    public function __construct(Node $body, int $lineno)\n    {\n        parent::__construct(['body' => $body], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->write(\"if (!\\$context['_iterated']) {\\n\")\n            ->indent()\n            ->subcompile($this->getNode('body'))\n            ->outdent()\n            ->write(\"}\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/ForLoopNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Internal node used by the for node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass ForLoopNode extends Node\n{\n    public function __construct(int $lineno)\n    {\n        parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        if ($this->getAttribute('else')) {\n            $compiler->write(\"\\$context['_iterated'] = true;\\n\");\n        }\n\n        if ($this->getAttribute('with_loop')) {\n            $compiler\n                ->write(\"++\\$context['loop']['index0'];\\n\")\n                ->write(\"++\\$context['loop']['index'];\\n\")\n                ->write(\"\\$context['loop']['first'] = false;\\n\")\n                ->write(\"if (isset(\\$context['loop']['revindex0'], \\$context['loop']['revindex'])) {\\n\")\n                ->indent()\n                ->write(\"--\\$context['loop']['revindex0'];\\n\")\n                ->write(\"--\\$context['loop']['revindex'];\\n\")\n                ->write(\"\\$context['loop']['last'] = 0 === \\$context['loop']['revindex0'];\\n\")\n                ->outdent()\n                ->write(\"}\\n\")\n            ;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/ForNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\n\n/**\n * Represents a for node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass ForNode extends Node\n{\n    private $loop;\n\n    public function __construct(AssignContextVariable $keyTarget, AssignContextVariable $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno)\n    {\n        $body = new Nodes([$body, $this->loop = new ForLoopNode($lineno)]);\n\n        if (null !== $ifexpr) {\n            trigger_deprecation('twig/twig', '3.19', \\sprintf('Passing not-null to the \"ifexpr\" argument of the \"%s\" constructor is deprecated.', static::class));\n        }\n\n        if (null !== $else && !$else instanceof ForElseNode) {\n            trigger_deprecation('twig/twig', '3.19', \\sprintf('Not passing an instance of \"%s\" to the \"else\" argument of the \"%s\" constructor is deprecated.', ForElseNode::class, static::class));\n\n            $else = new ForElseNode($else, $else->getTemplateLine());\n        }\n\n        $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body];\n        if (null !== $else) {\n            $nodes['else'] = $else;\n        }\n\n        parent::__construct($nodes, ['with_loop' => true], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->write(\"\\$context['_parent'] = \\$context;\\n\")\n            ->write(\"\\$context['_seq'] = CoreExtension::ensureTraversable(\")\n            ->subcompile($this->getNode('seq'))\n            ->raw(\");\\n\")\n        ;\n\n        if ($this->hasNode('else')) {\n            $compiler->write(\"\\$context['_iterated'] = false;\\n\");\n        }\n\n        if ($this->getAttribute('with_loop')) {\n            $compiler\n                ->write(\"\\$context['loop'] = [\\n\")\n                ->write(\"  'parent' => \\$context['_parent'],\\n\")\n                ->write(\"  'index0' => 0,\\n\")\n                ->write(\"  'index'  => 1,\\n\")\n                ->write(\"  'first'  => true,\\n\")\n                ->write(\"];\\n\")\n                ->write(\"if (is_array(\\$context['_seq']) || (is_object(\\$context['_seq']) && \\$context['_seq'] instanceof \\Countable)) {\\n\")\n                ->indent()\n                ->write(\"\\$length = count(\\$context['_seq']);\\n\")\n                ->write(\"\\$context['loop']['revindex0'] = \\$length - 1;\\n\")\n                ->write(\"\\$context['loop']['revindex'] = \\$length;\\n\")\n                ->write(\"\\$context['loop']['length'] = \\$length;\\n\")\n                ->write(\"\\$context['loop']['last'] = 1 === \\$length;\\n\")\n                ->outdent()\n                ->write(\"}\\n\")\n            ;\n        }\n\n        $this->loop->setAttribute('else', $this->hasNode('else'));\n        $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop'));\n\n        $compiler\n            ->write(\"foreach (\\$context['_seq'] as \")\n            ->subcompile($this->getNode('key_target'))\n            ->raw(' => ')\n            ->subcompile($this->getNode('value_target'))\n            ->raw(\") {\\n\")\n            ->indent()\n            ->subcompile($this->getNode('body'))\n            ->outdent()\n            ->write(\"}\\n\")\n        ;\n\n        if ($this->hasNode('else')) {\n            $compiler->subcompile($this->getNode('else'));\n        }\n\n        $compiler->write(\"\\$_parent = \\$context['_parent'];\\n\");\n\n        // remove some \"private\" loop variables (needed for nested loops)\n        $compiler->write('unset($context[\\'_seq\\'], $context[\\''.$this->getNode('key_target')->getAttribute('name').'\\'], $context[\\''.$this->getNode('value_target')->getAttribute('name').'\\'], $context[\\'_parent\\']');\n        if ($this->hasNode('else')) {\n            $compiler->raw(', $context[\\'_iterated\\']');\n        }\n        if ($this->getAttribute('with_loop')) {\n            $compiler->raw(', $context[\\'loop\\']');\n        }\n        $compiler->raw(\");\\n\");\n\n        // keep the values set in the inner context for variables defined in the outer context\n        $compiler->write(\"\\$context = array_intersect_key(\\$context, \\$_parent) + \\$_parent;\\n\");\n    }\n}\n"
  },
  {
    "path": "src/Node/IfNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ReturnPrimitiveTypeInterface;\nuse Twig\\Node\\Expression\\Test\\TrueTest;\nuse Twig\\TwigTest;\n\n/**\n * Represents an if node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass IfNode extends Node\n{\n    public function __construct(Node $tests, ?Node $else, int $lineno)\n    {\n        for ($i = 0, $count = \\count($tests); $i < $count; $i += 2) {\n            $test = $tests->getNode((string) $i);\n            if (!$test instanceof ReturnPrimitiveTypeInterface) {\n                $tests->setNode($i, new TrueTest($test, new TwigTest('true'), null, $test->getTemplateLine()));\n            }\n        }\n        $nodes = ['tests' => $tests];\n        if (null !== $else) {\n            $nodes['else'] = $else;\n        }\n\n        parent::__construct($nodes, [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n        for ($i = 0, $count = \\count($this->getNode('tests')); $i < $count; $i += 2) {\n            if ($i > 0) {\n                $compiler\n                    ->outdent()\n                    ->write('} elseif (')\n                ;\n            } else {\n                $compiler\n                    ->write('if (')\n                ;\n            }\n\n            $compiler\n                ->subcompile($this->getNode('tests')->getNode((string) $i))\n                ->raw(\") {\\n\")\n                ->indent()\n            ;\n            // The node might not exists if the content is empty\n            if ($this->getNode('tests')->hasNode((string) ($i + 1))) {\n                $compiler->subcompile($this->getNode('tests')->getNode((string) ($i + 1)));\n            }\n        }\n\n        if ($this->hasNode('else')) {\n            $compiler\n                ->outdent()\n                ->write(\"} else {\\n\")\n                ->indent()\n                ->subcompile($this->getNode('else'))\n            ;\n        }\n\n        $compiler\n            ->outdent()\n            ->write(\"}\\n\");\n    }\n}\n"
  },
  {
    "path": "src/Node/ImportNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\Variable\\AssignTemplateVariable;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\n\n/**\n * Represents an import node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass ImportNode extends Node\n{\n    public function __construct(AbstractExpression $expr, AbstractExpression|AssignTemplateVariable $var, int $lineno)\n    {\n        if (\\func_num_args() > 3) {\n            trigger_deprecation('twig/twig', '3.15', \\sprintf('Passing more than 3 arguments to \"%s()\" is deprecated.', __METHOD__));\n        }\n\n        if (!$var instanceof AssignTemplateVariable) {\n            trigger_deprecation('twig/twig', '3.15', \\sprintf('Passing a \"%s\" instance as the second argument of \"%s\" is deprecated, pass a \"%s\" instead.', $var::class, __CLASS__, AssignTemplateVariable::class));\n\n            $var = new AssignTemplateVariable($var->getAttribute('name'), $lineno);\n        }\n\n        parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->subcompile($this->getNode('var'));\n\n        if ($this->getNode('expr') instanceof ContextVariable && '_self' === $this->getNode('expr')->getAttribute('name')) {\n            $compiler->raw('$this');\n        } else {\n            $compiler\n                ->raw('$this->load(')\n                ->subcompile($this->getNode('expr'))\n                ->raw(', ')\n                ->repr($this->getTemplateLine())\n                ->raw(')->unwrap()')\n            ;\n        }\n\n        $compiler->raw(\";\\n\");\n    }\n}\n"
  },
  {
    "path": "src/Node/IncludeNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\n\n/**\n * Represents an include node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass IncludeNode extends Node implements NodeOutputInterface\n{\n    public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno)\n    {\n        $nodes = ['expr' => $expr];\n        if (null !== $variables) {\n            $nodes['variables'] = $variables;\n        }\n\n        parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n\n        if ($this->getAttribute('ignore_missing')) {\n            $template = $compiler->getVarName();\n\n            $compiler\n                ->write(\"try {\\n\")\n                ->indent()\n                ->write(\\sprintf('$%s = ', $template))\n            ;\n\n            $this->addGetTemplate($compiler, $template);\n\n            $compiler\n                ->raw(\";\\n\")\n                ->outdent()\n                ->write(\"} catch (LoaderError \\$e) {\\n\")\n                ->indent()\n                ->write(\"// ignore missing template\\n\")\n                ->write(\\sprintf(\"\\$$template = null;\\n\", $template))\n                ->outdent()\n                ->write(\"}\\n\")\n                ->write(\\sprintf(\"if ($%s) {\\n\", $template))\n                ->indent()\n                ->write(\\sprintf('yield from $%s->unwrap()->yield(', $template))\n            ;\n\n            $this->addTemplateArguments($compiler);\n            $compiler\n                ->raw(\");\\n\")\n                ->outdent()\n                ->write(\"}\\n\")\n            ;\n        } else {\n            $compiler->write('yield from ');\n            $this->addGetTemplate($compiler);\n            $compiler->raw('->unwrap()->yield(');\n            $this->addTemplateArguments($compiler);\n            $compiler->raw(\");\\n\");\n        }\n    }\n\n    /**\n     * @return void\n     */\n    protected function addGetTemplate(Compiler $compiler/* , string $template = '' */)\n    {\n        $compiler\n            ->raw('$this->load(')\n            ->subcompile($this->getNode('expr'))\n            ->raw(', ')\n            ->repr($this->getTemplateLine())\n            ->raw(')')\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function addTemplateArguments(Compiler $compiler)\n    {\n        if (!$this->hasNode('variables')) {\n            $compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]');\n        } elseif (false === $this->getAttribute('only')) {\n            $compiler\n                ->raw('CoreExtension::merge($context, ')\n                ->subcompile($this->getNode('variables'))\n                ->raw(')')\n            ;\n        } else {\n            $compiler->raw('CoreExtension::toArray(');\n            $compiler->subcompile($this->getNode('variables'));\n            $compiler->raw(')');\n        }\n    }\n}\n"
  },
  {
    "path": "src/Node/MacroNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\Variable\\LocalVariable;\n\n/**\n * Represents a macro node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass MacroNode extends Node\n{\n    public const VARARGS_NAME = 'varargs';\n\n    /**\n     * @param BodyNode        $body\n     * @param ArrayExpression $arguments\n     */\n    public function __construct(string $name, Node $body, Node $arguments, int $lineno)\n    {\n        if (!$body instanceof BodyNode) {\n            trigger_deprecation('twig/twig', '3.12', \\sprintf('Not passing a \"%s\" instance as the \"body\" argument of the \"%s\" constructor is deprecated (\"%s\" given).', BodyNode::class, static::class, $body::class));\n        }\n\n        if (!$arguments instanceof ArrayExpression) {\n            trigger_deprecation('twig/twig', '3.15', \\sprintf('Not passing a \"%s\" instance as the \"arguments\" argument of the \"%s\" constructor is deprecated (\"%s\" given).', ArrayExpression::class, static::class, $arguments::class));\n\n            $args = new ArrayExpression([], $arguments->getTemplateLine());\n            foreach ($arguments as $n => $default) {\n                $args->addElement($default, new LocalVariable($n, $default->getTemplateLine()));\n            }\n            $arguments = $args;\n        }\n\n        foreach ($arguments->getKeyValuePairs() as $pair) {\n            if (\"\\u{035C}\".self::VARARGS_NAME === $pair['key']->getAttribute('name')) {\n                throw new SyntaxError(\\sprintf('The argument \"%s\" in macro \"%s\" cannot be defined because the variable \"%s\" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $pair['value']->getTemplateLine(), $pair['value']->getSourceContext());\n            }\n        }\n\n        parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->write(\\sprintf('public function macro_%s(', $this->getAttribute('name')))\n        ;\n\n        /** @var ArrayExpression $arguments */\n        $arguments = $this->getNode('arguments');\n        foreach ($arguments->getKeyValuePairs() as $pair) {\n            $name = $pair['key'];\n            $default = $pair['value'];\n            $compiler\n                ->subcompile($name)\n                ->raw(' = ')\n                ->subcompile($default)\n                ->raw(', ')\n            ;\n        }\n\n        $compiler\n            ->raw('...$varargs')\n            ->raw(\"): string|Markup\\n\")\n            ->write(\"{\\n\")\n            ->indent()\n            ->write(\"\\$macros = \\$this->macros;\\n\")\n            ->write(\"\\$context = [\\n\")\n            ->indent()\n        ;\n\n        foreach ($arguments->getKeyValuePairs() as $pair) {\n            $name = $pair['key'];\n            $var = $name->getAttribute('name');\n            if (str_starts_with($var, \"\\u{035C}\")) {\n                $var = substr($var, \\strlen(\"\\u{035C}\"));\n            }\n            $compiler\n                ->write('')\n                ->string($var)\n                ->raw(' => ')\n                ->subcompile($name)\n                ->raw(\",\\n\")\n            ;\n        }\n\n        $node = new CaptureNode($this->getNode('body'), $this->getNode('body')->lineno);\n\n        $compiler\n            ->write('')\n            ->string(self::VARARGS_NAME)\n            ->raw(' => ')\n            ->raw(\"\\$varargs,\\n\")\n            ->outdent()\n            ->write(\"] + \\$this->env->getGlobals();\\n\\n\")\n            ->write(\"\\$blocks = [];\\n\\n\")\n            ->write('return ')\n            ->subcompile($node)\n            ->raw(\"\\n\")\n            ->outdent()\n            ->write(\"}\\n\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/ModuleNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Source;\n\n/**\n * Represents a module node.\n *\n * If you need to customize the behavior of the generated class, add nodes to\n * the following nodes: display_start, display_end, constructor_start,\n * constructor_end, and class_end.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nfinal class ModuleNode extends Node\n{\n    /**\n     * @param BodyNode $body\n     */\n    public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source)\n    {\n        if (!$body instanceof BodyNode) {\n            trigger_deprecation('twig/twig', '3.12', \\sprintf('Not passing a \"%s\" instance as the \"body\" argument of the \"%s\" constructor is deprecated.', BodyNode::class, static::class));\n        }\n        if (!$embeddedTemplates instanceof Node) {\n            trigger_deprecation('twig/twig', '3.21', \\sprintf('Not passing a \"%s\" instance as the \"embedded_templates\" argument of the \"%s\" constructor is deprecated.', Node::class, static::class));\n\n            if (null !== $embeddedTemplates) {\n                $embeddedTemplates = new Nodes($embeddedTemplates);\n            } else {\n                $embeddedTemplates = new EmptyNode();\n            }\n        }\n\n        $nodes = [\n            'body' => $body,\n            'blocks' => $blocks,\n            'macros' => $macros,\n            'traits' => $traits,\n            'display_start' => new Nodes(),\n            'display_end' => new Nodes(),\n            'constructor_start' => new Nodes(),\n            'constructor_end' => new Nodes(),\n            'class_end' => new Nodes(),\n        ];\n        if (null !== $parent) {\n            $nodes['parent'] = $parent;\n        }\n\n        // embedded templates are set as attributes so that they are only visited once by the visitors\n        parent::__construct($nodes, [\n            'index' => null,\n            'embedded_templates' => $embeddedTemplates,\n        ], 1);\n\n        // populate the template name of all node children\n        $this->setSourceContext($source);\n    }\n\n    /**\n     * @return void\n     */\n    public function setIndex($index)\n    {\n        $this->setAttribute('index', $index);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $this->compileTemplate($compiler);\n\n        foreach ($this->getAttribute('embedded_templates') as $template) {\n            $compiler->subcompile($template);\n        }\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileTemplate(Compiler $compiler)\n    {\n        if (!$this->getAttribute('index')) {\n            $compiler->write('<?php');\n        }\n\n        $this->compileClassHeader($compiler);\n\n        $this->compileConstructor($compiler);\n\n        $this->compileGetParent($compiler);\n\n        $this->compileDisplay($compiler);\n\n        $compiler->subcompile($this->getNode('blocks'));\n\n        $this->compileMacros($compiler);\n\n        $this->compileGetTemplateName($compiler);\n\n        $this->compileIsTraitable($compiler);\n\n        $this->compileDebugInfo($compiler);\n\n        $this->compileGetSourceContext($compiler);\n\n        $this->compileClassFooter($compiler);\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileGetParent(Compiler $compiler)\n    {\n        if (!$this->hasNode('parent')) {\n            return;\n        }\n        $parent = $this->getNode('parent');\n\n        $compiler\n            ->write(\"protected function doGetParent(array \\$context): bool|string|Template|TemplateWrapper\\n\", \"{\\n\")\n            ->indent()\n            ->addDebugInfo($parent)\n            ->write('return ')\n        ;\n\n        if ($parent instanceof ConstantExpression) {\n            $compiler->subcompile($parent);\n        } else {\n            $compiler\n                ->raw('$this->load(')\n                ->subcompile($parent)\n                ->raw(', ')\n                ->repr($parent->getTemplateLine())\n                ->raw(')')\n            ;\n        }\n\n        $compiler\n            ->raw(\";\\n\")\n            ->outdent()\n            ->write(\"}\\n\\n\")\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileClassHeader(Compiler $compiler)\n    {\n        $compiler\n            ->write(\"\\n\\n\")\n        ;\n        if (!$this->getAttribute('index')) {\n            $compiler\n                ->write(\"use Twig\\Environment;\\n\")\n                ->write(\"use Twig\\Error\\LoaderError;\\n\")\n                ->write(\"use Twig\\Error\\RuntimeError;\\n\")\n                ->write(\"use Twig\\Extension\\CoreExtension;\\n\")\n                ->write(\"use Twig\\Extension\\SandboxExtension;\\n\")\n                ->write(\"use Twig\\Markup;\\n\")\n                ->write(\"use Twig\\Sandbox\\SecurityError;\\n\")\n                ->write(\"use Twig\\Sandbox\\SecurityNotAllowedTagError;\\n\")\n                ->write(\"use Twig\\Sandbox\\SecurityNotAllowedFilterError;\\n\")\n                ->write(\"use Twig\\Sandbox\\SecurityNotAllowedFunctionError;\\n\")\n                ->write(\"use Twig\\Source;\\n\")\n                ->write(\"use Twig\\Template;\\n\")\n                ->write(\"use Twig\\TemplateWrapper;\\n\")\n                ->write(\"\\n\")\n            ;\n        }\n        $compiler\n            // if the template name contains */, add a blank to avoid a PHP parse error\n            ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName()).\" */\\n\")\n            ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index')))\n            ->raw(\" extends Template\\n\")\n            ->write(\"{\\n\")\n            ->indent()\n            ->write(\"private Source \\$source;\\n\")\n            ->write(\"/**\\n\")\n            ->write(\" * @var array<string, Template>\\n\")\n            ->write(\" */\\n\")\n            ->write(\"private array \\$macros = [];\\n\\n\")\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileConstructor(Compiler $compiler)\n    {\n        $compiler\n            ->write(\"public function __construct(Environment \\$env)\\n\", \"{\\n\")\n            ->indent()\n            ->subcompile($this->getNode('constructor_start'))\n            ->write(\"parent::__construct(\\$env);\\n\\n\")\n            ->write(\"\\$this->source = \\$this->getSourceContext();\\n\\n\")\n        ;\n\n        // parent\n        if (!$this->hasNode('parent')) {\n            $compiler->write(\"\\$this->parent = false;\\n\\n\");\n        }\n\n        $countTraits = \\count($this->getNode('traits'));\n        if ($countTraits) {\n            // traits\n            foreach ($this->getNode('traits') as $i => $trait) {\n                $node = $trait->getNode('template');\n\n                $compiler\n                    ->addDebugInfo($node)\n                    ->write(\\sprintf('$_trait_%s = $this->load(', $i))\n                    ->subcompile($node)\n                    ->raw(', ')\n                    ->repr($node->getTemplateLine())\n                    ->raw(\");\\n\")\n                    ->write(\\sprintf(\"if (!\\$_trait_%s->unwrap()->isTraitable()) {\\n\", $i))\n                    ->indent()\n                    ->write(\"throw new RuntimeError('Template \\\"'.\")\n                    ->subcompile($trait->getNode('template'))\n                    ->raw(\".'\\\" cannot be used as a trait.', \")\n                    ->repr($node->getTemplateLine())\n                    ->raw(\", \\$this->source);\\n\")\n                    ->outdent()\n                    ->write(\"}\\n\")\n                    ->write(\\sprintf(\"\\$_trait_%s_blocks = \\$_trait_%s->unwrap()->getBlocks();\\n\\n\", $i, $i))\n                ;\n\n                foreach ($trait->getNode('targets') as $key => $value) {\n                    $compiler\n                        ->write(\\sprintf('if (!isset($_trait_%s_blocks[', $i))\n                        ->string($key)\n                        ->raw(\"])) {\\n\")\n                        ->indent()\n                        ->write(\"throw new RuntimeError('Block \")\n                        ->string($key)\n                        ->raw(' is not defined in trait ')\n                        ->subcompile($trait->getNode('template'))\n                        ->raw(\".', \")\n                        ->repr($node->getTemplateLine())\n                        ->raw(\", \\$this->source);\\n\")\n                        ->outdent()\n                        ->write(\"}\\n\\n\")\n\n                        ->write(\\sprintf('$_trait_%s_blocks[', $i))\n                        ->subcompile($value)\n                        ->raw(\\sprintf('] = $_trait_%s_blocks[', $i))\n                        ->string($key)\n                        ->raw(\\sprintf(']; unset($_trait_%s_blocks[', $i))\n                        ->string($key)\n                        ->raw(']); $this->traitAliases[')\n                        ->subcompile($value)\n                        ->raw('] = ')\n                        ->string($key)\n                        ->raw(\";\\n\\n\")\n                    ;\n                }\n            }\n\n            if ($countTraits > 1) {\n                $compiler\n                    ->write(\"\\$this->traits = array_merge(\\n\")\n                    ->indent()\n                ;\n\n                for ($i = 0; $i < $countTraits; ++$i) {\n                    $compiler\n                        ->write(\\sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',').\"\\n\", $i))\n                    ;\n                }\n\n                $compiler\n                    ->outdent()\n                    ->write(\");\\n\\n\")\n                ;\n            } else {\n                $compiler\n                    ->write(\"\\$this->traits = \\$_trait_0_blocks;\\n\\n\")\n                ;\n            }\n\n            $compiler\n                ->write(\"\\$this->blocks = array_merge(\\n\")\n                ->indent()\n                ->write(\"\\$this->traits,\\n\")\n                ->write(\"[\\n\")\n            ;\n        } else {\n            $compiler\n                ->write(\"\\$this->blocks = [\\n\")\n            ;\n        }\n\n        // blocks\n        $compiler\n            ->indent()\n        ;\n\n        foreach ($this->getNode('blocks') as $name => $node) {\n            $compiler\n                ->write(\\sprintf(\"'%s' => [\\$this, 'block_%s'],\\n\", $name, $name))\n            ;\n        }\n\n        if ($countTraits) {\n            $compiler\n                ->outdent()\n                ->write(\"]\\n\")\n                ->outdent()\n                ->write(\");\\n\")\n            ;\n        } else {\n            $compiler\n                ->outdent()\n                ->write(\"];\\n\")\n            ;\n        }\n\n        $compiler\n            ->subcompile($this->getNode('constructor_end'))\n            ->outdent()\n            ->write(\"}\\n\\n\")\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileDisplay(Compiler $compiler)\n    {\n        $compiler\n            ->write(\"protected function doDisplay(array \\$context, array \\$blocks = []): iterable\\n\", \"{\\n\")\n            ->indent()\n            ->write(\"\\$macros = \\$this->macros;\\n\")\n            ->subcompile($this->getNode('display_start'))\n            ->subcompile($this->getNode('body'))\n        ;\n\n        if ($this->hasNode('parent')) {\n            $parent = $this->getNode('parent');\n\n            $compiler->addDebugInfo($parent);\n            if ($parent instanceof ConstantExpression) {\n                $compiler\n                    ->write('$this->parent = $this->load(')\n                    ->subcompile($parent)\n                    ->raw(', ')\n                    ->repr($parent->getTemplateLine())\n                    ->raw(\");\\n\")\n                ;\n            }\n            $compiler->write('yield from ');\n\n            if ($parent instanceof ConstantExpression) {\n                $compiler->raw('$this->parent');\n            } else {\n                $compiler->raw('$this->getParent($context)');\n            }\n            $compiler->raw(\"->unwrap()->yield(\\$context, array_merge(\\$this->blocks, \\$blocks));\\n\");\n        }\n\n        $compiler->subcompile($this->getNode('display_end'));\n\n        if (!$this->hasNode('parent')) {\n            $compiler->write(\"yield from [];\\n\");\n        }\n\n        $compiler\n            ->outdent()\n            ->write(\"}\\n\\n\")\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileClassFooter(Compiler $compiler)\n    {\n        $compiler\n            ->subcompile($this->getNode('class_end'))\n            ->outdent()\n            ->write(\"}\\n\")\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileMacros(Compiler $compiler)\n    {\n        $compiler->subcompile($this->getNode('macros'));\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileGetTemplateName(Compiler $compiler)\n    {\n        $compiler\n            ->write(\"/**\\n\")\n            ->write(\" * @codeCoverageIgnore\\n\")\n            ->write(\" */\\n\")\n            ->write(\"public function getTemplateName(): string\\n\", \"{\\n\")\n            ->indent()\n            ->write('return ')\n            ->repr($this->getSourceContext()->getName())\n            ->raw(\";\\n\")\n            ->outdent()\n            ->write(\"}\\n\\n\")\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileIsTraitable(Compiler $compiler)\n    {\n        // A template can be used as a trait if:\n        //   * it has no parent\n        //   * it has no macros\n        //   * it has no body\n        //\n        // Put another way, a template can be used as a trait if it\n        // only contains blocks and use statements.\n        $traitable = !$this->hasNode('parent') && 0 === \\count($this->getNode('macros'));\n        if ($traitable) {\n            if ($this->getNode('body') instanceof BodyNode) {\n                $nodes = $this->getNode('body')->getNode('0');\n            } else {\n                $nodes = $this->getNode('body');\n            }\n\n            if (!\\count($nodes)) {\n                $nodes = new Nodes([$nodes]);\n            }\n\n            foreach ($nodes as $node) {\n                if (!\\count($node)) {\n                    continue;\n                }\n\n                $traitable = false;\n                break;\n            }\n        }\n\n        if ($traitable) {\n            return;\n        }\n\n        $compiler\n            ->write(\"/**\\n\")\n            ->write(\" * @codeCoverageIgnore\\n\")\n            ->write(\" */\\n\")\n            ->write(\"public function isTraitable(): bool\\n\", \"{\\n\")\n            ->indent()\n            ->write(\"return false;\\n\")\n            ->outdent()\n            ->write(\"}\\n\\n\")\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileDebugInfo(Compiler $compiler)\n    {\n        $compiler\n            ->write(\"/**\\n\")\n            ->write(\" * @codeCoverageIgnore\\n\")\n            ->write(\" */\\n\")\n            ->write(\"public function getDebugInfo(): array\\n\", \"{\\n\")\n            ->indent()\n            ->write(\\sprintf(\"return %s;\\n\", str_replace(\"\\n\", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))\n            ->outdent()\n            ->write(\"}\\n\\n\")\n        ;\n    }\n\n    /**\n     * @return void\n     */\n    protected function compileGetSourceContext(Compiler $compiler)\n    {\n        $compiler\n            ->write(\"public function getSourceContext(): Source\\n\", \"{\\n\")\n            ->indent()\n            ->write('return new Source(')\n            ->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '')\n            ->raw(', ')\n            ->string($this->getSourceContext()->getName())\n            ->raw(', ')\n            ->string($this->getSourceContext()->getPath())\n            ->raw(\");\\n\")\n            ->outdent()\n            ->write(\"}\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/NameDeprecation.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\n/**\n * Represents a deprecation for a named node or attribute on a Node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass NameDeprecation\n{\n    private $package;\n    private $version;\n    private $newName;\n\n    public function __construct(string $package = '', string $version = '', string $newName = '')\n    {\n        $this->package = $package;\n        $this->version = $version;\n        $this->newName = $newName;\n    }\n\n    public function getPackage(): string\n    {\n        return $this->package;\n    }\n\n    public function getVersion(): string\n    {\n        return $this->version;\n    }\n\n    public function getNewName(): string\n    {\n        return $this->newName;\n    }\n}\n"
  },
  {
    "path": "src/Node/Node.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Source;\n\n/**\n * Represents a node in the AST.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @implements \\IteratorAggregate<int|string, Node>\n */\n#[YieldReady]\nclass Node implements \\Countable, \\IteratorAggregate\n{\n    /**\n     * @var array<string|int, Node>\n     */\n    protected $nodes;\n    protected $attributes;\n    protected $lineno;\n    protected $tag;\n\n    private $sourceContext;\n    /** @var array<string, NameDeprecation> */\n    private $nodeNameDeprecations = [];\n    /** @var array<string, NameDeprecation> */\n    private $attributeNameDeprecations = [];\n\n    /**\n     * @param array<string|int, Node> $nodes      An array of named nodes\n     * @param array                   $attributes An array of attributes (should not be nodes)\n     * @param int                     $lineno     The line number\n     */\n    public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0)\n    {\n        if (self::class === static::class) {\n            trigger_deprecation('twig/twig', '3.15', \\sprintf('Instantiating \"%s\" directly is deprecated; the class will become abstract in 4.0.', self::class));\n        }\n\n        foreach ($nodes as $name => $node) {\n            if (!$node instanceof self) {\n                throw new \\InvalidArgumentException(\\sprintf('Using \"%s\" for the value of node \"%s\" of \"%s\" is not supported. You must pass a \\Twig\\Node\\Node instance.', get_debug_type($node), $name, static::class));\n            }\n        }\n        $this->nodes = $nodes;\n        $this->attributes = $attributes;\n        $this->lineno = $lineno;\n\n        if (\\func_num_args() > 3) {\n            trigger_deprecation('twig/twig', '3.12', \\sprintf('The \"tag\" constructor argument of the \"%s\" class is deprecated and ignored (check which TokenParser class set it to \"%s\"), the tag is now automatically set by the Parser when needed.', static::class, func_get_arg(3) ?: 'null'));\n        }\n    }\n\n    public function __toString(): string\n    {\n        $repr = static::class;\n\n        if ($this->tag) {\n            $repr .= \\sprintf(\"\\n  tag: %s\", $this->tag);\n        }\n\n        $attributes = [];\n        foreach ($this->attributes as $name => $value) {\n            if (\\is_callable($value)) {\n                $v = '\\Closure';\n            } elseif ($value instanceof \\Stringable) {\n                $v = (string) $value;\n            } else {\n                $v = str_replace(\"\\n\", '', var_export($value, true));\n            }\n            $attributes[] = \\sprintf('%s: %s', $name, $v);\n        }\n\n        if ($attributes) {\n            $repr .= \\sprintf(\"\\n  attributes:\\n    %s\", implode(\"\\n    \", $attributes));\n        }\n\n        if (\\count($this->nodes)) {\n            $repr .= \"\\n  nodes:\";\n            foreach ($this->nodes as $name => $node) {\n                $len = \\strlen($name) + 6;\n                $noderepr = [];\n                foreach (explode(\"\\n\", (string) $node) as $line) {\n                    $noderepr[] = str_repeat(' ', $len).$line;\n                }\n\n                $repr .= \\sprintf(\"\\n    %s: %s\", $name, ltrim(implode(\"\\n\", $noderepr)));\n            }\n        }\n\n        return $repr;\n    }\n\n    public function __clone()\n    {\n        foreach ($this->nodes as $name => $node) {\n            $this->nodes[$name] = clone $node;\n        }\n    }\n\n    /**\n     * @return void\n     */\n    public function compile(Compiler $compiler)\n    {\n        foreach ($this->nodes as $node) {\n            $compiler->subcompile($node);\n        }\n    }\n\n    public function getTemplateLine(): int\n    {\n        return $this->lineno;\n    }\n\n    public function getNodeTag(): ?string\n    {\n        return $this->tag;\n    }\n\n    /**\n     * @internal\n     */\n    public function setNodeTag(string $tag): void\n    {\n        if ($this->tag) {\n            throw new \\LogicException('The tag of a node can only be set once.');\n        }\n\n        $this->tag = $tag;\n    }\n\n    public function hasAttribute(string $name): bool\n    {\n        return \\array_key_exists($name, $this->attributes);\n    }\n\n    public function getAttribute(string $name)\n    {\n        if (!\\array_key_exists($name, $this->attributes)) {\n            throw new \\LogicException(\\sprintf('Attribute \"%s\" does not exist for Node \"%s\".', $name, static::class));\n        }\n\n        $triggerDeprecation = \\func_num_args() > 1 ? func_get_arg(1) : true;\n        if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {\n            $dep = $this->attributeNameDeprecations[$name];\n            if ($dep->getNewName()) {\n                trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute \"%s\" on a \"%s\" class is deprecated, get the \"%s\" attribute instead.', $name, static::class, $dep->getNewName());\n            } else {\n                trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute \"%s\" on a \"%s\" class is deprecated.', $name, static::class);\n            }\n        }\n\n        return $this->attributes[$name];\n    }\n\n    public function setAttribute(string $name, $value): void\n    {\n        $triggerDeprecation = \\func_num_args() > 2 ? func_get_arg(2) : true;\n        if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {\n            $dep = $this->attributeNameDeprecations[$name];\n            if ($dep->getNewName()) {\n                trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute \"%s\" on a \"%s\" class is deprecated, set the \"%s\" attribute instead.', $name, static::class, $dep->getNewName());\n            } else {\n                trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute \"%s\" on a \"%s\" class is deprecated.', $name, static::class);\n            }\n        }\n\n        $this->attributes[$name] = $value;\n    }\n\n    public function deprecateAttribute(string $name, NameDeprecation $dep): void\n    {\n        $this->attributeNameDeprecations[$name] = $dep;\n    }\n\n    public function removeAttribute(string $name): void\n    {\n        unset($this->attributes[$name]);\n    }\n\n    /**\n     * @param string|int $name\n     */\n    public function hasNode(string $name): bool\n    {\n        return isset($this->nodes[$name]);\n    }\n\n    /**\n     * @param string|int $name\n     */\n    public function getNode(string $name): self\n    {\n        if (!isset($this->nodes[$name])) {\n            throw new \\LogicException(\\sprintf('Node \"%s\" does not exist for Node \"%s\".', $name, static::class));\n        }\n\n        $triggerDeprecation = \\func_num_args() > 1 ? func_get_arg(1) : true;\n        if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) {\n            $dep = $this->nodeNameDeprecations[$name];\n            if ($dep->getNewName()) {\n                trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node \"%s\" on a \"%s\" class is deprecated, get the \"%s\" node instead.', $name, static::class, $dep->getNewName());\n            } else {\n                trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node \"%s\" on a \"%s\" class is deprecated.', $name, static::class);\n            }\n        }\n\n        return $this->nodes[$name];\n    }\n\n    /**\n     * @param string|int $name\n     */\n    public function setNode(string $name, self $node): void\n    {\n        $triggerDeprecation = \\func_num_args() > 2 ? func_get_arg(2) : true;\n        if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) {\n            $dep = $this->nodeNameDeprecations[$name];\n            if ($dep->getNewName()) {\n                trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node \"%s\" on a \"%s\" class is deprecated, set the \"%s\" node instead.', $name, static::class, $dep->getNewName());\n            } else {\n                trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node \"%s\" on a \"%s\" class is deprecated.', $name, static::class);\n            }\n        }\n\n        if (null !== $this->sourceContext) {\n            $node->setSourceContext($this->sourceContext);\n        }\n        $this->nodes[$name] = $node;\n    }\n\n    /**\n     * @param string|int $name\n     */\n    public function removeNode(string $name): void\n    {\n        unset($this->nodes[$name]);\n    }\n\n    /**\n     * @param string|int $name\n     */\n    public function deprecateNode(string $name, NameDeprecation $dep): void\n    {\n        $this->nodeNameDeprecations[$name] = $dep;\n    }\n\n    /**\n     * @return int\n     */\n    #[\\ReturnTypeWillChange]\n    public function count()\n    {\n        return \\count($this->nodes);\n    }\n\n    public function getIterator(): \\Traversable\n    {\n        return new \\ArrayIterator($this->nodes);\n    }\n\n    public function getTemplateName(): ?string\n    {\n        return $this->sourceContext ? $this->sourceContext->getName() : null;\n    }\n\n    public function setSourceContext(Source $source): void\n    {\n        $this->sourceContext = $source;\n        foreach ($this->nodes as $node) {\n            $node->setSourceContext($source);\n        }\n    }\n\n    public function getSourceContext(): ?Source\n    {\n        return $this->sourceContext;\n    }\n}\n"
  },
  {
    "path": "src/Node/NodeCaptureInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\n/**\n * Represents a node that captures any nested displayable nodes.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface NodeCaptureInterface\n{\n}\n"
  },
  {
    "path": "src/Node/NodeOutputInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\n/**\n * Represents a displayable node in the AST.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface NodeOutputInterface\n{\n}\n"
  },
  {
    "path": "src/Node/Nodes.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\n\n/**\n * Represents a list of nodes.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nfinal class Nodes extends Node\n{\n    public function __construct(array $nodes = [], int $lineno = 0)\n    {\n        parent::__construct($nodes, [], $lineno);\n    }\n}\n"
  },
  {
    "path": "src/Node/PrintNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\AbstractExpression;\n\n/**\n * Represents a node that outputs an expression.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass PrintNode extends Node implements NodeOutputInterface\n{\n    public function __construct(AbstractExpression $expr, int $lineno)\n    {\n        parent::__construct(['expr' => $expr], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        /** @var AbstractExpression */\n        $expr = $this->getNode('expr');\n\n        $compiler\n            ->addDebugInfo($this)\n            ->write($expr->isGenerator() ? 'yield from ' : 'yield ')\n            ->subcompile($expr)\n            ->raw(\";\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/SandboxNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents a sandbox node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass SandboxNode extends Node\n{\n    public function __construct(Node $body, int $lineno)\n    {\n        parent::__construct(['body' => $body], [], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->write(\"if (!\\$alreadySandboxed = \\$this->sandbox->isSandboxed()) {\\n\")\n            ->indent()\n            ->write(\"\\$this->sandbox->enableSandbox();\\n\")\n            ->outdent()\n            ->write(\"}\\n\")\n            ->write(\"try {\\n\")\n            ->indent()\n            ->subcompile($this->getNode('body'))\n            ->outdent()\n            ->write(\"} finally {\\n\")\n            ->indent()\n            ->write(\"if (!\\$alreadySandboxed) {\\n\")\n            ->indent()\n            ->write(\"\\$this->sandbox->disableSandbox();\\n\")\n            ->outdent()\n            ->write(\"}\\n\")\n            ->outdent()\n            ->write(\"}\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/SetNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Expression\\ConstantExpression;\n\n/**\n * Represents a set node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass SetNode extends Node implements NodeCaptureInterface\n{\n    public function __construct(bool $capture, Node $names, Node $values, int $lineno)\n    {\n        /*\n         * Optimizes the node when capture is used for a large block of text.\n         *\n         * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\\Markup(\"foo\");\n         */\n        $safe = false;\n        if ($capture) {\n            $safe = true;\n            // Node::class === get_class($values) should be removed in Twig 4.0\n            if (($values instanceof Nodes || Node::class === $values::class) && !\\count($values)) {\n                $values = new ConstantExpression('', $values->getTemplateLine());\n                $capture = false;\n            } elseif ($values instanceof TextNode) {\n                $values = new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine());\n                $capture = false;\n            } elseif ($values instanceof PrintNode && $values->getNode('expr') instanceof ConstantExpression) {\n                $values = $values->getNode('expr');\n                $capture = false;\n            } else {\n                $values = new CaptureNode($values, $values->getTemplateLine());\n            }\n        }\n\n        parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => $safe], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n\n        if (\\count($this->getNode('names')) > 1) {\n            $compiler->write('[');\n            foreach ($this->getNode('names') as $idx => $node) {\n                if ($idx) {\n                    $compiler->raw(', ');\n                }\n\n                $compiler->subcompile($node);\n            }\n            $compiler->raw(']');\n        } else {\n            $compiler->subcompile($this->getNode('names'), false);\n        }\n        $compiler->raw(' = ');\n\n        if ($this->getAttribute('capture')) {\n            $compiler->subcompile($this->getNode('values'));\n        } else {\n            if (\\count($this->getNode('names')) > 1) {\n                $compiler->write('[');\n                foreach ($this->getNode('values') as $idx => $value) {\n                    if ($idx) {\n                        $compiler->raw(', ');\n                    }\n\n                    $compiler->subcompile($value);\n                }\n                $compiler->raw(']');\n            } else {\n                if ($this->getAttribute('safe')) {\n                    if ($this->getNode('values') instanceof ConstantExpression) {\n                        if ('' === $this->getNode('values')->getAttribute('value')) {\n                            $compiler->raw('\"\"');\n                        } else {\n                            $compiler\n                                ->raw('new Markup(')\n                                ->subcompile($this->getNode('values'))\n                                ->raw(', $this->env->getCharset())')\n                            ;\n                        }\n                    } else {\n                        $compiler\n                            ->raw(\"('' === \\$tmp = \")\n                            ->subcompile($this->getNode('values'))\n                            ->raw(\") ? '' : new Markup(\\$tmp, \\$this->env->getCharset())\")\n                        ;\n                    }\n                } else {\n                    $compiler->subcompile($this->getNode('values'));\n                }\n            }\n\n            $compiler->raw(';');\n        }\n\n        $compiler->raw(\"\\n\");\n    }\n}\n"
  },
  {
    "path": "src/Node/TextNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents a text node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass TextNode extends Node implements NodeOutputInterface\n{\n    public function __construct(string $data, int $lineno)\n    {\n        parent::__construct([], ['data' => $data], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n\n        $compiler\n            ->write('yield ')\n            ->string($this->getAttribute('data'))\n            ->raw(\";\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Node/TypesNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents a types node.\n *\n * @author Jeroen Versteeg <jeroen@alisqi.com>\n */\n#[YieldReady]\nclass TypesNode extends Node\n{\n    /**\n     * @param array<string, array{type: string, optional: bool}> $types\n     */\n    public function __construct(array $types, int $lineno)\n    {\n        parent::__construct([], ['mapping' => $types], $lineno);\n    }\n\n    /**\n     * @return void\n     */\n    public function compile(Compiler $compiler)\n    {\n        // Don't compile anything.\n    }\n}\n"
  },
  {
    "path": "src/Node/WithNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\n\n/**\n * Represents a nested \"with\" scope.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass WithNode extends Node\n{\n    public function __construct(Node $body, ?Node $variables, bool $only, int $lineno)\n    {\n        $nodes = ['body' => $body];\n        if (null !== $variables) {\n            $nodes['variables'] = $variables;\n        }\n\n        parent::__construct($nodes, ['only' => $only], $lineno);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler->addDebugInfo($this);\n\n        $parentContextName = $compiler->getVarName();\n\n        $compiler->write(\\sprintf(\"\\$%s = \\$context;\\n\", $parentContextName));\n\n        if ($this->hasNode('variables')) {\n            $node = $this->getNode('variables');\n            $varsName = $compiler->getVarName();\n            $compiler\n                ->write(\\sprintf('$%s = ', $varsName))\n                ->subcompile($node)\n                ->raw(\";\\n\")\n                ->write(\\sprintf(\"if (!is_iterable(\\$%s)) {\\n\", $varsName))\n                ->indent()\n                ->write(\"throw new RuntimeError('Variables passed to the \\\"with\\\" tag must be a mapping.', \")\n                ->repr($node->getTemplateLine())\n                ->raw(\", \\$this->getSourceContext());\\n\")\n                ->outdent()\n                ->write(\"}\\n\")\n                ->write(\\sprintf(\"\\$%s = CoreExtension::toArray(\\$%s);\\n\", $varsName, $varsName))\n            ;\n\n            if ($this->getAttribute('only')) {\n                $compiler->write(\"\\$context = [];\\n\");\n            }\n\n            $compiler->write(\\sprintf(\"\\$context = \\$%s + \\$context + \\$this->env->getGlobals();\\n\", $varsName));\n        }\n\n        $compiler\n            ->subcompile($this->getNode('body'))\n            ->write(\\sprintf(\"\\$context = \\$%s;\\n\", $parentContextName))\n        ;\n    }\n}\n"
  },
  {
    "path": "src/NodeTraverser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Node\\Node;\nuse Twig\\NodeVisitor\\NodeVisitorInterface;\n\n/**\n * A node traverser.\n *\n * It visits all nodes and their children and calls the given visitor for each.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class NodeTraverser\n{\n    private $env;\n    private $visitors = [];\n\n    /**\n     * @param NodeVisitorInterface[] $visitors\n     */\n    public function __construct(Environment $env, array $visitors = [])\n    {\n        $this->env = $env;\n        foreach ($visitors as $visitor) {\n            $this->addVisitor($visitor);\n        }\n    }\n\n    public function addVisitor(NodeVisitorInterface $visitor): void\n    {\n        $this->visitors[$visitor->getPriority()][] = $visitor;\n    }\n\n    /**\n     * Traverses a node and calls the registered visitors.\n     */\n    public function traverse(Node $node): Node\n    {\n        ksort($this->visitors);\n        foreach ($this->visitors as $visitors) {\n            foreach ($visitors as $visitor) {\n                $node = $this->traverseForVisitor($visitor, $node);\n            }\n        }\n\n        return $node;\n    }\n\n    private function traverseForVisitor(NodeVisitorInterface $visitor, Node $node): ?Node\n    {\n        $node = $visitor->enterNode($node, $this->env);\n\n        foreach ($node as $k => $n) {\n            if (null !== $m = $this->traverseForVisitor($visitor, $n)) {\n                if ($m !== $n) {\n                    $node->setNode($k, $m);\n                }\n            } else {\n                $node->removeNode($k);\n            }\n        }\n\n        return $visitor->leaveNode($node, $this->env);\n    }\n}\n"
  },
  {
    "path": "src/NodeVisitor/AbstractNodeVisitor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\NodeVisitor;\n\nuse Twig\\Environment;\nuse Twig\\Node\\Node;\n\n/**\n * Used to make node visitors compatible with Twig 1.x and 2.x.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @deprecated since Twig 3.9 (to be removed in 4.0)\n */\nabstract class AbstractNodeVisitor implements NodeVisitorInterface\n{\n    final public function enterNode(Node $node, Environment $env): Node\n    {\n        return $this->doEnterNode($node, $env);\n    }\n\n    final public function leaveNode(Node $node, Environment $env): ?Node\n    {\n        return $this->doLeaveNode($node, $env);\n    }\n\n    /**\n     * Called before child nodes are visited.\n     *\n     * @return Node The modified node\n     */\n    abstract protected function doEnterNode(Node $node, Environment $env);\n\n    /**\n     * Called after child nodes are visited.\n     *\n     * @return Node|null The modified node or null if the node must be removed\n     */\n    abstract protected function doLeaveNode(Node $node, Environment $env);\n}\n"
  },
  {
    "path": "src/NodeVisitor/EscaperNodeVisitor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\NodeVisitor;\n\nuse Twig\\Environment;\nuse Twig\\Extension\\EscaperExtension;\nuse Twig\\Node\\AutoEscapeNode;\nuse Twig\\Node\\BlockNode;\nuse Twig\\Node\\BlockReferenceNode;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FilterExpression;\nuse Twig\\Node\\Expression\\OperatorEscapeInterface;\nuse Twig\\Node\\ImportNode;\nuse Twig\\Node\\ModuleNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\PrintNode;\nuse Twig\\NodeTraverser;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nfinal class EscaperNodeVisitor implements NodeVisitorInterface\n{\n    private $statusStack = [];\n    private $blocks = [];\n    private $safeAnalysis;\n    private $traverser;\n    private $defaultStrategy = false;\n    private $safeVars = [];\n\n    public function __construct()\n    {\n        $this->safeAnalysis = new SafeAnalysisNodeVisitor();\n    }\n\n    public function enterNode(Node $node, Environment $env): Node\n    {\n        if ($node instanceof ModuleNode) {\n            if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) {\n                $this->defaultStrategy = $defaultStrategy;\n            }\n            $this->safeVars = [];\n            $this->blocks = [];\n        } elseif ($node instanceof AutoEscapeNode) {\n            $this->statusStack[] = $node->getAttribute('value');\n        } elseif ($node instanceof BlockNode) {\n            $this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping();\n        } elseif ($node instanceof ImportNode) {\n            $this->safeVars[] = $node->getNode('var')->getNode('var')->getAttribute('name');\n        }\n\n        return $node;\n    }\n\n    public function leaveNode(Node $node, Environment $env): ?Node\n    {\n        if ($node instanceof ModuleNode) {\n            $this->defaultStrategy = false;\n            $this->safeVars = [];\n            $this->blocks = [];\n        } elseif ($node instanceof FilterExpression) {\n            return $this->preEscapeFilterNode($node, $env);\n        } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping()) {\n            $expression = $node->getNode('expr');\n            if ($expression instanceof OperatorEscapeInterface) {\n                $this->escapeConditional($expression, $env, $type);\n            } else {\n                $node->setNode('expr', $this->escapeExpression($expression, $env, $type));\n            }\n\n            return $node;\n        }\n\n        if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) {\n            array_pop($this->statusStack);\n        } elseif ($node instanceof BlockReferenceNode) {\n            $this->blocks[$node->getAttribute('name')] = $this->needEscaping();\n        }\n\n        return $node;\n    }\n\n    /**\n     * @param AbstractExpression&OperatorEscapeInterface $expression\n     */\n    private function escapeConditional($expression, Environment $env, string $type): void\n    {\n        foreach ($expression->getOperandNamesToEscape() as $name) {\n            /** @var AbstractExpression $operand */\n            $operand = $expression->getNode($name);\n            if ($operand instanceof OperatorEscapeInterface) {\n                $this->escapeConditional($operand, $env, $type);\n            } else {\n                $expression->setNode($name, $this->escapeExpression($operand, $env, $type));\n            }\n        }\n    }\n\n    private function escapeExpression(AbstractExpression $expression, Environment $env, string $type): AbstractExpression\n    {\n        return $this->isSafeFor($type, $expression, $env) ? $expression : $this->getEscaperFilter($env, $type, $expression);\n    }\n\n    private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression\n    {\n        if ($filter->hasAttribute('twig_callable')) {\n            $type = $filter->getAttribute('twig_callable')->getPreEscape();\n        } else {\n            // legacy\n            $name = $filter->getNode('filter', false)->getAttribute('value');\n            $type = $env->getFilter($name)->getPreEscape();\n        }\n\n        if (null === $type) {\n            return $filter;\n        }\n\n        /** @var AbstractExpression $node */\n        $node = $filter->getNode('node');\n        if ($this->isSafeFor($type, $node, $env)) {\n            return $filter;\n        }\n\n        $filter->setNode('node', $this->getEscaperFilter($env, $type, $node));\n\n        return $filter;\n    }\n\n    private function isSafeFor(string $type, AbstractExpression $expression, Environment $env): bool\n    {\n        $safe = $this->safeAnalysis->getSafe($expression);\n\n        if (!$safe) {\n            if (null === $this->traverser) {\n                $this->traverser = new NodeTraverser($env, [$this->safeAnalysis]);\n            }\n\n            $this->safeAnalysis->setSafeVars($this->safeVars);\n\n            $this->traverser->traverse($expression);\n            $safe = $this->safeAnalysis->getSafe($expression);\n        }\n\n        return \\in_array($type, $safe, true) || \\in_array('all', $safe, true);\n    }\n\n    /**\n     * @return string|false\n     */\n    private function needEscaping(): string|bool\n    {\n        if (\\count($this->statusStack)) {\n            return $this->statusStack[\\count($this->statusStack) - 1];\n        }\n\n        return $this->defaultStrategy ?: false;\n    }\n\n    private function getEscaperFilter(Environment $env, string $type, AbstractExpression $node): FilterExpression\n    {\n        $line = $node->getTemplateLine();\n        $filter = $env->getFilter('escape');\n        $args = new Nodes([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]);\n\n        return new FilterExpression($node, $filter, $args, $line);\n    }\n\n    public function getPriority(): int\n    {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/NodeVisitor/NodeVisitorInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\NodeVisitor;\n\nuse Twig\\Environment;\nuse Twig\\Node\\Node;\n\n/**\n * Interface for node visitor classes.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface NodeVisitorInterface\n{\n    /**\n     * Called before child nodes are visited.\n     *\n     * @return Node The modified node\n     */\n    public function enterNode(Node $node, Environment $env): Node;\n\n    /**\n     * Called after child nodes are visited.\n     *\n     * @return Node|null The modified node or null if the node must be removed\n     */\n    public function leaveNode(Node $node, Environment $env): ?Node;\n\n    /**\n     * Returns the priority for this visitor.\n     *\n     * Priority should be between -10 and 10 (0 is the default).\n     *\n     * @return int The priority level\n     */\n    public function getPriority();\n}\n"
  },
  {
    "path": "src/NodeVisitor/OptimizerNodeVisitor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\NodeVisitor;\n\nuse Twig\\Environment;\nuse Twig\\Node\\BlockReferenceNode;\nuse Twig\\Node\\Expression\\BlockReferenceExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Expression\\ParentExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\ForNode;\nuse Twig\\Node\\IncludeNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Node\\TextNode;\n\n/**\n * Tries to optimize the AST.\n *\n * This visitor is always the last registered one.\n *\n * You can configure which optimizations you want to activate via the\n * optimizer mode.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nfinal class OptimizerNodeVisitor implements NodeVisitorInterface\n{\n    public const OPTIMIZE_ALL = -1;\n    public const OPTIMIZE_NONE = 0;\n    public const OPTIMIZE_FOR = 2;\n    public const OPTIMIZE_RAW_FILTER = 4;\n    public const OPTIMIZE_TEXT_NODES = 8;\n\n    private $loops = [];\n    private $loopsTargets = [];\n\n    /**\n     * @param int $optimizers The optimizer mode\n     */\n    public function __construct(\n        private int $optimizers = -1,\n    ) {\n        if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_TEXT_NODES)) {\n            throw new \\InvalidArgumentException(\\sprintf('Optimizer mode \"%s\" is not valid.', $optimizers));\n        }\n\n        if (-1 !== $optimizers && self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $optimizers)) {\n            trigger_deprecation('twig/twig', '3.11', 'The \"Twig\\NodeVisitor\\OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER\" option is deprecated and does nothing.');\n        }\n\n        if (-1 !== $optimizers && self::OPTIMIZE_TEXT_NODES === (self::OPTIMIZE_TEXT_NODES & $optimizers)) {\n            trigger_deprecation('twig/twig', '3.12', 'The \"Twig\\NodeVisitor\\OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES\" option is deprecated and does nothing.');\n        }\n    }\n\n    public function enterNode(Node $node, Environment $env): Node\n    {\n        if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {\n            $this->enterOptimizeFor($node);\n        }\n\n        return $node;\n    }\n\n    public function leaveNode(Node $node, Environment $env): ?Node\n    {\n        if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {\n            $this->leaveOptimizeFor($node);\n        }\n\n        $node = $this->optimizePrintNode($node);\n\n        return $node;\n    }\n\n    /**\n     * Optimizes print nodes.\n     *\n     * It replaces:\n     *\n     *   * \"echo $this->render(Parent)Block()\" with \"$this->display(Parent)Block()\"\n     */\n    private function optimizePrintNode(Node $node): Node\n    {\n        if (!$node instanceof PrintNode) {\n            return $node;\n        }\n\n        $exprNode = $node->getNode('expr');\n\n        if ($exprNode instanceof ConstantExpression && \\is_string($exprNode->getAttribute('value'))) {\n            return new TextNode($exprNode->getAttribute('value'), $exprNode->getTemplateLine());\n        }\n\n        if (\n            $exprNode instanceof BlockReferenceExpression\n            || $exprNode instanceof ParentExpression\n        ) {\n            $exprNode->setAttribute('output', true);\n\n            return $exprNode;\n        }\n\n        return $node;\n    }\n\n    /**\n     * Optimizes \"for\" tag by removing the \"loop\" variable creation whenever possible.\n     */\n    private function enterOptimizeFor(Node $node): void\n    {\n        if ($node instanceof ForNode) {\n            // disable the loop variable by default\n            $node->setAttribute('with_loop', false);\n            array_unshift($this->loops, $node);\n            array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name'));\n            array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name'));\n        } elseif (!$this->loops) {\n            // we are outside a loop\n            return;\n        }\n\n        // when do we need to add the loop variable back?\n\n        // the loop variable is referenced for the current loop\n        elseif ($node instanceof ContextVariable && 'loop' === $node->getAttribute('name')) {\n            $node->setAttribute('always_defined', true);\n            $this->addLoopToCurrent();\n        }\n\n        // optimize access to loop targets\n        elseif ($node instanceof ContextVariable && \\in_array($node->getAttribute('name'), $this->loopsTargets, true)) {\n            $node->setAttribute('always_defined', true);\n        }\n\n        // block reference\n        elseif ($node instanceof BlockReferenceNode || $node instanceof BlockReferenceExpression) {\n            $this->addLoopToCurrent();\n        }\n\n        // include without the only attribute\n        elseif ($node instanceof IncludeNode && !$node->getAttribute('only')) {\n            $this->addLoopToAll();\n        }\n\n        // include function without the with_context=false parameter\n        elseif ($node instanceof FunctionExpression\n            && 'include' === $node->getAttribute('name')\n            && (!$node->getNode('arguments')->hasNode('with_context')\n                 || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value')\n            )\n        ) {\n            $this->addLoopToAll();\n        }\n\n        // the loop variable is referenced via an attribute\n        elseif ($node instanceof GetAttrExpression\n            && (!$node->getNode('attribute') instanceof ConstantExpression\n                || 'parent' === $node->getNode('attribute')->getAttribute('value')\n            )\n            && (true === $this->loops[0]->getAttribute('with_loop')\n             || ($node->getNode('node') instanceof ContextVariable\n                 && 'loop' === $node->getNode('node')->getAttribute('name')\n             )\n            )\n        ) {\n            $this->addLoopToAll();\n        }\n    }\n\n    /**\n     * Optimizes \"for\" tag by removing the \"loop\" variable creation whenever possible.\n     */\n    private function leaveOptimizeFor(Node $node): void\n    {\n        if ($node instanceof ForNode) {\n            array_shift($this->loops);\n            array_shift($this->loopsTargets);\n            array_shift($this->loopsTargets);\n        }\n    }\n\n    private function addLoopToCurrent(): void\n    {\n        $this->loops[0]->setAttribute('with_loop', true);\n    }\n\n    private function addLoopToAll(): void\n    {\n        foreach ($this->loops as $loop) {\n            $loop->setAttribute('with_loop', true);\n        }\n    }\n\n    public function getPriority(): int\n    {\n        return 255;\n    }\n}\n"
  },
  {
    "path": "src/NodeVisitor/SafeAnalysisNodeVisitor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\NodeVisitor;\n\nuse Twig\\Environment;\nuse Twig\\Node\\Expression\\BlockReferenceExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FilterExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Expression\\MacroReferenceExpression;\nuse Twig\\Node\\Expression\\MethodCallExpression;\nuse Twig\\Node\\Expression\\OperatorEscapeInterface;\nuse Twig\\Node\\Expression\\ParentExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\n\n/**\n * @internal\n */\nfinal class SafeAnalysisNodeVisitor implements NodeVisitorInterface\n{\n    private $data = [];\n    private $safeVars = [];\n\n    public function setSafeVars(array $safeVars): void\n    {\n        $this->safeVars = $safeVars;\n    }\n\n    /**\n     * @return array\n     */\n    public function getSafe(Node $node)\n    {\n        $hash = spl_object_id($node);\n        if (!isset($this->data[$hash])) {\n            return [];\n        }\n\n        foreach ($this->data[$hash] as $bucket) {\n            if ($bucket['key'] !== $node) {\n                continue;\n            }\n\n            if (\\in_array('html_attr', $bucket['value'], true)) {\n                $bucket['value'][] = 'html';\n                $bucket['value'][] = 'html_attr_relaxed';\n            }\n\n            if (\\in_array('html_attr_relaxed', $bucket['value'], true)) {\n                $bucket['value'][] = 'html';\n            }\n\n            return $bucket['value'];\n        }\n\n        return [];\n    }\n\n    private function setSafe(Node $node, array $safe): void\n    {\n        $hash = spl_object_id($node);\n        if (isset($this->data[$hash])) {\n            foreach ($this->data[$hash] as &$bucket) {\n                if ($bucket['key'] === $node) {\n                    $bucket['value'] = $safe;\n\n                    return;\n                }\n            }\n        }\n        $this->data[$hash][] = [\n            'key' => $node,\n            'value' => $safe,\n        ];\n    }\n\n    public function enterNode(Node $node, Environment $env): Node\n    {\n        return $node;\n    }\n\n    public function leaveNode(Node $node, Environment $env): ?Node\n    {\n        if ($node instanceof ConstantExpression) {\n            // constants are marked safe for all\n            $this->setSafe($node, ['all']);\n        } elseif ($node instanceof BlockReferenceExpression) {\n            // blocks are safe by definition\n            $this->setSafe($node, ['all']);\n        } elseif ($node instanceof ParentExpression) {\n            // parent block is safe by definition\n            $this->setSafe($node, ['all']);\n        } elseif ($node instanceof OperatorEscapeInterface) {\n            // intersect safeness of operands\n            $operands = $node->getOperandNamesToEscape();\n            if (2 < \\count($operands)) {\n                throw new \\LogicException(\\sprintf('Operators with more than 2 operands are not supported yet, got %d.', \\count($operands)));\n            } elseif (2 === \\count($operands)) {\n                $safe = $this->intersectSafe($this->getSafe($node->getNode($operands[0])), $this->getSafe($node->getNode($operands[1])));\n                $this->setSafe($node, $safe);\n            }\n        } elseif ($node instanceof FilterExpression) {\n            // filter expression is safe when the filter is safe\n            if ($node->hasAttribute('twig_callable')) {\n                $filter = $node->getAttribute('twig_callable');\n            } else {\n                // legacy\n                $filter = $env->getFilter($node->getAttribute('name'));\n            }\n\n            if ($filter) {\n                $safe = $filter->getSafe($node->getNode('arguments'));\n                if (null === $safe) {\n                    trigger_deprecation('twig/twig', '3.16', 'The \"%s::getSafe()\" method should not return \"null\" anymore, return \"[]\" instead.', $filter::class);\n                    $safe = [];\n                }\n\n                if (!$safe) {\n                    $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety());\n                }\n                $this->setSafe($node, $safe);\n            }\n        } elseif ($node instanceof FunctionExpression) {\n            // function expression is safe when the function is safe\n            if ($node->hasAttribute('twig_callable')) {\n                $function = $node->getAttribute('twig_callable');\n            } else {\n                // legacy\n                $function = $env->getFunction($node->getAttribute('name'));\n            }\n\n            if ($function) {\n                $safe = $function->getSafe($node->getNode('arguments'));\n                if (null === $safe) {\n                    trigger_deprecation('twig/twig', '3.16', 'The \"%s::getSafe()\" method should not return \"null\" anymore, return \"[]\" instead.', $function::class);\n                    $safe = [];\n                }\n                $this->setSafe($node, $safe);\n            }\n        } elseif ($node instanceof MethodCallExpression || $node instanceof MacroReferenceExpression) {\n            // all macro calls are safe\n            $this->setSafe($node, ['all']);\n        } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof ContextVariable) {\n            $name = $node->getNode('node')->getAttribute('name');\n            if (\\in_array($name, $this->safeVars, true)) {\n                $this->setSafe($node, ['all']);\n            }\n        }\n\n        return $node;\n    }\n\n    private function intersectSafe(array $a, array $b): array\n    {\n        if (!$a || !$b) {\n            return [];\n        }\n\n        if (\\in_array('all', $a, true)) {\n            return $b;\n        }\n\n        if (\\in_array('all', $b, true)) {\n            return $a;\n        }\n\n        return array_intersect($a, $b);\n    }\n\n    public function getPriority(): int\n    {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/NodeVisitor/SandboxNodeVisitor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\NodeVisitor;\n\nuse Twig\\Environment;\nuse Twig\\Node\\CheckSecurityCallNode;\nuse Twig\\Node\\CheckSecurityNode;\nuse Twig\\Node\\CheckToStringNode;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\Binary\\ConcatBinary;\nuse Twig\\Node\\Expression\\Binary\\RangeBinary;\nuse Twig\\Node\\Expression\\FilterExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Expression\\Unary\\SpreadUnary;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\ModuleNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Node\\SetNode;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nfinal class SandboxNodeVisitor implements NodeVisitorInterface\n{\n    private $inAModule = false;\n    /** @var array<string, int> */\n    private $tags;\n    /** @var array<string, int> */\n    private $filters;\n    /** @var array<string, int> */\n    private $functions;\n    private $needsToStringWrap = false;\n\n    public function enterNode(Node $node, Environment $env): Node\n    {\n        if ($node instanceof ModuleNode) {\n            $this->inAModule = true;\n            $this->tags = [];\n            $this->filters = [];\n            $this->functions = [];\n\n            return $node;\n        } elseif ($this->inAModule) {\n            // look for tags\n            if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) {\n                $this->tags[$node->getNodeTag()] = $node->getTemplateLine();\n            }\n\n            // look for filters\n            if ($node instanceof FilterExpression && !isset($this->filters[$node->getAttribute('name')])) {\n                $this->filters[$node->getAttribute('name')] = $node->getTemplateLine();\n            }\n\n            // look for functions\n            if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) {\n                $this->functions[$node->getAttribute('name')] = $node->getTemplateLine();\n            }\n\n            // the .. operator is equivalent to the range() function\n            if ($node instanceof RangeBinary && !isset($this->functions['range'])) {\n                $this->functions['range'] = $node->getTemplateLine();\n            }\n\n            if ($node instanceof PrintNode) {\n                $this->needsToStringWrap = true;\n                $this->wrapNode($node, 'expr');\n            }\n\n            if ($node instanceof SetNode && !$node->getAttribute('capture')) {\n                $this->needsToStringWrap = true;\n            }\n\n            // wrap outer nodes that can implicitly call __toString()\n            if ($this->needsToStringWrap) {\n                if ($node instanceof ConcatBinary) {\n                    $this->wrapNode($node, 'left');\n                    $this->wrapNode($node, 'right');\n                }\n                if ($node instanceof FilterExpression) {\n                    $this->wrapNode($node, 'node');\n                    $this->wrapArrayNode($node, 'arguments');\n                }\n                if ($node instanceof FunctionExpression) {\n                    $this->wrapArrayNode($node, 'arguments');\n                }\n            }\n        }\n\n        return $node;\n    }\n\n    public function leaveNode(Node $node, Environment $env): ?Node\n    {\n        if ($node instanceof ModuleNode) {\n            $this->inAModule = false;\n\n            $node->setNode('constructor_end', new Nodes([new CheckSecurityCallNode(), $node->getNode('constructor_end')]));\n            $node->setNode('class_end', new Nodes([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')]));\n        } elseif ($this->inAModule) {\n            if ($node instanceof PrintNode || $node instanceof SetNode) {\n                $this->needsToStringWrap = false;\n            }\n        }\n\n        return $node;\n    }\n\n    private function wrapNode(Node $node, string $name): void\n    {\n        $expr = $node->getNode($name);\n        if (($expr instanceof ContextVariable || $expr instanceof GetAttrExpression) && !$expr->isGenerator()) {\n            $node->setNode($name, new CheckToStringNode($expr));\n        } elseif ($expr instanceof SpreadUnary) {\n            $this->wrapNode($expr, 'node');\n        } elseif ($expr instanceof ArrayExpression) {\n            foreach ($expr as $name => $_) {\n                $this->wrapNode($expr, $name);\n            }\n        }\n    }\n\n    private function wrapArrayNode(Node $node, string $name): void\n    {\n        $args = $node->getNode($name);\n        foreach ($args as $name => $_) {\n            $this->wrapNode($args, $name);\n        }\n    }\n\n    public function getPriority(): int\n    {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/NodeVisitor/YieldNotReadyNodeVisitor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\NodeVisitor;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Environment;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Node;\n\n/**\n * @internal to be removed in Twig 4\n */\nfinal class YieldNotReadyNodeVisitor implements NodeVisitorInterface\n{\n    private $yieldReadyNodes = [];\n\n    public function __construct(\n        private bool $useYield,\n    ) {\n    }\n\n    public function enterNode(Node $node, Environment $env): Node\n    {\n        $class = $node::class;\n\n        if ($node instanceof AbstractExpression || isset($this->yieldReadyNodes[$class])) {\n            return $node;\n        }\n\n        if (!$this->yieldReadyNodes[$class] = (bool) (new \\ReflectionClass($class))->getAttributes(YieldReady::class)) {\n            if ($this->useYield) {\n                throw new \\LogicException(\\sprintf('You cannot enable the \"use_yield\" option of Twig as node \"%s\" is not marked as ready for it; please make it ready and then flag it with the #[\\Twig\\Attribute\\YieldReady] attribute.', $class));\n            }\n\n            trigger_deprecation('twig/twig', '3.9', 'Twig node \"%s\" is not marked as ready for using \"yield\" instead of \"echo\"; please make it ready and then flag it with the #[\\Twig\\Attribute\\YieldReady] attribute.', $class);\n        }\n\n        return $node;\n    }\n\n    public function leaveNode(Node $node, Environment $env): ?Node\n    {\n        return $node;\n    }\n\n    public function getPriority(): int\n    {\n        return 255;\n    }\n}\n"
  },
  {
    "path": "src/OperatorPrecedenceChange.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\ExpressionParser\\PrecedenceChange;\n\n/**\n * Represents a precedence change.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @deprecated since Twig 1.20 Use Twig\\ExpressionParser\\PrecedenceChange instead\n */\nclass OperatorPrecedenceChange extends PrecedenceChange\n{\n    public function __construct(\n        private string $package,\n        private string $version,\n        private int $newPrecedence,\n    ) {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s\" class is deprecated since Twig 3.21. Use \"%s\" instead.', self::class, PrecedenceChange::class);\n\n        parent::__construct($package, $version, $newPrecedence);\n    }\n}\n"
  },
  {
    "path": "src/Parser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\ExpressionParserInterface;\nuse Twig\\ExpressionParser\\ExpressionParsers;\nuse Twig\\ExpressionParser\\ExpressionParserType;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\ExpressionParser\\Prefix\\LiteralExpressionParser;\nuse Twig\\ExpressionParser\\PrefixExpressionParserInterface;\nuse Twig\\Node\\BlockNode;\nuse Twig\\Node\\BlockReferenceNode;\nuse Twig\\Node\\BodyNode;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\Expression\\Variable\\AssignTemplateVariable;\nuse Twig\\Node\\Expression\\Variable\\TemplateVariable;\nuse Twig\\Node\\MacroNode;\nuse Twig\\Node\\ModuleNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\NodeCaptureInterface;\nuse Twig\\Node\\NodeOutputInterface;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Node\\TextNode;\nuse Twig\\TokenParser\\TokenParserInterface;\nuse Twig\\Util\\ReflectionCallable;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass Parser\n{\n    private $stack = [];\n    private ?\\WeakMap $expressionRefs = null;\n    private $stream;\n    private $parent;\n    private $visitors;\n    private $expressionParser;\n    private $blocks;\n    private $blockStack;\n    private $macros;\n    private $importedSymbols;\n    private $traits;\n    private $embeddedTemplates = [];\n    private $varNameSalt = 0;\n    private $ignoreUnknownTwigCallables = false;\n    private ExpressionParsers $parsers;\n\n    public function __construct(\n        private Environment $env,\n    ) {\n        $this->parsers = $env->getExpressionParsers();\n    }\n\n    public function getEnvironment(): Environment\n    {\n        return $this->env;\n    }\n\n    public function getVarName(): string\n    {\n        trigger_deprecation('twig/twig', '3.15', 'The \"%s()\" method is deprecated.', __METHOD__);\n\n        return \\sprintf('__internal_parse_%d', $this->varNameSalt++);\n    }\n\n    /**\n     * @throws SyntaxError\n     */\n    public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode\n    {\n        $vars = get_object_vars($this);\n        unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']);\n        $this->stack[] = $vars;\n\n        // node visitors\n        if (null === $this->visitors) {\n            $this->visitors = $this->env->getNodeVisitors();\n        }\n\n        $this->stream = $stream;\n        $this->parent = null;\n        $this->blocks = [];\n        $this->macros = [];\n        $this->traits = [];\n        $this->blockStack = [];\n        $this->importedSymbols = [[]];\n        $this->embeddedTemplates = [];\n        $this->expressionRefs = new \\WeakMap();\n\n        try {\n            $body = $this->subparse($test, $dropNeedle);\n\n            if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) {\n                $body = new EmptyNode();\n            }\n        } catch (SyntaxError $e) {\n            if (!$e->getSourceContext()) {\n                $e->setSourceContext($this->stream->getSourceContext());\n            }\n\n            if (!$e->getTemplateLine()) {\n                $e->setTemplateLine($this->getCurrentToken()->getLine());\n            }\n\n            throw $e;\n        } finally {\n            $this->expressionRefs = null;\n        }\n\n        $node = new ModuleNode(\n            new BodyNode([$body]),\n            $this->parent,\n            $this->blocks ? new Nodes($this->blocks) : new EmptyNode(),\n            $this->macros ? new Nodes($this->macros) : new EmptyNode(),\n            $this->traits ? new Nodes($this->traits) : new EmptyNode(),\n            $this->embeddedTemplates ? new Nodes($this->embeddedTemplates) : new EmptyNode(),\n            $stream->getSourceContext(),\n        );\n\n        $traverser = new NodeTraverser($this->env, $this->visitors);\n\n        /**\n         * @var ModuleNode $node\n         */\n        $node = $traverser->traverse($node);\n\n        // restore previous stack so previous parse() call can resume working\n        foreach (array_pop($this->stack) as $key => $val) {\n            $this->$key = $val;\n        }\n\n        return $node;\n    }\n\n    public function shouldIgnoreUnknownTwigCallables(): bool\n    {\n        return $this->ignoreUnknownTwigCallables;\n    }\n\n    public function subparseIgnoreUnknownTwigCallables($test, bool $dropNeedle = false): void\n    {\n        $previous = $this->ignoreUnknownTwigCallables;\n        $this->ignoreUnknownTwigCallables = true;\n        try {\n            $this->subparse($test, $dropNeedle);\n        } finally {\n            $this->ignoreUnknownTwigCallables = $previous;\n        }\n    }\n\n    /**\n     * @throws SyntaxError\n     */\n    public function subparse($test, bool $dropNeedle = false): Node\n    {\n        $lineno = $this->getCurrentToken()->getLine();\n        $rv = [];\n        while (!$this->stream->isEOF()) {\n            switch (true) {\n                case $this->stream->getCurrent()->test(Token::TEXT_TYPE):\n                    $token = $this->stream->next();\n                    $rv[] = new TextNode($token->getValue(), $token->getLine());\n                    break;\n\n                case $this->stream->getCurrent()->test(Token::VAR_START_TYPE):\n                    $token = $this->stream->next();\n                    $expr = $this->parseExpression();\n                    $this->stream->expect(Token::VAR_END_TYPE);\n                    $rv[] = new PrintNode($expr, $token->getLine());\n                    break;\n\n                case $this->stream->getCurrent()->test(Token::BLOCK_START_TYPE):\n                    $this->stream->next();\n                    $token = $this->getCurrentToken();\n\n                    if (!$token->test(Token::NAME_TYPE)) {\n                        throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext());\n                    }\n\n                    if (null !== $test && $test($token)) {\n                        if ($dropNeedle) {\n                            $this->stream->next();\n                        }\n\n                        if (1 === \\count($rv)) {\n                            return $rv[0];\n                        }\n\n                        return new Nodes($rv, $lineno);\n                    }\n\n                    if (!$subparser = $this->env->getTokenParser($token->getValue())) {\n                        if (null !== $test) {\n                            $e = new SyntaxError(\\sprintf('Unexpected \"%s\" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());\n\n                            $callable = (new ReflectionCallable(new TwigTest('decision', $test)))->getCallable();\n                            if (\\is_array($callable) && $callable[0] instanceof TokenParserInterface) {\n                                $e->appendMessage(\\sprintf(' (expecting closing tag for the \"%s\" tag defined near line %s).', $callable[0]->getTag(), $lineno));\n                            }\n                        } else {\n                            $e = new SyntaxError(\\sprintf('Unknown \"%s\" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());\n                            $e->addSuggestions($token->getValue(), array_keys($this->env->getTokenParsers()));\n                        }\n\n                        throw $e;\n                    }\n\n                    $this->stream->next();\n\n                    $subparser->setParser($this);\n                    $node = $subparser->parse($token);\n                    if (!$node) {\n                        trigger_deprecation('twig/twig', '3.12', 'Returning \"null\" from \"%s\" is deprecated and forbidden by \"TokenParserInterface\".', $subparser::class);\n                    } else {\n                        $node->setNodeTag($subparser->getTag());\n                        $rv[] = $node;\n                    }\n                    break;\n\n                default:\n                    throw new SyntaxError('The lexer or the parser ended up in an unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext());\n            }\n        }\n\n        if (1 === \\count($rv)) {\n            return $rv[0];\n        }\n\n        return new Nodes($rv, $lineno);\n    }\n\n    public function getBlockStack(): array\n    {\n        trigger_deprecation('twig/twig', '3.12', 'Method \"%s()\" is deprecated.', __METHOD__);\n\n        return $this->blockStack;\n    }\n\n    /**\n     * @return string|null\n     */\n    public function peekBlockStack()\n    {\n        return $this->blockStack[\\count($this->blockStack) - 1] ?? null;\n    }\n\n    public function popBlockStack(): void\n    {\n        array_pop($this->blockStack);\n    }\n\n    public function pushBlockStack($name): void\n    {\n        $this->blockStack[] = $name;\n    }\n\n    public function hasBlock(string $name): bool\n    {\n        trigger_deprecation('twig/twig', '3.12', 'Method \"%s()\" is deprecated.', __METHOD__);\n\n        return isset($this->blocks[$name]);\n    }\n\n    public function getBlock(string $name): Node\n    {\n        trigger_deprecation('twig/twig', '3.12', 'Method \"%s()\" is deprecated.', __METHOD__);\n\n        return $this->blocks[$name];\n    }\n\n    public function setBlock(string $name, BlockNode $value): void\n    {\n        if (isset($this->blocks[$name])) {\n            throw new SyntaxError(\\sprintf(\"The block '%s' has already been defined line %d.\", $name, $this->blocks[$name]->getTemplateLine()), $this->getCurrentToken()->getLine(), $this->blocks[$name]->getSourceContext());\n        }\n\n        $this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine());\n    }\n\n    public function hasMacro(string $name): bool\n    {\n        trigger_deprecation('twig/twig', '3.12', 'Method \"%s()\" is deprecated.', __METHOD__);\n\n        return isset($this->macros[$name]);\n    }\n\n    public function setMacro(string $name, MacroNode $node): void\n    {\n        $this->macros[$name] = $node;\n    }\n\n    public function addTrait($trait): void\n    {\n        $this->traits[] = $trait;\n    }\n\n    public function hasTraits(): bool\n    {\n        trigger_deprecation('twig/twig', '3.12', 'Method \"%s()\" is deprecated.', __METHOD__);\n\n        return \\count($this->traits) > 0;\n    }\n\n    /**\n     * @return void\n     */\n    public function embedTemplate(ModuleNode $template)\n    {\n        $template->setIndex(mt_rand());\n\n        $this->embeddedTemplates[] = $template;\n    }\n\n    public function addImportedSymbol(string $type, string $alias, ?string $name = null, AbstractExpression|AssignTemplateVariable|null $internalRef = null): void\n    {\n        if ($internalRef && !$internalRef instanceof AssignTemplateVariable) {\n            trigger_deprecation('twig/twig', '3.15', 'Not passing a \"%s\" instance as an internal reference is deprecated (\"%s\" given).', __METHOD__, AssignTemplateVariable::class, $internalRef::class);\n\n            $internalRef = new AssignTemplateVariable(new TemplateVariable($internalRef->getAttribute('name'), $internalRef->getTemplateLine()), $internalRef->getAttribute('global'));\n        }\n\n        $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $internalRef];\n    }\n\n    /**\n     * @return array{name: string, node: AssignTemplateVariable|null}|null\n     */\n    public function getImportedSymbol(string $type, string $alias)\n    {\n        // if the symbol does not exist in the current scope (0), try in the main/global scope (last index)\n        return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\\count($this->importedSymbols) - 1][$type][$alias] ?? null);\n    }\n\n    public function isMainScope(): bool\n    {\n        return 1 === \\count($this->importedSymbols);\n    }\n\n    public function pushLocalScope(): void\n    {\n        array_unshift($this->importedSymbols, []);\n    }\n\n    public function popLocalScope(): void\n    {\n        array_shift($this->importedSymbols);\n    }\n\n    /**\n     * @deprecated since Twig 3.21\n     */\n    public function getExpressionParser(): ExpressionParser\n    {\n        trigger_deprecation('twig/twig', '3.21', 'Method \"%s()\" is deprecated, use \"parseExpression()\" instead.', __METHOD__);\n\n        if (null === $this->expressionParser) {\n            $this->expressionParser = new ExpressionParser($this, $this->env);\n        }\n\n        return $this->expressionParser;\n    }\n\n    public function parseExpression(int $precedence = 0): AbstractExpression\n    {\n        $token = $this->getCurrentToken();\n        if ($token->test(Token::OPERATOR_TYPE) && $ep = $this->parsers->getByName(PrefixExpressionParserInterface::class, $token->getValue())) {\n            $this->getStream()->next();\n            $expr = $ep->parse($this, $token);\n            $this->checkPrecedenceDeprecations($ep, $expr);\n        } else {\n            $expr = $this->parsers->getByClass(LiteralExpressionParser::class)->parse($this, $token);\n        }\n\n        $token = $this->getCurrentToken();\n        while ($token->test(Token::OPERATOR_TYPE) && ($ep = $this->parsers->getByName(InfixExpressionParserInterface::class, $token->getValue())) && $ep->getPrecedence() >= $precedence) {\n            $this->getStream()->next();\n            $expr = $ep->parse($this, $expr, $token);\n            $this->checkPrecedenceDeprecations($ep, $expr);\n            $token = $this->getCurrentToken();\n        }\n\n        return $expr;\n    }\n\n    public function getParent(): ?Node\n    {\n        trigger_deprecation('twig/twig', '3.12', 'Method \"%s()\" is deprecated.', __METHOD__);\n\n        return $this->parent;\n    }\n\n    /**\n     * @return bool\n     */\n    public function hasInheritance()\n    {\n        return $this->parent || 0 < \\count($this->traits);\n    }\n\n    public function setParent(?Node $parent): void\n    {\n        if (null === $parent) {\n            trigger_deprecation('twig/twig', '3.12', 'Passing \"null\" to \"%s()\" is deprecated.', __METHOD__);\n        }\n\n        if (null !== $parent && !$parent instanceof AbstractExpression) {\n            trigger_deprecation('twig/twig', '3.24', 'Passing a \"%s\" instance to \"%s()\" is deprecated, pass an \"AbstractExpression\" instance instead.', $parent::class, __METHOD__);\n        }\n\n        if (null !== $this->parent) {\n            throw new SyntaxError('Multiple extends tags are forbidden.', $parent->getTemplateLine(), $parent->getSourceContext());\n        }\n\n        $this->parent = $parent;\n    }\n\n    public function getStream(): TokenStream\n    {\n        return $this->stream;\n    }\n\n    public function getCurrentToken(): Token\n    {\n        return $this->stream->getCurrent();\n    }\n\n    public function getFunction(string $name, int $line): TwigFunction\n    {\n        try {\n            $function = $this->env->getFunction($name);\n        } catch (SyntaxError $e) {\n            if (!$this->shouldIgnoreUnknownTwigCallables()) {\n                throw $e;\n            }\n\n            $function = null;\n        }\n\n        if (!$function) {\n            if ($this->shouldIgnoreUnknownTwigCallables()) {\n                return new TwigFunction($name, static fn () => '');\n            }\n            $e = new SyntaxError(\\sprintf('Unknown \"%s\" function.', $name), $line, $this->stream->getSourceContext());\n            $e->addSuggestions($name, array_keys($this->env->getFunctions()));\n\n            throw $e;\n        }\n\n        if ($function->isDeprecated()) {\n            $src = $this->stream->getSourceContext();\n            $function->triggerDeprecation($src->getPath() ?: $src->getName(), $line);\n        }\n\n        return $function;\n    }\n\n    public function getFilter(string $name, int $line): TwigFilter\n    {\n        try {\n            $filter = $this->env->getFilter($name);\n        } catch (SyntaxError $e) {\n            if (!$this->shouldIgnoreUnknownTwigCallables()) {\n                throw $e;\n            }\n\n            $filter = null;\n        }\n        if (!$filter) {\n            if ($this->shouldIgnoreUnknownTwigCallables()) {\n                return new TwigFilter($name, static fn () => '');\n            }\n            $e = new SyntaxError(\\sprintf('Unknown \"%s\" filter.', $name), $line, $this->stream->getSourceContext());\n            $e->addSuggestions($name, array_keys($this->env->getFilters()));\n\n            throw $e;\n        }\n\n        if ($filter->isDeprecated()) {\n            $src = $this->stream->getSourceContext();\n            $filter->triggerDeprecation($src->getPath() ?: $src->getName(), $line);\n        }\n\n        return $filter;\n    }\n\n    public function getTest(int $line): TwigTest\n    {\n        $name = $this->stream->expect(Token::NAME_TYPE)->getValue();\n\n        if ($this->stream->test(Token::NAME_TYPE)) {\n            // try 2-words tests\n            $name = $name.' '.$this->getCurrentToken()->getValue();\n\n            try {\n                $test = $this->env->getTest($name);\n            } catch (SyntaxError $e) {\n                if (!$this->shouldIgnoreUnknownTwigCallables()) {\n                    throw $e;\n                }\n\n                $test = null;\n            }\n            $this->stream->next();\n        } else {\n            try {\n                $test = $this->env->getTest($name);\n            } catch (SyntaxError $e) {\n                if (!$this->shouldIgnoreUnknownTwigCallables()) {\n                    throw $e;\n                }\n\n                $test = null;\n            }\n        }\n\n        if (!$test) {\n            if ($this->shouldIgnoreUnknownTwigCallables()) {\n                return new TwigTest($name, static fn () => '');\n            }\n            $e = new SyntaxError(\\sprintf('Unknown \"%s\" test.', $name), $line, $this->stream->getSourceContext());\n            $e->addSuggestions($name, array_keys($this->env->getTests()));\n\n            throw $e;\n        }\n\n        if ($test->isDeprecated()) {\n            $src = $this->stream->getSourceContext();\n            $test->triggerDeprecation($src->getPath() ?: $src->getName(), $this->stream->getCurrent()->getLine());\n        }\n\n        return $test;\n    }\n\n    private function filterBodyNodes(Node $node, bool $nested = false): ?Node\n    {\n        // check that the body does not contain non-empty output nodes\n        if (\n            ($node instanceof TextNode && !ctype_space($node->getAttribute('data')))\n            || (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface)\n        ) {\n            if (str_contains((string) $node, \\chr(0xEF).\\chr(0xBB).\\chr(0xBF))) {\n                $t = substr($node->getAttribute('data'), 3);\n                if ('' === $t || ctype_space($t)) {\n                    // bypass empty nodes starting with a BOM\n                    return null;\n                }\n            }\n\n            throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext());\n        }\n\n        // bypass nodes that \"capture\" the output\n        if ($node instanceof NodeCaptureInterface) {\n            // a \"block\" tag in such a node will serve as a block definition AND be displayed in place as well\n            return $node;\n        }\n\n        // \"block\" tags that are not captured (see above) are only used for defining\n        // the content of the block. In such a case, nesting it does not work as\n        // expected as the definition is not part of the default template code flow.\n        if ($nested && $node instanceof BlockReferenceNode) {\n            throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext());\n        }\n\n        if ($node instanceof NodeOutputInterface) {\n            return null;\n        }\n\n        // here, $nested means \"being at the root level of a child template\"\n        // we need to discard the wrapping \"Node\" for the \"body\" node\n        // Node::class !== \\get_class($node) should be removed in Twig 4.0\n        $nested = $nested || (Node::class !== $node::class && !$node instanceof Nodes);\n        foreach ($node as $k => $n) {\n            if (null !== $n && null === $this->filterBodyNodes($n, $nested)) {\n                $node->removeNode($k);\n            }\n        }\n\n        return $node;\n    }\n\n    private function checkPrecedenceDeprecations(ExpressionParserInterface $expressionParser, AbstractExpression $expr)\n    {\n        $this->expressionRefs[$expr] = $expressionParser;\n        $precedenceChanges = $this->parsers->getPrecedenceChanges();\n\n        // Check that the all nodes that are between the 2 precedences have explicit parentheses\n        if (!isset($precedenceChanges[$expressionParser])) {\n            return;\n        }\n\n        if ($expr->hasExplicitParentheses()) {\n            return;\n        }\n\n        if ($expressionParser instanceof PrefixExpressionParserInterface) {\n            /** @var AbstractExpression $node */\n            $node = $expr->getNode('node');\n            foreach ($precedenceChanges as $ep => $changes) {\n                if (!\\in_array($expressionParser, $changes, true)) {\n                    continue;\n                }\n                if (isset($this->expressionRefs[$node]) && $ep === $this->expressionRefs[$node]) {\n                    $change = $expressionParser->getPrecedenceChange();\n                    trigger_deprecation($change->getPackage(), $change->getVersion(), \\sprintf('As the \"%s\" %s operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"%s\" at line %d.', $expressionParser->getName(), ExpressionParserType::getType($expressionParser)->value, $this->getStream()->getSourceContext()->getName(), $node->getTemplateLine()));\n                }\n            }\n        }\n\n        foreach ($precedenceChanges[$expressionParser] as $ep) {\n            foreach ($expr as $node) {\n                /** @var AbstractExpression $node */\n                if (isset($this->expressionRefs[$node]) && $ep === $this->expressionRefs[$node] && !$node->hasExplicitParentheses()) {\n                    $change = $ep->getPrecedenceChange();\n                    trigger_deprecation($change->getPackage(), $change->getVersion(), \\sprintf('As the \"%s\" %s operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"%s\" at line %d.', $ep->getName(), ExpressionParserType::getType($ep)->value, $this->getStream()->getSourceContext()->getName(), $node->getTemplateLine()));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Dumper/BaseDumper.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Profiler\\Dumper;\n\nuse Twig\\Profiler\\Profile;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nabstract class BaseDumper\n{\n    private $root;\n\n    public function dump(Profile $profile): string\n    {\n        return $this->dumpProfile($profile);\n    }\n\n    abstract protected function formatTemplate(Profile $profile, $prefix): string;\n\n    abstract protected function formatNonTemplate(Profile $profile, $prefix): string;\n\n    abstract protected function formatTime(Profile $profile, $percent): string;\n\n    private function dumpProfile(Profile $profile, $prefix = '', $sibling = false): string\n    {\n        if ($profile->isRoot()) {\n            $this->root = $profile->getDuration();\n            $start = $profile->getName();\n        } else {\n            if ($profile->isTemplate()) {\n                $start = $this->formatTemplate($profile, $prefix);\n            } else {\n                $start = $this->formatNonTemplate($profile, $prefix);\n            }\n            $prefix .= $sibling ? '│ ' : '  ';\n        }\n\n        $percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0;\n\n        if ($profile->getDuration() * 1000 < 1) {\n            $str = $start.\"\\n\";\n        } else {\n            $str = \\sprintf(\"%s %s\\n\", $start, $this->formatTime($profile, $percent));\n        }\n\n        $nCount = \\count($profile->getProfiles());\n        foreach ($profile as $i => $p) {\n            $str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount);\n        }\n\n        return $str;\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Dumper/BlackfireDumper.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Profiler\\Dumper;\n\nuse Twig\\Profiler\\Profile;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class BlackfireDumper\n{\n    public function dump(Profile $profile): string\n    {\n        $data = [];\n        $this->dumpProfile('main()', $profile, $data);\n        $this->dumpChildren('main()', $profile, $data);\n\n        $start = \\sprintf('%f', microtime(true));\n        $str = <<<EOF\nfile-format: BlackfireProbe\ncost-dimensions: wt mu pmu\nrequest-start: $start\n\n\nEOF;\n\n        foreach ($data as $name => $values) {\n            $str .= \"$name//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\\n\";\n        }\n\n        return $str;\n    }\n\n    private function dumpChildren(string $parent, Profile $profile, &$data): void\n    {\n        foreach ($profile as $p) {\n            if ($p->isTemplate()) {\n                $name = $p->getTemplate();\n            } else {\n                $name = \\sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName());\n            }\n            $this->dumpProfile(\\sprintf('%s==>%s', $parent, $name), $p, $data);\n            $this->dumpChildren($name, $p, $data);\n        }\n    }\n\n    private function dumpProfile(string $edge, Profile $profile, &$data): void\n    {\n        if (isset($data[$edge])) {\n            ++$data[$edge]['ct'];\n            $data[$edge]['wt'] += floor($profile->getDuration() * 1000000);\n            $data[$edge]['mu'] += $profile->getMemoryUsage();\n            $data[$edge]['pmu'] += $profile->getPeakMemoryUsage();\n        } else {\n            $data[$edge] = [\n                'ct' => 1,\n                'wt' => floor($profile->getDuration() * 1000000),\n                'mu' => $profile->getMemoryUsage(),\n                'pmu' => $profile->getPeakMemoryUsage(),\n            ];\n        }\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Dumper/HtmlDumper.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Profiler\\Dumper;\n\nuse Twig\\Profiler\\Profile;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class HtmlDumper extends BaseDumper\n{\n    private static $colors = [\n        'block' => '#dfd',\n        'macro' => '#ddf',\n        'template' => '#ffd',\n        'big' => '#d44',\n    ];\n\n    public function dump(Profile $profile): string\n    {\n        return '<pre>'.parent::dump($profile).'</pre>';\n    }\n\n    protected function formatTemplate(Profile $profile, $prefix): string\n    {\n        return \\sprintf('%s└ <span style=\"background-color: %s\">%s</span>', $prefix, self::$colors['template'], $profile->getTemplate());\n    }\n\n    protected function formatNonTemplate(Profile $profile, $prefix): string\n    {\n        return \\sprintf('%s└ %s::%s(<span style=\"background-color: %s\">%s</span>)', $prefix, $profile->getTemplate(), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', $profile->getName());\n    }\n\n    protected function formatTime(Profile $profile, $percent): string\n    {\n        return \\sprintf('<span style=\"color: %s\">%.2fms/%.0f%%</span>', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent);\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Dumper/TextDumper.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Profiler\\Dumper;\n\nuse Twig\\Profiler\\Profile;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class TextDumper extends BaseDumper\n{\n    protected function formatTemplate(Profile $profile, $prefix): string\n    {\n        return \\sprintf('%s└ %s', $prefix, $profile->getTemplate());\n    }\n\n    protected function formatNonTemplate(Profile $profile, $prefix): string\n    {\n        return \\sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName());\n    }\n\n    protected function formatTime(Profile $profile, $percent): string\n    {\n        return \\sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent);\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Node/EnterProfileNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Profiler\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Node;\n\n/**\n * Represents a profile enter node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass EnterProfileNode extends Node\n{\n    public function __construct(string $extensionName, string $type, string $name, string $varName)\n    {\n        parent::__construct([], ['extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName]);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->write(\\sprintf('$%s = $this->extensions[', $this->getAttribute('var_name')))\n            ->repr($this->getAttribute('extension_name'))\n            ->raw(\"];\\n\")\n            ->write(\\sprintf('$%s->enter($%s = new \\Twig\\Profiler\\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof'))\n            ->repr($this->getAttribute('type'))\n            ->raw(', ')\n            ->repr($this->getAttribute('name'))\n            ->raw(\"));\\n\\n\")\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Node/LeaveProfileNode.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Profiler\\Node;\n\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Node\\Node;\n\n/**\n * Represents a profile leave node.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\n#[YieldReady]\nclass LeaveProfileNode extends Node\n{\n    public function __construct(string $varName)\n    {\n        parent::__construct([], ['var_name' => $varName]);\n    }\n\n    public function compile(Compiler $compiler): void\n    {\n        $compiler\n            ->write(\"\\n\")\n            ->write(\\sprintf(\"\\$%s->leave(\\$%s);\\n\\n\", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof'))\n        ;\n    }\n}\n"
  },
  {
    "path": "src/Profiler/NodeVisitor/ProfilerNodeVisitor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Profiler\\NodeVisitor;\n\nuse Twig\\Environment;\nuse Twig\\Node\\BlockNode;\nuse Twig\\Node\\BodyNode;\nuse Twig\\Node\\MacroNode;\nuse Twig\\Node\\ModuleNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\NodeVisitor\\NodeVisitorInterface;\nuse Twig\\Profiler\\Node\\EnterProfileNode;\nuse Twig\\Profiler\\Node\\LeaveProfileNode;\nuse Twig\\Profiler\\Profile;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class ProfilerNodeVisitor implements NodeVisitorInterface\n{\n    private $varName;\n\n    public function __construct(\n        private string $extensionName,\n    ) {\n        $this->varName = \\sprintf('__internal_%s', hash(\\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $extensionName));\n    }\n\n    public function enterNode(Node $node, Environment $env): Node\n    {\n        return $node;\n    }\n\n    public function leaveNode(Node $node, Environment $env): ?Node\n    {\n        if ($node instanceof ModuleNode) {\n            $node->setNode('display_start', new Nodes([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $this->varName), $node->getNode('display_start')]));\n            $node->setNode('display_end', new Nodes([new LeaveProfileNode($this->varName), $node->getNode('display_end')]));\n        } elseif ($node instanceof BlockNode) {\n            $node->setNode('body', new BodyNode([\n                new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $this->varName),\n                $node->getNode('body'),\n                new LeaveProfileNode($this->varName),\n            ]));\n        } elseif ($node instanceof MacroNode) {\n            $node->setNode('body', new BodyNode([\n                new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $this->varName),\n                $node->getNode('body'),\n                new LeaveProfileNode($this->varName),\n            ]));\n        }\n\n        return $node;\n    }\n\n    public function getPriority(): int\n    {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Profile.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Profiler;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class Profile implements \\IteratorAggregate, \\Serializable\n{\n    public const ROOT = 'ROOT';\n    public const BLOCK = 'block';\n    public const TEMPLATE = 'template';\n    public const MACRO = 'macro';\n    private $starts = [];\n    private $ends = [];\n    private $profiles = [];\n\n    public function __construct(\n        private string $template = 'main',\n        private string $type = self::ROOT,\n        private string $name = 'main',\n    ) {\n        $this->name = str_starts_with($name, '__internal_') ? 'INTERNAL' : $name;\n        $this->enter();\n    }\n\n    public function getTemplate(): string\n    {\n        return $this->template;\n    }\n\n    public function getType(): string\n    {\n        return $this->type;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function isRoot(): bool\n    {\n        return self::ROOT === $this->type;\n    }\n\n    public function isTemplate(): bool\n    {\n        return self::TEMPLATE === $this->type;\n    }\n\n    public function isBlock(): bool\n    {\n        return self::BLOCK === $this->type;\n    }\n\n    public function isMacro(): bool\n    {\n        return self::MACRO === $this->type;\n    }\n\n    /**\n     * @return Profile[]\n     */\n    public function getProfiles(): array\n    {\n        return $this->profiles;\n    }\n\n    public function addProfile(self $profile): void\n    {\n        $this->profiles[] = $profile;\n    }\n\n    /**\n     * Returns the duration in microseconds.\n     */\n    public function getDuration(): float\n    {\n        if ($this->isRoot() && $this->profiles) {\n            // for the root node with children, duration is the sum of all child durations\n            $duration = 0;\n            foreach ($this->profiles as $profile) {\n                $duration += $profile->getDuration();\n            }\n\n            return $duration;\n        }\n\n        return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0;\n    }\n\n    /**\n     * Returns the start time in microseconds.\n     */\n    public function getStartTime(): float\n    {\n        return $this->starts['wt'] ?? 0.0;\n    }\n\n    /**\n     * Returns the end time in microseconds.\n     */\n    public function getEndTime(): float\n    {\n        return $this->ends['wt'] ?? 0.0;\n    }\n\n    /**\n     * Returns the memory usage in bytes.\n     */\n    public function getMemoryUsage(): int\n    {\n        return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0;\n    }\n\n    /**\n     * Returns the peak memory usage in bytes.\n     */\n    public function getPeakMemoryUsage(): int\n    {\n        return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0;\n    }\n\n    /**\n     * Starts the profiling.\n     */\n    public function enter(): void\n    {\n        $this->starts = [\n            'wt' => microtime(true),\n            'mu' => memory_get_usage(),\n            'pmu' => memory_get_peak_usage(),\n        ];\n    }\n\n    /**\n     * Stops the profiling.\n     */\n    public function leave(): void\n    {\n        $this->ends = [\n            'wt' => microtime(true),\n            'mu' => memory_get_usage(),\n            'pmu' => memory_get_peak_usage(),\n        ];\n    }\n\n    public function reset(): void\n    {\n        $this->starts = $this->ends = $this->profiles = [];\n        $this->enter();\n    }\n\n    public function getIterator(): \\Traversable\n    {\n        return new \\ArrayIterator($this->profiles);\n    }\n\n    public function serialize(): string\n    {\n        return serialize($this->__serialize());\n    }\n\n    public function unserialize($data): void\n    {\n        $this->__unserialize(unserialize($data));\n    }\n\n    /**\n     * @internal\n     */\n    public function __serialize(): array\n    {\n        return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles];\n    }\n\n    /**\n     * @internal\n     */\n    public function __unserialize(array $data): void\n    {\n        [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles] = $data;\n    }\n}\n"
  },
  {
    "path": "src/Resources/core.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Extension\\CoreExtension;\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_cycle($values, $position)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::cycle($values, $position);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_random(Environment $env, $values = null, $max = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::random($env->getCharset(), $values, $max);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return $env->getExtension(CoreExtension::class)->formatDate($date, $format, $timezone);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_date_modify_filter(Environment $env, $date, $modifier)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return $env->getExtension(CoreExtension::class)->modifyDate($date, $modifier);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_sprintf($format, ...$values)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::sprintf($format, ...$values);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_date_converter(Environment $env, $date = null, $timezone = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return $env->getExtension(CoreExtension::class)->convertDate($date, $timezone);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_replace_filter($str, $from)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::replace($str, $from);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_round($value, $precision = 0, $method = 'common')\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::round($value, $precision, $method);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return $env->getExtension(CoreExtension::class)->formatNumber($number, $decimal, $decimalPoint, $thousandSep);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_urlencode_filter($url)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::urlencode($url);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_array_merge(...$arrays)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::merge(...$arrays);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::slice($env->getCharset(), $item, $start, $length, $preserveKeys);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_first(Environment $env, $item)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::first($env->getCharset(), $item);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_last(Environment $env, $item)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::last($env->getCharset(), $item);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_join_filter($value, $glue = '', $and = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::join($value, $glue, $and);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_split_filter(Environment $env, $value, $delimiter, $limit = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::split($env->getCharset(), $value, $delimiter, $limit);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_get_array_keys_filter($array)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::keys($array);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_reverse_filter(Environment $env, $item, $preserveKeys = false)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::reverse($env->getCharset(), $item, $preserveKeys);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_sort_filter(Environment $env, $array, $arrow = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::sort($env, $array, $arrow);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_matches(string $regexp, ?string $str)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::matches($regexp, $str);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_trim_filter($string, $characterMask = null, $side = 'both')\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::trim($string, $characterMask, $side);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_nl2br($string)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::nl2br($string);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_spaceless($content)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::spaceless($content);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_convert_encoding($string, $to, $from)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::convertEncoding($string, $to, $from);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_length_filter(Environment $env, $thing)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::length($env->getCharset(), $thing);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_upper_filter(Environment $env, $string)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::upper($env->getCharset(), $string);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_lower_filter(Environment $env, $string)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::lower($env->getCharset(), $string);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_striptags($string, $allowable_tags = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::striptags($string, $allowable_tags);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_title_string_filter(Environment $env, $string)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::titleCase($env->getCharset(), $string);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_capitalize_string_filter(Environment $env, $string)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::capitalize($env->getCharset(), $string);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_test_empty($value)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::testEmpty($value);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_test_iterable($value)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return is_iterable($value);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::include($env, $context, $template, $variables, $withContext, $ignoreMissing, $sandboxed);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_source(Environment $env, $name, $ignoreMissing = false)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::source($env, $name, $ignoreMissing);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_constant($constant, $object = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::constant($constant, $object);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_constant_is_defined($constant, $object = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::constant($constant, $object, true);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_array_batch($items, $size, $fill = null, $preserveKeys = true)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::batch($items, $size, $fill, $preserveKeys);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_array_column($array, $name, $index = null): array\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::column($array, $name, $index);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_array_filter(Environment $env, $array, $arrow)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::filter($env, $array, $arrow);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_array_map(Environment $env, $array, $arrow)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::map($env, $array, $arrow);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_array_reduce(Environment $env, $array, $arrow, $initial = null)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::reduce($env, $array, $arrow, $initial);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_array_some(Environment $env, $array, $arrow)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::arraySome($env, $array, $arrow);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_array_every(Environment $env, $array, $arrow)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return CoreExtension::arrayEvery($env, $array, $arrow);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    CoreExtension::checkArrow($env, $arrow, $thing, $type);\n}\n"
  },
  {
    "path": "src/Resources/debug.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Extension\\DebugExtension;\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_var_dump(Environment $env, $context, ...$vars)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    DebugExtension::dump($env, $context, ...$vars);\n}\n"
  },
  {
    "path": "src/Resources/escaper.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Extension\\EscaperExtension;\nuse Twig\\Node\\Node;\nuse Twig\\Runtime\\EscaperRuntime;\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_raw_filter($string)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return $string;\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return $env->getRuntime(EscaperRuntime::class)->escape($string, $strategy, $charset, $autoescape);\n}\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_escape_filter_is_safe(Node $filterArgs)\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return EscaperExtension::escapeFilterIsSafe($filterArgs);\n}\n"
  },
  {
    "path": "src/Resources/string_loader.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Extension\\StringLoaderExtension;\nuse Twig\\TemplateWrapper;\n\n/**\n * @internal\n *\n * @deprecated since Twig 3.9\n */\nfunction twig_template_from_string(Environment $env, $template, ?string $name = null): TemplateWrapper\n{\n    trigger_deprecation('twig/twig', '3.9', 'Using the internal \"%s\" function is deprecated.', __FUNCTION__);\n\n    return StringLoaderExtension::templateFromString($env, $template, $name);\n}\n"
  },
  {
    "path": "src/Runtime/EscaperRuntime.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Runtime;\n\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\RuntimeExtensionInterface;\nuse Twig\\Markup;\n\nfinal class EscaperRuntime implements RuntimeExtensionInterface\n{\n    /** @var array<string, callable(string, string): string> */\n    private $escapers = [];\n\n    /** @internal */\n    public $safeClasses = [];\n\n    /** @internal */\n    public $safeLookup = [];\n\n    public function __construct(\n        private $charset = 'UTF-8',\n    ) {\n    }\n\n    /**\n     * Defines a new escaper to be used via the escape filter.\n     *\n     * @param string                                            $strategy The strategy name that should be used as a strategy in the escape call\n     * @param callable(string $string, string $charset): string $callable A valid PHP callable\n     *\n     * @return void\n     */\n    public function setEscaper($strategy, callable $callable)\n    {\n        $this->escapers[$strategy] = $callable;\n    }\n\n    /**\n     * Gets all defined escapers.\n     *\n     * @return array<string, callable(string $string, string $charset): string> An array of escapers\n     */\n    public function getEscapers()\n    {\n        return $this->escapers;\n    }\n\n    /**\n     * @param array<class-string<\\Stringable>, string[]> $safeClasses\n     *\n     * @return void\n     */\n    public function setSafeClasses(array $safeClasses = [])\n    {\n        $this->safeClasses = [];\n        $this->safeLookup = [];\n        foreach ($safeClasses as $class => $strategies) {\n            $this->addSafeClass($class, $strategies);\n        }\n    }\n\n    /**\n     * @param class-string<\\Stringable> $class\n     * @param string[]                  $strategies\n     *\n     * @return void\n     */\n    public function addSafeClass(string $class, array $strategies)\n    {\n        $class = ltrim($class, '\\\\');\n        if (!isset($this->safeClasses[$class])) {\n            $this->safeClasses[$class] = [];\n        }\n        $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);\n\n        foreach ($strategies as $strategy) {\n            $this->safeLookup[$strategy][$class] = true;\n        }\n    }\n\n    /**\n     * Escapes a string.\n     *\n     * @param mixed       $string     The value to be escaped\n     * @param string      $strategy   The escaping strategy\n     * @param string|null $charset    The charset\n     * @param bool        $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)\n     *\n     * @throws RuntimeError\n     */\n    public function escape($string, string $strategy = 'html', ?string $charset = null, bool $autoescape = false)\n    {\n        if ($autoescape && $string instanceof Markup) {\n            return $string;\n        }\n\n        if (!\\is_string($string)) {\n            if ($string instanceof \\Stringable) {\n                if ($autoescape) {\n                    $c = $string::class;\n                    if (!isset($this->safeClasses[$c])) {\n                        $this->safeClasses[$c] = [];\n                        foreach (class_parents($string) + class_implements($string) as $class) {\n                            if (isset($this->safeClasses[$class])) {\n                                $this->safeClasses[$c] = array_unique(array_merge($this->safeClasses[$c], $this->safeClasses[$class]));\n                                foreach ($this->safeClasses[$class] as $s) {\n                                    $this->safeLookup[$s][$c] = true;\n                                }\n                            }\n                        }\n                    }\n                    if (isset($this->safeLookup[$strategy][$c]) || isset($this->safeLookup['all'][$c])) {\n                        return (string) $string;\n                    }\n                }\n\n                $string = (string) $string;\n            } elseif (\\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'html_attr_relaxed', 'url'], true)) {\n                // we return the input as is (which can be of any type)\n                return $string;\n            }\n        }\n\n        if ('' === $string) {\n            return '';\n        }\n\n        $charset = $charset ?: $this->charset;\n\n        switch ($strategy) {\n            case 'html':\n                // see https://www.php.net/htmlspecialchars\n\n                if ('UTF-8' === $charset) {\n                    return htmlspecialchars($string, \\ENT_QUOTES | \\ENT_SUBSTITUTE, 'UTF-8');\n                }\n\n                // Using a static variable to avoid initializing the array\n                // each time the function is called. Moving the declaration on the\n                // top of the function slow downs other escaping strategies.\n                static $htmlspecialcharsCharsets = [\n                    'ISO-8859-1' => true, 'ISO8859-1' => true,\n                    'ISO-8859-15' => true, 'ISO8859-15' => true,\n                    'utf-8' => true, 'UTF-8' => true,\n                    'CP866' => true, 'IBM866' => true, '866' => true,\n                    'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,\n                    '1251' => true,\n                    'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,\n                    'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,\n                    'BIG5' => true, '950' => true,\n                    'GB2312' => true, '936' => true,\n                    'BIG5-HKSCS' => true,\n                    'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,\n                    'EUC-JP' => true, 'EUCJP' => true,\n                    'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,\n                ];\n\n                if (isset($htmlspecialcharsCharsets[$charset])) {\n                    return htmlspecialchars($string, \\ENT_QUOTES | \\ENT_SUBSTITUTE, $charset);\n                }\n\n                if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {\n                    // cache the lowercase variant for future iterations\n                    $htmlspecialcharsCharsets[$charset] = true;\n\n                    return htmlspecialchars($string, \\ENT_QUOTES | \\ENT_SUBSTITUTE, $charset);\n                }\n\n                $string = $this->convertEncoding($string, 'UTF-8', $charset);\n                $string = htmlspecialchars($string, \\ENT_QUOTES | \\ENT_SUBSTITUTE, 'UTF-8');\n\n                return iconv('UTF-8', $charset, $string);\n\n            case 'js':\n                // escape all non-alphanumeric characters\n                // into their \\x or \\uHHHH representations\n                if ('UTF-8' !== $charset) {\n                    $string = $this->convertEncoding($string, 'UTF-8', $charset);\n                }\n\n                if (!preg_match('//u', $string)) {\n                    throw new RuntimeError('The string to escape is not a valid UTF-8 string.');\n                }\n\n                $string = preg_replace_callback('#[^a-zA-Z0-9,\\._]#Su', static function ($matches) {\n                    $char = $matches[0];\n\n                    /*\n                    * A few characters have short escape sequences in JSON and JavaScript.\n                    * Escape sequences supported only by JavaScript, not JSON, are omitted.\n                    * \\\" is also supported but omitted, because the resulting string is not HTML safe.\n                    */\n                    $short = match ($char) {\n                        '\\\\' => '\\\\\\\\',\n                        '/' => '\\\\/',\n                        \"\\x08\" => '\\b',\n                        \"\\x0C\" => '\\f',\n                        \"\\x0A\" => '\\n',\n                        \"\\x0D\" => '\\r',\n                        \"\\x09\" => '\\t',\n                        default => false,\n                    };\n\n                    if ($short) {\n                        return $short;\n                    }\n\n                    $codepoint = mb_ord($char, 'UTF-8');\n                    if (0x10000 > $codepoint) {\n                        return \\sprintf('\\u%04X', $codepoint);\n                    }\n\n                    // Split characters outside the BMP into surrogate pairs\n                    // https://tools.ietf.org/html/rfc2781.html#section-2.1\n                    $u = $codepoint - 0x10000;\n                    $high = 0xD800 | ($u >> 10);\n                    $low = 0xDC00 | ($u & 0x3FF);\n\n                    return \\sprintf('\\u%04X\\u%04X', $high, $low);\n                }, $string);\n\n                if ('UTF-8' !== $charset) {\n                    $string = iconv('UTF-8', $charset, $string);\n                }\n\n                return $string;\n\n            case 'css':\n                if ('UTF-8' !== $charset) {\n                    $string = $this->convertEncoding($string, 'UTF-8', $charset);\n                }\n\n                if (!preg_match('//u', $string)) {\n                    throw new RuntimeError('The string to escape is not a valid UTF-8 string.');\n                }\n\n                $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', static function ($matches) {\n                    $char = $matches[0];\n\n                    return \\sprintf('\\\\%X ', 1 === \\strlen($char) ? \\ord($char) : mb_ord($char, 'UTF-8'));\n                }, $string);\n\n                if ('UTF-8' !== $charset) {\n                    $string = iconv('UTF-8', $charset, $string);\n                }\n\n                return $string;\n\n            case 'html_attr':\n            case 'html_attr_relaxed':\n                if ('UTF-8' !== $charset) {\n                    $string = $this->convertEncoding($string, 'UTF-8', $charset);\n                }\n\n                if (!preg_match('//u', $string)) {\n                    throw new RuntimeError('The string to escape is not a valid UTF-8 string.');\n                }\n\n                $regex = match ($strategy) {\n                    'html_attr' => '#[^a-zA-Z0-9,\\.\\-_]#Su',\n                    'html_attr_relaxed' => '#[^a-zA-Z0-9,\\.\\-_:@\\[\\]]#Su',\n                };\n\n                $string = preg_replace_callback($regex, static function ($matches) {\n                    /**\n                     * This function is adapted from code coming from Zend Framework.\n                     *\n                     * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)\n                     * @license   https://framework.zend.com/license/new-bsd New BSD License\n                     */\n                    $chr = $matches[0];\n                    $ord = \\ord($chr[0]);\n\n                    /*\n                    * The following replaces characters undefined in HTML with the\n                    * hex entity for the Unicode replacement character.\n                    */\n                    if (($ord <= 0x1F && \"\\t\" != $chr && \"\\n\" != $chr && \"\\r\" != $chr) || ($ord >= 0x7F && $ord <= 0x9F)) {\n                        return '&#xFFFD;';\n                    }\n\n                    /*\n                    * Check if the current character to escape has a name entity we should\n                    * replace it with while grabbing the hex value of the character.\n                    */\n                    if (1 === \\strlen($chr)) {\n                        /*\n                        * While HTML supports far more named entities, the lowest common denominator\n                        * has become HTML5's XML Serialisation which is restricted to the those named\n                        * entities that XML supports. Using HTML entities would result in this error:\n                        *     XML Parsing Error: undefined entity\n                        */\n                        return match ($ord) {\n                            34 => '&quot;', /* quotation mark */\n                            38 => '&amp;',  /* ampersand */\n                            60 => '&lt;',   /* less-than sign */\n                            62 => '&gt;',   /* greater-than sign */\n                            default => \\sprintf('&#x%02X;', $ord),\n                        };\n                    }\n\n                    /*\n                    * Per OWASP recommendations, we'll use hex entities for any other\n                    * characters where a named entity does not exist.\n                    */\n                    return \\sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));\n                }, $string);\n\n                if ('UTF-8' !== $charset) {\n                    $string = iconv('UTF-8', $charset, $string);\n                }\n\n                return $string;\n\n            case 'url':\n                return rawurlencode($string);\n\n            default:\n                if (\\array_key_exists($strategy, $this->escapers)) {\n                    return $this->escapers[$strategy]($string, $charset);\n                }\n\n                $validStrategies = implode('\", \"', array_merge(['html', 'js', 'url', 'css', 'html_attr', 'html_attr_relaxed'], array_keys($this->escapers)));\n\n                throw new RuntimeError(\\sprintf('Invalid escaping strategy \"%s\" (valid ones: \"%s\").', $strategy, $validStrategies));\n        }\n    }\n\n    private function convertEncoding(string $string, string $to, string $from)\n    {\n        if (!\\function_exists('iconv')) {\n            throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');\n        }\n\n        return iconv($from, $to, $string);\n    }\n}\n"
  },
  {
    "path": "src/RuntimeLoader/ContainerRuntimeLoader.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\RuntimeLoader;\n\nuse Psr\\Container\\ContainerInterface;\n\n/**\n * Lazily loads Twig runtime implementations from a PSR-11 container.\n *\n * Note that the runtime services MUST use their class names as identifiers.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n * @author Robin Chalas <robin.chalas@gmail.com>\n */\nclass ContainerRuntimeLoader implements RuntimeLoaderInterface\n{\n    public function __construct(\n        private ContainerInterface $container,\n    ) {\n    }\n\n    public function load(string $class)\n    {\n        return $this->container->has($class) ? $this->container->get($class) : null;\n    }\n}\n"
  },
  {
    "path": "src/RuntimeLoader/FactoryRuntimeLoader.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\RuntimeLoader;\n\n/**\n * Lazy loads the runtime implementations for a Twig element.\n *\n * @author Robin Chalas <robin.chalas@gmail.com>\n */\nclass FactoryRuntimeLoader implements RuntimeLoaderInterface\n{\n    /**\n     * @param array $map An array where keys are class names and values factory callables\n     */\n    public function __construct(\n        private array $map = [],\n    ) {\n    }\n\n    public function load(string $class)\n    {\n        if (!isset($this->map[$class])) {\n            return null;\n        }\n\n        $runtimeFactory = $this->map[$class];\n\n        return $runtimeFactory();\n    }\n}\n"
  },
  {
    "path": "src/RuntimeLoader/RuntimeLoaderInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\RuntimeLoader;\n\n/**\n * Creates runtime implementations for Twig elements (filters/functions/tests).\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface RuntimeLoaderInterface\n{\n    /**\n     * Creates the runtime implementation of a Twig element (filter/function/test).\n     *\n     * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class\n     */\n    public function load(string $class);\n}\n"
  },
  {
    "path": "src/Sandbox/SecurityError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\nuse Twig\\Error\\Error;\n\n/**\n * Exception thrown when a security error occurs at runtime.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass SecurityError extends Error\n{\n}\n"
  },
  {
    "path": "src/Sandbox/SecurityNotAllowedFilterError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\n/**\n * Exception thrown when a not allowed filter is used in a template.\n *\n * @author Martin Hasoň <martin.hason@gmail.com>\n */\nfinal class SecurityNotAllowedFilterError extends SecurityError\n{\n    private string $filterName;\n\n    public function __construct(string $message, string $functionName)\n    {\n        parent::__construct($message);\n        $this->filterName = $functionName;\n    }\n\n    public function getFilterName(): string\n    {\n        return $this->filterName;\n    }\n}\n"
  },
  {
    "path": "src/Sandbox/SecurityNotAllowedFunctionError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\n/**\n * Exception thrown when a not allowed function is used in a template.\n *\n * @author Martin Hasoň <martin.hason@gmail.com>\n */\nfinal class SecurityNotAllowedFunctionError extends SecurityError\n{\n    private string $functionName;\n\n    public function __construct(string $message, string $functionName)\n    {\n        parent::__construct($message);\n        $this->functionName = $functionName;\n    }\n\n    public function getFunctionName(): string\n    {\n        return $this->functionName;\n    }\n}\n"
  },
  {
    "path": "src/Sandbox/SecurityNotAllowedMethodError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\n/**\n * Exception thrown when a not allowed class method is used in a template.\n *\n * @author Kit Burton-Senior <mail@kitbs.com>\n */\nfinal class SecurityNotAllowedMethodError extends SecurityError\n{\n    private string $className;\n    private string $methodName;\n\n    public function __construct(string $message, string $className, string $methodName)\n    {\n        parent::__construct($message);\n        $this->className = $className;\n        $this->methodName = $methodName;\n    }\n\n    public function getClassName(): string\n    {\n        return $this->className;\n    }\n\n    public function getMethodName(): string\n    {\n        return $this->methodName;\n    }\n}\n"
  },
  {
    "path": "src/Sandbox/SecurityNotAllowedPropertyError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\n/**\n * Exception thrown when a not allowed class property is used in a template.\n *\n * @author Kit Burton-Senior <mail@kitbs.com>\n */\nfinal class SecurityNotAllowedPropertyError extends SecurityError\n{\n    private string $className;\n    private string $propertyName;\n\n    public function __construct(string $message, string $className, string $propertyName)\n    {\n        parent::__construct($message);\n        $this->className = $className;\n        $this->propertyName = $propertyName;\n    }\n\n    public function getClassName(): string\n    {\n        return $this->className;\n    }\n\n    public function getPropertyName(): string\n    {\n        return $this->propertyName;\n    }\n}\n"
  },
  {
    "path": "src/Sandbox/SecurityNotAllowedTagError.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\n/**\n * Exception thrown when a not allowed tag is used in a template.\n *\n * @author Martin Hasoň <martin.hason@gmail.com>\n */\nfinal class SecurityNotAllowedTagError extends SecurityError\n{\n    private string $tagName;\n\n    public function __construct(string $message, string $tagName)\n    {\n        parent::__construct($message);\n        $this->tagName = $tagName;\n    }\n\n    public function getTagName(): string\n    {\n        return $this->tagName;\n    }\n}\n"
  },
  {
    "path": "src/Sandbox/SecurityPolicy.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\nuse Twig\\Markup;\nuse Twig\\Template;\n\n/**\n * Represents a security policy which need to be enforced when sandbox mode is enabled.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class SecurityPolicy implements SecurityPolicyInterface\n{\n    private $allowedTags;\n    private $allowedFilters;\n    private $allowedMethods;\n    private $allowedProperties;\n    private $allowedFunctions;\n\n    public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = [])\n    {\n        $this->allowedTags = $allowedTags;\n        $this->allowedFilters = $allowedFilters;\n        $this->setAllowedMethods($allowedMethods);\n        $this->allowedProperties = $allowedProperties;\n        $this->allowedFunctions = $allowedFunctions;\n    }\n\n    public function setAllowedTags(array $tags): void\n    {\n        $this->allowedTags = $tags;\n    }\n\n    public function setAllowedFilters(array $filters): void\n    {\n        $this->allowedFilters = $filters;\n    }\n\n    public function setAllowedMethods(array $methods): void\n    {\n        $this->allowedMethods = [];\n        foreach ($methods as $class => $m) {\n            $this->allowedMethods[$class] = array_map('strtolower', \\is_array($m) ? $m : [$m]);\n        }\n    }\n\n    public function setAllowedProperties(array $properties): void\n    {\n        $this->allowedProperties = $properties;\n    }\n\n    public function setAllowedFunctions(array $functions): void\n    {\n        $this->allowedFunctions = $functions;\n    }\n\n    public function checkSecurity($tags, $filters, $functions): void\n    {\n        foreach ($tags as $tag) {\n            if (!\\in_array($tag, $this->allowedTags, true)) {\n                if ('extends' === $tag) {\n                    trigger_deprecation('twig/twig', '3.12', 'The \"extends\" tag is always allowed in sandboxes, but won\\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.');\n                } elseif ('use' === $tag) {\n                    trigger_deprecation('twig/twig', '3.12', 'The \"use\" tag is always allowed in sandboxes, but won\\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.');\n                } else {\n                    throw new SecurityNotAllowedTagError(\\sprintf('Tag \"%s\" is not allowed.', $tag), $tag);\n                }\n            }\n        }\n\n        foreach ($filters as $filter) {\n            if (!\\in_array($filter, $this->allowedFilters, true)) {\n                throw new SecurityNotAllowedFilterError(\\sprintf('Filter \"%s\" is not allowed.', $filter), $filter);\n            }\n        }\n\n        foreach ($functions as $function) {\n            if (!\\in_array($function, $this->allowedFunctions, true)) {\n                throw new SecurityNotAllowedFunctionError(\\sprintf('Function \"%s\" is not allowed.', $function), $function);\n            }\n        }\n    }\n\n    public function checkMethodAllowed($obj, $method): void\n    {\n        if ($obj instanceof Template || $obj instanceof Markup) {\n            return;\n        }\n\n        $allowed = false;\n        $method = strtolower($method);\n        foreach ($this->allowedMethods as $class => $methods) {\n            if ($obj instanceof $class && \\in_array($method, $methods, true)) {\n                $allowed = true;\n                break;\n            }\n        }\n\n        if (!$allowed) {\n            $class = $obj::class;\n            throw new SecurityNotAllowedMethodError(\\sprintf('Calling \"%s\" method on a \"%s\" object is not allowed.', $method, $class), $class, $method);\n        }\n    }\n\n    public function checkPropertyAllowed($obj, $property): void\n    {\n        $allowed = false;\n        foreach ($this->allowedProperties as $class => $properties) {\n            if ($obj instanceof $class && \\in_array($property, \\is_array($properties) ? $properties : [$properties], true)) {\n                $allowed = true;\n                break;\n            }\n        }\n\n        if (!$allowed) {\n            $class = $obj::class;\n            throw new SecurityNotAllowedPropertyError(\\sprintf('Calling \"%s\" property on a \"%s\" object is not allowed.', $property, $class), $class, $property);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Sandbox/SecurityPolicyInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\n/**\n * Interface that all security policy classes must implements.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface SecurityPolicyInterface\n{\n    /**\n     * @param string[] $tags\n     * @param string[] $filters\n     * @param string[] $functions\n     *\n     * @throws SecurityError\n     */\n    public function checkSecurity($tags, $filters, $functions): void;\n\n    /**\n     * @param object $obj\n     * @param string $method\n     *\n     * @throws SecurityNotAllowedMethodError\n     */\n    public function checkMethodAllowed($obj, $method): void;\n\n    /**\n     * @param object $obj\n     * @param string $property\n     *\n     * @throws SecurityNotAllowedPropertyError\n     */\n    public function checkPropertyAllowed($obj, $property): void;\n}\n"
  },
  {
    "path": "src/Sandbox/SourcePolicyInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Sandbox;\n\nuse Twig\\Source;\n\n/**\n * Interface for a class that can optionally enable the sandbox mode based on a template's Twig\\Source.\n *\n * @author Yaakov Saxon\n */\ninterface SourcePolicyInterface\n{\n    public function enableSandbox(Source $source): bool;\n}\n"
  },
  {
    "path": "src/Source.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\n/**\n * Holds information about a non-compiled Twig template.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class Source\n{\n    /**\n     * @param string $code The template source code\n     * @param string $name The template logical name\n     * @param string $path The filesystem path of the template if any\n     */\n    public function __construct(\n        private string $code,\n        private string $name,\n        private string $path = '',\n    ) {\n    }\n\n    public function getCode(): string\n    {\n        return $this->code;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function getPath(): string\n    {\n        return $this->path;\n    }\n}\n"
  },
  {
    "path": "src/Template.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Error\\Error;\nuse Twig\\Error\\RuntimeError;\n\n/**\n * Default base class for compiled templates.\n *\n * This class is an implementation detail of how template compilation currently\n * works, which might change. It should never be used directly. Use $twig->load()\n * instead, which returns an instance of \\Twig\\TemplateWrapper.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nabstract class Template\n{\n    public const ANY_CALL = 'any';\n    public const ARRAY_CALL = 'array';\n    public const METHOD_CALL = 'method';\n\n    protected $parent;\n    protected $parents = [];\n    protected $blocks = [];\n    protected $traits = [];\n    protected $traitAliases = [];\n    protected $extensions = [];\n    protected $sandbox;\n\n    private $useYield;\n\n    public function __construct(\n        protected Environment $env,\n    ) {\n        $this->useYield = $env->useYield();\n        $this->extensions = $env->getExtensions();\n    }\n\n    /**\n     * Returns the template name.\n     */\n    abstract public function getTemplateName(): string;\n\n    /**\n     * Returns debug information about the template.\n     *\n     * @return array<int, int> Debug information\n     */\n    abstract public function getDebugInfo(): array;\n\n    /**\n     * Returns information about the original template source code.\n     */\n    abstract public function getSourceContext(): Source;\n\n    /**\n     * Returns the parent template.\n     *\n     * This method is for internal use only and should never be called\n     * directly.\n     *\n     * @return self|TemplateWrapper|false The parent template or false if there is no parent\n     */\n    public function getParent(array $context): self|TemplateWrapper|false\n    {\n        if (null !== $this->parent) {\n            return $this->parent;\n        }\n\n        if (!$parent = $this->doGetParent($context)) {\n            return false;\n        }\n\n        if ($parent instanceof self || $parent instanceof TemplateWrapper) {\n            return $this->parents[$parent->getSourceContext()->getName()] = $parent;\n        }\n\n        if (!isset($this->parents[$parent])) {\n            $this->parents[$parent] = $this->load($parent, -1);\n        }\n\n        return $this->parents[$parent];\n    }\n\n    protected function doGetParent(array $context): bool|string|self|TemplateWrapper\n    {\n        return false;\n    }\n\n    public function isTraitable(): bool\n    {\n        return true;\n    }\n\n    /**\n     * Displays a parent block.\n     *\n     * This method is for internal use only and should never be called\n     * directly.\n     *\n     * @param string $name    The block name to display from the parent\n     * @param array  $context The context\n     * @param array  $blocks  The current set of blocks\n     */\n    public function displayParentBlock($name, array $context, array $blocks = []): void\n    {\n        foreach ($this->yieldParentBlock($name, $context, $blocks) as $data) {\n            echo $data;\n        }\n    }\n\n    /**\n     * Displays a block.\n     *\n     * This method is for internal use only and should never be called\n     * directly.\n     *\n     * @param string $name      The block name to display\n     * @param array  $context   The context\n     * @param array  $blocks    The current set of blocks\n     * @param bool   $useBlocks Whether to use the current set of blocks\n     */\n    public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): void\n    {\n        foreach ($this->yieldBlock($name, $context, $blocks, $useBlocks, $templateContext) as $data) {\n            echo $data;\n        }\n    }\n\n    /**\n     * Renders a parent block.\n     *\n     * This method is for internal use only and should never be called\n     * directly.\n     *\n     * @param string $name    The block name to render from the parent\n     * @param array  $context The context\n     * @param array  $blocks  The current set of blocks\n     *\n     * @return string The rendered block\n     */\n    public function renderParentBlock($name, array $context, array $blocks = []): string\n    {\n        if (!$this->useYield) {\n            if ($this->env->isDebug()) {\n                ob_start();\n            } else {\n                ob_start(static function () { return ''; });\n            }\n            $this->displayParentBlock($name, $context, $blocks);\n\n            return ob_get_clean();\n        }\n\n        $content = '';\n        foreach ($this->yieldParentBlock($name, $context, $blocks) as $data) {\n            $content .= $data;\n        }\n\n        return $content;\n    }\n\n    /**\n     * Renders a block.\n     *\n     * This method is for internal use only and should never be called\n     * directly.\n     *\n     * @param string $name      The block name to render\n     * @param array  $context   The context\n     * @param array  $blocks    The current set of blocks\n     * @param bool   $useBlocks Whether to use the current set of blocks\n     *\n     * @return string The rendered block\n     */\n    public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true): string\n    {\n        if (!$this->useYield) {\n            $level = ob_get_level();\n            if ($this->env->isDebug()) {\n                ob_start();\n            } else {\n                ob_start(static function () { return ''; });\n            }\n            try {\n                $this->displayBlock($name, $context, $blocks, $useBlocks);\n            } catch (\\Throwable $e) {\n                while (ob_get_level() > $level) {\n                    ob_end_clean();\n                }\n\n                throw $e;\n            }\n\n            return ob_get_clean();\n        }\n\n        $content = '';\n        foreach ($this->yieldBlock($name, $context, $blocks, $useBlocks) as $data) {\n            $content .= $data;\n        }\n\n        return $content;\n    }\n\n    /**\n     * Returns whether a block exists or not in the current context of the template.\n     *\n     * This method checks blocks defined in the current template\n     * or defined in \"used\" traits or defined in parent templates.\n     *\n     * @param string $name    The block name\n     * @param array  $context The context\n     * @param array  $blocks  The current set of blocks\n     *\n     * @return bool true if the block exists, false otherwise\n     */\n    public function hasBlock($name, array $context, array $blocks = []): bool\n    {\n        if (isset($blocks[$name])) {\n            return $blocks[$name][0] instanceof self;\n        }\n\n        if (isset($this->blocks[$name])) {\n            return true;\n        }\n\n        if ($parent = $this->getParent($context)) {\n            return $parent->hasBlock($name, $context);\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns all block names in the current context of the template.\n     *\n     * This method checks blocks defined in the current template\n     * or defined in \"used\" traits or defined in parent templates.\n     *\n     * @param array $context The context\n     * @param array $blocks  The current set of blocks\n     *\n     * @return array<string> An array of block names\n     */\n    public function getBlockNames(array $context, array $blocks = []): array\n    {\n        $names = array_merge(array_keys($blocks), array_keys($this->blocks));\n\n        if ($parent = $this->getParent($context)) {\n            $names = array_merge($names, $parent->getBlockNames($context));\n        }\n\n        return array_unique($names);\n    }\n\n    /**\n     * @param string|TemplateWrapper|array<string|TemplateWrapper> $template\n     */\n    protected function load(string|TemplateWrapper|array $template, int $line, ?int $index = null): self\n    {\n        try {\n            if (\\is_array($template)) {\n                return $this->env->resolveTemplate($template)->unwrap();\n            }\n\n            if ($template instanceof TemplateWrapper) {\n                return $template->unwrap();\n            }\n\n            if ($template === $this->getTemplateName()) {\n                $class = static::class;\n                if (false !== $pos = strrpos($class, '___', -1)) {\n                    $class = substr($class, 0, $pos);\n                }\n            } else {\n                $class = $this->env->getTemplateClass($template);\n            }\n\n            return $this->env->loadTemplate($class, $template, $index);\n        } catch (Error $e) {\n            if (!$e->getSourceContext()) {\n                $e->setSourceContext($this->getSourceContext());\n            }\n\n            if ($e->getTemplateLine() > 0) {\n                throw $e;\n            }\n\n            if (-1 === $line) {\n                $e->guess();\n            } else {\n                $e->setTemplateLine($line);\n            }\n\n            throw $e;\n        }\n    }\n\n    /**\n     * @param string|TemplateWrapper|array<string|TemplateWrapper> $template\n     *\n     * @deprecated since Twig 3.21 and will be removed in 4.0. Use Template::load() instead.\n     */\n    protected function loadTemplate($template, $templateName = null, ?int $line = null, ?int $index = null): self|TemplateWrapper\n    {\n        trigger_deprecation('twig/twig', '3.21', 'The \"%s\" method is deprecated.', __METHOD__);\n\n        if (null === $line) {\n            $line = -1;\n        }\n\n        if ($template instanceof self) {\n            return $template;\n        }\n\n        return $this->load($template, $line, $index);\n    }\n\n    /**\n     * @internal\n     *\n     * @return $this\n     */\n    public function unwrap(): self\n    {\n        return $this;\n    }\n\n    /**\n     * Returns all blocks.\n     *\n     * This method is for internal use only and should never be called\n     * directly.\n     *\n     * @return array An array of blocks\n     */\n    public function getBlocks(): array\n    {\n        return $this->blocks;\n    }\n\n    public function display(array $context, array $blocks = []): void\n    {\n        foreach ($this->yield($context, $blocks) as $data) {\n            echo $data;\n        }\n    }\n\n    public function render(array $context): string\n    {\n        if (!$this->useYield) {\n            $level = ob_get_level();\n            if ($this->env->isDebug()) {\n                ob_start();\n            } else {\n                ob_start(static function () { return ''; });\n            }\n            try {\n                $this->display($context);\n            } catch (\\Throwable $e) {\n                while (ob_get_level() > $level) {\n                    ob_end_clean();\n                }\n\n                throw $e;\n            }\n\n            return ob_get_clean();\n        }\n\n        $content = '';\n        foreach ($this->yield($context) as $data) {\n            $content .= $data;\n        }\n\n        return $content;\n    }\n\n    /**\n     * @return iterable<scalar|\\Stringable|null>\n     */\n    public function yield(array $context, array $blocks = []): iterable\n    {\n        $context += $this->env->getGlobals();\n        $blocks = array_merge($this->blocks, $blocks);\n\n        try {\n            yield from $this->doDisplay($context, $blocks);\n        } catch (Error $e) {\n            if (!$e->getSourceContext()) {\n                $e->setSourceContext($this->getSourceContext());\n            }\n\n            // this is mostly useful for \\Twig\\Error\\LoaderError exceptions\n            // see \\Twig\\Error\\LoaderError\n            if (-1 === $e->getTemplateLine()) {\n                $e->guess();\n            }\n\n            throw $e;\n        } catch (\\Throwable $e) {\n            $e = new RuntimeError(\\sprintf('An exception has been thrown during the rendering of a template (\"%s\").', $e->getMessage()), -1, $this->getSourceContext(), $e);\n            $e->guess();\n\n            throw $e;\n        }\n    }\n\n    /**\n     * @return iterable<scalar|\\Stringable|null>\n     */\n    public function yieldBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): iterable\n    {\n        if ($useBlocks && isset($blocks[$name])) {\n            $template = $blocks[$name][0];\n            $block = $blocks[$name][1];\n        } elseif (isset($this->blocks[$name])) {\n            $template = $this->blocks[$name][0];\n            $block = $this->blocks[$name][1];\n        } else {\n            $template = null;\n            $block = null;\n        }\n\n        // avoid RCEs when sandbox is enabled\n        if (null !== $template && !$template instanceof self) {\n            throw new \\LogicException('A block must be a method on a \\Twig\\Template instance.');\n        }\n\n        if (null !== $template) {\n            try {\n                yield from $template->$block($context, $blocks);\n            } catch (Error $e) {\n                if (!$e->getSourceContext()) {\n                    $e->setSourceContext($template->getSourceContext());\n                }\n\n                // this is mostly useful for \\Twig\\Error\\LoaderError exceptions\n                // see \\Twig\\Error\\LoaderError\n                if (-1 === $e->getTemplateLine()) {\n                    $e->guess();\n                }\n\n                throw $e;\n            } catch (\\Throwable $e) {\n                $e = new RuntimeError(\\sprintf('An exception has been thrown during the rendering of a template (\"%s\").', $e->getMessage()), -1, $template->getSourceContext(), $e);\n                $e->guess();\n\n                throw $e;\n            }\n        } elseif ($parent = $this->getParent($context)) {\n            yield from $parent->unwrap()->yieldBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this);\n        } elseif (isset($blocks[$name])) {\n            throw new RuntimeError(\\sprintf('Block \"%s\" should not call parent() in \"%s\" as the block does not exist in the parent template \"%s\".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext());\n        } else {\n            throw new RuntimeError(\\sprintf('Block \"%s\" on template \"%s\" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());\n        }\n    }\n\n    /**\n     * Yields a parent block.\n     *\n     * This method is for internal use only and should never be called\n     * directly.\n     *\n     * @param string $name    The block name to display from the parent\n     * @param array  $context The context\n     * @param array  $blocks  The current set of blocks\n     *\n     * @return iterable<scalar|\\Stringable|null>\n     */\n    public function yieldParentBlock($name, array $context, array $blocks = []): iterable\n    {\n        if (isset($this->traits[$name])) {\n            yield from $this->traits[$name][0]->yieldBlock($this->traitAliases[$name] ?? $name, $context, $blocks, false);\n        } elseif ($parent = $this->getParent($context)) {\n            yield from $parent->unwrap()->yieldBlock($name, $context, $blocks, false);\n        } else {\n            throw new RuntimeError(\\sprintf('The template has no parent and no traits defining the \"%s\" block.', $name), -1, $this->getSourceContext());\n        }\n    }\n\n    protected function hasMacro(string $name, array $context): bool\n    {\n        if (method_exists($this, $name)) {\n            return true;\n        }\n\n        if (!$parent = $this->getParent($context)) {\n            return false;\n        }\n\n        return $parent->hasMacro($name, $context);\n    }\n\n    protected function getTemplateForMacro(string $name, array $context, int $line, Source $source): self\n    {\n        if (method_exists($this, $name)) {\n            return $this;\n        }\n\n        $parent = $this;\n        while ($parent = $parent->getParent($context)) {\n            if (method_exists($parent, $name)) {\n                return $parent;\n            }\n        }\n\n        throw new RuntimeError(\\sprintf('Macro \"%s\" is not defined in template \"%s\".', substr($name, \\strlen('macro_')), $this->getTemplateName()), $line, $source);\n    }\n\n    /**\n     * Auto-generated method to display the template with the given context.\n     *\n     * @param array $context An array of parameters to pass to the template\n     * @param array $blocks  An array of blocks to pass to the template\n     *\n     * @return iterable<scalar|\\Stringable|null>\n     */\n    abstract protected function doDisplay(array $context, array $blocks = []): iterable;\n}\n"
  },
  {
    "path": "src/TemplateWrapper.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\n/**\n * Exposes a template to userland.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class TemplateWrapper\n{\n    /**\n     * This method is for internal use only and should never be called\n     * directly (use Twig\\Environment::load() instead).\n     *\n     * @internal\n     */\n    public function __construct(\n        private Environment $env,\n        private Template $template,\n    ) {\n    }\n\n    /**\n     * @return iterable<scalar|\\Stringable|null>\n     */\n    public function stream(array $context = []): iterable\n    {\n        yield from $this->template->yield($context);\n    }\n\n    /**\n     * @return iterable<scalar|\\Stringable|null>\n     */\n    public function streamBlock(string $name, array $context = []): iterable\n    {\n        yield from $this->template->yieldBlock($name, $context);\n    }\n\n    public function render(array $context = []): string\n    {\n        return $this->template->render($context);\n    }\n\n    /**\n     * @return void\n     */\n    public function display(array $context = [])\n    {\n        // using func_get_args() allows to not expose the blocks argument\n        // as it should only be used by internal code\n        $this->template->display($context, \\func_get_args()[1] ?? []);\n    }\n\n    public function hasBlock(string $name, array $context = []): bool\n    {\n        return $this->template->hasBlock($name, $context);\n    }\n\n    /**\n     * @return string[] An array of defined template block names\n     */\n    public function getBlockNames(array $context = []): array\n    {\n        return $this->template->getBlockNames($context);\n    }\n\n    public function renderBlock(string $name, array $context = []): string\n    {\n        return $this->template->renderBlock($name, $context + $this->env->getGlobals());\n    }\n\n    /**\n     * @return void\n     */\n    public function displayBlock(string $name, array $context = [])\n    {\n        $context += $this->env->getGlobals();\n        foreach ($this->template->yieldBlock($name, $context) as $data) {\n            echo $data;\n        }\n    }\n\n    public function getSourceContext(): Source\n    {\n        return $this->template->getSourceContext();\n    }\n\n    public function getTemplateName(): string\n    {\n        return $this->template->getTemplateName();\n    }\n\n    /**\n     * @internal\n     *\n     * @return Template\n     */\n    public function unwrap()\n    {\n        return $this->template;\n    }\n}\n"
  },
  {
    "path": "src/Test/IntegrationTestCase.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Test;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Error\\Error;\nuse Twig\\Extension\\ExtensionInterface;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\RuntimeLoader\\RuntimeLoaderInterface;\nuse Twig\\TokenParser\\TokenParserInterface;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\n/**\n * Integration test helper.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n * @author Karma Dordrak <drak@zikula.org>\n */\nabstract class IntegrationTestCase extends TestCase\n{\n    /**\n     * @deprecated since Twig 3.13, use getFixturesDirectory() instead.\n     *\n     * @return string\n     */\n    protected function getFixturesDir()\n    {\n        throw new \\BadMethodCallException('Not implemented.');\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        throw new \\BadMethodCallException('Not implemented.');\n    }\n\n    /**\n     * @return RuntimeLoaderInterface[]\n     */\n    protected function getRuntimeLoaders()\n    {\n        return [];\n    }\n\n    /**\n     * @return ExtensionInterface[]\n     */\n    protected function getExtensions()\n    {\n        return [];\n    }\n\n    /**\n     * @return TwigFilter[]\n     */\n    protected function getTwigFilters()\n    {\n        return [];\n    }\n\n    /**\n     * @return TwigFunction[]\n     */\n    protected function getTwigFunctions()\n    {\n        return [];\n    }\n\n    /**\n     * @return TwigTest[]\n     */\n    protected function getTwigTests()\n    {\n        return [];\n    }\n\n    /**\n     * @return array<callable(string): (TwigFilter|false)>\n     */\n    protected function getUndefinedFilterCallbacks(): array\n    {\n        return [];\n    }\n\n    /**\n     * @return array<callable(string): (TwigFunction|false)>\n     */\n    protected function getUndefinedFunctionCallbacks(): array\n    {\n        return [];\n    }\n\n    /**\n     * @return array<callable(string): (TwigTest|false)>\n     */\n    protected function getUndefinedTestCallbacks(): array\n    {\n        return [];\n    }\n\n    /**\n     * @return array<callable(string): (TokenParserInterface|false)>\n     */\n    protected function getUndefinedTokenParserCallbacks(): array\n    {\n        return [];\n    }\n\n    /**\n     * @dataProvider getTests\n     *\n     * @return void\n     */\n    public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '')\n    {\n        $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation);\n    }\n\n    /**\n     * @dataProvider getLegacyTests\n     *\n     * @group legacy\n     *\n     * @return void\n     */\n    public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '')\n    {\n        $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation);\n    }\n\n    /**\n     * @return iterable\n     *\n     * @final since Twig 3.13\n     */\n    public function getTests($name, $legacyTests = false)\n    {\n        try {\n            $fixturesDir = static::getFixturesDirectory();\n        } catch (\\BadMethodCallException) {\n            trigger_deprecation('twig/twig', '3.13', 'Not overriding \"%s::getFixturesDirectory()\" in \"%s\" is deprecated. This method will be abstract in 4.0.', self::class, static::class);\n            $fixturesDir = $this->getFixturesDir();\n        }\n\n        $fixturesDir = realpath($fixturesDir);\n        $tests = [];\n\n        foreach (new \\RecursiveIteratorIterator(new \\RecursiveDirectoryIterator($fixturesDir), \\RecursiveIteratorIterator::LEAVES_ONLY) as $file) {\n            if (!preg_match('/\\.test$/', $file)) {\n                continue;\n            }\n\n            if ($legacyTests xor str_contains($file->getRealpath(), '.legacy.test')) {\n                continue;\n            }\n\n            $test = file_get_contents($file->getRealpath());\n\n            if (preg_match('/--TEST--\\s*(.*?)\\s*(?:--CONDITION--\\s*(.*))?\\s*(?:--DEPRECATION--\\s*(.*?))?\\s*((?:--TEMPLATE(?:\\(.*?\\))?--(?:.*?))+)\\s*(?:--DATA--\\s*(.*))?\\s*--EXCEPTION--\\s*(.*)/sx', $test, $match)) {\n                $message = $match[1];\n                $condition = $match[2];\n                $deprecation = $match[3];\n                $templates = self::parseTemplates($match[4]);\n                $exception = $match[6];\n                $outputs = [[null, $match[5], null, '']];\n            } elseif (preg_match('/--TEST--\\s*(.*?)\\s*(?:--CONDITION--\\s*(.*))?\\s*(?:--DEPRECATION--\\s*(.*?))?\\s*((?:--TEMPLATE(?:\\(.*?\\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) {\n                $message = $match[1];\n                $condition = $match[2];\n                $deprecation = $match[3];\n                $templates = self::parseTemplates($match[4]);\n                $exception = false;\n                preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\\-\\-DATA\\-\\-|$)/s', $test, $outputs, \\PREG_SET_ORDER);\n            } else {\n                throw new \\InvalidArgumentException(\\sprintf('Test \"%s\" is not valid.', str_replace($fixturesDir.'/', '', $file)));\n            }\n\n            $tests[str_replace($fixturesDir.'/', '', $file)] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs, $deprecation];\n        }\n\n        if ($legacyTests && !$tests) {\n            // add a dummy test to avoid a PHPUnit message\n            return [['not', '-', '', [], '', []]];\n        }\n\n        return $tests;\n    }\n\n    /**\n     * @final since Twig 3.13\n     *\n     * @return iterable\n     */\n    public function getLegacyTests()\n    {\n        return $this->getTests('testLegacyIntegration', true);\n    }\n\n    /**\n     * @return void\n     */\n    protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '')\n    {\n        if (!$outputs) {\n            $this->markTestSkipped('no tests to run');\n        }\n\n        if ($condition) {\n            $ret = '';\n            eval('$ret = '.$condition.';');\n            if (!$ret) {\n                $this->markTestSkipped($condition);\n            }\n        }\n\n        foreach ($outputs as $i => $match) {\n            $config = array_merge([\n                'cache' => false,\n                'strict_variables' => true,\n            ], $match[2] ? eval($match[2].';') : []);\n            // make sure that template are always compiled even if they are the same (useful when testing with more than one data/expect sections)\n            foreach ($templates as $j => $template) {\n                $templates[$j] = $template.str_repeat(' ', $i);\n            }\n            $loader = new ArrayLoader($templates);\n            $twig = new Environment($loader, $config);\n            $twig->addGlobal('global', 'global');\n            foreach ($this->getRuntimeLoaders() as $runtimeLoader) {\n                $twig->addRuntimeLoader($runtimeLoader);\n            }\n\n            foreach ($this->getExtensions() as $extension) {\n                $twig->addExtension($extension);\n            }\n\n            foreach ($this->getTwigFilters() as $filter) {\n                $twig->addFilter($filter);\n            }\n\n            foreach ($this->getTwigTests() as $test) {\n                $twig->addTest($test);\n            }\n\n            foreach ($this->getTwigFunctions() as $function) {\n                $twig->addFunction($function);\n            }\n\n            foreach ($this->getUndefinedFilterCallbacks() as $callback) {\n                $twig->registerUndefinedFilterCallback($callback);\n            }\n\n            foreach ($this->getUndefinedFunctionCallbacks() as $callback) {\n                $twig->registerUndefinedFunctionCallback($callback);\n            }\n\n            foreach ($this->getUndefinedTestCallbacks() as $callback) {\n                $twig->registerUndefinedTestCallback($callback);\n            }\n\n            foreach ($this->getUndefinedTokenParserCallbacks() as $callback) {\n                $twig->registerUndefinedTokenParserCallback($callback);\n            }\n\n            $deprecations = [];\n            try {\n                $prevHandler = set_error_handler(static function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) {\n                    if (\\E_USER_DEPRECATED === $type) {\n                        $deprecations[] = $msg;\n\n                        return true;\n                    }\n\n                    return $prevHandler ? $prevHandler($type, $msg, $file, $line, $context) : false;\n                });\n\n                $template = $twig->load('index.twig');\n            } catch (\\Exception $e) {\n                if (false !== $exception) {\n                    $message = $e->getMessage();\n                    $this->assertSame(trim($exception), trim(\\sprintf('%s: %s', $e::class, $message)));\n                    $last = substr($message, \\strlen($message) - 1);\n                    $this->assertTrue('.' === $last || '?' === $last, 'Exception message must end with a dot or a question mark.');\n\n                    return;\n                }\n\n                throw new Error(\\sprintf('%s: %s', $e::class, $e->getMessage()), -1, null, $e);\n            } finally {\n                restore_error_handler();\n            }\n\n            $this->assertSame($deprecation, implode(\"\\n\", $deprecations));\n\n            try {\n                $output = trim($template->render(eval($match[1].';')), \"\\n \");\n            } catch (\\Exception $e) {\n                if (false !== $exception) {\n                    $this->assertStringMatchesFormat(trim($exception), trim(\\sprintf('%s: %s', $e::class, $e->getMessage())));\n\n                    return;\n                }\n\n                $e = new Error(\\sprintf('%s: %s', $e::class, $e->getMessage()), -1, null, $e);\n\n                $output = trim(\\sprintf('%s: %s', $e::class, $e->getMessage()));\n            }\n\n            if (false !== $exception) {\n                [$class] = explode(':', $exception);\n                $constraintClass = class_exists('PHPUnit\\Framework\\Constraint\\Exception') ? 'PHPUnit\\Framework\\Constraint\\Exception' : 'PHPUnit_Framework_Constraint_Exception';\n                $this->assertThat(null, new $constraintClass($class));\n            }\n\n            $expected = trim($match[3], \"\\n \");\n\n            if ($expected !== $output) {\n                printf(\"Compiled templates that failed on case %d:\\n\", $i + 1);\n\n                foreach (array_keys($templates) as $name) {\n                    echo \"Template: $name\\n\";\n                    echo $twig->compile($twig->parse($twig->tokenize($twig->getLoader()->getSourceContext($name))));\n                }\n            }\n            $this->assertEquals($expected, $output, $message.' (in '.$file.')');\n        }\n    }\n\n    /**\n     * @return array<string, string>\n     */\n    protected static function parseTemplates($test)\n    {\n        $templates = [];\n        preg_match_all('/--TEMPLATE(?:\\((.*?)\\))?--(.*?)(?=\\-\\-TEMPLATE|$)/s', $test, $matches, \\PREG_SET_ORDER);\n        foreach ($matches as $match) {\n            $templates[$match[1] ?: 'index.twig'] = $match[2];\n        }\n\n        return $templates;\n    }\n}\n"
  },
  {
    "path": "src/Test/NodeTestCase.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Test;\n\nuse PHPUnit\\Framework\\Attributes\\BeforeClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Compiler;\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\Node;\n\nabstract class NodeTestCase extends TestCase\n{\n    /**\n     * @var Environment\n     */\n    private $currentEnv;\n\n    /**\n     * @return iterable<array{0: Node, 1: string, 2?: Environment|null, 3?: bool}>\n     */\n    public function getTests()\n    {\n        return [];\n    }\n\n    /**\n     * @return iterable<array{0: Node, 1: string, 2?: Environment|null, 3?: bool}>\n     */\n    public static function provideTests(): iterable\n    {\n        trigger_deprecation('twig/twig', '3.13', 'Not implementing \"%s()\" in \"%s\" is deprecated. This method will be abstract in 4.0.', __METHOD__, static::class);\n\n        return [];\n    }\n\n    /**\n     * @dataProvider getTests\n     * @dataProvider provideTests\n     *\n     * @return void\n     */\n    #[DataProvider('getTests'), DataProvider('provideTests')]\n    public function testCompile($node, $source, $environment = null, $isPattern = false)\n    {\n        $this->assertNodeCompilation($source, $node, $environment, $isPattern);\n    }\n\n    /**\n     * @return void\n     */\n    public function assertNodeCompilation($source, Node $node, ?Environment $environment = null, $isPattern = false)\n    {\n        $compiler = $this->getCompiler($environment);\n        $compiler->compile($node);\n\n        if ($isPattern) {\n            $this->assertStringMatchesFormat($source, trim($compiler->getSource()));\n        } else {\n            $this->assertEquals($source, trim($compiler->getSource()));\n        }\n    }\n\n    /**\n     * @return Compiler\n     */\n    protected function getCompiler(?Environment $environment = null)\n    {\n        return new Compiler($environment ?? $this->getEnvironment());\n    }\n\n    /**\n     * @return Environment\n     *\n     * @final since Twig 3.13\n     */\n    protected function getEnvironment()\n    {\n        return $this->currentEnv ??= static::createEnvironment();\n    }\n\n    protected static function createEnvironment(): Environment\n    {\n        return new Environment(new ArrayLoader());\n    }\n\n    /**\n     * @return string\n     *\n     * @deprecated since Twig 3.13, use createVariableGetter() instead.\n     */\n    protected function getVariableGetter($name, $line = false)\n    {\n        trigger_deprecation('twig/twig', '3.13', 'Method \"%s()\" is deprecated, use \"createVariableGetter()\" instead.', __METHOD__);\n\n        return self::createVariableGetter($name, $line);\n    }\n\n    final protected static function createVariableGetter(string $name, bool $line = false): string\n    {\n        $line = $line > 0 ? \"// line $line\\n\" : '';\n\n        return \\sprintf('%s($context[\"%s\"] ?? null)', $line, $name);\n    }\n\n    /**\n     * @return string\n     *\n     * @deprecated since Twig 3.13, use createAttributeGetter() instead.\n     */\n    protected function getAttributeGetter()\n    {\n        trigger_deprecation('twig/twig', '3.13', 'Method \"%s()\" is deprecated, use \"createAttributeGetter()\" instead.', __METHOD__);\n\n        return self::createAttributeGetter();\n    }\n\n    final protected static function createAttributeGetter(): string\n    {\n        return 'CoreExtension::getAttribute($this->env, $this->source, ';\n    }\n\n    /** @beforeClass */\n    #[BeforeClass]\n    final public static function checkDataProvider(): void\n    {\n        $r = new \\ReflectionMethod(static::class, 'getTests');\n        if (self::class !== $r->getDeclaringClass()->getName()) {\n            trigger_deprecation('twig/twig', '3.13', 'Implementing \"%s::getTests()\" in \"%s\" is deprecated, implement \"provideTests()\" instead.', self::class, static::class);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Token.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class Token\n{\n    public const EOF_TYPE = -1;\n    public const TEXT_TYPE = 0;\n    public const BLOCK_START_TYPE = 1;\n    public const VAR_START_TYPE = 2;\n    public const BLOCK_END_TYPE = 3;\n    public const VAR_END_TYPE = 4;\n    public const NAME_TYPE = 5;\n    public const NUMBER_TYPE = 6;\n    public const STRING_TYPE = 7;\n    public const OPERATOR_TYPE = 8;\n    public const PUNCTUATION_TYPE = 9;\n    public const INTERPOLATION_START_TYPE = 10;\n    public const INTERPOLATION_END_TYPE = 11;\n    /**\n     * @deprecated since Twig 3.21, \"arrow\" is now an operator\n     */\n    public const ARROW_TYPE = 12;\n    /**\n     * @deprecated since Twig 3.21, \"spread\" is now an operator\n     */\n    public const SPREAD_TYPE = 13;\n\n    public function __construct(\n        private int $type,\n        private $value,\n        private int $lineno,\n    ) {\n        if (self::ARROW_TYPE === $type) {\n            trigger_deprecation('twig/twig', '3.21', 'The \"%s\" token type is deprecated, \"arrow\" is now an operator.', self::ARROW_TYPE);\n        }\n        if (self::SPREAD_TYPE === $type) {\n            trigger_deprecation('twig/twig', '3.21', 'The \"%s\" token type is deprecated, \"spread\" is now an operator.', self::SPREAD_TYPE);\n        }\n    }\n\n    public function __toString(): string\n    {\n        return \\sprintf('%s(%s)', self::typeToString($this->type, true), $this->value);\n    }\n\n    /**\n     * Tests the current token for a type and/or a value.\n     *\n     * Parameters may be:\n     *  * just type\n     *  * type and value (or array of possible values)\n     *  * just value (or array of possible values) (NAME_TYPE is used as type)\n     *\n     * @param array|string|int  $type   The type to test\n     * @param array|string|null $values The token value\n     */\n    public function test($type, $values = null): bool\n    {\n        if (null === $values && !\\is_int($type)) {\n            $values = $type;\n            $type = self::NAME_TYPE;\n        }\n\n        if (self::ARROW_TYPE === $type) {\n            trigger_deprecation('twig/twig', '3.21', 'The \"%s\" token type is deprecated, \"arrow\" is now an operator.', self::typeToEnglish(self::ARROW_TYPE));\n\n            return self::OPERATOR_TYPE === $this->type && '=>' === $this->value;\n        }\n        if (self::SPREAD_TYPE === $type) {\n            trigger_deprecation('twig/twig', '3.21', 'The \"%s\" token type is deprecated, \"spread\" is now an operator.', self::typeToEnglish(self::SPREAD_TYPE));\n\n            return self::OPERATOR_TYPE === $this->type && '...' === $this->value;\n        }\n\n        $typeMatches = $this->type === $type;\n        if ($typeMatches && self::PUNCTUATION_TYPE === $type && \\in_array($this->value, ['(', '[', '|', '.', '?', '?:'], true) && $values) {\n            foreach ((array) $values as $value) {\n                if (\\in_array($value, ['(', '[', '|', '.', '?', '?:'], true)) {\n                    trigger_deprecation('twig/twig', '3.21', 'The \"%s\" token is now an \"%s\" token instead of a \"%s\" one.', $this->value, self::typeToEnglish(self::OPERATOR_TYPE), $this->toEnglish());\n\n                    break;\n                }\n            }\n        }\n        if (!$typeMatches) {\n            if (self::OPERATOR_TYPE === $type && self::PUNCTUATION_TYPE === $this->type) {\n                if ($values) {\n                    foreach ((array) $values as $value) {\n                        if (\\in_array($value, ['(', '[', '|', '.', '?', '?:'], true)) {\n                            $typeMatches = true;\n\n                            break;\n                        }\n                    }\n                } else {\n                    $typeMatches = true;\n                }\n            }\n        }\n\n        return $typeMatches && (\n            null === $values\n            || (\\is_array($values) && \\in_array($this->value, $values, true))\n            || $this->value == $values\n        );\n    }\n\n    public function getLine(): int\n    {\n        return $this->lineno;\n    }\n\n    /**\n     * @deprecated since Twig 3.19\n     */\n    public function getType(): int\n    {\n        trigger_deprecation('twig/twig', '3.19', \\sprintf('The \"%s()\" method is deprecated.', __METHOD__));\n\n        return $this->type;\n    }\n\n    public function getValue()\n    {\n        return $this->value;\n    }\n\n    public function toEnglish(): string\n    {\n        return self::typeToEnglish($this->type);\n    }\n\n    public static function typeToString(int $type, bool $short = false): string\n    {\n        switch ($type) {\n            case self::EOF_TYPE:\n                $name = 'EOF_TYPE';\n                break;\n            case self::TEXT_TYPE:\n                $name = 'TEXT_TYPE';\n                break;\n            case self::BLOCK_START_TYPE:\n                $name = 'BLOCK_START_TYPE';\n                break;\n            case self::VAR_START_TYPE:\n                $name = 'VAR_START_TYPE';\n                break;\n            case self::BLOCK_END_TYPE:\n                $name = 'BLOCK_END_TYPE';\n                break;\n            case self::VAR_END_TYPE:\n                $name = 'VAR_END_TYPE';\n                break;\n            case self::NAME_TYPE:\n                $name = 'NAME_TYPE';\n                break;\n            case self::NUMBER_TYPE:\n                $name = 'NUMBER_TYPE';\n                break;\n            case self::STRING_TYPE:\n                $name = 'STRING_TYPE';\n                break;\n            case self::OPERATOR_TYPE:\n                $name = 'OPERATOR_TYPE';\n                break;\n            case self::PUNCTUATION_TYPE:\n                $name = 'PUNCTUATION_TYPE';\n                break;\n            case self::INTERPOLATION_START_TYPE:\n                $name = 'INTERPOLATION_START_TYPE';\n                break;\n            case self::INTERPOLATION_END_TYPE:\n                $name = 'INTERPOLATION_END_TYPE';\n                break;\n            case self::ARROW_TYPE:\n                $name = 'ARROW_TYPE';\n                break;\n            case self::SPREAD_TYPE:\n                $name = 'SPREAD_TYPE';\n                break;\n            default:\n                throw new \\LogicException(\\sprintf('Token of type \"%s\" does not exist.', $type));\n        }\n\n        return $short ? $name : 'Twig\\Token::'.$name;\n    }\n\n    public static function typeToEnglish(int $type): string\n    {\n        switch ($type) {\n            case self::EOF_TYPE:\n                return 'end of template';\n            case self::TEXT_TYPE:\n                return 'text';\n            case self::BLOCK_START_TYPE:\n                return 'begin of statement block';\n            case self::VAR_START_TYPE:\n                return 'begin of print statement';\n            case self::BLOCK_END_TYPE:\n                return 'end of statement block';\n            case self::VAR_END_TYPE:\n                return 'end of print statement';\n            case self::NAME_TYPE:\n                return 'name';\n            case self::NUMBER_TYPE:\n                return 'number';\n            case self::STRING_TYPE:\n                return 'string';\n            case self::OPERATOR_TYPE:\n                return 'operator';\n            case self::PUNCTUATION_TYPE:\n                return 'punctuation';\n            case self::INTERPOLATION_START_TYPE:\n                return 'begin of string interpolation';\n            case self::INTERPOLATION_END_TYPE:\n                return 'end of string interpolation';\n            case self::ARROW_TYPE:\n                return 'arrow function';\n            case self::SPREAD_TYPE:\n                return 'spread operator';\n            default:\n                throw new \\LogicException(\\sprintf('Token of type \"%s\" does not exist.', $type));\n        }\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/AbstractTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Lexer;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Nodes;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * Base class for all token parsers.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nabstract class AbstractTokenParser implements TokenParserInterface\n{\n    /**\n     * @var Parser\n     */\n    protected $parser;\n\n    public function setParser(Parser $parser): void\n    {\n        $this->parser = $parser;\n    }\n\n    /**\n     * Parses an assignment expression like \"a, b\".\n     */\n    protected function parseAssignmentExpression(): Nodes\n    {\n        $stream = $this->parser->getStream();\n        $targets = [];\n        while (true) {\n            $token = $stream->getCurrent();\n            if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {\n                // in this context, string operators are variable names\n                $stream->next();\n            } else {\n                $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to');\n            }\n            $targets[] = new AssignContextVariable($token->getValue(), $token->getLine());\n\n            if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {\n                break;\n            }\n        }\n\n        return new Nodes($targets);\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/ApplyTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\ExpressionParser\\Infix\\FilterExpressionParser;\nuse Twig\\Node\\Expression\\Variable\\LocalVariable;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Node\\SetNode;\nuse Twig\\Token;\n\n/**\n * Applies filters on a section of a template.\n *\n *   {% apply upper %}\n *      This text becomes uppercase\n *   {% endapply %}\n *\n * @internal\n */\nfinal class ApplyTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $lineno = $token->getLine();\n        $ref = new LocalVariable(null, $lineno);\n        $filter = $ref;\n        $op = $this->parser->getEnvironment()->getExpressionParsers()->getByClass(FilterExpressionParser::class);\n        while (true) {\n            $filter = $op->parse($this->parser, $filter, $this->parser->getCurrentToken());\n            if (!$this->parser->getStream()->test(Token::OPERATOR_TYPE, '|')) {\n                break;\n            }\n            $this->parser->getStream()->next();\n        }\n\n        $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);\n        $body = $this->parser->subparse([$this, 'decideApplyEnd'], true);\n        $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);\n\n        return new Nodes([\n            new SetNode(true, $ref, $body, $lineno),\n            new PrintNode($filter, $lineno),\n        ], $lineno);\n    }\n\n    public function decideApplyEnd(Token $token): bool\n    {\n        return $token->test('endapply');\n    }\n\n    public function getTag(): string\n    {\n        return 'apply';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/AutoEscapeTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\AutoEscapeNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Marks a section of a template to be escaped or not.\n *\n * @internal\n */\nfinal class AutoEscapeTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $lineno = $token->getLine();\n        $stream = $this->parser->getStream();\n\n        if ($stream->test(Token::BLOCK_END_TYPE)) {\n            $value = 'html';\n        } else {\n            $expr = $this->parser->parseExpression();\n            if (!$expr instanceof ConstantExpression) {\n                throw new SyntaxError('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getSourceContext());\n            }\n            $value = $expr->getAttribute('value');\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $body = $this->parser->subparse([$this, 'decideBlockEnd'], true);\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return new AutoEscapeNode($value, $body, $lineno);\n    }\n\n    public function decideBlockEnd(Token $token): bool\n    {\n        return $token->test('endautoescape');\n    }\n\n    public function getTag(): string\n    {\n        return 'autoescape';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/BlockTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\BlockNode;\nuse Twig\\Node\\BlockReferenceNode;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Token;\n\n/**\n * Marks a section of a template as being reusable.\n *\n *  {% block head %}\n *    <link rel=\"stylesheet\" href=\"style.css\" />\n *    <title>{% block title %}{% endblock %} - My Webpage</title>\n *  {% endblock %}\n *\n * @internal\n */\nfinal class BlockTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $lineno = $token->getLine();\n        $stream = $this->parser->getStream();\n        $name = $stream->expect(Token::NAME_TYPE)->getValue();\n        $this->parser->setBlock($name, $block = new BlockNode($name, new EmptyNode(), $lineno));\n        $this->parser->pushLocalScope();\n        $this->parser->pushBlockStack($name);\n\n        if ($stream->nextIf(Token::BLOCK_END_TYPE)) {\n            $body = $this->parser->subparse([$this, 'decideBlockEnd'], true);\n            if ($token = $stream->nextIf(Token::NAME_TYPE)) {\n                $value = $token->getValue();\n\n                if ($value != $name) {\n                    throw new SyntaxError(\\sprintf('Expected endblock for block \"%s\" (but \"%s\" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext());\n                }\n            }\n        } else {\n            $body = new Nodes([\n                new PrintNode($this->parser->parseExpression(), $lineno),\n            ]);\n        }\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        $block->setNode('body', $body);\n        $this->parser->popBlockStack();\n        $this->parser->popLocalScope();\n\n        return new BlockReferenceNode($name, $lineno);\n    }\n\n    public function decideBlockEnd(Token $token): bool\n    {\n        return $token->test('endblock');\n    }\n\n    public function getTag(): string\n    {\n        return 'block';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/DeprecatedTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\DeprecatedNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Deprecates a section of a template.\n *\n *    {% deprecated 'The \"base.twig\" template is deprecated, use \"layout.twig\" instead.' %}\n *    {% extends 'layout.html.twig' %}\n *\n *    {% deprecated 'The \"base.twig\" template is deprecated, use \"layout.twig\" instead.' package=\"foo/bar\" version=\"1.1\" %}\n *\n * @author Yonel Ceruto <yonelceruto@gmail.com>\n *\n * @internal\n */\nfinal class DeprecatedTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $stream = $this->parser->getStream();\n        $expr = $this->parser->parseExpression();\n        $node = new DeprecatedNode($expr, $token->getLine());\n\n        while ($stream->test(Token::NAME_TYPE)) {\n            $k = $stream->getCurrent()->getValue();\n            $stream->next();\n            $stream->expect(Token::OPERATOR_TYPE, '=');\n\n            switch ($k) {\n                case 'package':\n                    $node->setNode('package', $this->parser->parseExpression());\n                    break;\n                case 'version':\n                    $node->setNode('version', $this->parser->parseExpression());\n                    break;\n                default:\n                    throw new SyntaxError(\\sprintf('Unknown \"%s\" option.', $k), $stream->getCurrent()->getLine(), $stream->getSourceContext());\n            }\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return $node;\n    }\n\n    public function getTag(): string\n    {\n        return 'deprecated';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/DoTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Node\\DoNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Evaluates an expression, discarding the returned value.\n *\n * @internal\n */\nfinal class DoTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $expr = $this->parser->parseExpression();\n\n        $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);\n\n        return new DoNode($expr, $token->getLine());\n    }\n\n    public function getTag(): string\n    {\n        return 'do';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/EmbedTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Node\\EmbedNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Embeds a template.\n *\n * @internal\n */\nfinal class EmbedTokenParser extends IncludeTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $stream = $this->parser->getStream();\n\n        $parent = $this->parser->parseExpression();\n\n        [$variables, $only, $ignoreMissing] = $this->parseArguments();\n\n        $parentToken = $fakeParentToken = new Token(Token::STRING_TYPE, '__parent__', $token->getLine());\n        if ($parent instanceof ConstantExpression) {\n            $parentToken = new Token(Token::STRING_TYPE, $parent->getAttribute('value'), $token->getLine());\n        } elseif ($parent instanceof ContextVariable) {\n            $parentToken = new Token(Token::NAME_TYPE, $parent->getAttribute('name'), $token->getLine());\n        }\n\n        // inject a fake parent to make the parent() function work\n        $stream->injectTokens([\n            new Token(Token::BLOCK_START_TYPE, '', $token->getLine()),\n            new Token(Token::NAME_TYPE, 'extends', $token->getLine()),\n            $parentToken,\n            new Token(Token::BLOCK_END_TYPE, '', $token->getLine()),\n        ]);\n\n        $module = $this->parser->parse($stream, [$this, 'decideBlockEnd'], true);\n\n        // override the parent with the correct one\n        if ($fakeParentToken === $parentToken) {\n            $module->setNode('parent', $parent);\n        }\n\n        $this->parser->embedTemplate($module);\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine());\n    }\n\n    public function decideBlockEnd(Token $token): bool\n    {\n        return $token->test('endembed');\n    }\n\n    public function getTag(): string\n    {\n        return 'embed';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/ExtendsTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Extends a template by another one.\n *\n *  {% extends \"base.html\" %}\n *\n * @internal\n */\nfinal class ExtendsTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $stream = $this->parser->getStream();\n\n        if ($this->parser->peekBlockStack()) {\n            throw new SyntaxError('Cannot use \"extend\" in a block.', $token->getLine(), $stream->getSourceContext());\n        } elseif (!$this->parser->isMainScope()) {\n            throw new SyntaxError('Cannot use \"extend\" in a macro.', $token->getLine(), $stream->getSourceContext());\n        }\n\n        $this->parser->setParent($this->parser->parseExpression());\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return new EmptyNode($token->getLine());\n    }\n\n    public function getTag(): string\n    {\n        return 'extends';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/FlushTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Node\\FlushNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Flushes the output to the client.\n *\n * @see flush()\n *\n * @internal\n */\nfinal class FlushTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);\n\n        return new FlushNode($token->getLine());\n    }\n\n    public function getTag(): string\n    {\n        return 'flush';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/ForTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\ForElseNode;\nuse Twig\\Node\\ForNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Loops over each item of a sequence.\n *\n *   <ul>\n *    {% for user in users %}\n *      <li>{{ user.username|e }}</li>\n *    {% endfor %}\n *   </ul>\n *\n * @internal\n */\nfinal class ForTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $lineno = $token->getLine();\n        $stream = $this->parser->getStream();\n        $targets = $this->parseAssignmentExpression();\n        $stream->expect(Token::OPERATOR_TYPE, 'in');\n        $seq = $this->parser->parseExpression();\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $body = $this->parser->subparse([$this, 'decideForFork']);\n        if ('else' == $stream->next()->getValue()) {\n            $elseLineno = $stream->getCurrent()->getLine();\n            $stream->expect(Token::BLOCK_END_TYPE);\n            $else = new ForElseNode($this->parser->subparse([$this, 'decideForEnd'], true), $elseLineno);\n        } else {\n            $else = null;\n        }\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        if (\\count($targets) > 1) {\n            $keyTarget = $targets->getNode('0');\n            $keyTarget = new AssignContextVariable($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine());\n            $valueTarget = $targets->getNode('1');\n        } else {\n            $keyTarget = new AssignContextVariable('_key', $lineno);\n            $valueTarget = $targets->getNode('0');\n        }\n        $valueTarget = new AssignContextVariable($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine());\n\n        return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno);\n    }\n\n    public function decideForFork(Token $token): bool\n    {\n        return $token->test(['else', 'endfor']);\n    }\n\n    public function decideForEnd(Token $token): bool\n    {\n        return $token->test('endfor');\n    }\n\n    public function getTag(): string\n    {\n        return 'for';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/FromTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\AssignTemplateVariable;\nuse Twig\\Node\\Expression\\Variable\\TemplateVariable;\nuse Twig\\Node\\ImportNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Imports macros.\n *\n *   {% from 'forms.html.twig' import forms %}\n *\n * @internal\n */\nfinal class FromTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $macro = $this->parser->parseExpression();\n        $stream = $this->parser->getStream();\n        $stream->expect(Token::NAME_TYPE, 'import');\n\n        $targets = [];\n        while (true) {\n            $name = $stream->expect(Token::NAME_TYPE)->getValue();\n\n            if ($stream->nextIf('as')) {\n                $alias = new AssignContextVariable($stream->expect(Token::NAME_TYPE)->getValue(), $token->getLine());\n            } else {\n                $alias = new AssignContextVariable($name, $token->getLine());\n            }\n\n            $targets[$name] = $alias;\n\n            if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {\n                break;\n            }\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        $internalRef = new AssignTemplateVariable(new TemplateVariable(null, $token->getLine()), $this->parser->isMainScope());\n        $node = new ImportNode($macro, $internalRef, $token->getLine());\n\n        foreach ($targets as $name => $alias) {\n            $this->parser->addImportedSymbol('function', $alias->getAttribute('name'), 'macro_'.$name, $internalRef);\n        }\n\n        return $node;\n    }\n\n    public function getTag(): string\n    {\n        return 'from';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/GuardTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Token;\n\n/**\n * @internal\n */\nfinal class GuardTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $stream = $this->parser->getStream();\n        $typeToken = $stream->expect(Token::NAME_TYPE);\n        if (!\\in_array($typeToken->getValue(), ['function', 'filter', 'test'], true)) {\n            throw new SyntaxError(\\sprintf('Supported guard types are function, filter and test, \"%s\" given.', $typeToken->getValue()), $typeToken->getLine(), $stream->getSourceContext());\n        }\n        $method = 'get'.$typeToken->getValue();\n\n        $nameToken = $stream->expect(Token::NAME_TYPE);\n        $name = $nameToken->getValue();\n        if ('test' === $typeToken->getValue() && $stream->test(Token::NAME_TYPE)) {\n            // try 2-words tests\n            $name .= ' '.$stream->getCurrent()->getValue();\n            $stream->next();\n        }\n\n        try {\n            $exists = null !== $this->parser->getEnvironment()->$method($name);\n        } catch (SyntaxError) {\n            $exists = false;\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n        if ($exists) {\n            $body = $this->parser->subparse([$this, 'decideGuardFork']);\n        } else {\n            $body = new EmptyNode();\n            $this->parser->subparseIgnoreUnknownTwigCallables([$this, 'decideGuardFork']);\n        }\n        $else = new EmptyNode();\n        if ('else' === $stream->next()->getValue()) {\n            $stream->expect(Token::BLOCK_END_TYPE);\n            $else = $this->parser->subparse([$this, 'decideGuardEnd'], true);\n        }\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return new Nodes([$exists ? $body : $else]);\n    }\n\n    public function decideGuardFork(Token $token): bool\n    {\n        return $token->test(['else', 'endguard']);\n    }\n\n    public function decideGuardEnd(Token $token): bool\n    {\n        return $token->test(['endguard']);\n    }\n\n    public function getTag(): string\n    {\n        return 'guard';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/IfTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\IfNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Token;\n\n/**\n * Tests a condition.\n *\n *   {% if users %}\n *    <ul>\n *      {% for user in users %}\n *        <li>{{ user.username|e }}</li>\n *      {% endfor %}\n *    </ul>\n *   {% endif %}\n *\n * @internal\n */\nfinal class IfTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $lineno = $token->getLine();\n        $expr = $this->parser->parseExpression();\n        $stream = $this->parser->getStream();\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $body = $this->parser->subparse([$this, 'decideIfFork']);\n        $tests = [$expr, $body];\n        $else = null;\n\n        $end = false;\n        while (!$end) {\n            switch ($stream->next()->getValue()) {\n                case 'else':\n                    $stream->expect(Token::BLOCK_END_TYPE);\n                    $else = $this->parser->subparse([$this, 'decideIfEnd']);\n                    break;\n\n                case 'elseif':\n                    $expr = $this->parser->parseExpression();\n                    $stream->expect(Token::BLOCK_END_TYPE);\n                    $body = $this->parser->subparse([$this, 'decideIfFork']);\n                    $tests[] = $expr;\n                    $tests[] = $body;\n                    break;\n\n                case 'endif':\n                    $end = true;\n                    break;\n\n                default:\n                    throw new SyntaxError(\\sprintf('Unexpected end of template. Twig was looking for the following tags \"else\", \"elseif\", or \"endif\" to close the \"if\" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext());\n            }\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return new IfNode(new Nodes($tests), $else, $lineno);\n    }\n\n    public function decideIfFork(Token $token): bool\n    {\n        return $token->test(['elseif', 'else', 'endif']);\n    }\n\n    public function decideIfEnd(Token $token): bool\n    {\n        return $token->test(['endif']);\n    }\n\n    public function getTag(): string\n    {\n        return 'if';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/ImportTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Node\\Expression\\Variable\\AssignTemplateVariable;\nuse Twig\\Node\\Expression\\Variable\\TemplateVariable;\nuse Twig\\Node\\ImportNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Imports macros.\n *\n *   {% import 'forms.html.twig' as forms %}\n *\n * @internal\n */\nfinal class ImportTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $macro = $this->parser->parseExpression();\n        $this->parser->getStream()->expect(Token::NAME_TYPE, 'as');\n        $name = $this->parser->getStream()->expect(Token::NAME_TYPE)->getValue();\n        $var = new AssignTemplateVariable(new TemplateVariable($name, $token->getLine()), $this->parser->isMainScope());\n        $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);\n        $this->parser->addImportedSymbol('template', $name);\n\n        return new ImportNode($macro, $var, $token->getLine());\n    }\n\n    public function getTag(): string\n    {\n        return 'import';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/IncludeTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Node\\Expression\\AbstractExpression;\nuse Twig\\Node\\IncludeNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Includes a template.\n *\n *   {% include 'header.html.twig' %}\n *     Body\n *   {% include 'footer.html.twig' %}\n *\n * @internal\n */\nclass IncludeTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $expr = $this->parser->parseExpression();\n\n        [$variables, $only, $ignoreMissing] = $this->parseArguments();\n\n        return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine());\n    }\n\n    /**\n     * @return array{0: ?AbstractExpression, 1: bool, 2: bool}\n     */\n    protected function parseArguments()\n    {\n        $stream = $this->parser->getStream();\n\n        $ignoreMissing = false;\n        if ($stream->nextIf(Token::NAME_TYPE, 'ignore')) {\n            $stream->expect(Token::NAME_TYPE, 'missing');\n\n            $ignoreMissing = true;\n        }\n\n        $variables = null;\n        if ($stream->nextIf(Token::NAME_TYPE, 'with')) {\n            $variables = $this->parser->parseExpression();\n        }\n\n        $only = false;\n        if ($stream->nextIf(Token::NAME_TYPE, 'only')) {\n            $only = true;\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return [$variables, $only, $ignoreMissing];\n    }\n\n    public function getTag(): string\n    {\n        return 'include';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/MacroTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\BodyNode;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Unary\\NegUnary;\nuse Twig\\Node\\Expression\\Unary\\PosUnary;\nuse Twig\\Node\\Expression\\Variable\\LocalVariable;\nuse Twig\\Node\\MacroNode;\nuse Twig\\Node\\Node;\nuse Twig\\Token;\n\n/**\n * Defines a macro.\n *\n *   {% macro input(name, value, type, size) %}\n *      <input type=\"{{ type|default('text') }}\" name=\"{{ name }}\" value=\"{{ value|e }}\" size=\"{{ size|default(20) }}\" />\n *   {% endmacro %}\n *\n * @internal\n */\nfinal class MacroTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $lineno = $token->getLine();\n        $stream = $this->parser->getStream();\n        $name = $stream->expect(Token::NAME_TYPE)->getValue();\n        $arguments = $this->parseDefinition();\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $this->parser->pushLocalScope();\n        $body = $this->parser->subparse([$this, 'decideBlockEnd'], true);\n        if ($token = $stream->nextIf(Token::NAME_TYPE)) {\n            $value = $token->getValue();\n\n            if ($value != $name) {\n                throw new SyntaxError(\\sprintf('Expected endmacro for macro \"%s\" (but \"%s\" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext());\n            }\n        }\n        $this->parser->popLocalScope();\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno));\n\n        return new EmptyNode($lineno);\n    }\n\n    public function decideBlockEnd(Token $token): bool\n    {\n        return $token->test('endmacro');\n    }\n\n    public function getTag(): string\n    {\n        return 'macro';\n    }\n\n    private function parseDefinition(): ArrayExpression\n    {\n        $arguments = new ArrayExpression([], $this->parser->getCurrentToken()->getLine());\n        $stream = $this->parser->getStream();\n        $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');\n        while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {\n            if (\\count($arguments)) {\n                $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');\n\n                // if the comma above was a trailing comma, early exit the argument parse loop\n                if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {\n                    break;\n                }\n            }\n\n            $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');\n            $name = new LocalVariable($token->getValue(), $this->parser->getCurrentToken()->getLine());\n            if ($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) {\n                $default = $this->parser->parseExpression();\n            } else {\n                $default = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());\n                $default->setAttribute('is_implicit', true);\n            }\n\n            if (!$this->checkConstantExpression($default)) {\n                throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());\n            }\n            $arguments->addElement($default, $name);\n        }\n        $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');\n\n        return $arguments;\n    }\n\n    // checks that the node only contains \"constant\" elements\n    private function checkConstantExpression(Node $node): bool\n    {\n        if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression\n            || $node instanceof NegUnary || $node instanceof PosUnary\n        )) {\n            return false;\n        }\n\n        foreach ($node as $n) {\n            if (!$this->checkConstantExpression($n)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/SandboxTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\IncludeNode;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\SandboxNode;\nuse Twig\\Node\\TextNode;\nuse Twig\\Token;\n\n/**\n * Marks a section of a template as untrusted code that must be evaluated in the sandbox mode.\n *\n *    {% sandbox %}\n *        {% include 'user.html.twig' %}\n *    {% endsandbox %}\n *\n * @see https://twig.symfony.com/doc/api.html#sandbox-extension for details\n *\n * @internal\n */\nfinal class SandboxTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $stream = $this->parser->getStream();\n        trigger_deprecation('twig/twig', '3.15', \\sprintf('The \"sandbox\" tag is deprecated in \"%s\" at line %d.', $stream->getSourceContext()->getName(), $token->getLine()));\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $body = $this->parser->subparse([$this, 'decideBlockEnd'], true);\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        // in a sandbox tag, only include tags are allowed\n        if (!$body instanceof IncludeNode) {\n            foreach ($body as $node) {\n                if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) {\n                    continue;\n                }\n\n                if (!$node instanceof IncludeNode) {\n                    throw new SyntaxError('Only \"include\" tags are allowed within a \"sandbox\" section.', $node->getTemplateLine(), $stream->getSourceContext());\n                }\n            }\n        }\n\n        return new SandboxNode($body, $token->getLine());\n    }\n\n    public function decideBlockEnd(Token $token): bool\n    {\n        return $token->test('endsandbox');\n    }\n\n    public function getTag(): string\n    {\n        return 'sandbox';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/SetTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\SetNode;\nuse Twig\\Token;\n\n/**\n * Defines a variable.\n *\n *  {% set foo = 'foo' %}\n *  {% set foo = [1, 2] %}\n *  {% set foo = {'foo': 'bar'} %}\n *  {% set foo = 'foo' ~ 'bar' %}\n *  {% set foo, bar = 'foo', 'bar' %}\n *  {% set foo %}Some content{% endset %}\n *\n * @internal\n */\nfinal class SetTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $lineno = $token->getLine();\n        $stream = $this->parser->getStream();\n        $names = $this->parseAssignmentExpression();\n\n        $capture = false;\n        if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) {\n            $values = $this->parseMultitargetExpression();\n\n            $stream->expect(Token::BLOCK_END_TYPE);\n\n            if (\\count($names) !== \\count($values)) {\n                throw new SyntaxError('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext());\n            }\n        } else {\n            $capture = true;\n\n            if (\\count($names) > 1) {\n                throw new SyntaxError('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext());\n            }\n\n            $stream->expect(Token::BLOCK_END_TYPE);\n\n            $values = $this->parser->subparse([$this, 'decideBlockEnd'], true);\n            $stream->expect(Token::BLOCK_END_TYPE);\n        }\n\n        return new SetNode($capture, $names, $values, $lineno);\n    }\n\n    public function decideBlockEnd(Token $token): bool\n    {\n        return $token->test('endset');\n    }\n\n    public function getTag(): string\n    {\n        return 'set';\n    }\n\n    private function parseMultitargetExpression(): Nodes\n    {\n        $targets = [];\n        while (true) {\n            $targets[] = $this->parser->parseExpression();\n            if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) {\n                break;\n            }\n        }\n\n        return new Nodes($targets);\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/TokenParserInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Node;\nuse Twig\\Parser;\nuse Twig\\Token;\n\n/**\n * Interface implemented by token parsers.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface TokenParserInterface\n{\n    /**\n     * Sets the parser associated with this token parser.\n     */\n    public function setParser(Parser $parser): void;\n\n    /**\n     * Parses a token and returns a node.\n     *\n     * @return Node\n     *\n     * @throws SyntaxError\n     */\n    public function parse(Token $token);\n\n    /**\n     * Gets the tag name associated with this token parser.\n     *\n     * @return string\n     */\n    public function getTag();\n}\n"
  },
  {
    "path": "src/TokenParser/TypesTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\TypesNode;\nuse Twig\\Token;\nuse Twig\\TokenStream;\n\n/**\n * Declare variable types.\n *\n *  {% types {foo: 'number', bar?: 'string'} %}\n *\n * @author Jeroen Versteeg <jeroen@alisqi.com>\n *\n * @internal\n */\nfinal class TypesTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $stream = $this->parser->getStream();\n        $types = $this->parseSimpleMappingExpression($stream);\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return new TypesNode($types, $token->getLine());\n    }\n\n    /**\n     * @return array<string, array{type: string, optional: bool}>\n     *\n     * @throws SyntaxError\n     */\n    private function parseSimpleMappingExpression(TokenStream $stream): array\n    {\n        $enclosed = null !== $stream->nextIf(Token::PUNCTUATION_TYPE, '{');\n        $types = [];\n        $first = true;\n        while (!($stream->test(Token::PUNCTUATION_TYPE, '}') || $stream->test(Token::BLOCK_END_TYPE))) {\n            if (!$first) {\n                $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma');\n\n                // trailing ,?\n                if ($stream->test(Token::PUNCTUATION_TYPE, '}') || $stream->test(Token::BLOCK_END_TYPE)) {\n                    break;\n                }\n            }\n            $first = false;\n\n            $nameToken = $stream->expect(Token::NAME_TYPE);\n\n            if ($stream->nextIf(Token::OPERATOR_TYPE, '?:')) {\n                $isOptional = true;\n            } else {\n                $isOptional = null !== $stream->nextIf(Token::OPERATOR_TYPE, '?');\n                $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A type name must be followed by a colon (:)');\n            }\n\n            $valueToken = $stream->expect(Token::STRING_TYPE);\n\n            $types[$nameToken->getValue()] = [\n                'type' => $valueToken->getValue(),\n                'optional' => $isOptional,\n            ];\n        }\n\n        if ($enclosed) {\n            $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed');\n        }\n\n        return $types;\n    }\n\n    public function getTag(): string\n    {\n        return 'types';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/UseTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Token;\n\n/**\n * Imports blocks defined in another template into the current template.\n *\n *    {% extends \"base.html\" %}\n *\n *    {% use \"blocks.html\" %}\n *\n *    {% block title %}{% endblock %}\n *    {% block content %}{% endblock %}\n *\n * @see https://twig.symfony.com/doc/templates.html#horizontal-reuse for details.\n *\n * @internal\n */\nfinal class UseTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $template = $this->parser->parseExpression();\n        $stream = $this->parser->getStream();\n\n        if (!$template instanceof ConstantExpression) {\n            throw new SyntaxError('The template references in a \"use\" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext());\n        }\n\n        $targets = [];\n        if ($stream->nextIf('with')) {\n            while (true) {\n                $name = $stream->expect(Token::NAME_TYPE)->getValue();\n\n                $alias = $name;\n                if ($stream->nextIf('as')) {\n                    $alias = $stream->expect(Token::NAME_TYPE)->getValue();\n                }\n\n                $targets[$name] = new ConstantExpression($alias, -1);\n\n                if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {\n                    break;\n                }\n            }\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        $this->parser->addTrait(new Nodes(['template' => $template, 'targets' => new Nodes($targets)]));\n\n        return new EmptyNode($token->getLine());\n    }\n\n    public function getTag(): string\n    {\n        return 'use';\n    }\n}\n"
  },
  {
    "path": "src/TokenParser/WithTokenParser.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\TokenParser;\n\nuse Twig\\Node\\Node;\nuse Twig\\Node\\WithNode;\nuse Twig\\Token;\n\n/**\n * Creates a nested scope.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nfinal class WithTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $stream = $this->parser->getStream();\n\n        $variables = null;\n        $only = false;\n        if (!$stream->test(Token::BLOCK_END_TYPE)) {\n            $variables = $this->parser->parseExpression();\n            $only = (bool) $stream->nextIf(Token::NAME_TYPE, 'only');\n        }\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        $body = $this->parser->subparse([$this, 'decideWithEnd'], true);\n\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        return new WithNode($body, $variables, $only, $token->getLine());\n    }\n\n    public function decideWithEnd(Token $token): bool\n    {\n        return $token->test('endwith');\n    }\n\n    public function getTag(): string\n    {\n        return 'with';\n    }\n}\n"
  },
  {
    "path": "src/TokenStream.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n * (c) Armin Ronacher\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Error\\SyntaxError;\n\n/**\n * Represents a token stream.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class TokenStream\n{\n    private $current = 0;\n\n    public function __construct(\n        private array $tokens,\n        private ?Source $source = null,\n    ) {\n        if (null === $this->source) {\n            trigger_deprecation('twig/twig', '3.16', \\sprintf('Not passing a \"%s\" object to \"%s\" constructor is deprecated.', Source::class, __CLASS__));\n\n            $this->source = new Source('', '');\n        }\n    }\n\n    public function __toString(): string\n    {\n        return implode(\"\\n\", $this->tokens);\n    }\n\n    /**\n     * @return void\n     */\n    public function injectTokens(array $tokens)\n    {\n        $this->tokens = array_merge(\\array_slice($this->tokens, 0, $this->current), $tokens, \\array_slice($this->tokens, $this->current));\n    }\n\n    /**\n     * Sets the pointer to the next token and returns the old one.\n     */\n    public function next(): Token\n    {\n        if (!isset($this->tokens[++$this->current])) {\n            throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source);\n        }\n\n        return $this->tokens[$this->current - 1];\n    }\n\n    /**\n     * Tests a token, sets the pointer to the next one and returns it or throws a syntax error.\n     *\n     * @return Token|null The next token if the condition is true, null otherwise\n     */\n    public function nextIf($primary, $secondary = null)\n    {\n        return $this->tokens[$this->current]->test($primary, $secondary) ? $this->next() : null;\n    }\n\n    /**\n     * Tests a token and returns it or throws a syntax error.\n     */\n    public function expect($type, $value = null, ?string $message = null): Token\n    {\n        $token = $this->tokens[$this->current];\n        if (!$token->test($type, $value)) {\n            $line = $token->getLine();\n            throw new SyntaxError(\\sprintf('%sUnexpected token \"%s\"%s (\"%s\" expected%s).',\n                $message ? $message.'. ' : '',\n                $token->toEnglish(),\n                $token->getValue() ? \\sprintf(' of value \"%s\"', $token->getValue()) : '',\n                Token::typeToEnglish($type), $value ? \\sprintf(' with value \"%s\"', $value) : ''),\n                $line,\n                $this->source\n            );\n        }\n        $this->next();\n\n        return $token;\n    }\n\n    /**\n     * Looks at the next token.\n     */\n    public function look(int $number = 1): Token\n    {\n        if (!isset($this->tokens[$this->current + $number])) {\n            throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source);\n        }\n\n        return $this->tokens[$this->current + $number];\n    }\n\n    /**\n     * Tests the current token.\n     */\n    public function test($primary, $secondary = null): bool\n    {\n        return $this->tokens[$this->current]->test($primary, $secondary);\n    }\n\n    /**\n     * Checks if end of stream was reached.\n     */\n    public function isEOF(): bool\n    {\n        return $this->tokens[$this->current]->test(Token::EOF_TYPE);\n    }\n\n    public function getCurrent(): Token\n    {\n        return $this->tokens[$this->current];\n    }\n\n    public function getSourceContext(): Source\n    {\n        return $this->source;\n    }\n}\n"
  },
  {
    "path": "src/TwigCallableInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\ninterface TwigCallableInterface extends \\Stringable\n{\n    public function getName(): string;\n\n    public function getType(): string;\n\n    public function getDynamicName(): string;\n\n    /**\n     * @return callable|array{class-string, string}|null\n     */\n    public function getCallable();\n\n    public function getNodeClass(): string;\n\n    public function needsCharset(): bool;\n\n    public function needsEnvironment(): bool;\n\n    public function needsContext(): bool;\n\n    public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self;\n\n    public function getArguments(): array;\n\n    public function isVariadic(): bool;\n\n    public function isDeprecated(): bool;\n\n    public function getDeprecatingPackage(): string;\n\n    public function getDeprecatedVersion(): string;\n\n    public function getAlternative(): ?string;\n\n    public function getMinimalNumberOfRequiredArguments(): int;\n}\n"
  },
  {
    "path": "src/TwigFilter.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Node\\Expression\\FilterExpression;\nuse Twig\\Node\\Node;\n\n/**\n * Represents a template filter.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @see https://twig.symfony.com/doc/templates.html#filters\n */\nfinal class TwigFilter extends AbstractTwigCallable\n{\n    /**\n     * @param callable|array{class-string, string}|null $callable A callable implementing the filter. If null, you need to overwrite the \"node_class\" option to customize compilation.\n     */\n    public function __construct(string $name, $callable = null, array $options = [])\n    {\n        parent::__construct($name, $callable, $options);\n\n        $this->options = array_merge([\n            'is_safe' => null,\n            'is_safe_callback' => null,\n            'pre_escape' => null,\n            'preserves_safety' => null,\n            'node_class' => FilterExpression::class,\n        ], $this->options);\n    }\n\n    public function getType(): string\n    {\n        return 'filter';\n    }\n\n    public function getSafe(Node $filterArgs): ?array\n    {\n        if (null !== $this->options['is_safe']) {\n            return $this->options['is_safe'];\n        }\n\n        if (null !== $this->options['is_safe_callback']) {\n            return $this->options['is_safe_callback']($filterArgs);\n        }\n\n        return [];\n    }\n\n    public function getPreservesSafety(): array\n    {\n        return $this->options['preserves_safety'] ?? [];\n    }\n\n    public function getPreEscape(): ?string\n    {\n        return $this->options['pre_escape'];\n    }\n\n    public function getMinimalNumberOfRequiredArguments(): int\n    {\n        return parent::getMinimalNumberOfRequiredArguments() + 1;\n    }\n}\n"
  },
  {
    "path": "src/TwigFunction.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\Node\\Node;\n\n/**\n * Represents a template function.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @see https://twig.symfony.com/doc/templates.html#functions\n */\nfinal class TwigFunction extends AbstractTwigCallable\n{\n    /**\n     * @param callable|array{class-string, string}|null $callable A callable implementing the function. If null, you need to overwrite the \"node_class\" option to customize compilation.\n     */\n    public function __construct(string $name, $callable = null, array $options = [])\n    {\n        parent::__construct($name, $callable, $options);\n\n        $this->options = array_merge([\n            'is_safe' => null,\n            'is_safe_callback' => null,\n            'node_class' => FunctionExpression::class,\n            'parser_callable' => null,\n        ], $this->options);\n    }\n\n    public function getType(): string\n    {\n        return 'function';\n    }\n\n    public function getParserCallable(): ?callable\n    {\n        return $this->options['parser_callable'];\n    }\n\n    public function getSafe(Node $functionArgs): ?array\n    {\n        if (null !== $this->options['is_safe']) {\n            return $this->options['is_safe'];\n        }\n\n        if (null !== $this->options['is_safe_callback']) {\n            return $this->options['is_safe_callback']($functionArgs);\n        }\n\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/TwigTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig;\n\nuse Twig\\Node\\Expression\\TestExpression;\n\n/**\n * Represents a template test.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @see https://twig.symfony.com/doc/templates.html#test-operator\n */\nfinal class TwigTest extends AbstractTwigCallable\n{\n    /**\n     * @param callable|array{class-string, string}|null $callable A callable implementing the test. If null, you need to overwrite the \"node_class\" option to customize compilation.\n     */\n    public function __construct(string $name, $callable = null, array $options = [])\n    {\n        parent::__construct($name, $callable, $options);\n\n        $this->options = array_merge([\n            'node_class' => TestExpression::class,\n            'one_mandatory_argument' => false,\n        ], $this->options);\n    }\n\n    public function getType(): string\n    {\n        return 'test';\n    }\n\n    public function needsCharset(): bool\n    {\n        return false;\n    }\n\n    public function needsEnvironment(): bool\n    {\n        return false;\n    }\n\n    public function needsContext(): bool\n    {\n        return false;\n    }\n\n    public function hasOneMandatoryArgument(): bool\n    {\n        return (bool) $this->options['one_mandatory_argument'];\n    }\n\n    public function getMinimalNumberOfRequiredArguments(): int\n    {\n        return parent::getMinimalNumberOfRequiredArguments() + 1;\n    }\n}\n"
  },
  {
    "path": "src/Util/CallableArgumentsExtractor.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Util;\n\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\VariadicExpression;\nuse Twig\\Node\\Node;\nuse Twig\\TwigCallableInterface;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nfinal class CallableArgumentsExtractor\n{\n    private ReflectionCallable $rc;\n\n    public function __construct(\n        private Node $node,\n        private TwigCallableInterface $twigCallable,\n    ) {\n        $this->rc = new ReflectionCallable($twigCallable);\n    }\n\n    /**\n     * @return array<Node>\n     */\n    public function extractArguments(Node $arguments): array\n    {\n        $extractedArguments = [];\n        $extractedArgumentNameMap = [];\n        $named = false;\n        foreach ($arguments as $name => $node) {\n            if (!\\is_int($name)) {\n                $named = true;\n            } elseif ($named) {\n                throw new SyntaxError(\\sprintf('Positional arguments cannot be used after named arguments for %s \"%s\".', $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());\n            }\n\n            $extractedArguments[$normalizedName = $this->normalizeName($name)] = $node;\n            $extractedArgumentNameMap[$normalizedName] = $name;\n        }\n\n        if (!$named && !$this->twigCallable->isVariadic()) {\n            $min = $this->twigCallable->getMinimalNumberOfRequiredArguments();\n            if (\\count($extractedArguments) < $this->rc->getReflector()->getNumberOfRequiredParameters() - $min) {\n                $argName = $this->toSnakeCase($this->rc->getReflector()->getParameters()[$min + \\count($extractedArguments)]->getName());\n\n                throw new SyntaxError(\\sprintf('Value for argument \"%s\" is required for %s \"%s\".', $argName, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());\n            }\n\n            return $extractedArguments;\n        }\n\n        if (!$callable = $this->twigCallable->getCallable()) {\n            if ($named) {\n                throw new SyntaxError(\\sprintf('Named arguments are not supported for %s \"%s\".', $this->twigCallable->getType(), $this->twigCallable->getName()));\n            }\n\n            throw new SyntaxError(\\sprintf('Arbitrary positional arguments are not supported for %s \"%s\".', $this->twigCallable->getType(), $this->twigCallable->getName()));\n        }\n\n        [$callableParameters, $isPhpVariadic] = $this->getCallableParameters();\n        $arguments = [];\n        $callableParameterNames = [];\n        $missingArguments = [];\n        $optionalArguments = [];\n        $pos = 0;\n        foreach ($callableParameters as $callableParameter) {\n            $callableParameterName = $callableParameter->name;\n            if (\\PHP_VERSION_ID >= 80000 && 'range' === $callable) {\n                if ('start' === $callableParameterName) {\n                    $callableParameterName = 'low';\n                } elseif ('end' === $callableParameterName) {\n                    $callableParameterName = 'high';\n                }\n            }\n\n            $callableParameterNames[] = $callableParameterName;\n            $normalizedCallableParameterName = $this->normalizeName($callableParameterName);\n\n            if (\\array_key_exists($normalizedCallableParameterName, $extractedArguments)) {\n                if (\\array_key_exists($pos, $extractedArguments)) {\n                    throw new SyntaxError(\\sprintf('Argument \"%s\" is defined twice for %s \"%s\".', $callableParameterName, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());\n                }\n\n                if (\\count($missingArguments)) {\n                    throw new SyntaxError(\\sprintf(\n                        'Argument \"%s\" could not be assigned for %s \"%s(%s)\" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s \"%s\".',\n                        $callableParameterName, $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', array_map([$this, 'toSnakeCase'], $callableParameterNames)), \\count($missingArguments) > 1 ? 's' : '', implode('\", \"', $missingArguments)\n                    ), $this->node->getTemplateLine(), $this->node->getSourceContext());\n                }\n\n                $arguments = array_merge($arguments, $optionalArguments);\n                $arguments[] = $extractedArguments[$normalizedCallableParameterName];\n                unset($extractedArguments[$normalizedCallableParameterName]);\n                $optionalArguments = [];\n            } elseif (\\array_key_exists($pos, $extractedArguments)) {\n                $arguments = array_merge($arguments, $optionalArguments);\n                $arguments[] = $extractedArguments[$pos];\n                unset($extractedArguments[$pos]);\n                $optionalArguments = [];\n                ++$pos;\n            } elseif ($callableParameter->isDefaultValueAvailable()) {\n                $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), $this->node->getTemplateLine());\n            } elseif ($callableParameter->isOptional()) {\n                if (!$extractedArguments) {\n                    break;\n                }\n\n                $missingArguments[] = $callableParameterName;\n            } else {\n                throw new SyntaxError(\\sprintf('Value for argument \"%s\" is required for %s \"%s\".', $this->toSnakeCase($callableParameterName), $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());\n            }\n        }\n\n        if ($this->twigCallable->isVariadic()) {\n            $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], $this->node->getTemplateLine()) : new ArrayExpression([], $this->node->getTemplateLine());\n            foreach ($extractedArguments as $key => $value) {\n                if (\\is_int($key)) {\n                    $arbitraryArguments->addElement($value);\n                } else {\n                    $originalKey = $extractedArgumentNameMap[$key];\n                    if ($originalKey !== $this->toSnakeCase($originalKey)) {\n                        trigger_deprecation('twig/twig', '3.15', \\sprintf('Using \"snake_case\" for variadic arguments is required for a smooth upgrade with Twig 4.0; rename \"%s\" to \"%s\" in \"%s\" at line %d.', $originalKey, $this->toSnakeCase($originalKey), $this->node->getSourceContext()->getName(), $this->node->getTemplateLine()));\n                    }\n                    $arbitraryArguments->addElement($value, new ConstantExpression($this->toSnakeCase($originalKey), $this->node->getTemplateLine()));\n                    // I Twig 4.0, don't convert the key:\n                    // $arbitraryArguments->addElement($value, new ConstantExpression($originalKey, $this->node->getTemplateLine()));\n                }\n                unset($extractedArguments[$key]);\n            }\n\n            if ($arbitraryArguments->count()) {\n                $arguments = array_merge($arguments, $optionalArguments);\n                $arguments[] = $arbitraryArguments;\n            }\n        }\n\n        if ($extractedArguments) {\n            $unknownArgument = null;\n            foreach ($extractedArguments as $extractedArgument) {\n                if ($extractedArgument instanceof Node) {\n                    $unknownArgument = $extractedArgument;\n                    break;\n                }\n            }\n\n            throw new SyntaxError(\n                \\sprintf(\n                    'Unknown argument%s \"%s\" for %s \"%s(%s)\".',\n                    \\count($extractedArguments) > 1 ? 's' : '', implode('\", \"', array_keys($extractedArguments)), $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', array_map([$this, 'toSnakeCase'], $callableParameterNames))\n                ),\n                $unknownArgument ? $unknownArgument->getTemplateLine() : $this->node->getTemplateLine(),\n                $unknownArgument ? $unknownArgument->getSourceContext() : $this->node->getSourceContext()\n            );\n        }\n\n        return $arguments;\n    }\n\n    private function normalizeName(string $name): string\n    {\n        return strtolower(str_replace('_', '', $name));\n    }\n\n    private function toSnakeCase(string $name): string\n    {\n        return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z0-9])([A-Z])/'], '\\1_\\2', $name));\n    }\n\n    private function getCallableParameters(): array\n    {\n        $parameters = $this->rc->getReflector()->getParameters();\n        if ($this->node->hasNode('node')) {\n            array_shift($parameters);\n        }\n        if ($this->twigCallable->needsCharset()) {\n            array_shift($parameters);\n        }\n        if ($this->twigCallable->needsEnvironment()) {\n            array_shift($parameters);\n        }\n        if ($this->twigCallable->needsContext()) {\n            array_shift($parameters);\n        }\n        foreach ($this->twigCallable->getArguments() as $argument) {\n            array_shift($parameters);\n        }\n\n        $isPhpVariadic = false;\n        if ($this->twigCallable->isVariadic()) {\n            $argument = end($parameters);\n            $isArray = $argument && $argument->hasType() && $argument->getType() instanceof \\ReflectionNamedType && 'array' === $argument->getType()->getName();\n            if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {\n                array_pop($parameters);\n            } elseif ($argument && $argument->isVariadic()) {\n                array_pop($parameters);\n                $isPhpVariadic = true;\n            } else {\n                throw new SyntaxError(\\sprintf('The last parameter of \"%s\" for %s \"%s\" must be an array with default value, eg. \"array $arg = []\".', $this->rc->getName(), $this->twigCallable->getType(), $this->twigCallable->getName()));\n            }\n        }\n\n        return [$parameters, $isPhpVariadic];\n    }\n}\n"
  },
  {
    "path": "src/Util/DeprecationCollector.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Util;\n\nuse Twig\\Environment;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Source;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nfinal class DeprecationCollector\n{\n    public function __construct(\n        private Environment $twig,\n    ) {\n    }\n\n    /**\n     * Returns deprecations for templates contained in a directory.\n     *\n     * @param string $dir A directory where templates are stored\n     * @param string $ext Limit the loaded templates by extension\n     *\n     * @return array An array of deprecations\n     */\n    public function collectDir(string $dir, string $ext = '.twig'): array\n    {\n        $iterator = new \\RegexIterator(\n            new \\RecursiveIteratorIterator(\n                new \\RecursiveDirectoryIterator($dir), \\RecursiveIteratorIterator::LEAVES_ONLY\n            ), '{'.preg_quote($ext).'$}'\n        );\n\n        return $this->collect(new TemplateDirIterator($iterator));\n    }\n\n    /**\n     * Returns deprecations for passed templates.\n     *\n     * @param \\Traversable $iterator An iterator of templates (where keys are template names and values the contents of the template)\n     *\n     * @return array An array of deprecations\n     */\n    public function collect(\\Traversable $iterator): array\n    {\n        $deprecations = [];\n        set_error_handler(static function ($type, $msg) use (&$deprecations) {\n            if (\\E_USER_DEPRECATED === $type) {\n                $deprecations[] = $msg;\n            }\n\n            return false;\n        });\n\n        foreach ($iterator as $name => $contents) {\n            try {\n                $this->twig->parse($this->twig->tokenize(new Source($contents, $name)));\n            } catch (SyntaxError $e) {\n                // ignore templates containing syntax errors\n            }\n        }\n\n        restore_error_handler();\n\n        return $deprecations;\n    }\n}\n"
  },
  {
    "path": "src/Util/ReflectionCallable.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Util;\n\nuse Twig\\TwigCallableInterface;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @internal\n */\nfinal class ReflectionCallable\n{\n    private $reflector;\n    private $callable;\n    private $name;\n\n    public function __construct(\n        TwigCallableInterface $twigCallable,\n    ) {\n        $callable = $twigCallable->getCallable();\n        if (\\is_string($callable) && false !== $pos = strpos($callable, '::')) {\n            $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];\n        }\n\n        if (\\is_array($callable) && method_exists($callable[0], $callable[1])) {\n            $this->reflector = $r = new \\ReflectionMethod($callable[0], $callable[1]);\n            $this->callable = $callable;\n            $this->name = $r->class.'::'.$r->name;\n\n            return;\n        }\n\n        $checkVisibility = $callable instanceof \\Closure;\n        try {\n            $closure = \\Closure::fromCallable($callable);\n        } catch (\\TypeError $e) {\n            throw new \\LogicException(\\sprintf('Callback for %s \"%s\" is not callable in the current scope.', $twigCallable->getType(), $twigCallable->getName()), 0, $e);\n        }\n        $this->reflector = $r = new \\ReflectionFunction($closure);\n\n        if (str_contains($r->name, '{closure')) {\n            $this->callable = $callable;\n            $this->name = 'Closure';\n\n            return;\n        }\n\n        if ($object = $r->getClosureThis()) {\n            $callable = [$object, $r->name];\n            $this->name = get_debug_type($object).'::'.$r->name;\n        } elseif (\\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {\n            $callable = [$class->name, $r->name];\n            $this->name = $class->name.'::'.$r->name;\n        } elseif (\\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {\n            $callable = [\\is_array($callable) ? $callable[0] : $class->name, $r->name];\n            $this->name = (\\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;\n        } else {\n            $callable = $this->name = $r->name;\n        }\n\n        if ($checkVisibility && \\is_array($callable) && method_exists(...$callable) && !(new \\ReflectionMethod(...$callable))->isPublic()) {\n            $callable = $r->getClosure();\n        }\n\n        $this->callable = $callable;\n    }\n\n    public function getReflector(): \\ReflectionFunctionAbstract\n    {\n        return $this->reflector;\n    }\n\n    /**\n     * @return callable\n     */\n    public function getCallable()\n    {\n        return $this->callable;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n}\n"
  },
  {
    "path": "src/Util/TemplateDirIterator.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Util;\n\n/**\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass TemplateDirIterator extends \\IteratorIterator\n{\n    /**\n     * @return string\n     */\n    #[\\ReturnTypeWillChange]\n    public function current()\n    {\n        return file_get_contents(parent::current());\n    }\n\n    /**\n     * @return string\n     */\n    #[\\ReturnTypeWillChange]\n    public function key()\n    {\n        return (string) parent::key();\n    }\n}\n"
  },
  {
    "path": "tests/Cache/ChainTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Cache;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Cache\\ChainCache;\nuse Twig\\Cache\\FilesystemCache;\nuse Twig\\Tests\\FilesystemHelper;\n\nclass ChainTest extends TestCase\n{\n    private $className;\n    private $directory;\n    private $cache;\n    private $key;\n\n    protected function setUp(): void\n    {\n        $nonce = hash(\\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', random_bytes(32));\n        $this->className = '__Twig_Tests_Cache_ChainTest_Template_'.$nonce;\n        $this->directory = sys_get_temp_dir().'/twig-test';\n        $this->cache = new ChainCache([\n            new FilesystemCache($this->directory.'/A'),\n            new FilesystemCache($this->directory.'/B'),\n        ]);\n        $this->key = $this->cache->generateKey('_test_', $this->className);\n    }\n\n    protected function tearDown(): void\n    {\n        if (file_exists($this->directory)) {\n            FilesystemHelper::removeDir($this->directory);\n        }\n    }\n\n    public function testLoadInA()\n    {\n        $cache = new FilesystemCache($this->directory.'/A');\n        $key = $cache->generateKey('_test_', $this->className);\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n        $this->assertFalse(class_exists($this->className, false));\n\n        $content = $this->generateSource();\n        file_put_contents($key, $content);\n\n        $this->cache->load($this->key);\n\n        $this->assertTrue(class_exists($this->className, false));\n    }\n\n    public function testLoadInB()\n    {\n        $cache = new FilesystemCache($this->directory.'/B');\n        $key = $cache->generateKey('_test_', $this->className);\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n        $this->assertFalse(class_exists($this->className, false));\n\n        $content = $this->generateSource();\n        file_put_contents($key, $content);\n\n        $this->cache->load($this->key);\n\n        $this->assertTrue(class_exists($this->className, false));\n    }\n\n    public function testLoadInBoth()\n    {\n        $cache = new FilesystemCache($this->directory.'/A');\n        $key = $cache->generateKey('_test_', $this->className);\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n        $this->assertFalse(class_exists($this->className, false));\n\n        $content = $this->generateSource();\n        file_put_contents($key, $content);\n\n        $cache = new FilesystemCache($this->directory.'/B');\n        $key = $cache->generateKey('_test_', $this->className);\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n        $this->assertFalse(class_exists($this->className, false));\n\n        $content = $this->generateSource();\n        file_put_contents($key, $content);\n\n        $this->cache->load($this->key);\n\n        $this->assertTrue(class_exists($this->className, false));\n    }\n\n    public function testLoadMissing()\n    {\n        $this->assertFalse(class_exists($this->className, false));\n\n        $this->cache->load($this->key);\n\n        $this->assertFalse(class_exists($this->className, false));\n    }\n\n    public function testWrite()\n    {\n        $content = $this->generateSource();\n\n        $cacheA = new FilesystemCache($this->directory.'/A');\n        $keyA = $cacheA->generateKey('_test_', $this->className);\n\n        $this->assertFileDoesNotExist($keyA);\n        $this->assertFileDoesNotExist($this->directory.'/A');\n\n        $cacheB = new FilesystemCache($this->directory.'/B');\n        $keyB = $cacheB->generateKey('_test_', $this->className);\n\n        $this->assertFileDoesNotExist($keyB);\n        $this->assertFileDoesNotExist($this->directory.'/B');\n\n        $this->cache->write($this->key, $content);\n\n        $this->assertFileExists($this->directory.'/A');\n        $this->assertFileExists($keyA);\n        $this->assertSame(file_get_contents($keyA), $content);\n\n        $this->assertFileExists($this->directory.'/B');\n        $this->assertFileExists($keyB);\n        $this->assertSame(file_get_contents($keyB), $content);\n    }\n\n    public function testGetTimestampInA()\n    {\n        $cache = new FilesystemCache($this->directory.'/A');\n        $key = $cache->generateKey('_test_', $this->className);\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n\n        // Create the file with a specific modification time.\n        touch($key, 1234567890);\n\n        $this->assertSame(1234567890, $this->cache->getTimestamp($this->key));\n    }\n\n    public function testGetTimestampInB()\n    {\n        $cache = new FilesystemCache($this->directory.'/B');\n        $key = $cache->generateKey('_test_', $this->className);\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n\n        // Create the file with a specific modification time.\n        touch($key, 1234567890);\n\n        $this->assertSame(1234567890, $this->cache->getTimestamp($this->key));\n    }\n\n    public function testGetTimestampInBoth()\n    {\n        $cacheA = new FilesystemCache($this->directory.'/A');\n        $keyA = $cacheA->generateKey('_test_', $this->className);\n\n        $dir = \\dirname($keyA);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n\n        // Create the file with a specific modification time.\n        touch($keyA, 1234567890);\n\n        $cacheB = new FilesystemCache($this->directory.'/B');\n        $keyB = $cacheB->generateKey('_test_', $this->className);\n\n        $dir = \\dirname($keyB);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n\n        // Create the file with a specific modification time.\n        touch($keyB, 1234567891);\n\n        $this->assertSame(1234567890, $this->cache->getTimestamp($this->key));\n    }\n\n    public function testGetTimestampMissingFile()\n    {\n        $this->assertSame(0, $this->cache->getTimestamp($this->key));\n    }\n\n    /**\n     * @dataProvider provideInput\n     */\n    public function testGenerateKey($expected, $input)\n    {\n        $cache = new ChainCache([]);\n        $this->assertSame($expected, $cache->generateKey($input, static::class));\n    }\n\n    public static function provideInput()\n    {\n        return [\n            ['Twig\\Tests\\Cache\\ChainTest#_test_', '_test_'],\n            ['Twig\\Tests\\Cache\\ChainTest#_test#with#hashtag_', '_test#with#hashtag_'],\n        ];\n    }\n\n    private function generateSource()\n    {\n        return strtr('<?php class {{class_name}} {}', [\n            '{{class_name}}' => $this->className,\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/Cache/FilesystemTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Cache;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Cache\\FilesystemCache;\nuse Twig\\Tests\\FilesystemHelper;\n\nclass FilesystemTest extends TestCase\n{\n    private $className;\n    private $directory;\n    private $cache;\n\n    protected function setUp(): void\n    {\n        $nonce = hash(\\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', random_bytes(32));\n        $this->className = '__Twig_Tests_Cache_FilesystemTest_Template_'.$nonce;\n        $this->directory = sys_get_temp_dir().'/twig-test';\n        $this->cache = new FilesystemCache($this->directory);\n    }\n\n    protected function tearDown(): void\n    {\n        if (file_exists($this->directory)) {\n            FilesystemHelper::removeDir($this->directory);\n        }\n    }\n\n    public function testLoad()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n        $this->assertFalse(class_exists($this->className, false));\n\n        $content = $this->generateSource();\n        file_put_contents($key, $content);\n\n        $this->cache->load($key);\n\n        $this->assertTrue(class_exists($this->className, false));\n    }\n\n    public function testLoadMissing()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n\n        $this->assertFalse(class_exists($this->className, false));\n\n        $this->cache->load($key);\n\n        $this->assertFalse(class_exists($this->className, false));\n    }\n\n    public function testWrite()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n        $content = $this->generateSource();\n\n        $this->assertFileDoesNotExist($key);\n        $this->assertFileDoesNotExist($this->directory);\n\n        $this->cache->write($key, $content);\n\n        $this->assertFileExists($this->directory);\n        $this->assertFileExists($key);\n        $this->assertSame(file_get_contents($key), $content);\n    }\n\n    public function testWriteFailMkdir()\n    {\n        if (\\defined('PHP_WINDOWS_VERSION_BUILD')) {\n            $this->markTestSkipped('Read-only directories not possible on Windows.');\n        }\n\n        $key = $this->directory.'/cache/cachefile.php';\n        $content = $this->generateSource();\n\n        $this->assertFileDoesNotExist($key);\n\n        // Create read-only root directory.\n        @mkdir($this->directory, 0555, true);\n        $this->assertDirectoryExists($this->directory);\n\n        $this->expectException(\\RuntimeException::class);\n        $this->expectExceptionMessage('Unable to create the cache directory');\n\n        $this->cache->write($key, $content);\n    }\n\n    public function testWriteFailDirWritable()\n    {\n        if (\\defined('PHP_WINDOWS_VERSION_BUILD')) {\n            $this->markTestSkipped('Read-only directories not possible on Windows.');\n        }\n\n        $key = $this->directory.'/cache/cachefile.php';\n        $content = $this->generateSource();\n\n        $this->assertFileDoesNotExist($key);\n\n        // Create root directory.\n        @mkdir($this->directory, 0777, true);\n        // Create read-only subdirectory.\n        @mkdir($this->directory.'/cache', 0555);\n        $this->assertDirectoryExists($this->directory.'/cache');\n\n        $this->expectException(\\RuntimeException::class);\n        $this->expectExceptionMessage('Unable to write in the cache directory');\n\n        $this->cache->write($key, $content);\n    }\n\n    public function testWriteFailWriteFile()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n        $content = $this->generateSource();\n\n        $this->assertFileDoesNotExist($key);\n\n        // Create a directory in the place of the cache file.\n        @mkdir($key, 0777, true);\n        $this->assertDirectoryExists($key);\n\n        $this->expectException(\\RuntimeException::class);\n        $this->expectExceptionMessage('Failed to write cache file');\n\n        $this->cache->write($key, $content);\n    }\n\n    public function testGetTimestamp()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n\n        // Create the file with a specific modification time.\n        touch($key, 1234567890);\n\n        $this->assertSame(1234567890, $this->cache->getTimestamp($key));\n    }\n\n    public function testGetTimestampMissingFile()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n        $this->assertSame(0, $this->cache->getTimestamp($key));\n    }\n\n    /**\n     * Test file cache is tolerant towards trailing (back)slashes on the configured cache directory.\n     *\n     * @dataProvider provideDirectories\n     */\n    public function testGenerateKey($expected, $input)\n    {\n        $cache = new FilesystemCache($input);\n        $this->assertMatchesRegularExpression($expected, $cache->generateKey('_test_', static::class));\n    }\n\n    public static function provideDirectories()\n    {\n        $pattern = '#a/b/[a-zA-Z0-9]+/[a-zA-Z0-9]+.php$#';\n\n        return [\n            [$pattern, 'a/b'],\n            [$pattern, 'a/b/'],\n            [$pattern, 'a/b\\\\'],\n            [$pattern, 'a/b\\\\/'],\n            [$pattern, 'a/b\\\\//'],\n            ['#/'.substr($pattern, 1), '/a/b'],\n        ];\n    }\n\n    private function generateSource()\n    {\n        return strtr('<?php class {{class_name}} {}', [\n            '{{class_name}}' => $this->className,\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/Cache/ReadOnlyFilesystemTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Cache;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Cache\\ReadOnlyFilesystemCache;\nuse Twig\\Tests\\FilesystemHelper;\n\nclass ReadOnlyFilesystemTest extends TestCase\n{\n    private $className;\n    private $directory;\n    private $cache;\n\n    protected function setUp(): void\n    {\n        $nonce = hash(\\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', random_bytes(32));\n        $this->className = '__Twig_Tests_Cache_ReadOnlyFilesystemTest_Template_'.$nonce;\n        $this->directory = sys_get_temp_dir().'/twig-test';\n        $this->cache = new ReadOnlyFilesystemCache($this->directory);\n    }\n\n    protected function tearDown(): void\n    {\n        if (file_exists($this->directory)) {\n            FilesystemHelper::removeDir($this->directory);\n        }\n    }\n\n    public function testLoad()\n    {\n        $key = $this->directory.'/cache/ro-cachefile.php';\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n        $this->assertFalse(class_exists($this->className, false));\n\n        $content = $this->generateSource();\n        file_put_contents($key, $content);\n\n        $this->cache->load($key);\n\n        $this->assertTrue(class_exists($this->className, false));\n    }\n\n    public function testLoadMissing()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n\n        $this->assertFalse(class_exists($this->className, false));\n\n        $this->cache->load($key);\n\n        $this->assertFalse(class_exists($this->className, false));\n    }\n\n    public function testWrite()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n        $content = $this->generateSource();\n\n        $this->assertFileDoesNotExist($key);\n        $this->assertFileDoesNotExist($this->directory);\n\n        $this->cache->write($key, $content);\n\n        $this->assertFileDoesNotExist($this->directory);\n        $this->assertFileDoesNotExist($key);\n    }\n\n    public function testGetTimestamp()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n\n        $dir = \\dirname($key);\n        @mkdir($dir, 0777, true);\n        $this->assertDirectoryExists($dir);\n\n        // Create the file with a specific modification time.\n        touch($key, 1234567890);\n\n        $this->assertSame(1234567890, $this->cache->getTimestamp($key));\n    }\n\n    public function testGetTimestampMissingFile()\n    {\n        $key = $this->directory.'/cache/cachefile.php';\n        $this->assertSame(0, $this->cache->getTimestamp($key));\n    }\n\n    /**\n     * Test file cache is tolerant towards trailing (back)slashes on the configured cache directory.\n     *\n     * @dataProvider provideDirectories\n     */\n    public function testGenerateKey($expected, $input)\n    {\n        $cache = new ReadOnlyFilesystemCache($input);\n        $this->assertMatchesRegularExpression($expected, $cache->generateKey('_test_', static::class));\n    }\n\n    public static function provideDirectories()\n    {\n        $pattern = '#a/b/[a-zA-Z0-9]+/[a-zA-Z0-9]+.php$#';\n\n        return [\n            [$pattern, 'a/b'],\n            [$pattern, 'a/b/'],\n            [$pattern, 'a/b\\\\'],\n            [$pattern, 'a/b\\\\/'],\n            [$pattern, 'a/b\\\\//'],\n            ['#/'.substr($pattern, 1), '/a/b'],\n        ];\n    }\n\n    private function generateSource()\n    {\n        return strtr('<?php class {{class_name}} {}', [\n            '{{class_name}}' => $this->className,\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/CompilerTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Compiler;\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\n\nclass CompilerTest extends TestCase\n{\n    public function testReprNumericValueWithLocale()\n    {\n        $compiler = new Compiler(new Environment(new ArrayLoader()));\n\n        $locale = setlocale(\\LC_NUMERIC, '0');\n        if (false === $locale) {\n            $this->markTestSkipped('Your platform does not support locales.');\n        }\n\n        $required_locales = ['fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252'];\n        if (false === setlocale(\\LC_NUMERIC, $required_locales)) {\n            $this->markTestSkipped('Could not set any of required locales: '.implode(', ', $required_locales));\n        }\n\n        $this->assertEquals('1.2', $compiler->repr(1.2)->getSource());\n        $this->assertStringContainsString('fr', strtolower(setlocale(\\LC_NUMERIC, '0')));\n\n        setlocale(\\LC_NUMERIC, $locale);\n    }\n}\n"
  },
  {
    "path": "tests/ContainerRuntimeLoaderTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\ContainerInterface;\nuse Twig\\RuntimeLoader\\ContainerRuntimeLoader;\n\nclass ContainerRuntimeLoaderTest extends TestCase\n{\n    public function testLoad()\n    {\n        $container = $this->createMock(ContainerInterface::class);\n        $container->expects($this->once())->method('has')->with('stdClass')->willReturn(true);\n        $container->expects($this->once())->method('get')->with('stdClass')->willReturn(new \\stdClass());\n\n        $loader = new ContainerRuntimeLoader($container);\n\n        $this->assertInstanceOf('stdClass', $loader->load('stdClass'));\n    }\n\n    public function testLoadUnknownRuntimeReturnsNull()\n    {\n        $container = $this->createMock(ContainerInterface::class);\n        $container->expects($this->once())->method('has')->with('Foo');\n        $container->expects($this->never())->method('get');\n\n        $this->assertNull((new ContainerRuntimeLoader($container))->load('Foo'));\n    }\n}\n"
  },
  {
    "path": "tests/CustomExtensionTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Extension\\ExtensionInterface;\nuse Twig\\Loader\\ArrayLoader;\n\nclass CustomExtensionTest extends TestCase\n{\n    /**\n     * @group legacy\n     *\n     * @dataProvider provideInvalidExtensions\n     */\n    public function testGetInvalidOperators(ExtensionInterface $extension, $expectedExceptionMessage)\n    {\n        $env = new Environment(new ArrayLoader());\n        $env->addExtension($extension);\n\n        $this->expectException(\\InvalidArgumentException::class);\n        $this->expectExceptionMessage($expectedExceptionMessage);\n\n        $env->getExpressionParsers();\n    }\n\n    public static function provideInvalidExtensions()\n    {\n        return [\n            [new InvalidOperatorExtension([1, 2, 3]), '\"Twig\\Tests\\InvalidOperatorExtension::getOperators()\" must return an array of 2 elements, got 3.'],\n        ];\n    }\n}\n\nclass InvalidOperatorExtension implements ExtensionInterface\n{\n    private $operators;\n\n    public function __construct($operators)\n    {\n        $this->operators = $operators;\n    }\n\n    public function getTokenParsers(): array\n    {\n        return [];\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return [];\n    }\n\n    public function getFilters(): array\n    {\n        return [];\n    }\n\n    public function getTests(): array\n    {\n        return [];\n    }\n\n    public function getFunctions(): array\n    {\n        return [];\n    }\n\n    public function getOperators(): array\n    {\n        return $this->operators;\n    }\n}\n"
  },
  {
    "path": "tests/DeprecatedCallableInfoTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\DeprecatedCallableInfo;\n\nclass DeprecatedCallableInfoTest extends TestCase\n{\n    /**\n     * @dataProvider provideTestsForTriggerDeprecation\n     */\n    public function testTriggerDeprecation($expected, DeprecatedCallableInfo $info)\n    {\n        $info->setType('function');\n        $info->setName('foo');\n\n        $deprecations = [];\n        try {\n            set_error_handler(static function ($type, $msg) use (&$deprecations) {\n                if (\\E_USER_DEPRECATED === $type) {\n                    $deprecations[] = $msg;\n                }\n\n                return false;\n            });\n\n            $info->triggerDeprecation('foo.twig', 1);\n        } finally {\n            restore_error_handler();\n        }\n\n        $this->assertSame([$expected], $deprecations);\n    }\n\n    public static function provideTestsForTriggerDeprecation(): iterable\n    {\n        yield ['Since foo/bar 1.1: Twig Function \"foo\" is deprecated in foo.twig at line 1.', new DeprecatedCallableInfo('foo/bar', '1.1')];\n        yield ['Since foo/bar 1.1: Twig Function \"foo\" is deprecated; use \"alt_foo\" from the \"all/bar\" package (available since version 12.10) instead in foo.twig at line 1.', new DeprecatedCallableInfo('foo/bar', '1.1', 'alt_foo', 'all/bar', '12.10')];\n        yield ['Since foo/bar 1.1: Twig Function \"foo\" is deprecated; use \"alt_foo\" from the \"all/bar\" package instead in foo.twig at line 1.', new DeprecatedCallableInfo('foo/bar', '1.1', 'alt_foo', 'all/bar')];\n        yield ['Since foo/bar 1.1: Twig Function \"foo\" is deprecated; use \"alt_foo\" instead in foo.twig at line 1.', new DeprecatedCallableInfo('foo/bar', '1.1', 'alt_foo')];\n    }\n\n    public function testTriggerDeprecationWithoutFileOrLine()\n    {\n        $info = new DeprecatedCallableInfo('foo/bar', '1.1');\n        $info->setType('function');\n        $info->setName('foo');\n\n        $deprecations = [];\n        try {\n            set_error_handler(static function ($type, $msg) use (&$deprecations) {\n                if (\\E_USER_DEPRECATED === $type) {\n                    $deprecations[] = $msg;\n                }\n\n                return false;\n            });\n\n            $info->triggerDeprecation();\n            $info->triggerDeprecation('foo.twig');\n        } finally {\n            restore_error_handler();\n        }\n\n        $this->assertSame([\n            'Since foo/bar 1.1: Twig Function \"foo\" is deprecated.',\n            'Since foo/bar 1.1: Twig Function \"foo\" is deprecated in foo.twig.',\n        ], $deprecations);\n    }\n}\n"
  },
  {
    "path": "tests/DummyBackedEnum.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\nenum DummyBackedEnum: string\n{\n    case FOO = 'foo';\n    case BAR = 'bar';\n}\n"
  },
  {
    "path": "tests/DummyUnitEnum.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\nenum DummyUnitEnum\n{\n    case BAR;\n    case BAZ;\n}\n"
  },
  {
    "path": "tests/EnvironmentTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Bridge\\PhpUnit\\ExpectDeprecationTrait;\nuse Twig\\Cache\\CacheInterface;\nuse Twig\\Cache\\FilesystemCache;\nuse Twig\\Environment;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\Infix\\BinaryOperatorExpressionParser;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\ExpressionParser\\Prefix\\UnaryOperatorExpressionParser;\nuse Twig\\ExpressionParser\\PrefixExpressionParserInterface;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\Extension\\ExtensionInterface;\nuse Twig\\Extension\\GlobalsInterface;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Loader\\FilesystemLoader;\nuse Twig\\Loader\\LoaderInterface;\nuse Twig\\Node\\Node;\nuse Twig\\NodeVisitor\\NodeVisitorInterface;\nuse Twig\\RuntimeLoader\\RuntimeLoaderInterface;\nuse Twig\\Source;\nuse Twig\\Token;\nuse Twig\\TokenParser\\AbstractTokenParser;\nuse Twig\\TokenParser\\TokenParserInterface;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\nclass EnvironmentTest extends TestCase\n{\n    use ExpectDeprecationTrait;\n\n    public function testVersionConstants()\n    {\n        $version = Environment::VERSION;\n        $exploded = explode('-', $version);\n        $this->assertEquals(Environment::EXTRA_VERSION, $exploded[1] ?? '');\n\n        $version = $exploded[0];\n        $exploded = explode('.', $version);\n        $this->assertEquals(Environment::MAJOR_VERSION, $exploded[0]);\n        $this->assertEquals(Environment::MINOR_VERSION, $exploded[1]);\n        $this->assertEquals(Environment::RELEASE_VERSION, $exploded[2]);\n\n        $this->assertEquals(Environment::VERSION_ID, Environment::MAJOR_VERSION * 10000 + Environment::MINOR_VERSION * 100 + Environment::RELEASE_VERSION);\n    }\n\n    public function testAutoescapeOption()\n    {\n        $loader = new ArrayLoader([\n            'html' => '{{ foo }} {{ foo }}',\n            'js' => '{{ bar }} {{ bar }}',\n        ]);\n\n        $twig = new Environment($loader, [\n            'debug' => true,\n            'cache' => false,\n            'autoescape' => [$this, 'escapingStrategyCallback'],\n        ]);\n\n        $this->assertEquals('foo&lt;br/ &gt; foo&lt;br/ &gt;', $twig->render('html', ['foo' => 'foo<br/ >']));\n        $this->assertEquals('foo\\u003Cbr\\/\\u0020\\u003E foo\\u003Cbr\\/\\u0020\\u003E', $twig->render('js', ['bar' => 'foo<br/ >']));\n    }\n\n    public function escapingStrategyCallback($name)\n    {\n        return $name;\n    }\n\n    public function testGlobals()\n    {\n        $loader = $this->createMock(LoaderInterface::class);\n        $loader->expects($this->any())->method('getSourceContext')->willReturn(new Source('', ''));\n\n        // globals can be added after calling getGlobals\n        $twig = new Environment($loader);\n        $twig->addGlobal('foo', 'foo');\n        $twig->getGlobals();\n        $twig->addGlobal('foo', 'bar');\n        $globals = $twig->getGlobals();\n        $this->assertEquals('bar', $globals['foo']);\n\n        // globals can be modified after a template has been loaded\n        $twig = new Environment($loader);\n        $twig->addGlobal('foo', 'foo');\n        $twig->getGlobals();\n        $twig->load('index');\n        $twig->addGlobal('foo', 'bar');\n        $globals = $twig->getGlobals();\n        $this->assertEquals('bar', $globals['foo']);\n\n        // globals can be modified after extensions init\n        $twig = new Environment($loader);\n        $twig->addGlobal('foo', 'foo');\n        $twig->getGlobals();\n        $twig->getFunctions();\n        $twig->addGlobal('foo', 'bar');\n        $globals = $twig->getGlobals();\n        $this->assertEquals('bar', $globals['foo']);\n\n        // globals can be modified after extensions and a template has been loaded\n        $arrayLoader = new ArrayLoader(['index' => '{{foo}}']);\n        $twig = new Environment($arrayLoader);\n        $twig->addGlobal('foo', 'foo');\n        $twig->getGlobals();\n        $twig->getFunctions();\n        $twig->load('index');\n        $twig->addGlobal('foo', 'bar');\n        $globals = $twig->getGlobals();\n        $this->assertEquals('bar', $globals['foo']);\n\n        $twig = new Environment($arrayLoader);\n        $twig->getGlobals();\n        $twig->addGlobal('foo', 'bar');\n        $template = $twig->load('index');\n        $this->assertEquals('bar', $template->render([]));\n\n        // globals cannot be added after a template has been loaded\n        $twig = new Environment($loader);\n        $twig->addGlobal('foo', 'foo');\n        $twig->getGlobals();\n        $twig->load('index');\n        try {\n            $twig->addGlobal('bar', 'bar');\n            $this->fail();\n        } catch (\\LogicException $e) {\n            $this->assertArrayNotHasKey('bar', $twig->getGlobals());\n        }\n\n        // globals cannot be added after extensions init\n        $twig = new Environment($loader);\n        $twig->addGlobal('foo', 'foo');\n        $twig->getGlobals();\n        $twig->getFunctions();\n        try {\n            $twig->addGlobal('bar', 'bar');\n            $this->fail();\n        } catch (\\LogicException $e) {\n            $this->assertArrayNotHasKey('bar', $twig->getGlobals());\n        }\n\n        // globals cannot be added after extensions and a template has been loaded\n        $twig = new Environment($loader);\n        $twig->addGlobal('foo', 'foo');\n        $twig->getGlobals();\n        $twig->getFunctions();\n        $twig->load('index');\n        try {\n            $twig->addGlobal('bar', 'bar');\n            $this->fail();\n        } catch (\\LogicException $e) {\n            $this->assertArrayNotHasKey('bar', $twig->getGlobals());\n        }\n\n        // test adding globals after a template has been loaded without call to getGlobals\n        $twig = new Environment($loader);\n        $twig->load('index');\n        try {\n            $twig->addGlobal('bar', 'bar');\n            $this->fail();\n        } catch (\\LogicException $e) {\n            $this->assertArrayNotHasKey('bar', $twig->getGlobals());\n        }\n    }\n\n    public function testExtensionsAreNotInitializedWhenRenderingACompiledTemplate()\n    {\n        $cache = new FilesystemCache($dir = sys_get_temp_dir().'/twig');\n        $options = ['cache' => $cache, 'auto_reload' => false, 'debug' => false];\n\n        // force compilation\n        $twig = new Environment($loader = new ArrayLoader(['index' => '{{ foo }}']), $options);\n        $twig->addExtension($extension = new class extends AbstractExtension {\n            public bool $throw = false;\n\n            public function getFilters(): array\n            {\n                if ($this->throw) {\n                    throw new \\RuntimeException('Extension are not supposed to be initialized.');\n                }\n\n                return parent::getFilters();\n            }\n        });\n\n        $key = $cache->generateKey('index', $twig->getTemplateClass('index'));\n        $cache->write($key, $twig->compileSource(new Source('{{ foo }}', 'index')));\n\n        // check that extensions won't be initialized when rendering a template that is already in the cache\n        $twig = new Environment($loader, $options);\n        $extension->throw = true;\n        $twig->addExtension($extension);\n\n        // render template\n        $output = $twig->render('index', ['foo' => 'bar']);\n        $this->assertEquals('bar', $output);\n\n        FilesystemHelper::removeDir($dir);\n    }\n\n    public function testAutoReloadCacheMiss()\n    {\n        $templateName = __FUNCTION__;\n        $templateContent = __FUNCTION__;\n\n        $cache = $this->createMock(CacheInterface::class);\n        $loader = $this->getMockLoader($templateName, $templateContent);\n        $twig = new Environment($loader, ['cache' => $cache, 'auto_reload' => true, 'debug' => false]);\n\n        // Cache miss: getTimestamp returns 0 and as a result the load() is\n        // skipped.\n        $cache->expects($this->once())\n            ->method('generateKey')\n            ->willReturn('key');\n        $cache->expects($this->once())\n            ->method('getTimestamp')\n            ->willReturn(0);\n        $loader->expects($this->never())\n            ->method('isFresh');\n        $cache->expects($this->once())\n            ->method('write');\n        $cache->expects($this->once())\n            ->method('load');\n\n        $twig->load($templateName);\n    }\n\n    public function testAutoReloadCacheHit()\n    {\n        $templateName = __FUNCTION__;\n        $templateContent = __FUNCTION__;\n\n        $cache = $this->createMock(CacheInterface::class);\n        $loader = $this->getMockLoader($templateName, $templateContent);\n        $twig = new Environment($loader, ['cache' => $cache, 'auto_reload' => true, 'debug' => false]);\n\n        $now = time();\n\n        // Cache hit: getTimestamp returns something > extension timestamps and\n        // the loader returns true for isFresh().\n        $cache->expects($this->once())\n            ->method('generateKey')\n            ->willReturn('key');\n        $cache->expects($this->once())\n            ->method('getTimestamp')\n            ->willReturn($now);\n        $loader->expects($this->once())\n            ->method('isFresh')\n            ->willReturn(true);\n        $cache->expects($this->atLeastOnce())\n            ->method('load');\n\n        $twig->load($templateName);\n    }\n\n    public function testAutoReloadOutdatedCacheHit()\n    {\n        $templateName = __FUNCTION__;\n        $templateContent = __FUNCTION__;\n\n        $cache = $this->createMock(CacheInterface::class);\n        $loader = $this->getMockLoader($templateName, $templateContent);\n        $twig = new Environment($loader, ['cache' => $cache, 'auto_reload' => true, 'debug' => false]);\n\n        $now = time();\n\n        $cache->expects($this->once())\n            ->method('generateKey')\n            ->willReturn('key');\n        $cache->expects($this->once())\n            ->method('getTimestamp')\n            ->willReturn($now);\n        $loader->expects($this->once())\n            ->method('isFresh')\n            ->willReturn(false);\n        $cache->expects($this->once())\n            ->method('write');\n        $cache->expects($this->once())\n            ->method('load');\n\n        $twig->load($templateName);\n    }\n\n    public function testHasGetExtensionByClassName()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->addExtension($ext = new EnvironmentTest_Extension());\n        $this->assertSame($ext, $twig->getExtension(EnvironmentTest_Extension::class));\n        $this->assertSame($ext, $twig->getExtension(EnvironmentTest_Extension::class));\n    }\n\n    public function testAddExtension()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->addExtension(new EnvironmentTest_Extension());\n\n        $this->assertArrayHasKey('test', $twig->getTokenParsers());\n        $this->assertArrayHasKey('foo_filter', $twig->getFilters());\n        $this->assertArrayHasKey('foo_function', $twig->getFunctions());\n        $this->assertArrayHasKey('foo_test', $twig->getTests());\n        $this->assertNotNull($twig->getExpressionParsers()->getByName(PrefixExpressionParserInterface::class, 'foo_unary'));\n        $this->assertNotNull($twig->getExpressionParsers()->getByName(InfixExpressionParserInterface::class, 'foo_binary'));\n        $this->assertArrayHasKey('foo_global', $twig->getGlobals());\n        $visitors = $twig->getNodeVisitors();\n        $found = false;\n        foreach ($visitors as $visitor) {\n            if ($visitor instanceof EnvironmentTest_NodeVisitor) {\n                $found = true;\n            }\n        }\n        $this->assertTrue($found);\n    }\n\n    public function testAddMockExtension()\n    {\n        $extension = $this->createMock(ExtensionInterface::class);\n        $loader = new ArrayLoader(['page' => 'hey']);\n\n        $twig = new Environment($loader);\n        $twig->addExtension($extension);\n\n        $this->assertInstanceOf(ExtensionInterface::class, $twig->getExtension($extension::class));\n        $this->assertTrue($twig->isTemplateFresh('page', time()));\n    }\n\n    public function testOverrideExtension()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->addExtension(new EnvironmentTest_Extension());\n\n        $this->expectException(\\LogicException::class);\n        $this->expectExceptionMessage('Unable to register extension \"Twig\\Tests\\EnvironmentTest_Extension\" as it is already registered.');\n\n        $twig->addExtension(new EnvironmentTest_Extension());\n    }\n\n    public function testAddRuntimeLoader()\n    {\n        $runtimeLoader = $this->createMock(RuntimeLoaderInterface::class);\n        $runtimeLoader->expects($this->any())->method('load')->willReturn(new EnvironmentTest_Runtime());\n\n        $loader = new ArrayLoader([\n            'func_array' => '{{ from_runtime_array(\"foo\") }}',\n            'func_array_default' => '{{ from_runtime_array() }}',\n            'func_array_named_args' => '{{ from_runtime_array(name=\"foo\") }}',\n            'func_string' => '{{ from_runtime_string(\"foo\") }}',\n            'func_string_default' => '{{ from_runtime_string() }}',\n            'func_string_named_args' => '{{ from_runtime_string(name=\"foo\") }}',\n        ]);\n\n        $twig = new Environment($loader, ['autoescape' => false]);\n        $twig->addExtension(new EnvironmentTest_ExtensionWithoutRuntime());\n        $twig->addRuntimeLoader($runtimeLoader);\n\n        $this->assertEquals('foo', $twig->render('func_array'));\n        $this->assertEquals('bar', $twig->render('func_array_default'));\n        $this->assertEquals('foo', $twig->render('func_array_named_args'));\n        $this->assertEquals('foo', $twig->render('func_string'));\n        $this->assertEquals('bar', $twig->render('func_string_default'));\n        $this->assertEquals('foo', $twig->render('func_string_named_args'));\n    }\n\n    public function testFailLoadTemplate()\n    {\n        $template = 'testFailLoadTemplate.twig';\n        $twig = new Environment(new ArrayLoader([$template => false]));\n\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('Failed to load Twig template \"testFailLoadTemplate.twig\", index \"112233\": cache might be corrupted in \"testFailLoadTemplate.twig\".');\n\n        $twig->loadTemplate($twig->getTemplateClass($template), $template, 112233);\n    }\n\n    public function testUndefinedFunctionCallback()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->registerUndefinedFunctionCallback(static function (string $name) {\n            if ('dynamic' === $name) {\n                return new TwigFunction('dynamic', static function () { return 'dynamic'; });\n            }\n\n            return false;\n        });\n\n        $this->assertNull($twig->getFunction('does_not_exist'));\n        $this->assertInstanceOf(TwigFunction::class, $function = $twig->getFunction('dynamic'));\n        $this->assertSame('dynamic', $function->getName());\n    }\n\n    public function testUndefinedFilterCallback()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->registerUndefinedFilterCallback(static function (string $name) {\n            if ('dynamic' === $name) {\n                return new TwigFilter('dynamic', static function () { return 'dynamic'; });\n            }\n\n            return false;\n        });\n\n        $this->assertNull($twig->getFilter('does_not_exist'));\n        $this->assertInstanceOf(TwigFilter::class, $filter = $twig->getFilter('dynamic'));\n        $this->assertSame('dynamic', $filter->getName());\n    }\n\n    public function testUndefinedTestCallback()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->registerUndefinedTestCallback(static function (string $name) {\n            if ('dynamic' === $name) {\n                return new TwigTest('dynamic', static function () { return 'dynamic'; });\n            }\n\n            return false;\n        });\n\n        $this->assertNull($twig->getTest('does_not_exist'));\n        $this->assertInstanceOf(TwigTest::class, $test = $twig->getTest('dynamic'));\n        $this->assertSame('dynamic', $test->getName());\n    }\n\n    public function testUndefinedTokenParserCallback()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->registerUndefinedTokenParserCallback(function (string $name) {\n            if ('dynamic' === $name) {\n                $parser = $this->createMock(TokenParserInterface::class);\n                $parser->expects($this->once())->method('getTag')->willReturn('dynamic');\n\n                return $parser;\n            }\n\n            return false;\n        });\n\n        $this->assertNull($twig->getTokenParser('does_not_exist'));\n        $this->assertInstanceOf(TokenParserInterface::class, $parser = $twig->getTokenParser('dynamic'));\n        $this->assertSame('dynamic', $parser->getTag());\n    }\n\n    /**\n     * @group legacy\n     *\n     * @requires PHP 8\n     */\n    public function testLegacyEchoingNode()\n    {\n        $loader = new ArrayLoader(['echo_bar' => 'A{% set v %}B{% test %}C{% endset %}D{% test %}E{{ v }}F{% set w %}{% test %}{% endset %}G{{ w }}H']);\n\n        $twig = new Environment($loader);\n        $twig->addExtension(new EnvironmentTest_Extension());\n\n        if ($twig->useYield()) {\n            $this->expectException(SyntaxError::class);\n            $this->expectExceptionMessage('An exception has been thrown during the compilation of a template (\"You cannot enable the \"use_yield\" option of Twig as node \"Twig\\Tests\\EnvironmentTest_LegacyEchoingNode\" is not marked as ready for it; please make it ready and then flag it with the #[\\Twig\\Attribute\\YieldReady] attribute.\") in \"echo_bar\".');\n        } else {\n            $this->expectDeprecation(<<<'EOF'\nSince twig/twig 3.9: Twig node \"Twig\\Tests\\EnvironmentTest_LegacyEchoingNode\" is not marked as ready for using \"yield\" instead of \"echo\"; please make it ready and then flag it with the #[\\Twig\\Attribute\\YieldReady] attribute.\n  Since twig/twig 3.9: Using \"echo\" is deprecated, use \"yield\" instead in \"Twig\\Tests\\EnvironmentTest_LegacyEchoingNode\", then flag the class with #[\\Twig\\Attribute\\YieldReady].\nEOF\n            );\n        }\n\n        $this->assertSame('ADbarEBbarCFGbarH', $twig->render('echo_bar'));\n    }\n\n    protected function getMockLoader($templateName, $templateContent)\n    {\n        $loader = $this->createMock(LoaderInterface::class);\n        $loader->expects($this->any())\n          ->method('getSourceContext')\n          ->with($templateName)\n          ->willReturn(new Source($templateContent, $templateName));\n        $loader->expects($this->any())\n          ->method('getCacheKey')\n          ->with($templateName)\n          ->willReturn($templateName);\n\n        return $loader;\n    }\n\n    public function testResettingGlobals()\n    {\n        $twig = new Environment(new ArrayLoader(['index' => '']));\n        $twig->addExtension(new class extends AbstractExtension implements GlobalsInterface {\n            public function getGlobals(): array\n            {\n                return [\n                    'global_ext' => bin2hex(random_bytes(16)),\n                ];\n            }\n        });\n\n        // Force extensions initialization\n        $twig->load('index');\n\n        // Simulate request\n        $g1 = $twig->getGlobals();\n        // Simulate another call from request 1 (the globals are cached)\n        $g2 = $twig->getGlobals();\n        $this->assertSame($g1['global_ext'], $g2['global_ext']);\n\n        // Simulate request 2\n        $twig->resetGlobals();\n        $g3 = $twig->getGlobals();\n        $this->assertNotSame($g3['global_ext'], $g2['global_ext']);\n    }\n\n    public function testHotCache()\n    {\n        $dir = sys_get_temp_dir().'/twig-hot-cache-test';\n        if (is_dir($dir)) {\n            FilesystemHelper::removeDir($dir);\n        }\n        mkdir($dir);\n        file_put_contents($dir.'/index.twig', 'x');\n        try {\n            $twig = new Environment(new FilesystemLoader($dir), [\n                'debug' => false,\n                'auto_reload' => false,\n                'cache' => $dir.'/cache',\n            ]);\n\n            // prime the cache\n            $this->assertSame('x', $twig->load('index.twig')->render([]));\n\n            // update the template\n            file_put_contents($dir.'/index.twig', 'y');\n\n            // re-render, should use the cached version\n            $this->assertSame('x', $twig->load('index.twig')->render([]));\n\n            // clear the cache\n            $twig->removeCache('index.twig');\n\n            // re-render, should use the updated template\n            $this->assertSame('y', $twig->load('index.twig')->render([]));\n\n            // the new template should not be cached\n            $iterator = new \\RecursiveIteratorIterator(new \\RecursiveDirectoryIterator($dir.'/cache', \\FilesystemIterator::SKIP_DOTS), \\RecursiveIteratorIterator::CHILD_FIRST);\n            $count = 0;\n            foreach ($iterator as $fileInfo) {\n                if (!$fileInfo->isDir()) {\n                    ++$count;\n                }\n            }\n            $this->assertSame(0, $count);\n\n            // re-render, should use the updated template\n            $this->assertSame('y', $twig->load('index.twig')->render([]));\n        } finally {\n            FilesystemHelper::removeDir($dir);\n        }\n    }\n}\n\nclass EnvironmentTest_Extension_WithGlobals extends AbstractExtension\n{\n    public function getGlobals()\n    {\n        return [\n            'foo_global' => 'foo_global',\n        ];\n    }\n}\n\nclass EnvironmentTest_Extension extends AbstractExtension implements GlobalsInterface\n{\n    public function getTokenParsers(): array\n    {\n        return [\n            new EnvironmentTest_TokenParser(),\n        ];\n    }\n\n    public function getNodeVisitors(): array\n    {\n        return [\n            new EnvironmentTest_NodeVisitor(),\n        ];\n    }\n\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('foo_filter'),\n        ];\n    }\n\n    public function getTests(): array\n    {\n        return [\n            new TwigTest('foo_test'),\n        ];\n    }\n\n    public function getFunctions(): array\n    {\n        return [\n            new TwigFunction('foo_function'),\n        ];\n    }\n\n    public function getExpressionParsers(): array\n    {\n        return [\n            new UnaryOperatorExpressionParser('', 'foo_unary', 0),\n            new BinaryOperatorExpressionParser('', 'foo_binary', 0),\n        ];\n    }\n\n    public function getGlobals(): array\n    {\n        return [\n            'foo_global' => 'foo_global',\n        ];\n    }\n}\n\nclass EnvironmentTest_TokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);\n\n        return new EnvironmentTest_LegacyEchoingNode([], [], 1);\n    }\n\n    public function getTag(): string\n    {\n        return 'test';\n    }\n}\n\nclass EnvironmentTest_NodeVisitor implements NodeVisitorInterface\n{\n    public function enterNode(Node $node, Environment $env): Node\n    {\n        return $node;\n    }\n\n    public function leaveNode(Node $node, Environment $env): ?Node\n    {\n        return $node;\n    }\n\n    public function getPriority(): int\n    {\n        return 0;\n    }\n}\n\nclass EnvironmentTest_ExtensionWithoutRuntime extends AbstractExtension\n{\n    public function getFunctions(): array\n    {\n        return [\n            new TwigFunction('from_runtime_array', ['Twig\\Tests\\EnvironmentTest_Runtime', 'fromRuntime']),\n            new TwigFunction('from_runtime_string', 'Twig\\Tests\\EnvironmentTest_Runtime::fromRuntime'),\n        ];\n    }\n}\n\nclass EnvironmentTest_Runtime\n{\n    public function fromRuntime($name = 'bar')\n    {\n        return $name;\n    }\n}\n\nclass EnvironmentTest_LegacyEchoingNode extends Node\n{\n    public function compile($compiler)\n    {\n        $compiler\n            ->addDebugInfo($this)\n            ->write('echo \"bar\";')\n        ;\n    }\n}\n"
  },
  {
    "path": "tests/ErrorTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Attribute\\YieldReady;\nuse Twig\\Compiler;\nuse Twig\\Environment;\nuse Twig\\Error\\Error;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Loader\\FilesystemLoader;\nuse Twig\\Loader\\LoaderInterface;\nuse Twig\\Node\\Node;\nuse Twig\\Source;\nuse Twig\\Token;\nuse Twig\\TokenParser\\AbstractTokenParser;\n\nclass ErrorTest extends TestCase\n{\n    public function testErrorWithObjectFilename()\n    {\n        $error = new Error('foo');\n        $error->setSourceContext(new Source('', new \\SplFileInfo(__FILE__)));\n\n        $this->assertStringContainsString('tests'.\\DIRECTORY_SEPARATOR.'ErrorTest.php', $error->getMessage());\n    }\n\n    public function testTwigExceptionGuessWithMissingVarAndArrayLoader()\n    {\n        $loader = new ArrayLoader([\n            'base.html' => '{% block content %}{% endblock %}',\n            'index.html' => <<<EOHTML\n{% extends 'base.html' %}\n{% block content %}\n    {{ foo.bar }}\n{% endblock %}\n{% block foo %}\n    {{ foo.bar }}\n{% endblock %}\nEOHTML,\n        ]);\n\n        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);\n\n        $template = $twig->load('index.html');\n        try {\n            $template->render([]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals('Variable \"foo\" does not exist in \"index.html\" at line 3.', $e->getMessage());\n            $this->assertEquals(3, $e->getTemplateLine());\n            $this->assertEquals('index.html', $e->getSourceContext()->getName());\n        }\n    }\n\n    public function testTwigExceptionGuessWithExceptionAndArrayLoader()\n    {\n        $loader = new ArrayLoader([\n            'base.html' => '{% block content %}{% endblock %}',\n            'index.html' => <<<EOHTML\n{% extends 'base.html' %}\n{% block content %}\n    {{ foo.bar }}\n{% endblock %}\n{% block foo %}\n    {{ foo.bar }}\n{% endblock %}\nEOHTML,\n        ]);\n        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);\n\n        $template = $twig->load('index.html');\n        try {\n            $template->render(['foo' => new ErrorTest_Foo()]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals('An exception has been thrown during the rendering of a template (\"Runtime error...\") in \"index.html\" at line 3.', $e->getMessage());\n            $this->assertEquals(3, $e->getTemplateLine());\n            $this->assertEquals('index.html', $e->getSourceContext()->getName());\n        }\n    }\n\n    public function testTwigExceptionGuessWithMissingVarAndFilesystemLoader()\n    {\n        $loader = new FilesystemLoader(__DIR__.'/Fixtures/errors');\n        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);\n\n        $template = $twig->load('index.html');\n        try {\n            $template->render([]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals('Variable \"foo\" does not exist in \"index.html\" at line 3.', $e->getMessage());\n            $this->assertEquals(3, $e->getTemplateLine());\n            $this->assertEquals('index.html', $e->getSourceContext()->getName());\n            $this->assertEquals(3, $e->getLine());\n            $this->assertEquals(strtr(__DIR__.'/Fixtures/errors/index.html', '/', \\DIRECTORY_SEPARATOR), $e->getFile());\n        }\n    }\n\n    public function testTwigExceptionGuessWithExceptionAndFilesystemLoader()\n    {\n        $loader = new FilesystemLoader(__DIR__.'/Fixtures/errors');\n        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);\n\n        $template = $twig->load('index.html');\n        try {\n            $template->render(['foo' => new ErrorTest_Foo()]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals('An exception has been thrown during the rendering of a template (\"Runtime error...\") in \"index.html\" at line 3.', $e->getMessage());\n            $this->assertEquals(3, $e->getTemplateLine());\n            $this->assertEquals('index.html', $e->getSourceContext()->getName());\n            $this->assertEquals(3, $e->getLine());\n            $this->assertEquals(strtr(__DIR__.'/Fixtures/errors/index.html', '/', \\DIRECTORY_SEPARATOR), $e->getFile());\n        }\n    }\n\n    /**\n     * @dataProvider getErroredTemplates\n     */\n    public function testTwigExceptionAddsFileAndLine($templates, $name, $line)\n    {\n        $loader = new ArrayLoader($templates);\n        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);\n\n        $template = $twig->load('index');\n\n        try {\n            $template->render([]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals(\\sprintf('Variable \"foo\" does not exist in \"%s\" at line %d.', $name, $line), $e->getMessage());\n            $this->assertEquals($line, $e->getTemplateLine());\n            $this->assertEquals($name, $e->getSourceContext()->getName());\n        }\n\n        try {\n            $template->render(['foo' => new ErrorTest_Foo()]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals(\\sprintf('An exception has been thrown during the rendering of a template (\"Runtime error...\") in \"%s\" at line %d.', $name, $line), $e->getMessage());\n            $this->assertEquals($line, $e->getTemplateLine());\n            $this->assertEquals($name, $e->getSourceContext()->getName());\n        }\n    }\n\n    public function testTwigArrayFilterThrowsRuntimeExceptions()\n    {\n        $loader = new ArrayLoader([\n            'filter-null.html' => <<<EOHTML\n{# Argument 1 passed to IteratorIterator::__construct() must implement interface Traversable, null given: #}\n{% for n in variable|filter(x => x > 3) %}\n    This list contains {{n}}.\n{% endfor %}\nEOHTML,\n        ]);\n\n        $twig = new Environment($loader, ['debug' => true, 'cache' => false]);\n\n        $template = $twig->load('filter-null.html');\n        $out = $template->render(['variable' => [1, 2, 3, 4]]);\n        $this->assertEquals('This list contains 4.', trim($out));\n\n        try {\n            $template->render(['variable' => null]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals(2, $e->getTemplateLine());\n            $this->assertEquals('filter-null.html', $e->getSourceContext()->getName());\n        }\n    }\n\n    public function testTwigArrayMapThrowsRuntimeExceptions()\n    {\n        $loader = new ArrayLoader([\n            'map-null.html' => <<<EOHTML\n{# We expect a runtime error if `variable` is not traversable #}\n{% for n in variable|map(x => x * 3) %}\n    {{- n -}}\n{% endfor %}\nEOHTML,\n        ]);\n\n        $twig = new Environment($loader, ['debug' => true, 'cache' => false]);\n\n        $template = $twig->load('map-null.html');\n        $out = $template->render(['variable' => [1, 2, 3, 4]]);\n        $this->assertEquals('36912', trim($out));\n\n        try {\n            $template->render(['variable' => null]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals(2, $e->getTemplateLine());\n            $this->assertEquals('map-null.html', $e->getSourceContext()->getName());\n        }\n    }\n\n    public function testTwigArrayReduceThrowsRuntimeExceptions()\n    {\n        $loader = new ArrayLoader([\n            'reduce-null.html' => <<<EOHTML\n{# We expect a runtime error if `variable` is not traversable #}\n{{ variable|reduce((carry, x) => carry + x) }}\nEOHTML,\n        ]);\n\n        $twig = new Environment($loader, ['debug' => true, 'cache' => false]);\n\n        $template = $twig->load('reduce-null.html');\n        $out = $template->render(['variable' => [1, 2, 3, 4]]);\n        $this->assertEquals('10', trim($out));\n\n        try {\n            $template->render(['variable' => null]);\n\n            $this->fail();\n        } catch (RuntimeError $e) {\n            $this->assertEquals(2, $e->getTemplateLine());\n            $this->assertEquals('reduce-null.html', $e->getSourceContext()->getName());\n        }\n    }\n\n    public function testTwigExceptionUpdateFileAndLineTogether()\n    {\n        $twig = new Environment(new ArrayLoader([\n            'index' => \"\\n\\n\\n\\n{{ foo() }}\",\n        ]), ['debug' => true, 'cache' => false]);\n\n        try {\n            $twig->load('index')->render([]);\n        } catch (SyntaxError $e) {\n            $this->assertSame('Unknown \"foo\" function in \"index\" at line 5.', $e->getMessage());\n            $this->assertSame(5, $e->getTemplateLine());\n            // as we are using an ArrayLoader, we don't have a file, so the line should not be the template line,\n            // but the line of the error in the Parser.php file\n            $this->assertStringContainsString('Parser.php', $e->getFile());\n            $this->assertNotSame(5, $e->getLine());\n        }\n    }\n\n    /**\n     * @dataProvider getErrorWithoutLineAndContextData\n     */\n    public function testErrorWithoutLineAndContext(LoaderInterface $loader, bool $debug, bool $addDebugInfo, bool $exceptionWithLineAndContext, int $errorLine)\n    {\n        $twig = new Environment($loader, ['debug' => $debug, 'cache' => false]);\n        $twig->removeCache('no_line_and_context_exception.twig');\n        $twig->removeCache('no_line_and_context_exception_include_line_5.twig');\n        $twig->removeCache('no_line_and_context_exception_include_line_1.twig');\n        $twig->addTokenParser(new class($addDebugInfo, $exceptionWithLineAndContext) extends AbstractTokenParser {\n            public function __construct(private bool $addDebugInfo, private bool $exceptionWithLineAndContext)\n            {\n            }\n\n            public function parse(Token $token)\n            {\n                $stream = $this->parser->getStream();\n                $lineno = $stream->getCurrent()->getLine();\n                $stream->expect(Token::BLOCK_END_TYPE);\n\n                return new #[YieldReady] class($lineno, $this->addDebugInfo, $this->exceptionWithLineAndContext) extends Node {\n                    public function __construct(int $lineno, private bool $addDebugInfo, private bool $exceptionWithLineAndContext)\n                    {\n                        parent::__construct([], [], $lineno);\n                    }\n\n                    public function compile(Compiler $compiler): void\n                    {\n                        if ($this->addDebugInfo) {\n                            $compiler->addDebugInfo($this);\n                        }\n                        if ($this->exceptionWithLineAndContext) {\n                            $compiler\n                                ->write('throw new \\Twig\\Error\\RuntimeError(\"Runtime error.\", ')\n                                ->repr($this->lineno)->raw(', $this->getSourceContext()')\n                                ->raw(\");\\n\")\n                            ;\n                        } else {\n                            $compiler->write('throw new \\Twig\\Error\\RuntimeError(\"Runtime error.\");');\n                        }\n                    }\n                };\n            }\n\n            public function getTag()\n            {\n                return 'foo';\n            }\n        });\n\n        try {\n            $twig->render('no_line_and_context_exception.twig', ['line' => $errorLine]);\n            $this->fail();\n        } catch (RuntimeError $e) {\n            if (1 === $errorLine && !$addDebugInfo && !$exceptionWithLineAndContext) {\n                // When the template only has the custom node that throws the error, we cannot find the line of the error\n                // as we have no debug info and no line and context in the exception\n                $this->assertSame(\\sprintf('Runtime error in \"no_line_and_context_exception_include_line_%d.twig\".', $errorLine), $e->getMessage());\n                $this->assertSame(0, $e->getTemplateLine());\n            } else {\n                // When the template has some space before the custom node, the associated TextNode outputs some debug info at line 1\n                // that's why the line is 1 when we have no debug info and no line and context in the exception\n                $line = $addDebugInfo || $exceptionWithLineAndContext ? $errorLine : 1;\n                $this->assertSame(\\sprintf('Runtime error in \"no_line_and_context_exception_include_line_%d.twig\" at line %d.', $errorLine, $line), $e->getMessage());\n                $this->assertSame($line, $e->getTemplateLine());\n            }\n\n            $line = $addDebugInfo || $exceptionWithLineAndContext ? $errorLine : 1;\n            if ($loader instanceof FilesystemLoader) {\n                $this->assertStringContainsString(\\sprintf('errors/no_line_and_context_exception_include_line_%d.twig', $errorLine), $e->getFile());\n                $line = $addDebugInfo || $exceptionWithLineAndContext ? $errorLine : (1 === $errorLine ? -1 : 1);\n                $this->assertSame($line, $e->getLine());\n            } else {\n                $this->assertStringContainsString('Environment.php', $e->getFile());\n                $this->assertNotSame($line, $e->getLine());\n            }\n        }\n    }\n\n    public static function getErrorWithoutLineAndContextData(): iterable\n    {\n        $fileLoaders = [\n            new ArrayLoader([\n                'no_line_and_context_exception.twig' => \"\\n\\n{{ include('no_line_and_context_exception_include_line_' ~ line ~ '.twig') }}\",\n                'no_line_and_context_exception_include_line_5.twig' => \"\\n\\n\\n\\n{% foo %}\",\n                'no_line_and_context_exception_include_line_1.twig' => '{% foo %}',\n            ]),\n            new FilesystemLoader(__DIR__.'/Fixtures/errors'),\n        ];\n\n        foreach ($fileLoaders as $loader) {\n            foreach ([false, true] as $exceptionWithLineAndContext) {\n                foreach ([false, true] as $addDebugInfo) {\n                    foreach ([false, true] as $debug) {\n                        foreach ([5, 1] as $line) {\n                            $name = ($loader instanceof FilesystemLoader ? 'filesystem' : 'array')\n                                .($debug ? '_with_debug' : '_without_debug')\n                                .($addDebugInfo ? '_with_debug_info' : '_without_debug_info')\n                                .($exceptionWithLineAndContext ? '_with_context' : '_without_context')\n                                .('_line_'.$line)\n                            ;\n                            yield $name => [$loader, $debug, $addDebugInfo, $exceptionWithLineAndContext, $line];\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    public static function getErroredTemplates()\n    {\n        return [\n            // error occurs in a template\n            [\n                [\n                    'index' => \"\\n\\n{{ foo.bar }}\\n\\n\\n{{ 'foo' }}\",\n                ],\n                'index', 3,\n            ],\n\n            // error occurs in an included template\n            [\n                [\n                    'index' => \"{% include 'partial' %}\",\n                    'partial' => '{{ foo.bar }}',\n                ],\n                'partial', 1,\n            ],\n\n            // error occurs in a parent block when called via parent()\n            [\n                [\n                    'index' => \"{% extends 'base' %}\n                    {% block content %}\n                        {{ parent() }}\n                    {% endblock %}\",\n                    'base' => '{% block content %}{{ foo.bar }}{% endblock %}',\n                ],\n                'base', 1,\n            ],\n\n            // error occurs in a block from the child\n            [\n                [\n                    'index' => \"{% extends 'base' %}\n                    {% block content %}\n                        {{ foo.bar }}\n                    {% endblock %}\n                    {% block foo %}\n                        {{ foo.bar }}\n                    {% endblock %}\",\n                    'base' => '{% block content %}{% endblock %}',\n                ],\n                'index', 3,\n            ],\n\n            // error occurs in an embed tag\n            [\n                [\n                    'index' => \"\n                    {% embed 'base' %}\n                    {% endembed %}\",\n                    'base' => '{% block foo %}{{ foo.bar }}{% endblock %}',\n                ],\n                'base', 1,\n            ],\n\n            // error occurs in an overridden block from an embed tag\n            [\n                [\n                    'index' => \"\n                    {% embed 'base' %}\n                        {% block foo %}\n                            {{ foo.bar }}\n                        {% endblock %}\n                    {% endembed %}\",\n                    'base' => '{% block foo %}{% endblock %}',\n                ],\n                'index', 4,\n            ],\n        ];\n    }\n\n    public function testErrorFromArrayLoader()\n    {\n        $templates = [\n            'index.twig' => '{% include \"include.twig\" %}',\n            'include.twig' => $include = <<<EOF\n\n\n\n            {% extends 'invalid.twig' %}\n            EOF,\n        ];\n        $twig = new Environment(new ArrayLoader($templates), ['debug' => true, 'cache' => false]);\n        try {\n            $twig->render('index.twig');\n            $this->fail('Expected LoaderError to be thrown');\n        } catch (LoaderError $e) {\n            $this->assertSame('Template \"invalid.twig\" is not defined.', $e->getRawMessage());\n            $this->assertSame(4, $e->getTemplateLine());\n            $this->assertSame('include.twig', $e->getSourceContext()->getName());\n            $this->assertSame($include, $e->getSourceContext()->getCode());\n        }\n    }\n\n    public function testErrorFromFilesystemLoader()\n    {\n        $twig = new Environment(new FilesystemLoader([$dir = __DIR__.'/Fixtures/errors/extends']), ['debug' => true, 'cache' => false]);\n        $include = file_get_contents($dir.'/include.twig');\n        try {\n            $twig->render('index.twig');\n            $this->fail('Expected LoaderError to be thrown');\n        } catch (LoaderError $e) {\n            $this->assertStringContainsString('Unable to find template \"invalid.twig\"', $e->getRawMessage());\n            $this->assertSame(4, $e->getTemplateLine());\n            $this->assertSame('include.twig', $e->getSourceContext()->getName());\n            $this->assertSame($include, $e->getSourceContext()->getCode());\n        }\n    }\n}\n\nclass ErrorTest_Foo\n{\n    public function bar()\n    {\n        throw new \\Exception('Runtime error...');\n    }\n}\n"
  },
  {
    "path": "tests/ExpressionParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Bridge\\PhpUnit\\ExpectDeprecationTrait;\nuse Twig\\Attribute\\FirstClassTwigCallableReady;\nuse Twig\\Compiler;\nuse Twig\\Environment;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\ExpressionParser\\InfixExpressionParserInterface;\nuse Twig\\ExpressionParser\\Prefix\\LiteralExpressionParser;\nuse Twig\\ExpressionParser\\Prefix\\UnaryOperatorExpressionParser;\nuse Twig\\ExpressionParser\\PrefixExpressionParserInterface;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\Binary\\ConcatBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FilterExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\Node\\Expression\\TestExpression;\nuse Twig\\Node\\Expression\\Unary\\AbstractUnary;\nuse Twig\\Node\\Expression\\Unary\\SpreadUnary;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Node;\nuse Twig\\Parser;\nuse Twig\\Source;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\nclass ExpressionParserTest extends TestCase\n{\n    use ExpectDeprecationTrait;\n\n    /**\n     * @dataProvider getFailingTestsForAssignment\n     */\n    public function testCanOnlyAssignToNames($template)\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $parser->parse($env->tokenize(new Source($template, 'index')));\n    }\n\n    public static function getFailingTestsForAssignment()\n    {\n        return [\n            ['{% set false = \"foo\" %}'],\n            ['{% set FALSE = \"foo\" %}'],\n            ['{% set true = \"foo\" %}'],\n            ['{% set TRUE = \"foo\" %}'],\n            ['{% set none = \"foo\" %}'],\n            ['{% set NONE = \"foo\" %}'],\n            ['{% set null = \"foo\" %}'],\n            ['{% set NULL = \"foo\" %}'],\n            ['{% set 3 = \"foo\" %}'],\n            ['{% set 1 + 2 = \"foo\" %}'],\n            ['{% set \"bar\" = \"foo\" %}'],\n            ['{% set %}{% endset %}'],\n        ];\n    }\n\n    /**\n     * @dataProvider getTestsForSequence\n     */\n    public function testSequenceExpression($template, $expected)\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $stream = $env->tokenize($source = new Source($template, ''));\n        $parser = new Parser($env);\n        $expected->setSourceContext($source);\n\n        $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode('0')->getNode('expr'));\n    }\n\n    /**\n     * @dataProvider getFailingTestsForSequence\n     */\n    public function testSequenceSyntaxError($template)\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $parser->parse($env->tokenize(new Source($template, 'index')));\n    }\n\n    public static function getFailingTestsForSequence()\n    {\n        return [\n            ['{{ [1, \"a\": \"b\"] }}'],\n            ['{{ {\"a\": \"b\", 2} }}'],\n            ['{{ {\"a\"} }}'],\n        ];\n    }\n\n    public static function getTestsForSequence()\n    {\n        return [\n            // simple sequence\n            ['{{ [1, 2] }}', new ArrayExpression([\n                new ConstantExpression(0, 1),\n                new ConstantExpression(1, 1),\n\n                new ConstantExpression(1, 1),\n                new ConstantExpression(2, 1),\n            ], 1),\n            ],\n\n            // sequence with trailing ,\n            ['{{ [1, 2, ] }}', new ArrayExpression([\n                new ConstantExpression(0, 1),\n                new ConstantExpression(1, 1),\n\n                new ConstantExpression(1, 1),\n                new ConstantExpression(2, 1),\n            ], 1),\n            ],\n\n            // simple mapping\n            ['{{ {\"a\": \"b\", \"b\": \"c\"} }}', new ArrayExpression([\n                new ConstantExpression('a', 1),\n                new ConstantExpression('b', 1),\n\n                new ConstantExpression('b', 1),\n                new ConstantExpression('c', 1),\n            ], 1),\n            ],\n\n            // mapping with trailing ,\n            ['{{ {\"a\": \"b\", \"b\": \"c\", } }}', new ArrayExpression([\n                new ConstantExpression('a', 1),\n                new ConstantExpression('b', 1),\n\n                new ConstantExpression('b', 1),\n                new ConstantExpression('c', 1),\n            ], 1),\n            ],\n\n            // mapping in a sequence\n            ['{{ [1, {\"a\": \"b\", \"b\": \"c\"}] }}', new ArrayExpression([\n                new ConstantExpression(0, 1),\n                new ConstantExpression(1, 1),\n\n                new ConstantExpression(1, 1),\n                new ArrayExpression([\n                    new ConstantExpression('a', 1),\n                    new ConstantExpression('b', 1),\n\n                    new ConstantExpression('b', 1),\n                    new ConstantExpression('c', 1),\n                ], 1),\n            ], 1),\n            ],\n\n            // sequence in a mapping\n            ['{{ {\"a\": [1, 2], \"b\": \"c\"} }}', new ArrayExpression([\n                new ConstantExpression('a', 1),\n                new ArrayExpression([\n                    new ConstantExpression(0, 1),\n                    new ConstantExpression(1, 1),\n\n                    new ConstantExpression(1, 1),\n                    new ConstantExpression(2, 1),\n                ], 1),\n                new ConstantExpression('b', 1),\n                new ConstantExpression('c', 1),\n            ], 1),\n            ],\n            ['{{ {a, b} }}', new ArrayExpression([\n                new ConstantExpression('a', 1),\n                new ContextVariable('a', 1),\n                new ConstantExpression('b', 1),\n                new ContextVariable('b', 1),\n            ], 1)],\n\n            // sequence with spread operator\n            ['{{ [1, 2, ...foo] }}',\n                new ArrayExpression([\n                    new ConstantExpression(0, 1),\n                    new ConstantExpression(1, 1),\n\n                    new ConstantExpression(1, 1),\n                    new ConstantExpression(2, 1),\n\n                    new ConstantExpression(2, 1),\n                    new SpreadUnary(new ContextVariable('foo', 1), 1),\n                ], 1)],\n\n            // mapping with spread operator\n            ['{{ {\"a\": \"b\", \"b\": \"c\", ...otherLetters} }}',\n                new ArrayExpression([\n                    new ConstantExpression('a', 1),\n                    new ConstantExpression('b', 1),\n\n                    new ConstantExpression('b', 1),\n                    new ConstantExpression('c', 1),\n\n                    new ConstantExpression(0, 1),\n                    new SpreadUnary(new ContextVariable('otherLetters', 1), 1),\n                ], 1)],\n        ];\n    }\n\n    public function testStringExpressionDoesNotConcatenateTwoConsecutiveStrings()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);\n        $stream = $env->tokenize(new Source('{{ \"a\" \"b\" }}', 'index'));\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $parser->parse($stream);\n    }\n\n    public function testSequenceCompilationError()\n    {\n        $env = new Environment(new ArrayLoader(['index' => '{{ [1,,2] }}']), ['cache' => false, 'autoescape' => false]);\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Empty array elements are only allowed in destructuring assignments');\n        $env->compileSource(new Source('{{ [1,,2] }}', 'index'));\n    }\n\n    /**\n     * @dataProvider getTestsForString\n     */\n    public function testStringExpression($template, $expected)\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);\n        $stream = $env->tokenize($source = new Source($template, ''));\n        $parser = new Parser($env);\n        $expected->setSourceContext($source);\n\n        $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode('0')->getNode('expr'));\n    }\n\n    public static function getTestsForString()\n    {\n        return [\n            [\n                '{{ \"foo #{bar}\" }}', new ConcatBinary(\n                    new ConstantExpression('foo ', 1),\n                    new ContextVariable('bar', 1),\n                    1\n                ),\n            ],\n            [\n                '{{ \"foo #{bar} baz\" }}', new ConcatBinary(\n                    new ConcatBinary(\n                        new ConstantExpression('foo ', 1),\n                        new ContextVariable('bar', 1),\n                        1\n                    ),\n                    new ConstantExpression(' baz', 1),\n                    1\n                ),\n            ],\n\n            [\n                '{{ \"foo #{\"foo #{bar} baz\"} baz\" }}', new ConcatBinary(\n                    new ConcatBinary(\n                        new ConstantExpression('foo ', 1),\n                        new ConcatBinary(\n                            new ConcatBinary(\n                                new ConstantExpression('foo ', 1),\n                                new ContextVariable('bar', 1),\n                                1\n                            ),\n                            new ConstantExpression(' baz', 1),\n                            1\n                        ),\n                        1\n                    ),\n                    new ConstantExpression(' baz', 1),\n                    1\n                ),\n            ],\n        ];\n    }\n\n    /**\n     * @dataProvider getTestsForNullSafeOperator\n     */\n    public function testNullSafeOperator($template, $data, $expected)\n    {\n        $env = new Environment(new ArrayLoader(['template' => $template]), ['strict_variables' => true]);\n\n        $this->assertSame($expected, $env->render('template', $data));\n    }\n\n    public static function getTestsForNullSafeOperator()\n    {\n        return [\n            [\n                '{{ foo?.bar }}',\n                ['foo' => (object) ['bar' => 'baz']],\n                'baz',\n            ],\n            [\n                '{{ foo?.bar }}',\n                ['foo' => null],\n                '',\n            ],\n            [\n                '{{ foo?.bar?.baz }}',\n                ['foo' => (object) ['bar' => (object) ['baz' => 'qux']]],\n                'qux',\n            ],\n            [\n                '{{ foo?.bar?.baz }}',\n                ['foo' => (object) ['bar' => null]],\n                '',\n            ],\n            [\n                '{{ foo?.bar?.baz }}',\n                ['foo' => null],\n                '',\n            ],\n            [\n                '{{ foo?.bar?.baz ?? \"qux\" }}',\n                ['foo' => null],\n                'qux',\n            ],\n            [\n                '{{ foo?.bar ?? \"qux\" }}',\n                ['foo' => (object) ['bar' => 0]],\n                '0',\n            ],\n            [\n                '{{ foo?.bar ?? \"qux\" }}',\n                ['foo' => (object) ['bar' => false]],\n                '',\n            ],\n            // short-circuiting\n            [\n                '{{ foo?.bar.baz }}',\n                ['foo' => null],\n                '',\n            ],\n            [\n                '{{ foo?.bar.baz?.qux.corge }}',\n                ['foo' => null],\n                '',\n            ],\n            [\n                '{{ foo?.bar.baz?.qux.corge }}',\n                ['foo' => (object) ['bar' => (object) ['baz' => null]]],\n                '',\n            ],\n        ];\n    }\n\n    /**\n     * @dataProvider getTestForInvalidNullSafeOperatorShortCircuiting\n     */\n    public function testInvalidNullSafeOperatorShortCircuiting(string $template, array $data, string $expectedMessage)\n    {\n        $env = new Environment(new ArrayLoader(['template' => $template]), ['strict_variables' => true]);\n\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage($expectedMessage);\n\n        $env->render('template', $data);\n    }\n\n    public static function getTestForInvalidNullSafeOperatorShortCircuiting()\n    {\n        yield [\n            '{{ foo?.bar.baz }}',\n            ['foo' => (object) ['bar' => null]],\n            'Impossible to access an attribute (\"baz\") on a null variable in \"template\" at line 1.',\n        ];\n        yield [\n            '{{ foo?.bar.baz?.qux.corge }}',\n            ['foo' => (object) ['bar' => (object) ['baz' => (object) ['qux' => null]]]],\n            'Impossible to access an attribute (\"corge\") on a null variable in \"template\" at line 1.',\n        ];\n    }\n\n    public function testMacroDefinitionDoesNotSupportNonNameVariableName()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('An argument must be a name. Unexpected token \"string\" of value \"a\" (\"name\" expected) in \"index\" at line 1.');\n\n        $parser->parse($env->tokenize(new Source('{% macro foo(\"a\") %}{% endmacro %}', 'index')));\n    }\n\n    /**\n     * @dataProvider             getMacroDefinitionDoesNotSupportNonConstantDefaultValues\n     */\n    public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($template)\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping) in \"index\" at line 1');\n\n        $parser->parse($env->tokenize(new Source($template, 'index')));\n    }\n\n    public static function getMacroDefinitionDoesNotSupportNonConstantDefaultValues()\n    {\n        return [\n            ['{% macro foo(name = \"a #{foo} a\") %}{% endmacro %}'],\n            ['{% macro foo(name = [[\"b\", \"a #{foo} a\"]]) %}{% endmacro %}'],\n        ];\n    }\n\n    /**\n     * @dataProvider getMacroDefinitionSupportsConstantDefaultValues\n     */\n    public function testMacroDefinitionSupportsConstantDefaultValues($template)\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source($template, 'index')));\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public static function getMacroDefinitionSupportsConstantDefaultValues()\n    {\n        return [\n            ['{% macro foo(name = \"aa\") %}{% endmacro %}'],\n            ['{% macro foo(name = 12) %}{% endmacro %}'],\n            ['{% macro foo(name = true) %}{% endmacro %}'],\n            ['{% macro foo(name = [\"a\"]) %}{% endmacro %}'],\n            ['{% macro foo(name = [[\"a\"]]) %}{% endmacro %}'],\n            ['{% macro foo(name = {a: \"a\"}) %}{% endmacro %}'],\n            ['{% macro foo(name = {a: {b: \"a\"}}) %}{% endmacro %}'],\n        ];\n    }\n\n    public function testUnknownFunction()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown \"cycl\" function. Did you mean \"cycle\" in \"index\" at line 1?');\n\n        $parser->parse($env->tokenize(new Source('{{ cycl() }}', 'index')));\n    }\n\n    public function testUnknownFunctionWithoutSuggestions()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown \"foobar\" function in \"index\" at line 1.');\n\n        $parser->parse($env->tokenize(new Source('{{ foobar() }}', 'index')));\n    }\n\n    public function testUnknownFilter()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown \"lowe\" filter. Did you mean \"lower\" in \"index\" at line 1?');\n\n        $parser->parse($env->tokenize(new Source('{{ 1|lowe }}', 'index')));\n    }\n\n    public function testUnknownFilterWithoutSuggestions()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown \"foobar\" filter in \"index\" at line 1.');\n\n        $parser->parse($env->tokenize(new Source('{{ 1|foobar }}', 'index')));\n    }\n\n    public function testUnknownTest()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n        $stream = $env->tokenize(new Source('{{ 1 is nul }}', 'index'));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown \"nul\" test. Did you mean \"null\" in \"index\" at line 1');\n\n        $parser->parse($stream);\n    }\n\n    public function testUnknownTestWithoutSuggestions()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $parser = new Parser($env);\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown \"foobar\" test in \"index\" at line 1.');\n\n        $parser->parse($env->tokenize(new Source('{{ 1 is foobar }}', 'index')));\n    }\n\n    public function testCompiledCodeForDynamicTest()\n    {\n        $env = new Environment(new ArrayLoader(['index' => '{{ \"a\" is foo_foo_bar_bar }}']), ['cache' => false, 'autoescape' => false]);\n        $env->addExtension(new class extends AbstractExtension {\n            public function getTests()\n            {\n                return [\n                    new TwigTest('*_foo_*_bar', static function ($foo, $bar, $a) {}),\n                ];\n            }\n        });\n\n        $this->assertStringContainsString('$this->env->getTest(\\'*_foo_*_bar\\')->getCallable()(\"foo\", \"bar\", \"a\")', $env->compile($env->parse($env->tokenize(new Source($env->getLoader()->getSourceContext('index')->getCode(), 'index')))));\n    }\n\n    public function testCompiledCodeForDynamicFunction()\n    {\n        $env = new Environment(new ArrayLoader(['index' => '{{ foo_foo_bar_bar(\"a\") }}']), ['cache' => false, 'autoescape' => false]);\n        $env->addExtension(new class extends AbstractExtension {\n            public function getFunctions()\n            {\n                return [\n                    new TwigFunction('*_foo_*_bar', static function ($foo, $bar, $a) {}),\n                ];\n            }\n        });\n\n        $this->assertStringContainsString('$this->env->getFunction(\\'*_foo_*_bar\\')->getCallable()(\"foo\", \"bar\", \"a\")', $env->compile($env->parse($env->tokenize(new Source($env->getLoader()->getSourceContext('index')->getCode(), 'index')))));\n    }\n\n    public function testCompiledCodeForDynamicFilter()\n    {\n        $env = new Environment(new ArrayLoader(['index' => '{{ \"a\"|foo_foo_bar_bar }}']), ['cache' => false, 'autoescape' => false]);\n        $env->addExtension(new class extends AbstractExtension {\n            public function getFilters()\n            {\n                return [\n                    new TwigFilter('*_foo_*_bar', static function ($foo, $bar, $a) {}),\n                ];\n            }\n        });\n\n        $this->assertStringContainsString('$this->env->getFilter(\\'*_foo_*_bar\\')->getCallable()(\"foo\", \"bar\", \"a\")', $env->compile($env->parse($env->tokenize(new Source($env->getLoader()->getSourceContext('index')->getCode(), 'index')))));\n    }\n\n    public function testNotReadyFunctionWithNoConstructor()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addFunction(new TwigFunction('foo', 'foo', ['node_class' => NotReadyFunctionExpressionWithNoConstructor::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ foo() }}', 'index')));\n        $this->expectNotToPerformAssertions();\n    }\n\n    public function testNotReadyFilterWithNoConstructor()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addFilter(new TwigFilter('foo', 'foo', ['node_class' => NotReadyFilterExpressionWithNoConstructor::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ 1|foo }}', 'index')));\n        $this->expectNotToPerformAssertions();\n    }\n\n    public function testNotReadyTestWithNoConstructor()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addTest(new TwigTest('foo', 'foo', ['node_class' => NotReadyTestExpressionWithNoConstructor::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ 1 is foo }}', 'index')));\n        $this->expectNotToPerformAssertions();\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testNotReadyFunction()\n    {\n        $this->expectDeprecation('Since twig/twig 3.12: Twig node \"Twig\\Tests\\NotReadyFunctionExpression\" is not marked as ready for passing a \"TwigFunction\" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.');\n        $this->expectDeprecation('Since twig/twig 3.12: Not passing an instance of \"TwigFunction\" when creating a \"foo\" function of type \"Twig\\Tests\\NotReadyFunctionExpression\" is deprecated.');\n\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addFunction(new TwigFunction('foo', 'foo', ['node_class' => NotReadyFunctionExpression::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ foo() }}', 'index')));\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testNotReadyFilter()\n    {\n        $this->expectDeprecation('Since twig/twig 3.12: Twig node \"Twig\\Tests\\NotReadyFilterExpression\" is not marked as ready for passing a \"TwigFilter\" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.');\n        $this->expectDeprecation('Since twig/twig 3.12: Not passing an instance of \"TwigFilter\" when creating a \"foo\" filter of type \"Twig\\Tests\\NotReadyFilterExpression\" is deprecated.');\n\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addFilter(new TwigFilter('foo', 'foo', ['node_class' => NotReadyFilterExpression::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ 1|foo }}', 'index')));\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testNotReadyTest()\n    {\n        $this->expectDeprecation('Since twig/twig 3.12: Twig node \"Twig\\Tests\\NotReadyTestExpression\" is not marked as ready for passing a \"TwigTest\" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.');\n        $this->expectDeprecation('Since twig/twig 3.12: Not passing an instance of \"TwigTest\" when creating a \"foo\" test of type \"Twig\\Tests\\NotReadyTestExpression\" is deprecated.');\n\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addTest(new TwigTest('foo', 'foo', ['node_class' => NotReadyTestExpression::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ 1 is foo }}', 'index')));\n    }\n\n    public function testReadyFunction()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addFunction(new TwigFunction('foo', 'foo', ['node_class' => ReadyFunctionExpression::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ foo() }}', 'index')));\n        $this->expectNotToPerformAssertions();\n    }\n\n    public function testReadyFilter()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addFilter(new TwigFilter('foo', 'foo', ['node_class' => ReadyFilterExpression::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ 1|foo }}', 'index')));\n        $this->expectNotToPerformAssertions();\n    }\n\n    public function testReadyTest()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addTest(new TwigTest('foo', 'foo', ['node_class' => ReadyTestExpression::class]));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ 1 is foo }}', 'index')));\n        $this->expectNotToPerformAssertions();\n    }\n\n    public function testTwoWordTestPrecedence()\n    {\n        // a \"empty element\" test must have precedence over \"empty\"\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addTest(new TwigTest('empty element', 'foo'));\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ 1 is empty element }}', 'index')));\n        $this->expectNotToPerformAssertions();\n    }\n\n    public function testUnaryPrecedenceChange()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->addExtension(new class extends AbstractExtension {\n            public function getExpressionParsers(): array\n            {\n                $class = new class(new ConstantExpression('foo', 1), 1) extends AbstractUnary {\n                    public function operator(Compiler $compiler): Compiler\n                    {\n                        return $compiler->raw('!');\n                    }\n                };\n\n                return [\n                    new UnaryOperatorExpressionParser($class::class, '!', 50),\n                ];\n            }\n        });\n        $parser = new Parser($env);\n\n        $parser->parse($env->tokenize(new Source('{{ !false ? \"OK\" : \"KO\" }}', 'index')));\n        $this->expectNotToPerformAssertions();\n    }\n\n    /**\n     * @dataProvider getBindingPowerTests\n     */\n    public function testBindingPower(string $expression, string $expectedExpression, mixed $expectedResult, array $context = [])\n    {\n        $env = new Environment(new ArrayLoader([\n            'expression' => $expression,\n            'expected' => $expectedExpression,\n        ]));\n\n        $this->assertSame($env->render('expected', $context), $env->render('expression', $context));\n        $this->assertEquals($expectedResult, $env->render('expression', $context));\n    }\n\n    public static function getBindingPowerTests(): iterable\n    {\n        // * / // % stronger than + -\n        foreach (['*', '/', '//', '%'] as $op1) {\n            foreach (['+', '-'] as $op2) {\n                $e = \"12 $op1 6 $op2 3\";\n                if ('//' === $op1) {\n                    $php = eval(\"return (int) floor(12 / 6) $op2 3;\");\n                } else {\n                    $php = eval(\"return $e;\");\n                }\n                yield \"$op1 vs $op2\" => [\"{{ $e }}\", \"{{ (12 $op1 6) $op2 3 }}\", $php];\n\n                $e = \"12 $op2 6 $op1 3\";\n                if ('//' === $op1) {\n                    $php = eval(\"return 12 $op2 (int) floor(6 / 3);\");\n                } else {\n                    $php = eval(\"return $e;\");\n                }\n                yield \"$op2 vs $op1\" => [\"{{ $e }}\", \"{{ 12 $op2 (6 $op1 3) }}\", $php];\n            }\n        }\n\n        // + - * / // % stronger than == != <=> < > >= <= `not in` `in` `matches` `starts with` `ends with` `has some` `has every`\n        foreach (['+', '-', '*', '/', '//', '%'] as $op1) {\n            foreach (['==', '!=', '<=>', '<', '>', '>=', '<='] as $op2) {\n                $e = \"12 $op1 6 $op2 3\";\n                if ('//' === $op1) {\n                    $php = eval(\"return (int) floor(12 / 6) $op2 3;\");\n                } else {\n                    $php = eval(\"return $e;\");\n                }\n                yield \"$op1 vs $op2\" => [\"{{ $e }}\", \"{{ (12 $op1 6) $op2 3 }}\", $php];\n            }\n        }\n        yield '+ vs not in' => ['{{ 1 + 2 not in [3, 4] }}', '{{ (1 + 2) not in [3, 4] }}', eval('return !in_array(1 + 2, [3, 4]);')];\n        yield '+ vs in' => ['{{ 1 + 2 in [3, 4] }}', '{{ (1 + 2) in [3, 4] }}', eval('return in_array(1 + 2, [3, 4]);')];\n        yield '+ vs matches' => ['{{ 1 + 2 matches \"/^3$/\" }}', '{{ (1 + 2) matches \"/^3$/\" }}', eval(\"return preg_match('/^3$/', 1 + 2);\")];\n\n        // ~ stronger than `starts with` `ends with`\n        yield '~ vs starts with' => ['{{ \"a\" ~ \"b\" starts with \"a\" }}', '{{ (\"a\" ~ \"b\") starts with \"a\" }}', eval(\"return str_starts_with('ab', 'a');\")];\n        yield '~ vs ends with' => ['{{ \"a\" ~ \"b\" ends with \"b\" }}', '{{ (\"a\" ~ \"b\") ends with \"b\" }}', eval(\"return str_ends_with('ab', 'b');\")];\n\n        // [] . stronger than anything else\n        $context = ['a' => ['b' => 1, 'c' => ['d' => 2]]];\n        yield '[] vs unary -' => ['{{ -a[\"b\"] + 3 }}', '{{ -(a[\"b\"]) + 3 }}', eval(\"\\$a = ['b' => 1]; return -\\$a['b'] + 3;\"), $context];\n        yield '[] vs unary - (multiple levels)' => ['{{ -a[\"c\"][\"d\"] }}', '{{ -((a[\"c\"])[\"d\"]) }}', eval(\"\\$a = ['c' => ['d' => 2]]; return -\\$a['c']['d'];\"), $context];\n        yield '. vs unary -' => ['{{ -a.b }}', '{{ -(a.b) }}', eval(\"\\$a = ['b' => 1]; return -\\$a['b'];\"), $context];\n        yield '. vs unary - (multiple levels)' => ['{{ -a.c.d }}', '{{ -((a.c).d) }}', eval(\"\\$a = ['c' => ['d' => 2]]; return -\\$a['c']['d'];\"), $context];\n        yield '. [] vs unary -' => ['{{ -a.c[\"d\"] }}', '{{ -((a.c)[\"d\"]) }}', eval(\"\\$a = ['c' => ['d' => 2]]; return -\\$a['c']['d'];\"), $context];\n        yield '[] . vs unary -' => ['{{ -a[\"c\"].d }}', '{{ -((a[\"c\"]).d) }}', eval(\"\\$a = ['c' => ['d' => 2]]; return -\\$a['c']['d'];\"), $context];\n\n        // () stronger than anything else\n        yield '() vs unary -' => ['{{ -random(1, 1) + 3 }}', '{{ -(random(1, 1)) + 3 }}', eval('return -rand(1, 1) + 3;')];\n\n        // + - stronger than |\n        yield '+ vs |' => ['{{ 10 + 2|length }}', '{{ 10 + (2|length) }}', eval('return 10 + strlen(2);'), $context];\n\n        // - unary stronger than |\n        // To be uncomment in Twig 4.0\n        // yield '- vs |' => ['{{ -1|abs }}', '{{ (-1)|abs }}', eval(\"return abs(-1);\"), $context];\n\n        // ?? stronger than ()\n        // yield '?? vs ()' => ['{{ (1 ?? \"a\") }}', '{{ ((1 ?? \"a\")) }}', eval(\"return 1;\")];\n\n        // = stronger than anything else\n        yield '= same as literal' => ['{% do c = \"a\" %}{{ c }}', '{% do c = (\"a\") %}{{ c }}', eval(\"return 'a';\")];\n        yield '= stronger than .' => ['{% do c = a.b %}{{ c }}', '{% do c = (a.b) %}{{ c }}', eval(\"\\$a = ['b' => 1]; return \\$a['b'];\"), $context];\n        yield '= stronger than math' => ['{% do a = 1 + 3 %}{{ a }}', '{% do a = (1 + 3) %}{{ a }}', eval('$a = 1 + 3; return $a;')];\n        yield '= stronger than logical' => ['{% do a = false or true %}{{ a }}', '{% do a = (false or true) %}{{ a }}', eval('$a = false || true; return $a;')];\n        yield '= stronger than ternary' => ['{% do c = 4 ? 0 : -1 %}{{ c }}', '{% do c = (4 ? 0 : -1) %}{{ c }}', eval('return 4 ? 0 : -1;')];\n    }\n\n    public function testLiteralExpressionParserGetOperatorTokensReturnsEmptyArray()\n    {\n        $env = new Environment(new ArrayLoader());\n        $parser = $env->getExpressionParsers()->getByClass(LiteralExpressionParser::class);\n\n        $this->assertSame([], $parser->getOperatorTokens());\n        $this->assertSame('literal', $parser->getName());\n    }\n\n    public function testExpressionParserGetOperatorTokensDefaultBehavior()\n    {\n        $env = new Environment(new ArrayLoader());\n\n        foreach ($env->getExpressionParsers() as $parser) {\n            if ($parser instanceof LiteralExpressionParser) {\n                continue;\n            }\n            $expected = [$parser->getName(), ...$parser->getAliases()];\n            $this->assertSame($expected, $parser->getOperatorTokens(), \\sprintf('getOperatorTokens() for %s should return name + aliases.', $parser::class));\n        }\n    }\n\n    public function testLiteralIsNotRegisteredAsOperator()\n    {\n        // Ensure \"literal\" is not in the operator registry\n        $env = new Environment(new ArrayLoader());\n        $this->assertNull($env->getExpressionParsers()->getByName(PrefixExpressionParserInterface::class, 'literal'));\n        $this->assertNull($env->getExpressionParsers()->getByName(InfixExpressionParserInterface::class, 'literal'));\n    }\n}\n\nclass NotReadyFunctionExpression extends FunctionExpression\n{\n    public function __construct(string $function, Node $arguments, int $lineno)\n    {\n        parent::__construct($function, $arguments, $lineno);\n    }\n}\n\nclass NotReadyFilterExpression extends FilterExpression\n{\n    public function __construct(Node $node, ConstantExpression $filter, Node $arguments, int $lineno)\n    {\n        parent::__construct($node, $filter, $arguments, $lineno);\n    }\n}\n\nclass NotReadyTestExpression extends TestExpression\n{\n    public function __construct(Node $node, string $test, ?Node $arguments, int $lineno)\n    {\n        parent::__construct($node, $test, $arguments, $lineno);\n    }\n}\n\nclass NotReadyFunctionExpressionWithNoConstructor extends FunctionExpression\n{\n}\n\nclass NotReadyFilterExpressionWithNoConstructor extends FilterExpression\n{\n}\n\nclass NotReadyTestExpressionWithNoConstructor extends TestExpression\n{\n}\n\nclass ReadyFunctionExpression extends FunctionExpression\n{\n    #[FirstClassTwigCallableReady]\n    public function __construct(TwigFunction|string $function, Node $arguments, int $lineno)\n    {\n        parent::__construct($function, $arguments, $lineno);\n    }\n}\n\nclass ReadyFilterExpression extends FilterExpression\n{\n    #[FirstClassTwigCallableReady]\n    public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno)\n    {\n        parent::__construct($node, $filter, $arguments, $lineno);\n    }\n}\n\nclass ReadyTestExpression extends TestExpression\n{\n    #[FirstClassTwigCallableReady]\n    public function __construct(Node $node, TwigTest|string $test, ?Node $arguments, int $lineno)\n    {\n        parent::__construct($node, $test, $arguments, $lineno);\n    }\n}\n"
  },
  {
    "path": "tests/Extension/AttributeExtensionTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\DeprecatedCallableInfo;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\AttributeExtension;\nuse Twig\\ExtensionSet;\nuse Twig\\Tests\\Extension\\Fixtures\\ExtensionWithAttributes;\nuse Twig\\Tests\\Extension\\Fixtures\\FilterWithoutValue;\nuse Twig\\Tests\\Extension\\Fixtures\\TestWithoutValue;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\nclass AttributeExtensionTest extends TestCase\n{\n    /**\n     * @dataProvider provideFilters\n     */\n    public function testFilter(string $name, string $method, array $options)\n    {\n        $extension = new AttributeExtension(ExtensionWithAttributes::class);\n        foreach ($extension->getFilters() as $filter) {\n            if ($filter->getName() === $name) {\n                $this->assertEquals(new TwigFilter($name, [ExtensionWithAttributes::class, $method], $options), $filter);\n\n                return;\n            }\n        }\n\n        $this->fail(\\sprintf('Filter \"%s\" is not registered.', $name));\n    }\n\n    public static function provideFilters()\n    {\n        yield 'with name' => ['foo', 'fooFilter', ['is_safe' => ['html']]];\n        yield 'with env' => ['with_env_filter', 'withEnvFilter', ['needs_environment' => true]];\n        yield 'with context' => ['with_context_filter', 'withContextFilter', ['needs_context' => true]];\n        yield 'with env and context' => ['with_env_and_context_filter', 'withEnvAndContextFilter', ['needs_environment' => true, 'needs_context' => true]];\n        yield 'variadic' => ['variadic_filter', 'variadicFilter', ['is_variadic' => true]];\n        yield 'deprecated' => ['deprecated_filter', 'deprecatedFilter', ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.2')]];\n        yield 'pattern' => ['pattern_*_filter', 'patternFilter', []];\n    }\n\n    /**\n     * @dataProvider provideFunctions\n     */\n    public function testFunction(string $name, string $method, array $options)\n    {\n        $extension = new AttributeExtension(ExtensionWithAttributes::class);\n        foreach ($extension->getFunctions() as $function) {\n            if ($function->getName() === $name) {\n                $this->assertEquals(new TwigFunction($name, [ExtensionWithAttributes::class, $method], $options), $function);\n\n                return;\n            }\n        }\n\n        $this->fail(\\sprintf('Function \"%s\" is not registered.', $name));\n    }\n\n    public static function provideFunctions()\n    {\n        yield 'with name' => ['foo', 'fooFunction', ['is_safe' => ['html']]];\n        yield 'with env' => ['with_env_function', 'withEnvFunction', ['needs_environment' => true]];\n        yield 'with context' => ['with_context_function', 'withContextFunction', ['needs_context' => true]];\n        yield 'with env and context' => ['with_env_and_context_function', 'withEnvAndContextFunction', ['needs_environment' => true, 'needs_context' => true]];\n        yield 'no argument' => ['no_arg_function', 'noArgFunction', []];\n        yield 'variadic' => ['variadic_function', 'variadicFunction', ['is_variadic' => true]];\n        yield 'deprecated' => ['deprecated_function', 'deprecatedFunction', ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.2')]];\n    }\n\n    /**\n     * @dataProvider provideTests\n     */\n    public function testTest(string $name, string $method, array $options)\n    {\n        $extension = new AttributeExtension(ExtensionWithAttributes::class);\n        foreach ($extension->getTests() as $test) {\n            if ($test->getName() === $name) {\n                $this->assertEquals(new TwigTest($name, [ExtensionWithAttributes::class, $method], $options), $test);\n\n                return;\n            }\n        }\n\n        $this->fail(\\sprintf('Test \"%s\" is not registered.', $name));\n    }\n\n    public static function provideTests()\n    {\n        yield 'with name' => ['foo', 'fooTest', []];\n        yield 'with env' => ['with_env_test', 'withEnvTest', ['needs_environment' => true]];\n        yield 'with context' => ['with_context_test', 'withContextTest', ['needs_context' => true]];\n        yield 'with env and context' => ['with_env_and_context_test', 'withEnvAndContextTest', ['needs_environment' => true, 'needs_context' => true]];\n        yield 'variadic' => ['variadic_test', 'variadicTest', ['is_variadic' => true]];\n        yield 'deprecated' => ['deprecated_test', 'deprecatedTest', ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.2')]];\n    }\n\n    public function testFilterRequireOneArgument()\n    {\n        $extension = new AttributeExtension(FilterWithoutValue::class);\n\n        $this->expectException(\\LogicException::class);\n        $this->expectExceptionMessage('\"'.FilterWithoutValue::class.'::myFilter()\" needs at least 1 arguments to be used AsTwigFilter, but only 0 defined.');\n\n        $extension->getTests();\n    }\n\n    public function testTestRequireOneArgument()\n    {\n        $extension = new AttributeExtension(TestWithoutValue::class);\n\n        $this->expectException(\\LogicException::class);\n        $this->expectExceptionMessage('\"'.TestWithoutValue::class.'::myTest()\" needs at least 1 arguments to be used AsTwigTest, but only 0 defined.');\n\n        $extension->getTests();\n    }\n\n    public function testLastModifiedWithObject()\n    {\n        $extension = new AttributeExtension(\\stdClass::class);\n\n        $this->assertSame(filemtime((new \\ReflectionClass(AttributeExtension::class))->getFileName()), $extension->getLastModified());\n    }\n\n    public function testLastModifiedWithClass()\n    {\n        $extension = new AttributeExtension('__CLASS_FOR_TEST_LAST_MODIFIED__');\n\n        $filename = tempnam(sys_get_temp_dir(), 'twig');\n        try {\n            file_put_contents($filename, '<?php class __CLASS_FOR_TEST_LAST_MODIFIED__ {}');\n            require $filename;\n\n            $this->assertSame(filemtime($filename), $extension->getLastModified());\n        } finally {\n            unlink($filename);\n        }\n    }\n\n    public function testMultipleRegistrations()\n    {\n        $extensionSet = new ExtensionSet();\n        $extensionSet->addExtension($extension1 = new AttributeExtension(ExtensionWithAttributes::class));\n        $extensionSet->addExtension($extension2 = new AttributeExtension(\\stdClass::class));\n\n        $this->assertCount(2, $extensionSet->getExtensions());\n        $this->assertNotNull($extensionSet->getFilter('foo'));\n\n        $this->assertSame($extension1, $extensionSet->getExtension(ExtensionWithAttributes::class));\n        $this->assertSame($extension2, $extensionSet->getExtension(\\stdClass::class));\n\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('The \"Twig\\Extension\\AttributeExtension\" extension is not enabled.');\n        $extensionSet->getExtension(AttributeExtension::class);\n    }\n}\n"
  },
  {
    "path": "tests/Extension/CoreTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Bridge\\PhpUnit\\ExpectDeprecationTrait;\nuse Twig\\Environment;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Sandbox\\SecurityError;\nuse Twig\\Sandbox\\SecurityPolicy;\n\nclass CoreTest extends TestCase\n{\n    use ExpectDeprecationTrait;\n\n    /**\n     * @dataProvider provideCycleCases\n     */\n    public function testCycleFunction($values, $position, $expected)\n    {\n        $this->assertSame($expected, CoreExtension::cycle($values, $position));\n    }\n\n    public static function provideCycleCases()\n    {\n        return [\n            [[1, 2, 3], 0, 1],\n            [[1, 2, 3], 1, 2],\n            [[1, 2, 3], 2, 3],\n            [[1, 2, 3], 3, 1],\n            [[false, 0, null], 0, false],\n            [[false, 0, null], 1, 0],\n            [[false, 0, null], 2, null],\n\n            [[['a', 'b'], ['c', 'd']], 3, ['c', 'd']],\n        ];\n    }\n\n    /**\n     * @dataProvider provideCycleInvalidCases\n     */\n    public function testCycleFunctionThrowRuntimeError($values, mixed $position = null)\n    {\n        $this->expectException(RuntimeError::class);\n        CoreExtension::cycle($values, $position ?? 0);\n    }\n\n    public static function provideCycleInvalidCases()\n    {\n        return [\n            'empty' => [[]],\n            'non-countable' => [new class extends \\ArrayObject {\n            }],\n        ];\n    }\n\n    /**\n     * @dataProvider getRandomFunctionTestData\n     */\n    public function testRandomFunction(array $expectedInArray, $value1, $value2 = null)\n    {\n        for ($i = 0; $i < 100; ++$i) {\n            $this->assertTrue(\\in_array(CoreExtension::random('UTF-8', $value1, $value2), $expectedInArray, true)); // assertContains() would not consider the type\n        }\n    }\n\n    public static function getRandomFunctionTestData()\n    {\n        return [\n            'array' => [\n                ['apple', 'orange', 'citrus'],\n                ['apple', 'orange', 'citrus'],\n            ],\n            'Traversable' => [\n                ['apple', 'orange', 'citrus'],\n                new \\ArrayObject(['apple', 'orange', 'citrus']),\n            ],\n            'unicode string' => [\n                ['Ä', '€', 'é'],\n                'Ä€é',\n            ],\n            'numeric but string' => [\n                ['1', '2', '3'],\n                '123',\n            ],\n            'integer' => [\n                range(0, 5, 1),\n                5,\n            ],\n            'float' => [\n                range(0, 5, 1),\n                5.9,\n            ],\n            'negative' => [\n                [0, -1, -2],\n                -2,\n            ],\n            'min max int' => [\n                range(50, 100),\n                50,\n                100,\n            ],\n            'min max float' => [\n                range(-10, 10),\n                -9.5,\n                9.5,\n            ],\n            'min null' => [\n                range(0, 100),\n                null,\n                100,\n            ],\n        ];\n    }\n\n    public function testRandomFunctionWithoutParameter()\n    {\n        $max = mt_getrandmax();\n\n        for ($i = 0; $i < 100; ++$i) {\n            $val = CoreExtension::random('UTF-8');\n            $this->assertTrue(\\is_int($val) && $val >= 0 && $val <= $max);\n        }\n    }\n\n    public function testRandomFunctionReturnsAsIs()\n    {\n        $this->assertSame('', CoreExtension::random('UTF-8', ''));\n\n        $instance = new \\stdClass();\n        $this->assertSame($instance, CoreExtension::random('UTF-8', $instance));\n    }\n\n    public function testRandomFunctionOfEmptyArrayThrowsException()\n    {\n        $this->expectException(RuntimeError::class);\n        CoreExtension::random('UTF-8', []);\n    }\n\n    public function testRandomFunctionOnNonUTF8String()\n    {\n        $text = iconv('UTF-8', 'ISO-8859-1', 'Äé');\n        for ($i = 0; $i < 30; ++$i) {\n            $rand = CoreExtension::random('ISO-8859-1', $text);\n            $this->assertTrue(\\in_array(iconv('ISO-8859-1', 'UTF-8', $rand), ['Ä', 'é'], true));\n        }\n    }\n\n    public function testReverseFilterOnNonUTF8String()\n    {\n        $input = iconv('UTF-8', 'ISO-8859-1', 'Äé');\n        $output = iconv('ISO-8859-1', 'UTF-8', CoreExtension::reverse('ISO-8859-1', $input));\n\n        $this->assertEquals($output, 'éÄ');\n    }\n\n    /**\n     * @dataProvider provideTwigFirstCases\n     */\n    public function testTwigFirst($expected, $input)\n    {\n        $this->assertSame($expected, CoreExtension::first('UTF-8', $input));\n    }\n\n    public static function provideTwigFirstCases()\n    {\n        $i = [1 => 'a', 2 => 'b', 3 => 'c'];\n\n        return [\n            ['a', 'abc'],\n            [1, [1, 2, 3]],\n            ['', null],\n            ['', ''],\n            ['a', new CoreTestIterator($i, array_keys($i), true, 3)],\n        ];\n    }\n\n    /**\n     * @dataProvider provideTwigLastCases\n     */\n    public function testTwigLast($expected, $input)\n    {\n        $this->assertSame($expected, CoreExtension::last('UTF-8', $input));\n    }\n\n    public static function provideTwigLastCases()\n    {\n        $i = [1 => 'a', 2 => 'b', 3 => 'c'];\n\n        return [\n            ['c', 'abc'],\n            [3, [1, 2, 3]],\n            ['', null],\n            ['', ''],\n            ['c', new CoreTestIterator($i, array_keys($i), true)],\n        ];\n    }\n\n    /**\n     * @dataProvider provideArrayKeyCases\n     */\n    public function testArrayKeysFilter(array $expected, $input)\n    {\n        $this->assertSame($expected, CoreExtension::keys($input));\n    }\n\n    public static function provideArrayKeyCases()\n    {\n        $array = ['a' => 'a1', 'b' => 'b1', 'c' => 'c1'];\n        $keys = array_keys($array);\n\n        return [\n            [$keys, $array],\n            [$keys, new CoreTestIterator($array, $keys)],\n            [$keys, new CoreTestIteratorAggregate($array, $keys)],\n            [$keys, new CoreTestIteratorAggregateAggregate($array, $keys)],\n            [[], null],\n            [['a'], new \\SimpleXMLElement('<xml><a></a></xml>')],\n        ];\n    }\n\n    /**\n     * @dataProvider provideInFilterCases\n     */\n    public function testInFilter($expected, $value, $compare)\n    {\n        $this->assertSame($expected, CoreExtension::inFilter($value, $compare));\n    }\n\n    public static function provideInFilterCases()\n    {\n        $array = [1, 2, 'a' => 3, 5, 6, 7];\n        $keys = array_keys($array);\n\n        return [\n            [true, 1, $array],\n            [true, '3', $array],\n            [true, '3', 'abc3def'],\n            [true, 1, new CoreTestIterator($array, $keys, true, 1)],\n            [true, '3', new CoreTestIterator($array, $keys, true, 3)],\n            [true, '3', new CoreTestIteratorAggregateAggregate($array, $keys, true, 3)],\n            [false, 4, $array],\n            [false, 4, new CoreTestIterator($array, $keys, true)],\n            [false, 4, new CoreTestIteratorAggregateAggregate($array, $keys, true)],\n            [false, 1, 1],\n            [true, 'b', new \\SimpleXMLElement('<xml><a>b</a></xml>')],\n        ];\n    }\n\n    /**\n     * @dataProvider provideSliceFilterCases\n     */\n    public function testSliceFilter($expected, $input, $start, $length = null, $preserveKeys = false)\n    {\n        $this->assertSame($expected, CoreExtension::slice('UTF-8', $input, $start, $length, $preserveKeys));\n    }\n\n    public static function provideSliceFilterCases()\n    {\n        $i = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];\n        $keys = array_keys($i);\n\n        return [\n            [['a' => 1], $i, 0, 1, true],\n            [['a' => 1], $i, 0, 1, false],\n            [['b' => 2, 'c' => 3], $i, 1, 2],\n            [[1], [1, 2, 3, 4], 0, 1],\n            [[2, 3], [1, 2, 3, 4], 1, 2],\n            [[2, 3], new CoreTestIterator($i, $keys, true), 1, 2],\n            [['c' => 3, 'd' => 4], new CoreTestIteratorAggregate($i, $keys, true), 2, null, true],\n            [$i, new CoreTestIterator($i, $keys, true), 0, \\count($keys) + 10, true],\n            [[], new CoreTestIterator($i, $keys, true), \\count($keys) + 10],\n            ['de', 'abcdef', 3, 2],\n            [[], new \\SimpleXMLElement('<items><item>1</item><item>2</item></items>'), 3],\n            [[], new \\ArrayIterator([1, 2]), 3],\n        ];\n    }\n\n    /**\n     * @dataProvider provideCompareCases\n     */\n    public function testCompare($expected, $a, $b)\n    {\n        $this->assertSame($expected, CoreExtension::compare($a, $b));\n        $this->assertSame($expected, -CoreExtension::compare($b, $a));\n    }\n\n    public function testCompareNAN()\n    {\n        $this->assertSame(1, CoreExtension::compare(\\NAN, 'NAN'));\n        $this->assertSame(1, CoreExtension::compare('NAN', \\NAN));\n        $this->assertSame(1, CoreExtension::compare(\\NAN, 'foo'));\n        $this->assertSame(1, CoreExtension::compare('foo', \\NAN));\n    }\n\n    public static function provideCompareCases()\n    {\n        return [\n            [0, 'a', 'a'],\n\n            // from https://wiki.php.net/rfc/string_to_number_comparison\n            [0, 0, '0'],\n            [0, 0, '0.0'],\n\n            [-1, 0, 'foo'],\n            [1, 0, ''],\n            [0, 42, '   42'],\n            [-1, 42, '42foo'],\n\n            [0, '0', '0'],\n            [0, '0', '0.0'],\n            [-1, '0', 'foo'],\n            [1, '0', ''],\n            [0, '42', '   42'],\n            [-1, '42', '42foo'],\n\n            [0, 42, '000042'],\n            [0, 42, '42.0'],\n            [0, 42.0, '+42.0E0'],\n            [0, 0, '0e214987142012'],\n\n            [0, '42', '000042'],\n            [0, '42', '42.0'],\n            [0, '42.0', '+42.0E0'],\n            [0, '0', '0e214987142012'],\n\n            [0, 42, '   42'],\n            [0, 42, '42   '],\n            [-1, 42, '42abc'],\n            [-1, 42, 'abc42'],\n            [-1, 0, 'abc42'],\n\n            [0, 42.0, '   42.0'],\n            [0, 42.0, '42.0   '],\n            [-1, 42.0, '42.0abc'],\n            [-1, 42.0, 'abc42.0'],\n            [-1, 0.0, 'abc42.0'],\n\n            [0, \\INF, 'INF'],\n            [0, -\\INF, '-INF'],\n            [0, \\INF, '1e1000'],\n            [0, -\\INF, '-1e1000'],\n\n            [-1, 10, 20],\n            [-1, '10', 20],\n            [-1, 10, '20'],\n\n            [1, 42, ' foo'],\n            [0, 42, \"42\\f\"],\n            [1, 42, \"\\x00\\x34\\x32\"],\n        ];\n    }\n\n    public function testSandboxedInclude()\n    {\n        $twig = new Environment(new ArrayLoader([\n            'index' => '{{ include(\"included\", sandboxed: true) }}',\n            'included' => '{{ \"included\"|e }}',\n        ]));\n        $policy = new SecurityPolicy(allowedFunctions: ['include']);\n        $sandbox = new SandboxExtension($policy, false);\n        $twig->addExtension($sandbox);\n\n        // We expect a compile error\n        $this->expectException(SecurityError::class);\n        $twig->render('index');\n    }\n\n    public function testSandboxedIncludeWithPreloadedTemplate()\n    {\n        $twig = new Environment(new ArrayLoader([\n            'index' => '{{ include(\"included\", sandboxed: true) }}',\n            'included' => '{{ \"included\"|e }}',\n        ]));\n        $policy = new SecurityPolicy(allowedFunctions: ['include']);\n        $sandbox = new SandboxExtension($policy, false);\n        $twig->addExtension($sandbox);\n\n        // The template is loaded without the sandbox enabled\n        // so, no compile error\n        $twig->load('included');\n\n        // We expect a runtime error\n        $this->expectException(SecurityError::class);\n        $twig->render('index');\n    }\n\n    public function testLastModified()\n    {\n        $this->assertGreaterThan(1000000000, (new CoreExtension())->getLastModified());\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testCycleWithArrayAccessAndTraversableButNotCountable()\n    {\n        $this->expectDeprecation('Since twig/twig 3.12: Passing a non-countable sequence of values to \"Twig\\Extension\\CoreExtension::cycle()\" is deprecated.');\n\n        $seq = new class implements \\ArrayAccess, \\IteratorAggregate {\n            public function offsetExists($offset): bool\n            {\n                return true;\n            }\n\n            public function offsetGet($offset): mixed\n            {\n                return 'val';\n            }\n\n            public function offsetSet($offset, $value): void\n            {\n            }\n\n            public function offsetUnset($offset): void\n            {\n            }\n\n            public function getIterator(): \\Traversable\n            {\n                yield 'odd';\n                yield 'even';\n            }\n        };\n\n        $result = CoreExtension::cycle($seq, 0);\n\n        $this->assertEquals('odd', $result, 'cycle should return the first item from the traversable sequence, not the sequence itself.');\n    }\n}\n\nfinal class CoreTestIteratorAggregate implements \\IteratorAggregate\n{\n    private $iterator;\n\n    public function __construct(array $array, array $keys, $allowAccess = false, $maxPosition = false)\n    {\n        $this->iterator = new CoreTestIterator($array, $keys, $allowAccess, $maxPosition);\n    }\n\n    public function getIterator(): \\Traversable\n    {\n        return $this->iterator;\n    }\n}\n\nfinal class CoreTestIteratorAggregateAggregate implements \\IteratorAggregate\n{\n    private $iterator;\n\n    public function __construct(array $array, array $keys, $allowValueAccess = false, $maxPosition = false)\n    {\n        $this->iterator = new CoreTestIteratorAggregate($array, $keys, $allowValueAccess, $maxPosition);\n    }\n\n    public function getIterator(): \\Traversable\n    {\n        return $this->iterator;\n    }\n}\n\nfinal class CoreTestIterator implements \\Iterator\n{\n    private $position;\n    private $array;\n    private $arrayKeys;\n    private $allowValueAccess;\n    private $maxPosition;\n\n    public function __construct(array $values, array $keys, $allowValueAccess = false, $maxPosition = false)\n    {\n        $this->array = $values;\n        $this->arrayKeys = $keys;\n        $this->position = 0;\n        $this->allowValueAccess = $allowValueAccess;\n        $this->maxPosition = false === $maxPosition ? \\count($values) + 1 : $maxPosition;\n    }\n\n    public function rewind(): void\n    {\n        $this->position = 0;\n    }\n\n    #[\\ReturnTypeWillChange]\n    public function current()\n    {\n        if ($this->allowValueAccess) {\n            return $this->array[$this->key()];\n        }\n\n        throw new \\LogicException('Code should only use the keys, not the values provided by iterator.');\n    }\n\n    #[\\ReturnTypeWillChange]\n    public function key()\n    {\n        return $this->arrayKeys[$this->position];\n    }\n\n    public function next(): void\n    {\n        ++$this->position;\n        if ($this->position === $this->maxPosition) {\n            throw new \\LogicException(\\sprintf('Code should not iterate beyond %d.', $this->maxPosition));\n        }\n    }\n\n    public function valid(): bool\n    {\n        return isset($this->arrayKeys[$this->position]);\n    }\n}\n"
  },
  {
    "path": "tests/Extension/EscaperTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Extension\\EscaperExtension;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Runtime\\EscaperRuntime;\n\nclass EscaperTest extends TestCase\n{\n    /**\n     * @dataProvider provideCustomEscaperCases\n     *\n     * @group legacy\n     */\n    public function testCustomEscaper($expected, $string, $strategy)\n    {\n        $twig = new Environment(new ArrayLoader());\n        $escaperExt = $twig->getExtension(EscaperExtension::class);\n        $escaperExt->setEscaper('foo', 'Twig\\Tests\\legacy_escaper');\n        $this->assertSame($expected, $twig->getRuntime(EscaperRuntime::class)->escape($string, $strategy, 'ISO-8859-1'));\n    }\n\n    public static function provideCustomEscaperCases()\n    {\n        return [\n            ['foo**ISO-8859-1**UTF-8', 'foo', 'foo'],\n            ['**ISO-8859-1**UTF-8', null, 'foo'],\n            ['42**ISO-8859-1**UTF-8', 42, 'foo'],\n        ];\n    }\n\n    /**\n     * @dataProvider provideCustomEscaperCases\n     *\n     * @group legacy\n     */\n    public function testCustomEscaperWithoutCallingSetEscaperRuntime($expected, $string, $strategy)\n    {\n        $twig = new Environment(new ArrayLoader());\n        $escaperExt = $twig->getExtension(EscaperExtension::class);\n        $escaperExt->setEscaper('foo', 'Twig\\Tests\\legacy_escaper');\n        $this->assertSame($expected, $twig->getRuntime(EscaperRuntime::class)->escape($string, $strategy, 'ISO-8859-1'));\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testCustomEscapersOnMultipleEnvs()\n    {\n        $env1 = new Environment(new ArrayLoader());\n        $escaperExt1 = $env1->getExtension(EscaperExtension::class);\n        $escaperExt1->setEscaper('foo', 'Twig\\Tests\\legacy_escaper');\n\n        $env2 = new Environment(new ArrayLoader());\n        $escaperExt2 = $env2->getExtension(EscaperExtension::class);\n        $escaperExt2->setEscaper('foo', 'Twig\\Tests\\legacy_escaper_again');\n\n        $this->assertSame('foo**ISO-8859-1**UTF-8', $env1->getRuntime(EscaperRuntime::class)->escape('foo', 'foo', 'ISO-8859-1'));\n        $this->assertSame('foo**ISO-8859-1**UTF-8**again', $env2->getRuntime(EscaperRuntime::class)->escape('foo', 'foo', 'ISO-8859-1'));\n    }\n\n    public function testLastModified()\n    {\n        $this->assertGreaterThan(1000000000, (new EscaperExtension())->getLastModified());\n    }\n}\n\nfunction legacy_escaper(Environment $twig, $string, $charset)\n{\n    return $string.'**'.$charset.'**'.$twig->getCharset();\n}\n\nfunction legacy_escaper_again(Environment $twig, $string, $charset)\n{\n    return $string.'**'.$charset.'**'.$twig->getCharset().'**again';\n}\n"
  },
  {
    "path": "tests/Extension/Fixtures/ExtensionWithAttributes.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension\\Fixtures;\n\nuse Twig\\Attribute\\AsTwigFilter;\nuse Twig\\Attribute\\AsTwigFunction;\nuse Twig\\Attribute\\AsTwigTest;\nuse Twig\\DeprecatedCallableInfo;\nuse Twig\\Environment;\n\nclass ExtensionWithAttributes\n{\n    #[AsTwigFilter(name: 'foo', isSafe: ['html'])]\n    public function fooFilter(string|int $string)\n    {\n    }\n\n    #[AsTwigFilter('with_context_filter', needsContext: true)]\n    public function withContextFilter(array $context, string $string)\n    {\n    }\n\n    #[AsTwigFilter('with_env_filter')]\n    public function withEnvFilter(Environment $env, string $string)\n    {\n    }\n\n    #[AsTwigFilter('with_env_and_context_filter', needsContext: true)]\n    public function withEnvAndContextFilter(Environment $env, array $context, array $data)\n    {\n    }\n\n    #[AsTwigFilter('variadic_filter')]\n    public function variadicFilter(string ...$strings)\n    {\n    }\n\n    #[AsTwigFilter('deprecated_filter', deprecationInfo: new DeprecatedCallableInfo('foo/bar', '1.2'))]\n    public function deprecatedFilter(string $string)\n    {\n    }\n\n    #[AsTwigFilter('pattern_*_filter')]\n    public function patternFilter(string $string)\n    {\n    }\n\n    #[AsTwigFunction(name: 'foo', isSafe: ['html'])]\n    public function fooFunction(string|int $string)\n    {\n    }\n\n    #[AsTwigFunction('with_context_function', needsContext: true)]\n    public function withContextFunction(array $context, string $string)\n    {\n    }\n\n    #[AsTwigFunction('with_env_function')]\n    public function withEnvFunction(Environment $env, string $string)\n    {\n    }\n\n    #[AsTwigFunction('with_env_and_context_function', needsContext: true)]\n    public function withEnvAndContextFunction(Environment $env, array $context, string $string)\n    {\n    }\n\n    #[AsTwigFunction('no_arg_function')]\n    public function noArgFunction()\n    {\n    }\n\n    #[AsTwigFunction('variadic_function')]\n    public function variadicFunction(string ...$strings)\n    {\n    }\n\n    #[AsTwigFunction('deprecated_function', deprecationInfo: new DeprecatedCallableInfo('foo/bar', '1.2'))]\n    public function deprecatedFunction(string $string)\n    {\n    }\n\n    #[AsTwigTest(name: 'foo')]\n    public function fooTest(string|int $value)\n    {\n    }\n\n    #[AsTwigTest('variadic_test')]\n    public function variadicTest(string ...$value)\n    {\n    }\n\n    #[AsTwigTest('with_context_test', needsContext: true)]\n    public function withContextTest(array $context, $argument)\n    {\n    }\n\n    #[AsTwigTest('with_env_test')]\n    public function withEnvTest(Environment $env, $argument)\n    {\n    }\n\n    #[AsTwigTest('with_env_and_context_test', needsContext: true)]\n    public function withEnvAndContextTest(Environment $env, array $context, $argument)\n    {\n    }\n\n    #[AsTwigTest('deprecated_test', deprecationInfo: new DeprecatedCallableInfo('foo/bar', '1.2'))]\n    public function deprecatedTest($value, $argument)\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Extension/Fixtures/FilterWithoutValue.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension\\Fixtures;\n\nuse Twig\\Attribute\\AsTwigFilter;\n\nclass FilterWithoutValue\n{\n    #[AsTwigFilter('my_filter')]\n    public function myFilter()\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Extension/Fixtures/TestWithoutValue.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension\\Fixtures;\n\nuse Twig\\Attribute\\AsTwigTest;\n\nclass TestWithoutValue\n{\n    #[AsTwigTest('my_test')]\n    public function myTest()\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Extension/LegacyDebugFunctionsTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Extension\\DebugExtension;\nuse Twig\\Loader\\ArrayLoader;\n\n/**\n * @group legacy\n */\nclass LegacyDebugFunctionsTest extends TestCase\n{\n    public function testDump()\n    {\n        $env = new Environment(new ArrayLoader());\n\n        $this->assertSame(DebugExtension::dump($env, 'Foo'), twig_var_dump($env, 'Foo'));\n    }\n}\n"
  },
  {
    "path": "tests/Extension/LegacyStringLoaderFunctionsTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Extension\\StringLoaderExtension;\nuse Twig\\Loader\\ArrayLoader;\n\n/**\n * @group legacy\n */\nclass LegacyStringLoaderFunctionsTest extends TestCase\n{\n    public function testTemplateFromString()\n    {\n        $env = new Environment(new ArrayLoader());\n\n        $this->assertSame(StringLoaderExtension::templateFromString($env, 'Foo')->render(), twig_template_from_string($env, 'Foo')->render());\n    }\n}\n"
  },
  {
    "path": "tests/Extension/SandboxTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Bridge\\PhpUnit\\ExpectDeprecationTrait;\nuse Twig\\Environment;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Extension\\StringLoaderExtension;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Sandbox\\SecurityError;\nuse Twig\\Sandbox\\SecurityNotAllowedFilterError;\nuse Twig\\Sandbox\\SecurityNotAllowedFunctionError;\nuse Twig\\Sandbox\\SecurityNotAllowedMethodError;\nuse Twig\\Sandbox\\SecurityNotAllowedPropertyError;\nuse Twig\\Sandbox\\SecurityNotAllowedTagError;\nuse Twig\\Sandbox\\SecurityPolicy;\nuse Twig\\Source;\n\nclass SandboxTest extends TestCase\n{\n    use ExpectDeprecationTrait;\n\n    protected static $params;\n    protected static $templates;\n\n    protected function setUp(): void\n    {\n        self::$params = [\n            'name' => 'Fabien',\n            'obj' => new FooObject(),\n            'arr' => ['obj' => new FooObject()],\n            'child_obj' => new ChildClass(),\n            'some_array' => [5, 6, 7, new FooObject()],\n            'array_like' => new ArrayLikeObject(),\n            'magic' => new MagicObject(),\n            'recursion' => [4],\n        ];\n        self::$params['recursion'][] = &self::$params['recursion'];\n        self::$params['recursion'][] = new FooObject();\n\n        self::$templates = [\n            '1_basic1' => '{{ obj.foo }}',\n            '1_basic2' => '{{ name|upper }}',\n            '1_basic3' => '{% if name %}foo{% endif %}',\n            '1_basic4' => '{{ obj.bar }}',\n            '1_basic5' => '{{ obj }}',\n            '1_basic7' => '{{ cycle([\"foo\",\"bar\"], 1) }}',\n            '1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}',\n            '1_basic9' => '{{ obj.foobar }}{{ obj.fooBar }}',\n            '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',\n            '1_layout' => '{% block content %}{% endblock %}',\n            '1_child' => \"{% extends \\\"1_layout\\\" %}\\n{% block content %}\\n{{ \\\"a\\\"|json_encode }}\\n{% endblock %}\",\n            '1_include' => '{{ include(\"1_basic1\", sandboxed=true) }}',\n            '1_basic2_include_template_from_string_sandboxed' => '{{ include(template_from_string(\"{{ name|upper }}\"), sandboxed=true) }}',\n            '1_basic2_include_template_from_string' => '{{ include(template_from_string(\"{{ name|upper }}\")) }}',\n            '1_range_operator' => '{{ (1..2)[0] }}',\n            '1_syntax_error_wrapper_legacy' => '{% sandbox %}{% include \"1_syntax_error\" %}{% endsandbox %}',\n            '1_syntax_error_wrapper' => '{{ include(\"1_syntax_error\", sandboxed: true) }}',\n            '1_syntax_error' => '{% syntax error }}',\n            '1_childobj_parentmethod' => '{{ child_obj.ParentMethod() }}',\n            '1_childobj_childmethod' => '{{ child_obj.ChildMethod() }}',\n            '1_empty' => '',\n            '1_array_like' => '{{ array_like[\"foo\"] }}',\n        ];\n    }\n\n    /**\n     * @dataProvider getSandboxedForCoreTagsTests\n     */\n    public function testSandboxForCoreTags(string $tag, string $template)\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, []);\n\n        $this->expectException(SecurityError::class);\n        $this->expectExceptionMessageMatches(\\sprintf('/Tag \"%s\" is not allowed in \"index \\(string template .+?\\)\" at line 1/', $tag));\n\n        $twig->createTemplate($template, 'index')->render([]);\n    }\n\n    public static function getSandboxedForCoreTagsTests()\n    {\n        yield ['apply', '{% apply upper %}foo{% endapply %}'];\n        yield ['autoescape', '{% autoescape %}foo{% endautoescape %}'];\n        yield ['block', '{% block foo %}foo{% endblock %}'];\n        yield ['deprecated', '{% deprecated \"message\" %}'];\n        yield ['do', '{% do 1 + 2 %}'];\n        yield ['embed', '{% embed \"base.twig\" %}{% endembed %}'];\n        // To be uncommented in 4.0\n        // yield ['extends', '{% extends \"base.twig\" %}'];\n        yield ['flush', '{% flush %}'];\n        yield ['for', '{% for i in 1..2 %}{% endfor %}'];\n        yield ['from', '{% from \"macros\" import foo %}'];\n        yield ['if', '{% if false %}{% endif %}'];\n        yield ['import', '{% import \"macros\" as macros %}'];\n        yield ['include', '{% include \"macros\" %}'];\n        yield ['macro', '{% macro foo() %}{% endmacro %}'];\n        yield ['set', '{% set foo = 1 %}'];\n        // To be uncommented in 4.0\n        // yield ['use', '{% use \"1_empty\" %}'];\n        yield ['with', '{% with foo %}{% endwith %}'];\n    }\n\n    /**\n     * @dataProvider getSandboxedForExtendsAndUseTagsTests\n     *\n     * @group legacy\n     */\n    public function testSandboxForExtendsAndUseTags(string $tag, string $template)\n    {\n        $this->expectDeprecation(\\sprintf('Since twig/twig 3.12: The \"%s\" tag is always allowed in sandboxes, but won\\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.', $tag));\n\n        $twig = $this->getEnvironment(true, [], self::$templates, []);\n        $twig->createTemplate($template, 'index')->render([]);\n    }\n\n    public static function getSandboxedForExtendsAndUseTagsTests()\n    {\n        yield ['extends', '{% extends \"1_empty\" %}'];\n        yield ['use', '{% use \"1_empty\" %}'];\n    }\n\n    public function testSandboxWithInheritance()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, ['extends', 'block']);\n\n        $this->expectException(SecurityError::class);\n        $this->expectExceptionMessage('Filter \"json_encode\" is not allowed in \"1_child\" at line 3.');\n\n        $twig->load('1_child')->render([]);\n    }\n\n    public function testSandboxGloballySet()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates);\n        $this->assertEquals('FOO', $twig->load('1_basic')->render(self::$params), 'Sandbox does nothing if it is disabled globally');\n    }\n\n    public function testSandboxUnallowedPropertyAccessor()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates);\n        try {\n            $twig->load('1_basic1')->render(['obj' => new MagicObject()]);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed method is called');\n        } catch (SecurityNotAllowedPropertyError $e) {\n            $this->assertEquals('Twig\\Tests\\Extension\\MagicObject', $e->getClassName(), 'Exception should be raised on the \"Twig\\Tests\\Extension\\MagicObject\" class');\n            $this->assertEquals('foo', $e->getPropertyName(), 'Exception should be raised on the \"foo\" property');\n        }\n    }\n\n    public function testSandboxUnallowedArrayIndexAccessor()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates);\n\n        // ArrayObject and other internal array-like classes are exempted from sandbox restrictions\n        $this->assertSame('bar', $twig->load('1_array_like')->render(['array_like' => new \\ArrayObject(['foo' => 'bar'])]));\n\n        try {\n            $twig->load('1_array_like')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed method is called');\n        } catch (SecurityNotAllowedPropertyError $e) {\n            $this->assertEquals('Twig\\Tests\\Extension\\ArrayLikeObject', $e->getClassName(), 'Exception should be raised on the \"Twig\\Tests\\Extension\\ArrayLikeObject\" class');\n            $this->assertEquals('foo', $e->getPropertyName(), 'Exception should be raised on the \"foo\" property');\n        }\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testIfSandBoxIsDisabledAfterSyntaxErrorLegacy()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates);\n        try {\n            $twig->load('1_syntax_error_wrapper_legacy')->render(self::$params);\n        } catch (SyntaxError $e) {\n            /** @var SandboxExtension $sandbox */\n            $sandbox = $twig->getExtension(SandboxExtension::class);\n            $this->assertFalse($sandbox->isSandboxed());\n        }\n    }\n\n    public function testIfSandBoxIsDisabledAfterSyntaxError()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates);\n        try {\n            $twig->load('1_syntax_error_wrapper')->render(self::$params);\n        } catch (SyntaxError $e) {\n            /** @var SandboxExtension $sandbox */\n            $sandbox = $twig->getExtension(SandboxExtension::class);\n            $this->assertFalse($sandbox->isSandboxed());\n        }\n    }\n\n    public function testSandboxGloballyFalseUnallowedFilterWithIncludeTemplateFromStringSandboxed()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates);\n        $twig->addExtension(new StringLoaderExtension());\n        try {\n            $twig->load('1_basic2_include_template_from_string_sandboxed')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');\n        } catch (SecurityNotAllowedFilterError $e) {\n            $this->assertEquals('upper', $e->getFilterName(), 'Exception should be raised on the \"upper\" filter');\n        }\n    }\n\n    public function testSandboxGloballyTrueUnallowedFilterWithIncludeTemplateFromStringSandboxed()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['include', 'template_from_string']);\n        $twig->addExtension(new StringLoaderExtension());\n        try {\n            $twig->load('1_basic2_include_template_from_string_sandboxed')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');\n        } catch (SecurityNotAllowedFilterError $e) {\n            $this->assertEquals('upper', $e->getFilterName(), 'Exception should be raised on the \"upper\" filter');\n        }\n    }\n\n    public function testSandboxGloballyFalseUnallowedFilterWithIncludeTemplateFromStringNotSandboxed()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates);\n        $twig->addExtension(new StringLoaderExtension());\n        $this->assertSame('FABIEN', $twig->load('1_basic2_include_template_from_string')->render(self::$params));\n    }\n\n    public function testSandboxGloballyTrueUnallowedFilterWithIncludeTemplateFromStringNotSandboxed()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['include', 'template_from_string']);\n        $twig->addExtension(new StringLoaderExtension());\n        try {\n            $twig->load('1_basic2_include_template_from_string')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');\n        } catch (SecurityNotAllowedFilterError $e) {\n            $this->assertEquals('upper', $e->getFilterName(), 'Exception should be raised on the \"upper\" filter');\n        }\n    }\n\n    public function testSandboxUnallowedFilter()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates);\n        try {\n            $twig->load('1_basic2')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');\n        } catch (SecurityNotAllowedFilterError $e) {\n            $this->assertEquals('upper', $e->getFilterName(), 'Exception should be raised on the \"upper\" filter');\n        }\n    }\n\n    public function testSandboxUnallowedTag()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates);\n        try {\n            $twig->load('1_basic3')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template');\n        } catch (SecurityNotAllowedTagError $e) {\n            $this->assertEquals('if', $e->getTagName(), 'Exception should be raised on the \"if\" tag');\n        }\n    }\n\n    public function testSandboxUnallowedProperty()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates);\n        try {\n            $twig->load('1_basic4')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template');\n        } catch (SecurityNotAllowedPropertyError $e) {\n            $this->assertEquals('Twig\\Tests\\Extension\\FooObject', $e->getClassName(), 'Exception should be raised on the \"Twig\\Tests\\Extension\\FooObject\" class');\n            $this->assertEquals('bar', $e->getPropertyName(), 'Exception should be raised on the \"bar\" property');\n        }\n    }\n\n    /**\n     * @dataProvider getSandboxUnallowedToStringTests\n     */\n    public function testSandboxUnallowedToString($template)\n    {\n        $twig = $this->getEnvironment(true, [], ['index' => $template], [], ['upper', 'join', 'replace'], ['Twig\\Tests\\Extension\\FooObject' => 'getAnotherFooObject'], [], ['random']);\n        try {\n            $twig->load('index')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed method \"__toString()\" method is called in the template');\n        } catch (SecurityNotAllowedMethodError $e) {\n            $this->assertEquals('Twig\\Tests\\Extension\\FooObject', $e->getClassName(), 'Exception should be raised on the \"Twig\\Tests\\Extension\\FooObject\" class');\n            $this->assertEquals('__tostring', $e->getMethodName(), 'Exception should be raised on the \"__toString\" method');\n        }\n    }\n\n    public static function getSandboxUnallowedToStringTests()\n    {\n        return [\n            'simple' => ['{{ obj }}'],\n            'object_from_array' => ['{{ arr.obj }}'],\n            'object_chain' => ['{{ obj.anotherFooObject }}'],\n            'filter' => ['{{ obj|upper }}'],\n            'filter_from_array' => ['{{ arr.obj|upper }}'],\n            'function' => ['{{ random(obj) }}'],\n            'function_from_array' => ['{{ random(arr.obj) }}'],\n            'function_and_filter' => ['{{ random(obj|upper) }}'],\n            'function_and_filter_from_array' => ['{{ random(arr.obj|upper) }}'],\n            'object_chain_and_filter' => ['{{ obj.anotherFooObject|upper }}'],\n            'object_chain_and_function' => ['{{ random(obj.anotherFooObject) }}'],\n            'concat' => ['{{ obj ~ \"\" }}'],\n            'concat_again' => ['{{ \"\" ~ obj }}'],\n            'object_in_arguments' => ['{{ \"__toString\"|replace({\"__toString\": obj}) }}'],\n            'object_in_array' => ['{{ [12, \"foo\", obj]|join(\", \") }}'],\n            'object_in_array_var' => ['{{ some_array|join(\", \") }}'],\n            'object_in_array_nested' => ['{{ [12, \"foo\", [12, \"foo\", obj]]|join(\", \") }}'],\n            'object_in_array_var_nested' => ['{{ [12, \"foo\", some_array]|join(\", \") }}'],\n            'object_in_array_dynamic_key' => ['{{ {(obj): \"foo\"}|join(\", \") }}'],\n            'object_in_array_dynamic_key_nested' => ['{{ {\"foo\": { (obj): \"foo\" }}|join(\", \") }}'],\n            'context' => ['{{ _context|join(\", \") }}'],\n            'spread_array_operator' => ['{{ [1, 2, ...[5, 6, 7, obj]]|join(\",\") }}'],\n            'spread_array_operator_var' => ['{{ [1, 2, ...some_array]|join(\",\") }}'],\n            'recursion' => ['{{ recursion|join(\", \") }}'],\n        ];\n    }\n\n    /**\n     * @dataProvider getSandboxAllowedToStringTests\n     */\n    public function testSandboxAllowedToString($template, $output)\n    {\n        $twig = $this->getEnvironment(true, [], ['index' => $template], ['set'], [], ['Twig\\Tests\\Extension\\FooObject' => ['foo', 'getAnotherFooObject']]);\n        $this->assertEquals($output, $twig->load('index')->render(self::$params));\n    }\n\n    public static function getSandboxAllowedToStringTests()\n    {\n        return [\n            'constant_test' => ['{{ obj is constant(\"PHP_INT_MAX\") }}', ''],\n            'set_object' => ['{% set a = obj.anotherFooObject %}{{ a.foo }}', 'foo'],\n            'is_defined1' => ['{{ obj.anotherFooObject is defined }}', '1'],\n            'is_defined2' => ['{{ magic.foo is defined }}', ''],\n            'is_null' => ['{{ obj is null }}', ''],\n            'is_sameas' => ['{{ obj is same as(obj) }}', '1'],\n            'is_sameas_no_brackets' => ['{{ obj is same as obj }}', '1'],\n            'is_sameas_from_array' => ['{{ arr.obj is same as(arr.obj) }}', '1'],\n            'is_sameas_from_array_no_brackets' => ['{{ arr.obj is same as arr.obj }}', '1'],\n            'is_sameas_from_another_method' => ['{{ obj.anotherFooObject is same as(obj.anotherFooObject) }}', ''],\n            'is_sameas_from_another_method_no_brackets' => ['{{ obj.anotherFooObject is same as obj.anotherFooObject }}', ''],\n        ];\n    }\n\n    public function testSandboxAllowMethodToString()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], [], ['Twig\\Tests\\Extension\\FooObject' => '__toString']);\n        FooObject::reset();\n        $this->assertEquals('foo', $twig->load('1_basic5')->render(self::$params), 'Sandbox allow some methods');\n        $this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once');\n    }\n\n    public function testSandboxAllowMethodToStringDisabled()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates);\n        FooObject::reset();\n        $this->assertEquals('foo', $twig->load('1_basic5')->render(self::$params), 'Sandbox allows __toString when sandbox disabled');\n        $this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once');\n    }\n\n    public function testSandboxUnallowedFunction()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates);\n        try {\n            $twig->load('1_basic7')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if an unallowed function is called in the template');\n        } catch (SecurityNotAllowedFunctionError $e) {\n            $this->assertEquals('cycle', $e->getFunctionName(), 'Exception should be raised on the \"cycle\" function');\n        }\n    }\n\n    public function testSandboxUnallowedRangeOperator()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates);\n        try {\n            $twig->load('1_range_operator')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception if the unallowed range operator is called');\n        } catch (SecurityNotAllowedFunctionError $e) {\n            $this->assertEquals('range', $e->getFunctionName(), 'Exception should be raised on the \"range\" function');\n        }\n    }\n\n    public function testSandboxAllowMethodFoo()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], [], ['Twig\\Tests\\Extension\\FooObject' => 'foo']);\n        FooObject::reset();\n        $this->assertEquals('foo', $twig->load('1_basic1')->render(self::$params), 'Sandbox allow some methods');\n        $this->assertEquals(1, FooObject::$called['foo'], 'Sandbox only calls method once');\n    }\n\n    public function testSandboxAllowFilter()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], ['upper']);\n        $this->assertEquals('FABIEN', $twig->load('1_basic2')->render(self::$params), 'Sandbox allow some filters');\n    }\n\n    public function testSandboxAllowTag()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, ['if']);\n        $this->assertEquals('foo', $twig->load('1_basic3')->render(self::$params), 'Sandbox allow some tags');\n    }\n\n    public function testSandboxAllowProperty()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], ['Twig\\Tests\\Extension\\FooObject' => 'bar']);\n        $this->assertEquals('bar', $twig->load('1_basic4')->render(self::$params), 'Sandbox allow some properties');\n    }\n\n    public function testSandboxAllowFunction()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['cycle']);\n        $this->assertEquals('bar', $twig->load('1_basic7')->render(self::$params), 'Sandbox allow some functions');\n    }\n\n    public function testSandboxAllowRangeOperator()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['range']);\n        $this->assertEquals('1', $twig->load('1_range_operator')->render(self::$params), 'Sandbox allow the range operator');\n    }\n\n    public function testSandboxAllowMethodsCaseInsensitive()\n    {\n        foreach (['getfoobar', 'getFoobar', 'getFooBar'] as $name) {\n            $twig = $this->getEnvironment(true, [], self::$templates, [], [], ['Twig\\Tests\\Extension\\FooObject' => $name]);\n            FooObject::reset();\n            $this->assertEquals('foobarfoobar', $twig->load('1_basic8')->render(self::$params), 'Sandbox allow methods in a case-insensitive way');\n            $this->assertEquals(2, FooObject::$called['getFooBar'], 'Sandbox only calls method once');\n\n            $this->assertEquals('foobarfoobar', $twig->load('1_basic9')->render(self::$params), 'Sandbox allow methods via shortcut names (ie. without get/set)');\n        }\n    }\n\n    public function testSandboxLocallySetForAnInclude()\n    {\n        self::$templates = [\n            '2_basic' => '{{ obj.foo }}{% include \"2_included\" %}{{ obj.foo }}',\n            '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',\n        ];\n\n        $twig = $this->getEnvironment(false, [], self::$templates);\n        $this->assertEquals('fooFOOfoo', $twig->load('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');\n\n        self::$templates = [\n            '3_basic' => '{{ include(\"3_included\", sandboxed: true) }}',\n            '3_included' => '{% if true %}{{ \"foo\"|upper }}{% endif %}',\n        ];\n\n        $twig = $this->getEnvironment(true, [], self::$templates, functions: ['include']);\n        try {\n            $twig->load('3_basic')->render(self::$params);\n            $this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed');\n        } catch (SecurityNotAllowedTagError $e) {\n            $this->assertEquals('if', $e->getTagName());\n        }\n    }\n\n    public function testMacrosInASandbox()\n    {\n        $twig = $this->getEnvironment(true, ['autoescape' => 'html'], ['index' => <<<EOF\n{%- import _self as macros %}\n\n{%- macro test(text) %}<p>{{ text }}</p>{% endmacro %}\n\n{{- macros.test('username') }}\nEOF\n        ], ['macro', 'import'], ['escape']);\n\n        $this->assertEquals('<p>username</p>', $twig->load('index')->render([]));\n    }\n\n    public function testSandboxDisabledAfterIncludeFunctionError()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates);\n\n        $e = null;\n        try {\n            $twig->load('1_include')->render(self::$params);\n        } catch (\\Throwable $e) {\n        }\n        if (null === $e) {\n            $this->fail('An exception should be thrown for this test to be valid.');\n        }\n\n        $this->assertFalse($twig->getExtension(SandboxExtension::class)->isSandboxed(), 'Sandboxed include() function call should not leave Sandbox enabled when an error occurs.');\n    }\n\n    public function testSandboxWithNoClosureFilter()\n    {\n        $twig = $this->getEnvironment(true, ['autoescape' => 'html'], ['index' => <<<EOF\n{{ [\"foo\", \"bar\", \"\"]|filter(\"trim\")|join(\", \") }}\nEOF\n        ], [], ['escape', 'filter', 'join']);\n\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('The callable passed to the \"filter\" filter must be a Closure in sandbox mode in \"index\" at line 1.');\n\n        $twig->load('index')->render([]);\n    }\n\n    public function testSandboxWithClosureFilter()\n    {\n        $twig = $this->getEnvironment(true, ['autoescape' => 'html'], ['index' => <<<EOF\n{{ [\"foo\", \"bar\", \"\"]|filter(v => v != \"\")|join(\", \") }}\nEOF\n        ], [], ['escape', 'filter', 'join']);\n\n        $this->assertSame('foo, bar', $twig->load('index')->render([]));\n    }\n\n    public function testMultipleClassMatchesViaInheritanceInAllowedMethods()\n    {\n        $twig_child_first = $this->getEnvironment(true, [], self::$templates, [], [], [\n            'Twig\\Tests\\Extension\\ChildClass' => ['ChildMethod'],\n            'Twig\\Tests\\Extension\\ParentClass' => ['ParentMethod'],\n        ]);\n        $twig_parent_first = $this->getEnvironment(true, [], self::$templates, [], [], [\n            'Twig\\Tests\\Extension\\ParentClass' => ['ParentMethod'],\n            'Twig\\Tests\\Extension\\ChildClass' => ['ChildMethod'],\n        ]);\n\n        try {\n            $twig_child_first->load('1_childobj_childmethod')->render(self::$params);\n        } catch (SecurityError $e) {\n            $this->fail('This test case is malfunctioning as even the child class method which comes first is not being allowed.');\n        }\n\n        try {\n            $twig_parent_first->load('1_childobj_parentmethod')->render(self::$params);\n        } catch (SecurityError $e) {\n            $this->fail('This test case is malfunctioning as even the parent class method which comes first is not being allowed.');\n        }\n\n        try {\n            $twig_parent_first->load('1_childobj_childmethod')->render(self::$params);\n        } catch (SecurityError $e) {\n            $this->fail('checkMethodAllowed is exiting prematurely after matching a parent class and not seeing a method allowed on a child class later in the list');\n        }\n\n        try {\n            $twig_child_first->load('1_childobj_parentmethod')->render(self::$params);\n        } catch (SecurityError $e) {\n            $this->fail('checkMethodAllowed is exiting prematurely after matching a child class and not seeing a method allowed on its parent class later in the list');\n        }\n\n        $this->expectNotToPerformAssertions();\n    }\n\n    protected function getEnvironment($sandboxed, $options, $templates, $tags = [], $filters = [], $methods = [], $properties = [], $functions = [], $sourcePolicy = null)\n    {\n        $loader = new ArrayLoader($templates);\n        $twig = new Environment($loader, array_merge(['debug' => true, 'cache' => false, 'autoescape' => false], $options));\n        $policy = new SecurityPolicy($tags, $filters, $methods, $properties, $functions);\n        $twig->addExtension(new SandboxExtension($policy, $sandboxed, $sourcePolicy));\n\n        return $twig;\n    }\n\n    public function testSandboxSourcePolicyEnableReturningFalse()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class implements \\Twig\\Sandbox\\SourcePolicyInterface {\n            public function enableSandbox(Source $source): bool\n            {\n                return '1_basic' != $source->getName();\n            }\n        });\n        $this->assertEquals('FOO', $twig->load('1_basic')->render(self::$params));\n    }\n\n    public function testSandboxSourcePolicyEnableReturningTrue()\n    {\n        $twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class implements \\Twig\\Sandbox\\SourcePolicyInterface {\n            public function enableSandbox(Source $source): bool\n            {\n                return '1_basic' === $source->getName();\n            }\n        });\n        $this->expectException(SecurityError::class);\n        $twig->load('1_basic')->render([]);\n    }\n\n    public function testSandboxSourcePolicyFalseDoesntOverrideOtherEnables()\n    {\n        $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], [], new class implements \\Twig\\Sandbox\\SourcePolicyInterface {\n            public function enableSandbox(Source $source): bool\n            {\n                return false;\n            }\n        });\n        $this->expectException(SecurityError::class);\n        $twig->load('1_basic')->render([]);\n    }\n}\n\nclass ParentClass\n{\n    public function ParentMethod()\n    {\n    }\n}\nclass ChildClass extends ParentClass\n{\n    public function ChildMethod()\n    {\n    }\n}\n\nclass FooObject\n{\n    public static $called = ['__toString' => 0, 'foo' => 0, 'getFooBar' => 0];\n\n    public $bar = 'bar';\n\n    public static function reset()\n    {\n        self::$called = ['__toString' => 0, 'foo' => 0, 'getFooBar' => 0];\n    }\n\n    public function __toString()\n    {\n        ++self::$called['__toString'];\n\n        return 'foo';\n    }\n\n    public function foo()\n    {\n        ++self::$called['foo'];\n\n        return 'foo';\n    }\n\n    public function getFooBar()\n    {\n        ++self::$called['getFooBar'];\n\n        return 'foobar';\n    }\n\n    public function getAnotherFooObject()\n    {\n        return new self();\n    }\n}\n\nclass ArrayLikeObject extends \\ArrayObject\n{\n    public function offsetExists($offset): bool\n    {\n        throw new \\BadMethodCallException('Should not be called.');\n    }\n\n    public function offsetGet($offset): mixed\n    {\n        throw new \\BadMethodCallException('Should not be called.');\n    }\n\n    public function offsetSet($offset, $value): void\n    {\n    }\n\n    public function offsetUnset($offset): void\n    {\n    }\n}\n\nclass MagicObject\n{\n    public function __get($name): mixed\n    {\n        throw new \\BadMethodCallException('Should not be called.');\n    }\n\n    public function __isset($name): bool\n    {\n        throw new \\BadMethodCallException('Should not be called.');\n    }\n}\n"
  },
  {
    "path": "tests/Extension/StringLoaderExtensionTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Extension;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Extension\\StringLoaderExtension;\nuse Twig\\Loader\\ArrayLoader;\n\nclass StringLoaderExtensionTest extends TestCase\n{\n    public function testIncludeWithTemplateStringAndNoSandbox()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->addExtension(new StringLoaderExtension());\n        $this->assertSame('something', CoreExtension::include($twig, [], StringLoaderExtension::templateFromString($twig, 'something')));\n    }\n}\n"
  },
  {
    "path": "tests/FactoryRuntimeLoaderTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\RuntimeLoader\\FactoryRuntimeLoader;\n\nclass FactoryRuntimeLoaderTest extends TestCase\n{\n    public function testLoad()\n    {\n        $loader = new FactoryRuntimeLoader(['stdClass' => '\\Twig\\Tests\\getRuntime']);\n\n        $this->assertInstanceOf('stdClass', $loader->load('stdClass'));\n    }\n\n    public function testLoadReturnsNullForUnmappedRuntime()\n    {\n        $loader = new FactoryRuntimeLoader();\n\n        $this->assertNull($loader->load('stdClass'));\n    }\n}\n\nfunction getRuntime()\n{\n    return new \\stdClass();\n}\n"
  },
  {
    "path": "tests/FileExtensionEscapingStrategyTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\FileExtensionEscapingStrategy;\n\nclass FileExtensionEscapingStrategyTest extends TestCase\n{\n    /**\n     * @dataProvider getGuessData\n     */\n    public function testGuess($strategy, $filename)\n    {\n        $this->assertSame($strategy, FileExtensionEscapingStrategy::guess($filename));\n    }\n\n    public static function getGuessData()\n    {\n        return [\n            // default\n            ['html', 'foo.html'],\n            ['html', 'foo.html.twig'],\n            ['html', 'foo'],\n            ['html', 'foo.bar.twig'],\n            ['html', 'foo.txt/foo'],\n            ['html', 'foo.txt/foo.js/'],\n\n            // css\n            ['css', 'foo.css'],\n            ['css', 'foo.css.twig'],\n            ['css', 'foo.twig.css'],\n            ['css', 'foo.js.css'],\n            ['css', 'foo.js.css.twig'],\n\n            // js\n            ['js', 'foo.js'],\n            ['js', 'foo.js.twig'],\n            ['js', 'foo.txt/foo.js'],\n            ['js', 'foo.txt.twig/foo.js'],\n\n            // txt\n            [false, 'foo.txt'],\n            [false, 'foo.txt.twig'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/FilesystemHelper.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nclass FilesystemHelper\n{\n    public static function removeDir($dir)\n    {\n        $iterator = new \\RecursiveIteratorIterator(new \\RecursiveDirectoryIterator($dir, \\FilesystemIterator::SKIP_DOTS), \\RecursiveIteratorIterator::CHILD_FIRST);\n        foreach ($iterator as $filename => $fileInfo) {\n            if ($fileInfo->isDir()) {\n                rmdir($filename);\n            } else {\n                unlink($filename);\n            }\n        }\n        rmdir($dir);\n    }\n}\n"
  },
  {
    "path": "tests/Fixtures/autoescape/block.test",
    "content": "--TEST--\nblocks and autoescape\n--TEMPLATE--\n{{ include('unrelated.txt.twig') -}}\n{{ include('template.html.twig') -}}\n--TEMPLATE(unrelated.txt.twig)--\n{% block content %}{% endblock %}\n--TEMPLATE(template.html.twig)--\n{% extends 'parent.html.twig' %}\n{% block content %}\n{{ br -}}\n{% endblock %}\n--TEMPLATE(parent.html.twig)--\n{% set _content = block('content')|raw %}\n{{ _content|raw }}\n--DATA--\nreturn ['br' => '<br />']\n--CONFIG--\nreturn ['autoescape' => 'name']\n--EXPECT--\n&lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/autoescape/name.test",
    "content": "--TEST--\n\"name\" autoescape strategy\n--TEMPLATE--\n{{ br -}}\n{{ include('index.js.twig') -}}\n{{ include('index.html.twig') -}}\n{{ include('index.txt.twig') -}}\n--TEMPLATE(index.js.twig)--\n{{ br -}}\n--TEMPLATE(index.html.twig)--\n{{ br -}}\n--TEMPLATE(index.txt.twig)--\n{{ br -}}\n--DATA--\nreturn ['br' => '<br />']\n--CONFIG--\nreturn ['autoescape' => 'name']\n--EXPECT--\n&lt;br /&gt;\n\\u003Cbr\\u0020\\/\\u003E\n&lt;br /&gt;\n<br />\n"
  },
  {
    "path": "tests/Fixtures/errors/base.html",
    "content": "{% block content %}{% endblock %}\n"
  },
  {
    "path": "tests/Fixtures/errors/extends/include.twig",
    "content": "\n\n\n{% extends 'invalid.twig' %}\n"
  },
  {
    "path": "tests/Fixtures/errors/extends/index.twig",
    "content": "{% include \"include.twig\" %}\n"
  },
  {
    "path": "tests/Fixtures/errors/index.html",
    "content": "{% extends 'base.html' %}\n{% block content %}\n    {{ foo.bar }}\n{% endblock %}\n{% block foo %}\n    {{ foo.bar }}\n{% endblock %}\n"
  },
  {
    "path": "tests/Fixtures/errors/no_line_and_context_exception.twig",
    "content": "\n\n{{ include('no_line_and_context_exception_include_line_' ~ line ~ '.twig') }}\n"
  },
  {
    "path": "tests/Fixtures/errors/no_line_and_context_exception_include_line_1.twig",
    "content": "{% foo %}\n"
  },
  {
    "path": "tests/Fixtures/errors/no_line_and_context_exception_include_line_5.twig",
    "content": "\n\n\n\n{% foo %}\n"
  },
  {
    "path": "tests/Fixtures/exceptions/child_contents_outside_blocks.test",
    "content": "--TEST--\nException for child templates defining content outside blocks defined by parent\n--TEMPLATE--\n{% extends 'base.twig' %}\n\nContent outside a block.\n\n{% block sidebar %}\n    Content inside a block.\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% block sidebar %}\n{% endblock %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag in \"index.twig\" at line 3?\n"
  },
  {
    "path": "tests/Fixtures/exceptions/exception_in_extension_extends.test",
    "content": "--TEST--\nException thrown from a child for an extension error\n--TEMPLATE--\n{% extends 'base.twig' %}\n--TEMPLATE(base.twig)--\n\n\n{{ random([]) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: The \"random\" function cannot pick from an empty sequence or mapping in \"base.twig\" at line 4.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/exception_in_extension_include.test",
    "content": "--TEST--\nException thrown from an include for an extension error\n--TEMPLATE--\n{% include 'content.twig' %}\n--TEMPLATE(content.twig)--\n\n\n{{ random([]) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: The \"random\" function cannot pick from an empty sequence or mapping in \"content.twig\" at line 4.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/multiline_array_with_undefined_variable.test",
    "content": "--TEST--\nException for multiline array with undefined variable\n--TEMPLATE--\n{% set foo = {\n   foo: 'foo',\n   bar: 'bar',\n\n\n   foobar: foobar,\n\n\n\n   foo2: foo2,\n} %}\n--DATA--\nreturn ['foobar' => 'foobar']\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"foo2\" does not exist in \"index.twig\" at line 11.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/multiline_array_with_undefined_variable_again.test",
    "content": "--TEST--\nException for multiline array with undefined variable\n--TEMPLATE--\n{% set foo = {\n   foo: 'foo',\n   bar: 'bar',\n\n\n   foobar: foobar,\n\n\n\n   foo2: foo2,\n} %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"foobar\" does not exist in \"index.twig\" at line 7.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test",
    "content": "--TEST--\nException for multile function with undefined variable\n--TEMPLATE--\n{{ include('foo',\n   with_context=with_context\n) }}\n--TEMPLATE(foo)--\nFoo\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"with_context\" does not exist in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/multiline_function_with_unknown_argument.test",
    "content": "--TEST--\nException for multiline function with unknown argument\n--TEMPLATE--\n{{ include('foo',\n   with_context=True,\n   invalid=False\n) }}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unknown argument \"invalid\" for function \"include(template, variables, with_context, ignore_missing, sandboxed)\" in \"index.twig\" at line 4.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/multiline_tag_with_undefined_variable.test",
    "content": "--TEST--\nException for multiline tag with undefined variable\n--TEMPLATE--\n{% include 'foo'\n   with vars\n%}\n--TEMPLATE(foo)--\nFoo\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"vars\" does not exist in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/syntax_error_in_reused_template.test",
    "content": "--TEST--\nException for syntax error in reused template\n--TEMPLATE--\n{% use 'foo.twig' %}\n--TEMPLATE(foo.twig)--\n{% block bar %}\n    {% do node.data 5 %}\n{% endblock %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unexpected token \"number\" of value \"5\" (\"end of statement block\" expected) in \"foo.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/unclosed_tag.test",
    "content": "--TEST--\nException for an unclosed tag\n--TEMPLATE--\n{% block foo %}\n     {% if foo %}\n\n\n\n\n         {% for i in fo %}\n\n\n\n         {% endfor %}\n\n\n\n{% endblock %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unexpected \"endblock\" tag (expecting closing tag for the \"if\" tag defined near line 4) in \"index.twig\" at line 16.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/undefined_parent.test",
    "content": "--TEST--\nException for an undefined parent\n--TEMPLATE--\n{% extends 'foo.html' %}\n\n{% set foo = \"foo\" %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"foo.html\" is not defined in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/undefined_template_in_child_template.test",
    "content": "--TEST--\nException for an undefined template in a child template\n--TEMPLATE--\n{% extends 'base.twig' %}\n\n{% block sidebar %}\n    {{ include('include.twig') }}\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% block sidebar %}\n{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"include.twig\" is not defined in \"index.twig\" at line 5.\n"
  },
  {
    "path": "tests/Fixtures/exceptions/undefined_trait.test",
    "content": "--TEST--\nException for an undefined trait\n--TEMPLATE--\n{% use 'foo' with foobar as bar %}\n--TEMPLATE(foo)--\n{% block bar %}\n{% endblock %}\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Block \"foobar\" is not defined in trait \"foo\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/expressions/_self.test",
    "content": "--TEST--\n_self returns the template name\n--TEMPLATE--\n{{ _self }}\n--DATA--\nreturn []\n--EXPECT--\nindex.twig\n"
  },
  {
    "path": "tests/Fixtures/expressions/array.test",
    "content": "--TEST--\nTwig supports array notation\n--TEMPLATE--\n{# empty array #}\n{{ []|join(',') }}\n\n{{ [1, 2]|join(',') }}\n{{ ['foo', \"bar\"]|join(',') }}\n{{ {0: 1, 'foo': 'bar'}|join(',') }}\n{{ {0: 1, 'foo': 'bar'}|keys|join(',') }}\n\n{{ {0: 1, foo: 'bar'}|join(',') }}\n{{ {0: 1, foo: 'bar'}|keys|join(',') }}\n\n{# nested arrays #}\n{% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %}\n{{ a[2]|join(',') }}\n{{ a[3][\"foo\"]|join(',') }}\n\n{# works even if [] is used inside the array #}\n{{ [foo[bar]]|join(',') }}\n\n{# elements can be any expression #}\n{{ ['foo'|upper, bar|upper, bar == foo]|join(',') }}\n\n{# arrays can have a trailing , like in PHP #}\n{{\n  [\n    1,\n    2,\n  ]|join(',')\n}}\n\n{# keys can be any expression #}\n{% set a = 1 %}\n{% set b = \"foo\" %}\n{% set markup_instance %}fooe{% endset %}\n{% set ary = { (a): 'a', (b): 'b', 'c': 'c', (a ~ b): 'd', (markup_instance): 'e' } %}\n{{ ary|keys|join(',') }}\n{{ ary|join(',') }}\n\n{# ArrayAccess #}\n{{ array_access['a'] }}\n\n{# ObjectStorage #}\n{{ object_storage[object] }}\n{{ object_storage[object_storage]|default('bar') }}\n\n{# array that does not exist #}\n{{ does_not_exist[0]|default('ok') }}\n{{ does_not_exist[0].does_not_exist_either|default('ok') }}\n{{ does_not_exist[0]['does_not_exist_either']|default('ok') }}\n\n{# indexes are kept #}\n{% set trad = {194:'ABC',141:'DEF',100:'GHI',170:'JKL',110:'MNO',111:'PQR'} %}\n{% set trad2 = {'194':'ABC','141':'DEF','100':'GHI','170':'JKL','110':'MNO','111':'PQR'} %}\n{{ trad == trad2 ? 'OK' : 'KO' }}\n{% set trad = {11: 'ABC', 2: 'DEF', 4: 'GHI', 3: 'JKL'} %}\n{% set trad2 = {'11': 'ABC', '2': 'DEF', '4': 'GHI', '3': 'JKL'} %}\n{{ trad == trad2 ? 'OK' : 'KO' }}\n\n{# indexes are kept #}\n{{ { 1: \"first\", 0: \"second\" } == { '1': \"first\", '0': \"second\" } ? 'OK' : 'KO' }}\n{{ { 1: \"first\", 0: \"second\" } == indices_1 ? 'OK' : 'KO' }}\n{{ { 1: \"first\", 'foo': \"second\", 2: \"third\" } == { '1': \"first\", 'foo': \"second\", '2': \"third\" } ? 'OK' : 'KO' }}\n{{ { 1: \"first\", 'foo': \"second\", 2: \"third\" } == indices_2 ? 'OK' : 'KO' }}\n--DATA--\n$objectStorage = new SplObjectStorage();\n$object = new stdClass();\n$objectStorage[$object] = 'foo';\nreturn [\n  'bar' => 'bar',\n  'foo' => ['bar' => 'bar'],\n  'array_access' => new \\ArrayObject(['a' => 'b']),\n  'object_storage' => $objectStorage,\n  'object' => $object,\n  'indices_1' => [ 1 => 'first', 0 => 'second' ],\n  'indices_2' => [ 1 => 'first', 'foo' => 'second', 2 => 'third' ],\n]\n--EXPECT--\n1,2\nfoo,bar\n1,bar\n0,foo\n\n1,bar\n0,foo\n\n1,2\nbar\n\nbar\n\nFOO,BAR,\n\n1,2\n\n1,foo,c,1foo,fooe\na,b,c,d,e\n\nb\n\nfoo\nbar\n\nok\nok\nok\n\nOK\nOK\n\nOK\nOK\nOK\nOK\n--DATA--\nreturn [\n  'bar' => 'bar',\n  'foo' => ['bar' => 'bar'],\n  'array_access' => new \\ArrayObject(['a' =>  'b']),\n  'object' => new stdClass(),\n  'indices_1' => [ 1 => 'first', 0 => 'second' ],\n  'indices_2' => [ 1 => 'first', 'foo' => 'second', 2 => 'third' ],\n]\n--CONFIG--\nreturn ['strict_variables' => false]\n--EXPECT--\n1,2\nfoo,bar\n1,bar\n0,foo\n\n1,bar\n0,foo\n\n1,2\nbar\n\nbar\n\nFOO,BAR,\n\n1,2\n\n1,foo,c,1foo,fooe\na,b,c,d,e\n\nb\n\n\nbar\n\nok\nok\nok\n\nOK\nOK\n\nOK\nOK\nOK\nOK\n"
  },
  {
    "path": "tests/Fixtures/expressions/array_call.test",
    "content": "--TEST--\nTwig supports method calls\n--TEMPLATE--\n{{ items.foo }}\n{{ items['foo'] }}\n{{ items[foo] }}\n{{ items[items[foo]] }}\n--DATA--\nreturn ['foo' => 'bar', 'items' => ['foo' => 'bar', 'bar' => 'foo']]\n--EXPECT--\nbar\nbar\nfoo\nbar\n"
  },
  {
    "path": "tests/Fixtures/expressions/attributes.test",
    "content": "--TEST--\n\".\" notation\n--TEMPLATE--\n{{ property.foo }}\n{{ date.timezone }}\n--DATA--\nreturn [\n    'date' => new \\DateTimeImmutable('now', new \\DateTimeZone('Europe/Paris')),\n    'property' => (object) array('foo' => 'bar'),\n]\n--EXPECT--\nbar\nEurope/Paris\n"
  },
  {
    "path": "tests/Fixtures/expressions/binary.test",
    "content": "--TEST--\nTwig supports binary operations (+, -, *, /, ~, %, and, xor, or)\n--TEMPLATE--\n{{ 1 + 1 }}\n{{ 2 - 1 }}\n{{ 2 * 2 }}\n{{ 2 / 2 }}\n{{ 3 % 2 }}\n{{ 1 and 1 }}\n{{ 1 and 0 }}\n{{ 0 and 1 }}\n{{ 0 and 0 }}\n{{ 1 or 1 }}\n{{ 1 or 0 }}\n{{ 0 or 1 }}\n{{ 0 or 0 }}\n{{ 0 or 1 and 0 }}\n{{ 1 or 0 and 1 }}\n{{ 1 xor 1 }}\n{{ 1 xor 0 }}\n{{ 0 xor 1 }}\n{{ 0 xor 0 }}\n{{ 0 and 1 or 1 xor 1 }}\n{{ 0 and 1 or 0 xor 1 }}\n{{ \"foo\" ~ \"bar\" }}\n{{ foo ~ \"bar\" }}\n{{ \"foo\" ~ bar }}\n{{ foo ~ bar }}\n{{ 20 // 7 }}\n--DATA--\nreturn ['foo' => 'bar', 'bar' => 'foo']\n--EXPECT--\n2\n1\n4\n1\n1\n1\n\n\n\n1\n1\n1\n\n\n1\n\n1\n1\n\n\n1\nfoobar\nbarbar\nfoofoo\nbarfoo\n2\n"
  },
  {
    "path": "tests/Fixtures/expressions/bitwise.test",
    "content": "--TEST--\nTwig supports bitwise operations\n--TEMPLATE--\n{{ 1 b-and 5 }}\n{{ 1 b-or 5 }}\n{{ 1 b-xor 5 }}\n{{ (1 and 0 b-or 0) is same as(1 and (0 b-or 0)) ? 'ok' : 'ko' }}\n--DATA--\nreturn []\n--EXPECT--\n1\n5\n4\nok\n"
  },
  {
    "path": "tests/Fixtures/expressions/call_argument_defined_twice.test",
    "content": "--TEST--\nArgument is defined twice in a call\n--TEMPLATE--\n{{ date(987654, date = 123456) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Argument \"date\" is defined twice for function \"date\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/expressions/call_argument_unpacking.test",
    "content": "--TEST--\nTwig supports array unpacking for function calls\n--TEMPLATE--\n{{ '%s %s %s'|format(...[1, 2, 3]) }}\n{{ '%s %s %s'|format(...[1], ...[2, 3]) }}\n{{ '%s %s %s'|format(1, ...[2, 3]) }}\n{{ '%s %s %s'|format(1, ...[2], ...[3]) }}\n{{ '%s %s %s'|format(...it) }}\n--DATA--\nreturn ['it' => new \\ArrayIterator([1, 2, 3])]\n--EXPECT--\n1 2 3\n1 2 3\n1 2 3\n1 2 3\n1 2 3\n"
  },
  {
    "path": "tests/Fixtures/expressions/call_argument_unpacking_before_normal.test",
    "content": "--TEST--\nTwig supports array unpacking for function calls (but not before normal args)\n--TEMPLATE--\n{{ '%s %s %s'|format(...[1, 2], 3) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Normal arguments must be placed before argument unpacking in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/expressions/call_positional_arg_after_named_arg.test",
    "content": "--TEST--\nPositional arguments after named arguments in a call\n--TEMPLATE--\n{{ date(date = 123456, 'Y-m-d') }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Positional arguments cannot be used after named arguments for function \"date\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/expressions/comparison.test",
    "content": "--TEST--\nTwig supports comparison operators (==, !=, <, >, >=, <=)\n--TEMPLATE--\n{{ 1 > 2 }}/{{ 1 > 1 }}/{{ 1 >= 2 }}/{{ 1 >= 1 }}\n{{ 1 < 2 }}/{{ 1 < 1 }}/{{ 1 <= 2 }}/{{ 1 <= 1 }}\n{{ 1 == 1 }}/{{ 1 == 2 }}\n{{ 1 != 1 }}/{{ 1 != 2 }}\n--DATA--\nreturn []\n--EXPECT--\n///1\n1//1/1\n1/\n/1\n"
  },
  {
    "path": "tests/Fixtures/expressions/comparison_precedence.test",
    "content": "--TEST--\nTwig comparison operators precendence\n--TEMPLATE--\n{{ not(1 > 2) }}/{{ not(1 > 1) }}/{{ not(1 >= 2) }}/{{ not(1 >= 1) }}\n{{ not(1 < 2) }}/{{ not(1 < 1) }}/{{ not(1 <= 2) }}/{{ not(1 <= 1) }}\n{{ not(1 == 1) }}/{{ not(1 == 2) }}\n{{ not(1 != 1) }}/{{ not(1 != 2) }}\n--DATA--\nreturn []\n--EXPECT--\n1/1/1/\n/1//\n/1\n1/\n"
  },
  {
    "path": "tests/Fixtures/expressions/const.test",
    "content": "--TEST--\nTwig supports accessing constants\n--TEMPLATE--\n{{ foo.BAR_NAME }}\n--DATA--\nreturn ['foo' => new Twig\\Tests\\TwigTestFoo()]\n--CONFIG--\nreturn ['strict_variables' => false]\n--EXPECT--\nbar\n"
  },
  {
    "path": "tests/Fixtures/expressions/divisibleby.test",
    "content": "--TEST--\nTwig supports the \"divisible by\" operator\n--TEMPLATE--\n{{ 8 is divisible by(2) ? 'OK' }}\n{{ 8 is divisible by 2 ? 'OK' }}\n{{ 8 is not divisible by(3) ? 'OK' }}\n{{ 8 is    divisible   by   (2) ? 'OK' }}\n{{ 8 is    divisible   by   2 ? 'OK' }}\n{{ 8 is not\n   divisible\n   by\n   (3) ? 'OK' }}\n--DATA--\nreturn []\n--EXPECT--\nOK\nOK\nOK\nOK\nOK\nOK\n"
  },
  {
    "path": "tests/Fixtures/expressions/dot_as_concatenation.test",
    "content": "--TEST--\nTwig does not support using . for concatenation\n--TEMPLATE--\n{{ 'a'.'b' }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Expected name or number, got value \"b\" of type \"string\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/expressions/dotdot.test",
    "content": "--TEST--\nTwig supports the .. operator\n--TEMPLATE--\n{% for i in 0..10 %}{{ i }} {% endfor %}\n\n{% for letter in 'a'..'z' %}{{ letter }} {% endfor %}\n\n{% for letter in 'a'|upper..'z'|upper %}{{ letter }} {% endfor %}\n\n{% for i in foo[0]..foo[1] %}{{ i }} {% endfor %}\n\n{% for i in 0 + 1 .. 10 - 1 %}{{ i }} {% endfor %}\n--DATA--\nreturn ['foo' => [1, 10]]\n--EXPECT--\n0 1 2 3 4 5 6 7 8 9 10 \na b c d e f g h i j k l m n o p q r s t u v w x y z \nA B C D E F G H I J K L M N O P Q R S T U V W X Y Z \n1 2 3 4 5 6 7 8 9 10 \n1 2 3 4 5 6 7 8 9\n"
  },
  {
    "path": "tests/Fixtures/expressions/dynamic_attribute.test",
    "content": "--TEST--\n\".\" notation with dynamic attributes\n--TEMPLATE--\n{{ obj.(method) }}\n{{ array.(item) }}\n{{ obj.(\"bar\")(\"a\", \"b\") }}\n{{ obj.(\"bar\")(param1: \"a\", param2: \"b\") }}\n{{ obj.(\"bar\")(param2: \"b\", param1: \"a\") }}\n{{ obj.(\"bar\")(\"a\", param2: \"b\") }}\n{{ obj.(\"bar\")(...arguments) }}\n{{ obj.(method) is defined ? 'ok' : 'ko' }}\n{{ obj.(nonmethod) is defined ? 'ok' : 'ko' }}\n--DATA--\nreturn ['obj' => new Twig\\Tests\\TwigTestFoo(), 'method' => 'foo', 'array' => ['foo' => 'bar'], 'item' => 'foo', 'nonmethod' => 'xxx', 'arguments' => ['a', 'b']]\n--EXPECT--\nfoo\nbar\nbar_a-b\nbar_a-b\nbar_a-b\nbar_a-b\nbar_a-b\nok\nko\n"
  },
  {
    "path": "tests/Fixtures/expressions/ends_with.test",
    "content": "--TEST--\nTwig supports the \"ends with\" operator\n--TEMPLATE--\n{{ 'foo' ends with 'o' ? 'OK' : 'KO' }}\n{{ not ('foo' ends with 'f') ? 'OK' : 'KO' }}\n{{ not ('foo' ends with 'foowaytoolong') ? 'OK' : 'KO' }}\n{{ 'foo' ends with '' ? 'OK' : 'KO' }}\n{{ '1' ends with true ? 'OK' : 'KO' }}\n{{ 1 ends with true ? 'OK' : 'KO' }}\n{{ 0 ends with false ? 'OK' : 'KO' }}\n{{ '' ends with false ? 'OK' : 'KO' }}\n{{ false ends with false ? 'OK' : 'KO' }}\n{{ false ends with '' ? 'OK' : 'KO' }}\n--DATA--\nreturn []\n--EXPECT--\nOK\nOK\nOK\nOK\nKO\nKO\nKO\nKO\nKO\nKO\n"
  },
  {
    "path": "tests/Fixtures/expressions/exponential_numbers.test",
    "content": "--TEST--\nTwig manages exponentiel numbers correctly\n--TEMPLATE--\n{{ 1.99E+3 }}\n--DATA--\nreturn []\n--EXPECT--\n1990\n"
  },
  {
    "path": "tests/Fixtures/expressions/floats.test",
    "content": "--TEST--\nTwig compiles floats properly\n--TEMPLATE--\n{% set val2 = 0.0 %}\n\n{{ val is same as (0.0) ? 'Yes' : 'No' }}\n{{ val2 is same as (0.0) ? 'Yes' : 'No' }}\n{{ val is same as (val2) ? 'Yes' : 'No' }}\n--DATA--\nreturn ['val' => 0.0]\n--EXPECT--\nYes\nYes\nYes\n"
  },
  {
    "path": "tests/Fixtures/expressions/grouping.test",
    "content": "--TEST--\nTwig supports grouping of expressions\n--TEMPLATE--\n{{ (2 + 2) / 2 }}\n--DATA--\nreturn []\n--EXPECT--\n2\n"
  },
  {
    "path": "tests/Fixtures/expressions/has_every.test",
    "content": "--TEST--\nTwig supports the \"has every\" operator\n--TEMPLATE--\n{% if [0, 2, 4] has every v => 0 == v % 2 %}Every{% else %}Not every{% endif %} items are even in array\n{{ ([0, 2, 4] has every v => 0 == v % 2) ? 'Every' : 'Not every' }} items are even in array\n{{ ({ a: 0, b: 2, c: 4 } has every v => 0 == v % 2) ? 'Every' : 'Not every' }} items are even in object\n{{ ({ a: 0, b: 2, c: 4 } has every (v, k) => \"d\" > k)? 'Every' : 'Not every' }} keys are before \"d\" in object\n{{ (it has every v => 0 == v % 2) ? 'Every' : 'Not every' }} items are even in iterator\n{{ ([0, 1, 2] has every v => 0 == v % 2) ? 'Every' : 'Not every' }} items are even in array\n--DATA--\nreturn ['it' => new \\ArrayIterator([0, 2, 4])]\n--EXPECT--\nEvery items are even in array\nEvery items are even in array\nEvery items are even in object\nEvery keys are before \"d\" in object\nEvery items are even in iterator\nNot every items are even in array\n\n"
  },
  {
    "path": "tests/Fixtures/expressions/has_some.test",
    "content": "--TEST--\nTwig supports the \"has some\" operator\n--TEMPLATE--\n{% if [1, 2, 3] has some v => 0 == v % 2 %}At least one{% else %}No{% endif %} item is even in array\n{{ ([1, 2, 3] has some v => 0 == v % 2) ? 'At least one' : 'No' }} item is even in array\n{{ ({ a: 1, b: 2, c: 3 } has some v => 0 == v % 2) ? 'At least one' : 'No' }} item is even in object\n{{ ({ a: 1, b: 2, c: 3 } has some (v, k) => \"b\" == k)? 'At least one' : 'No' }} key is \"b\" in object\n{{ (it has some v => 0 == v % 2) ? 'At least one' : 'No' }} item is even in iterator\n{{ ([1, 3, 5] has some v => 0 == v % 2) ? 'At least one' : 'No' }} item is even in array\n--DATA--\nreturn ['it' => new \\ArrayIterator([1, 2, 3])]\n--EXPECT--\nAt least one item is even in array\nAt least one item is even in array\nAt least one item is even in object\nAt least one key is \"b\" in object\nAt least one item is even in iterator\nNo item is even in array\n\n"
  },
  {
    "path": "tests/Fixtures/expressions/literals.test",
    "content": "--TEST--\nTwig supports literals\n--TEMPLATE--\n1 {{ true }}\n2 {{ TRUE }}\n3 {{ false }}\n4 {{ FALSE }}\n5 {{ none }}\n6 {{ NONE }}\n7 {{ null }}\n8 {{ NULL }}\n--DATA--\nreturn []\n--EXPECT--\n1 1\n2 1\n3 \n4 \n5 \n6 \n7 \n8 \n"
  },
  {
    "path": "tests/Fixtures/expressions/magic_call.test",
    "content": "--TEST--\nTwig supports __call() for attributes\n--TEMPLATE--\n{{ foo.foo }}\n{{ foo.bar }}\n--DATA--\nclass TestClassForMagicCallAttributes\n{\n    public function getBar()\n    {\n        return 'bar_from_getbar';\n    }\n\n    public function __call($method, $arguments)\n    {\n        if ('foo' === $method) {\n            return 'foo_from_call';\n        }\n\n        return false;\n    }\n}\n\nreturn ['foo' => new TestClassForMagicCallAttributes()]\n--EXPECT--\nfoo_from_call\nbar_from_getbar\n"
  },
  {
    "path": "tests/Fixtures/expressions/matches.test",
    "content": "--TEST--\nTwig supports the \"matches\" operator\n--TEMPLATE--\n{{ 'foo' matches '/o/' ? 'OK' : 'KO' }}\n{{ 'foo' matches '/o/'|lower ? 'OK' : 'KO' }}\n{{ 'foo' matches '/^fo/' ? 'OK' : 'KO' }}\n{{ 'foo' matches '/^' ~ 'fo/' ? 'OK' : 'KO' }}\n{{ 'foo' matches '/O/i' ? 'OK' : 'KO' }}\n{{ null matches '/o/' }}\n{{ markup matches '/test/' ? 'OK': 'KO' }}\n--DATA--\nreturn ['markup' => new \\Twig\\Markup('test', 'UTF-8')]\n--EXPECT--\nOK\nOK\nOK\nOK\nOK\n0\nOK\n"
  },
  {
    "path": "tests/Fixtures/expressions/matches_error_compilation.test",
    "content": "--TEST--\nTwig supports the \"matches\" operator with a great error message\n--TEMPLATE--\n{{ 'foo' matches '/o' }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Regexp \"/o\" passed to \"matches\" is not valid: No ending delimiter '/' found in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/expressions/matches_error_runtime.test",
    "content": "--TEST--\nTwig supports the \"matches\" operator with a great error message\n--TEMPLATE--\n{{ 'foo' matches 1 + 2 }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Regexp \"3\" passed to \"matches\" is not valid: Delimiter must not be alphanumeric%sbackslash%sin \"index.twig\" at line 2\n"
  },
  {
    "path": "tests/Fixtures/expressions/method_call.test",
    "content": "--TEST--\nTwig supports method calls\n--TEMPLATE--\n{{ items.foo.foo }}\n{{ items.foo.getFoo() }}\n{{ items.foo.bar }}\n{{ items.foo['bar'] }}\n{{ items.foo.bar('a', 43) }}\n{{ items.foo.bar(param1: 'a', param2: 43) }}\n{{ items.foo.bar(param2: 43, param1: 'a') }}\n{{ items.foo.bar('a', param2: 43) }}\n{{ items.foo.bar(foo) }}\n{{ items.foo.self.foo() }}\n{{ items.foo.is }}\n{{ items.foo.in }}\n{{ items.foo.not }}\n--DATA--\nreturn ['foo' => 'bar', 'items' => ['foo' => new Twig\\Tests\\TwigTestFoo(), 'bar' => 'foo']]\n--CONFIG--\nreturn ['strict_variables' => false]\n--EXPECT--\nfoo\nfoo\nbar\n\nbar_a-43\nbar_a-43\nbar_a-43\nbar_a-43\nbar_bar\nfoo\nis\nin\nnot\n"
  },
  {
    "path": "tests/Fixtures/expressions/negative_numbers.test",
    "content": "--TEST--\nTwig manages negative numbers correctly\n--TEMPLATE--\n{{ -1 }}\n{{ - 1 }}\n{{ 5 - 1 }}\n{{ 5-1 }}\n{{ 5 + -1 }}\n{{ 5 + - 1 }}\n--DATA--\nreturn []\n--EXPECT--\n-1\n-1\n4\n4\n4\n4\n"
  },
  {
    "path": "tests/Fixtures/expressions/not.test",
    "content": "--TEST--\nnot\n--TEMPLATE--\n{{ (not false) ? 'OK' : 'KO' }}\n{{ not false ? 'OK' : 'KO' }}\n{{ (not false)?'OK':'KO' }}\n{{ not false?'OK':'KO' }}\n{{not true ? 'KO' : 'OK'}}\n--DATA--\nreturn []\n--EXPECT--\nOK\nOK\nOK\nOK\nOK\n"
  },
  {
    "path": "tests/Fixtures/expressions/not_arrow_fn.test",
    "content": "--TEST--\nA string in parentheses cannot be confused with an arrow function\n--TEMPLATE--\n{{ [\"foo\", \"bar\"]|join((\"f\")) }}\n--DATA--\nreturn []\n--EXPECT--\nfoofbar\n"
  },
  {
    "path": "tests/Fixtures/expressions/operators_as_variables.test",
    "content": "--TEST--\nTwig allows to use named operators as variable names\n--TEMPLATE--\n{% for match in matches %}\n    {{- match }}\n{% endfor %}\n{{ in }}\n{{ is }}\n--DATA--\nreturn ['matches' => [1, 2, 3], 'in' => 'in', 'is' => 'is']\n--EXPECT--\n1\n2\n3\nin\nis\n"
  },
  {
    "path": "tests/Fixtures/expressions/postfix.test",
    "content": "--TEST--\nTwig parses postfix expressions\n--TEMPLATE--\n{% import _self as macros %}\n\n{% macro foo() %}foo{% endmacro %}\n\n{{ 'a' }}\n{{ 'a'|upper }}\n{{ ('a')|upper }}\n{{ (-1)|abs }}\n{{ macros.foo() }}\n{{ (macros).foo() }}\n--DATA--\nreturn []\n--EXPECT--\na\nA\nA\n1\nfoo\nfoo\n"
  },
  {
    "path": "tests/Fixtures/expressions/power.test",
    "content": "--TEST--\nTwig parses power expressions\n--TEMPLATE--\n{{ 2**3 }}\n{{ (-2)**3 }}\n{{ (-2)**(-3) }}\n{{ a ** a }}\n{{ a ** b }}\n{{ b ** a }}\n{{ b ** b }}\n{{ -1**0 }}\n{{ (-1)**0 }}\n{{ -a**0 }}\n{{ (-a)**0 }}\n--DATA--\nreturn ['a' => 4, 'b' => -2]\n--EXPECT--\n8\n-8\n-0.125\n256\n0.0625\n16\n0.25\n-1\n1\n-1\n1\n"
  },
  {
    "path": "tests/Fixtures/expressions/sameas.test",
    "content": "--TEST--\nTwig supports the \"same as\" operator\n--TEMPLATE--\n{{ 1 is same as(1) ? 'OK' }}\n{{ 1 is same as 1 ? 'OK' }}\n{{ 1 === 1 ? 'OK' }}\n\n{{ 1 is not same as(true) ? 'OK' }}\n{{ 1 is not same as true ? 'OK' }}\n{{ 1 !== true ? 'OK' }}\n\n{{ 1 is   same    as   (1) ? 'OK' }}\n{{ 1 is   same    as   1 ? 'OK' }}\n{{ 1 is  not  same    as   '1' ? 'OK' }}\n{{ 1 is not\n    same\n    as\n    (true) ? 'OK' }}\n--DATA--\nreturn []\n--EXPECT--\nOK\nOK\nOK\n\nOK\nOK\nOK\n\nOK\nOK\nOK\nOK\n"
  },
  {
    "path": "tests/Fixtures/expressions/set.test",
    "content": "--TEST--\nTwig supports the \"=\" operator (assignment)\n--TEMPLATE--\n{# stores #}\n{% do b = 1 + 3 %}{{ b }}\n{# stores and displays #}\n{{ b = 1 + 3 }}{{ b }}\n{# stores and returns #}\n{% do (c = 4) ? 0 : -1 %}{{ c }}\n{# = can be chained #}\n{% do c = d = \"a\" %}{{ c }}{{ d }}\n{% do a = (b = 4) + 5 %}{{ a }}{{ b }}\n\n# Array destructuring\n{% do [first, last] = ['Fabien', 'Potencier'] %}{{ first }} {{ last }}\n{% do [a, b] = [b, a] %}{{ a }}{{ b }}\n{% do [, second] = ['first', 'second'] %}{{ second }}\n{% do [x, y, z] = ['one', 'two'] %}{{ x }} {{ y }} {{ z is same as(null) ? 'null' : z }}\n\n# Object destructuring\n{% do {name, email} = user %}{{ name }} {{ email }}\n{% do {first_name, last_name} = user_map %}{{ first_name }} {{ last_name }}\n{% do {name} = user_obj %}{{ name }}\n\n# Object destructuring with renaming\n{% do {name: userName, email: userEmail} = user %}{{ userName }} {{ userEmail }}\n{% do {first_name: first, last_name: last} = user_map %}{{ first }} {{ last }}\n{% do {name: objName} = user_obj %}{{ objName }}\n{% do {name: n1} = user %}{% do {name: n2} = user_obj %}{{ n1 }} {{ n2 }}\n--DATA--\nreturn [\n    'user' => (object)['name' => 'Fabien', 'email' => 'fabien@example.com'],\n    'user_map' => ['first_name' => 'Fabien', 'last_name' => 'Potencier'],\n    'user_obj' => new class {\n        public function getName() { return 'Fabien'; }\n    },\n    'null_obj' => null\n]\n--EXPECT--\n4\n44\n4\naa\n94\n\n# Array destructuring\nFabien Potencier\n49\nsecond\none two null\n\n# Object destructuring\nFabien fabien@example.com\nFabien Potencier\nFabien\n\n# Object destructuring with renaming\nFabien fabien@example.com\nFabien Potencier\nFabien\nFabien Fabien\n"
  },
  {
    "path": "tests/Fixtures/expressions/spread_array_operator.test",
    "content": "--TEST--\nTwig supports the spread operator on arrays\n--TEMPLATE--\n{{ [1, 2, ...[3, 4]]|join(',') }}\n{{ [1, 2, ...moreNumbers]|join(',') }}\n{{ [1, 2, ...iterableNumbers]|join(',') }}\n{{ [1, 2, ...iterableNumbers, 0, ...moreNumbers]|join(',') }}\n--DATA--\nreturn ['moreNumbers' => [5, 6, 7, 8], 'iterableNumbers' => new \\ArrayObject([6, 7, 8, 9])]\n--EXPECT--\n1,2,3,4\n1,2,5,6,7,8\n1,2,6,7,8,9\n1,2,6,7,8,9,0,5,6,7,8\n"
  },
  {
    "path": "tests/Fixtures/expressions/spread_mapping_operator.test",
    "content": "--TEST--\nTwig supports the spread operator on mappings\n--TEMPLATE--\n{% for key, value in { firstName: 'Ryan', lastName: 'Weaver', favoriteFood: 'popcorn', ...{favoriteFood: 'pizza', sport: 'running'} } %}\n    {{ key }}: {{ value }}\n{% endfor %}\n\n{% for key, value in { firstName: 'Ryan', ...morePersonalDetails} %}\n    {{ key }}: {{ value }}\n{% endfor %}\n\n{% for key, value in { firstName: 'Ryan', ...iterablePersonalDetails} %}\n    {{ key }}: {{ value }}\n{% endfor %}\n\n{# multiple spreads #}\n{% for key, value in { firstName: 'Ryan', ...iterablePersonalDetails, lastName: 'Weaver', ...morePersonalDetails} %}\n    {{ key }}: {{ value }}\n{% endfor %}\n--DATA--\nreturn ['morePersonalDetails' => ['favoriteColor' => 'orange'], 'iterablePersonalDetails' => new \\ArrayObject(['favoriteShoes' => 'barefoot'])];\n--EXPECT--\n    firstName: Ryan\n    lastName: Weaver\n    favoriteFood: pizza\n    sport: running\n\n    firstName: Ryan\n    favoriteColor: orange\n\n    firstName: Ryan\n    favoriteShoes: barefoot\n\n    firstName: Ryan\n    favoriteShoes: barefoot\n    lastName: Weaver\n    favoriteColor: orange\n"
  },
  {
    "path": "tests/Fixtures/expressions/spread_ternary_precedence.test",
    "content": "--TEST--\nTwig spread operator precedence with ternary operator\n--TEMPLATE--\n{{ [...true ? ['a'] : ['b']]|join }}\n{{ [...false ? ['a'] : ['b']]|join }}\n{{ [...(true ? ['a'] : ['b'])]|join }}\n{{ {a: 1, ...true ? {b: 2} : {c: 3}}|keys|join(',') }}\n--DATA--\nreturn []\n--EXPECT--\na\nb\na\na,b\n"
  },
  {
    "path": "tests/Fixtures/expressions/starts_with.test",
    "content": "--TEST--\nTwig supports the \"starts with\" operator\n--TEMPLATE--\n{{ 'foo' starts with 'f' ? 'OK' : 'KO' }}\n{{ not ('foo' starts with 'oo') ? 'OK' : 'KO' }}\n{{ not ('foo' starts with 'foowaytoolong') ? 'OK' : 'KO' }}\n{{ 'foo' starts      with 'f' ? 'OK' : 'KO' }}\n{{ 'foo' starts\nwith 'f' ? 'OK' : 'KO' }}\n{{ 'foo' starts with '' ? 'OK' : 'KO' }}\n{{ '1' starts with true ? 'OK' : 'KO' }}\n{{ '' starts with false ? 'OK' : 'KO' }}\n{{ 'a' starts with false ? 'OK' : 'KO' }}\n{{ false starts with '' ? 'OK' : 'KO' }}\n--DATA--\nreturn []\n--EXPECT--\nOK\nOK\nOK\nOK\nOK\nOK\nKO\nKO\nKO\nKO\n"
  },
  {
    "path": "tests/Fixtures/expressions/string_operator_as_var_assignment.test",
    "content": "--TEST--\nTwig supports the string operators as variable names in assignments\n--TEMPLATE--\n{% for matches in [1, 2] %}\n    {{- matches }}\n{% endfor %}\n\n{% set matches = [1, 2] %}\n\nOK\n--DATA--\nreturn []\n--EXPECT--\n1\n2\n\n\nOK\n"
  },
  {
    "path": "tests/Fixtures/expressions/strings.test",
    "content": "--TEST--\nTwig supports string interpolation\n--TEMPLATE--\n{{ \"foo #{\"foo #{bar} baz\"} baz\" }}\n{{ \"foo #{bar}#{bar} baz\" }}\n--DATA--\nreturn ['bar' => 'BAR']\n--EXPECT--\nfoo foo BAR baz baz\nfoo BARBAR baz\n"
  },
  {
    "path": "tests/Fixtures/expressions/ternary_operator.test",
    "content": "--TEST--\nTwig supports the ternary operator\n--TEMPLATE--\n{{ 1 ? 'YES' : 'NO' }}\n{{ 0 ? 'YES' : 'NO' }}\n{{ 0 ? 'YES' : (1 ? 'YES1' : 'NO1') }}\n{{ 0 ? 'YES' : (0 ? 'YES1' : 'NO1') }}\n{{ 1 == 1 ? 'foo<br />' : '' }}\n{{ foo ~ (bar ? ('-' ~ bar) : '') }}\n{{ true ? tag : 'KO' }}\n--DATA--\nreturn ['foo' => 'foo', 'bar' => 'bar', 'tag' => '<br>']\n--EXPECT--\nYES\nNO\nYES1\nNO1\nfoo<br />\nfoo-bar\n&lt;br&gt;\n"
  },
  {
    "path": "tests/Fixtures/expressions/ternary_operator_noelse.test",
    "content": "--TEST--\nTwig supports the ternary operator\n--TEMPLATE--\n{{ 1 ? 'YES' }}\n{{ 0 ? 'YES' }}\n{{ tag ? tag }}\n--DATA--\nreturn ['tag' => '<br>']\n--EXPECT--\nYES\n\n&lt;br&gt;\n"
  },
  {
    "path": "tests/Fixtures/expressions/ternary_operator_nothen.test",
    "content": "--TEST--\nTwig supports the ternary operator\n--TEMPLATE--\n{{ 'YES' ?: 'NO' }}\n{{ 0 ?: 'NO' }}\n{{ 'YES' ? : 'NO' }}\n{{ 0 ? : 'NO' }}\n{{ 'YES' ?  : 'NO' }}\n{{ 0 ?     : 'NO' }}\n{{ tag ?: 'KO' }}\n--DATA--\nreturn ['tag' => '<br>']\n--EXPECT--\nYES\nNO\nYES\nNO\nYES\nNO\n&lt;br&gt;\n"
  },
  {
    "path": "tests/Fixtures/expressions/two_word_operators_as_variables.test",
    "content": "--TEST--\nTwig does not allow to use two-word named operators as variable names\n--TEMPLATE--\n{{ starts with }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unexpected token \"operator\" of value \"starts with\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/expressions/unary.test",
    "content": "--TEST--\nTwig supports unary operators (not, -, +)\n--TEMPLATE--\n{{ not 1 }}/{{ not 0 }}\n{{ +1 + 1 }}/{{ -1 - 1 }}\n{{ not (false or true) }}\n--DATA--\nreturn []\n--EXPECT--\n/1\n2/-2\n\n"
  },
  {
    "path": "tests/Fixtures/expressions/unary_macro_arguments.test",
    "content": "--TEST--\nTwig manages negative numbers as default parameters\n--TEMPLATE--\n{% import _self as macros %}\n{{ macros.negative_number1() }}\n{{ macros.negative_number2() }}\n{{ macros.negative_number3() }}\n{{ macros.positive_number1() }}\n{{ macros.positive_number2() }}\n{% macro negative_number1(nb=-1) %}{{ nb }}{% endmacro %}\n{% macro negative_number2(nb = --1) %}{{ nb }}{% endmacro %}\n{% macro negative_number3(nb = - 1) %}{{ nb }}{% endmacro %}\n{% macro positive_number1(nb = +1) %}{{ nb }}{% endmacro %}\n{% macro positive_number2(nb = ++1) %}{{ nb }}{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n-1\n1\n-1\n1\n1\n"
  },
  {
    "path": "tests/Fixtures/expressions/unary_precedence.test",
    "content": "--TEST--\nTwig unary operators precedence\n--TEMPLATE--\n{{ -1 - 1 }}\n{{ -1 - -1 }}\n{{ -1 * -1 }}\n{{ 4 / -1 * 5 }}\n--DATA--\nreturn []\n--EXPECT--\n-2\n0\n1\n-20\n"
  },
  {
    "path": "tests/Fixtures/expressions/underscored_numbers.test",
    "content": "--TEST--\nTwig compile numbers literals with underscores correctly\n--TEMPLATE--\n{{ 0_0 is same as 0 ? 'ok' : 'ko' }}\n{{ 1_23 is same as 123 ? 'ok' : 'ko' }}\n{{ 12_3 is same as 123 ? 'ok' : 'ko' }}\n{{ 1_2_3 is same as 123 ? 'ok' : 'ko' }}\n{{ -1_2 is same as -12 ? 'ok' : 'ko' }}\n{{ 1_2.3_4 is same as 12.34 ? 'ok' : 'ko' }}\n{{ -1_2.3_4 is same as -12.34 ? 'ok' : 'ko' }}\n{{ 1.2_3e-4 is same as 1.23e-4 ? 'ok' : 'ko' }}\n{{ -1.2_3e+4 is same as -1.23e+4 ? 'ok' : 'ko' }}\n--DATA--\nreturn []\n--EXPECT--\nok\nok\nok\nok\nok\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/expressions/underscored_numbers_error.test",
    "content": "--TEST--\nTwig does not allow to use 2 underscored between digits in numbers\n--TEMPLATE--\n{{ 1__2 }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unexpected token \"name\" of value \"__2\" (\"end of print statement\" expected) in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/extensions/anonymous_functions.test",
    "content": "--TEST--\nuse an anonymous function as a function\n--TEMPLATE--\n{{ anon_foo('bar') }}\n{{ 'bar'|anon_foo }}\n--DATA--\nreturn []\n--EXPECT--\n*bar*\n*bar*\n"
  },
  {
    "path": "tests/Fixtures/filters/abs.test",
    "content": "--TEST--\n\"abs\" filter\n--TEMPLATE--\n{{ (-5.5)|abs }}\n{{ (-5)|abs }}\n{{ (-0)|abs }}\n{{ 0|abs }}\n{{ 5|abs }}\n{{ 5.5|abs }}\n{{ number1|abs }}\n{{ number2|abs }}\n{{ number3|abs }}\n{{ number4|abs }}\n{{ number5|abs }}\n{{ number6|abs }}\n--DATA--\nreturn ['number1' => -5.5, 'number2' => -5, 'number3' => -0, 'number4' => 0, 'number5' => 5, 'number6' => 5.5]\n--EXPECT--\n5.5\n5\n0\n0\n5\n5.5\n5.5\n5\n0\n0\n5\n5.5\n"
  },
  {
    "path": "tests/Fixtures/filters/arrow_reserved_names.test",
    "content": "--TEST--\n\"map\" filter\n--TEMPLATE--\n{{ [1, 2]|map(true => true * 2)|join(', ') }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The arrow function argument must be a list of variables or a single variable in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/filters/batch.test",
    "content": "--TEST--\n\"batch\" filter\n--TEMPLATE--\n{% for row in items|batch(3) %}\n  <div class=row>\n  {% for column in row %}\n    <div class=item>{{ column }}</div>\n  {% endfor %}\n  </div>\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']]\n--EXPECT--\n<div class=row>\n      <div class=item>a</div>\n      <div class=item>b</div>\n      <div class=item>c</div>\n    </div>\n  <div class=row>\n      <div class=item>d</div>\n      <div class=item>e</div>\n      <div class=item>f</div>\n    </div>\n  <div class=row>\n      <div class=item>g</div>\n      <div class=item>h</div>\n      <div class=item>i</div>\n    </div>\n  <div class=row>\n      <div class=item>j</div>\n    </div>\n"
  },
  {
    "path": "tests/Fixtures/filters/batch_float.test",
    "content": "--TEST--\n\"batch\" filter\n--TEMPLATE--\n{% for row in items|batch(3.1) %}\n  <div class=row>\n  {% for column in row %}\n    <div class=item>{{ column }}</div>\n  {% endfor %}\n  </div>\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']]\n--EXPECT--\n<div class=row>\n      <div class=item>a</div>\n      <div class=item>b</div>\n      <div class=item>c</div>\n      <div class=item>d</div>\n    </div>\n  <div class=row>\n      <div class=item>e</div>\n      <div class=item>f</div>\n      <div class=item>g</div>\n      <div class=item>h</div>\n    </div>\n  <div class=row>\n      <div class=item>i</div>\n      <div class=item>j</div>\n    </div>\n"
  },
  {
    "path": "tests/Fixtures/filters/batch_with_empty_fill.test",
    "content": "--TEST--\n\"batch\" filter\n--TEMPLATE--\n<table>\n{% for row in items|batch(3, '') %}\n  <tr>\n  {% for column in row %}\n    <td>{{ column }}</td>\n  {% endfor %}\n  </tr>\n{% endfor %}\n</table>\n--DATA--\nreturn ['items' => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']]\n--EXPECT--\n<table>\n  <tr>\n      <td>a</td>\n      <td>b</td>\n      <td>c</td>\n    </tr>\n  <tr>\n      <td>d</td>\n      <td>e</td>\n      <td>f</td>\n    </tr>\n  <tr>\n      <td>g</td>\n      <td>h</td>\n      <td>i</td>\n    </tr>\n  <tr>\n      <td>j</td>\n      <td></td>\n      <td></td>\n    </tr>\n</table>\n"
  },
  {
    "path": "tests/Fixtures/filters/batch_with_exact_elements.test",
    "content": "--TEST--\n\"batch\" filter\n--TEMPLATE--\n{% for row in items|batch(3, 'fill') %}\n  <div class=row>\n  {% for column in row %}\n    <div class=item>{{ column }}</div>\n  {% endfor %}\n  </div>\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']]\n--EXPECT--\n<div class=row>\n      <div class=item>a</div>\n      <div class=item>b</div>\n      <div class=item>c</div>\n    </div>\n  <div class=row>\n      <div class=item>d</div>\n      <div class=item>e</div>\n      <div class=item>f</div>\n    </div>\n  <div class=row>\n      <div class=item>g</div>\n      <div class=item>h</div>\n      <div class=item>i</div>\n    </div>\n  <div class=row>\n      <div class=item>j</div>\n      <div class=item>k</div>\n      <div class=item>l</div>\n    </div>\n"
  },
  {
    "path": "tests/Fixtures/filters/batch_with_fill.test",
    "content": "--TEST--\n\"batch\" filter\n--TEMPLATE--\n<table>\n{% for row in items|batch(3, 'fill') %}\n  <tr>\n  {% for column in row %}\n    <td>{{ column }}</td>\n  {% endfor %}\n  </tr>\n{% endfor %}\n</table>\n--DATA--\nreturn ['items' => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']]\n--EXPECT--\n<table>\n  <tr>\n      <td>a</td>\n      <td>b</td>\n      <td>c</td>\n    </tr>\n  <tr>\n      <td>d</td>\n      <td>e</td>\n      <td>f</td>\n    </tr>\n  <tr>\n      <td>g</td>\n      <td>h</td>\n      <td>i</td>\n    </tr>\n  <tr>\n      <td>j</td>\n      <td>fill</td>\n      <td>fill</td>\n    </tr>\n</table>\n"
  },
  {
    "path": "tests/Fixtures/filters/batch_with_keys.test",
    "content": "--TEST--\n\"batch\" filter preserves array keys\n--TEMPLATE--\n{{ {'foo': 'bar', 'key': 'value'}|batch(4)|first|keys|join(',') }}\n{{ {'foo': 'bar', 'key': 'value'}|batch(4, 'fill')|first|keys|join(',') }}\n--DATA--\nreturn []\n--EXPECT--\nfoo,key\nfoo,key,0,1\n"
  },
  {
    "path": "tests/Fixtures/filters/batch_with_more_elements.test",
    "content": "--TEST--\n\"batch\" filter\n--TEMPLATE--\n{% for row in items|batch(3, 'fill') %}\n  <div class=row>\n  {% for key, column in row %}\n    <div class={{ key }}>{{ column }}</div>\n  {% endfor %}\n  </div>\n{% endfor %}\n--DATA--\nreturn ['items' => ['a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd', '123' => 'e']]\n--EXPECT--\n<div class=row>\n      <div class=a>a</div>\n      <div class=b>b</div>\n      <div class=c>c</div>\n    </div>\n  <div class=row>\n      <div class=d>d</div>\n      <div class=123>e</div>\n      <div class=124>fill</div>\n    </div>\n"
  },
  {
    "path": "tests/Fixtures/filters/batch_with_zero_elements.test",
    "content": "--TEST--\n\"batch\" filter with zero elements\n--TEMPLATE--\n{{ []|batch(3)|length }}\n{{ []|batch(3, 'fill')|length }}\n--DATA--\nreturn []\n--EXPECT--\n0\n0\n"
  },
  {
    "path": "tests/Fixtures/filters/capitalize.test",
    "content": "--TEST--\n\"capitalize\" filter\n--TEMPLATE--\n{{ \"super helpful\"|capitalize }}\n{{ \"a\"|capitalize }}\n*{{ \"\"|capitalize }}*\n*{{ null|capitalize }}*\n--DATA--\nreturn []\n--EXPECT--\nSuper helpful\nA\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/column.test",
    "content": "--TEST--\n\"column\" filter\n--TEMPLATE--\n{{ array|column('foo')|join }}\n{{ traversable|column('foo')|join }}\n--DATA--\n$items = [['bar' => 'foo', 'foo' => 'bar'], ['foo' => 'foo', 'bar' => 'bar']];\nreturn ['array' => $items, 'traversable' => new ArrayIterator($items)];\n--EXPECT--\nbarfoo\nbarfoo\n"
  },
  {
    "path": "tests/Fixtures/filters/convert_encoding.test",
    "content": "--TEST--\n\"convert_encoding\" filter\n--TEMPLATE--\n{{ \"愛していますか？\"|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }}\n*{{ \"\"|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }}*\n*{{ null|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }}*\n--DATA--\nreturn []\n--EXPECT--\n愛していますか？\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/date.test",
    "content": "--TEST--\n\"date\" filter\n--TEMPLATE--\n{{ date1|date }}\n{{ date1|date('d/m/Y') }}\n{{ date1|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }}\n{{ date1|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}\n{{ date1|date('d/m/Y H:i:s P', 'America/Chicago') }}\n{{ date1|date('e') }}\n{{ date1|date('d/m/Y H:i:s') }}\n\n{{ date2|date }}\n{{ date2|date('d/m/Y') }}\n{{ date2|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }}\n{{ date2|date('d/m/Y H:i:s', timezone1) }}\n{{ date2|date('d/m/Y H:i:s') }}\n\n{{ date3|date }}\n{{ date3|date('d/m/Y') }}\n\n{{ date4|date }}\n{{ date4|date('d/m/Y') }}\n\n{{ date5|date }}\n{{ date5|date('d/m/Y') }}\n\n{{ date6|date('d/m/Y H:i:s P', 'Europe/Paris') }}\n{{ date6|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}\n{{ date6|date('d/m/Y H:i:s P', false) }}\n{{ date6|date('e', 'Europe/Paris') }}\n{{ date6|date('e', false) }}\n\n{{ date7|date }}\n{{ date7|date(timezone='Europe/Paris') }}\n{{ date7|date(timezone='Asia/Hong_Kong') }}\n{{ date7|date(timezone=false) }}\n{{ date7|date(timezone='Indian/Mauritius') }}\n\n{{ '2010-01-28 15:00:00'|date(timezone=\"Europe/Paris\") }}\n{{ '2010-01-28 15:00:00'|date(timezone=\"Asia/Hong_Kong\") }}\n--DATA--\ndate_default_timezone_set('Europe/Paris');\nreturn [\n    'date1' => mktime(13, 45, 0, 10, 4, 2010),\n    'date2' => new \\DateTime('2010-10-04 13:45'),\n    'date3' => '2010-10-04 13:45',\n    'date4' => 1286199900, // \\DateTime::createFromFormat('Y-m-d H:i', '2010-10-04 13:45', new \\DateTimeZone('UTC'))->getTimestamp() -- A unixtimestamp is always GMT\n    'date5' => -189291360, // \\DateTime::createFromFormat('Y-m-d H:i', '1964-01-02 03:04', new \\DateTimeZone('UTC'))->getTimestamp(),\n    'date6' => new \\DateTime('2010-10-04 13:45', new \\DateTimeZone('America/New_York')),\n    'date7' => '2010-01-28T15:00:00+04:00',\n    'timezone1' => new \\DateTimeZone('America/New_York'),\n]\n--EXPECT--\nOctober 4, 2010 13:45\n04/10/2010\n04/10/2010 19:45:00\n04/10/2010 19:45:00 +08:00\n04/10/2010 06:45:00 -05:00\nEurope/Paris\n04/10/2010 13:45:00\n\nOctober 4, 2010 13:45\n04/10/2010\n04/10/2010 19:45:00\n04/10/2010 07:45:00\n04/10/2010 13:45:00\n\nOctober 4, 2010 13:45\n04/10/2010\n\nOctober 4, 2010 15:45\n04/10/2010\n\nJanuary 2, 1964 04:04\n02/01/1964\n\n04/10/2010 19:45:00 +02:00\n05/10/2010 01:45:00 +08:00\n04/10/2010 13:45:00 -04:00\nEurope/Paris\nAmerica/New_York\n\nJanuary 28, 2010 12:00\nJanuary 28, 2010 12:00\nJanuary 28, 2010 19:00\nJanuary 28, 2010 15:00\nJanuary 28, 2010 15:00\n\nJanuary 28, 2010 15:00\nJanuary 28, 2010 22:00\n"
  },
  {
    "path": "tests/Fixtures/filters/date_default_format.test",
    "content": "--TEST--\n\"date\" filter\n--TEMPLATE--\n{{ date1|date }}\n{{ date1|date('d/m/Y') }}\n--DATA--\ndate_default_timezone_set('UTC');\n$twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setDateFormat('Y-m-d', '%d days %h hours');\nreturn [\n    'date1' => mktime(13, 45, 0, 10, 4, 2010),\n]\n--EXPECT--\n2010-10-04\n04/10/2010\n"
  },
  {
    "path": "tests/Fixtures/filters/date_default_format_interval.test",
    "content": "--TEST--\n\"date\" filter (interval support)\n--TEMPLATE--\n{{ date2|date }}\n{{ date2|date('%d days') }}\n--DATA--\ndate_default_timezone_set('UTC');\n$twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setDateFormat('Y-m-d', '%d days %h hours');\nreturn [\n    'date2' => new \\DateInterval('P2D'),\n]\n--EXPECT--\n2 days 0 hours\n2 days\n"
  },
  {
    "path": "tests/Fixtures/filters/date_immutable.test",
    "content": "--TEST--\n\"date\" filter\n--TEMPLATE--\n{{ date1|date }}\n{{ date1|date('d/m/Y') }}\n{{ date1|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }}\n{{ date1|date('d/m/Y H:i:s', timezone1) }}\n{{ date1|date('d/m/Y H:i:s') }}\n{{ date1|date_modify('+1 hour')|date('d/m/Y H:i:s') }}\n\n{{ date2|date('d/m/Y H:i:s P', 'Europe/Paris') }}\n{{ date2|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}\n{{ date2|date('d/m/Y H:i:s P', false) }}\n{{ date2|date('e', 'Europe/Paris') }}\n{{ date2|date('e', false) }}\n--DATA--\ndate_default_timezone_set('Europe/Paris');\nreturn [\n    'date1' => new \\DateTimeImmutable('2010-10-04 13:45'),\n    'date2' => new \\DateTimeImmutable('2010-10-04 13:45', new \\DateTimeZone('America/New_York')),\n    'timezone1' => new \\DateTimeZone('America/New_York'),\n]\n--EXPECT--\nOctober 4, 2010 13:45\n04/10/2010\n04/10/2010 19:45:00\n04/10/2010 07:45:00\n04/10/2010 13:45:00\n04/10/2010 14:45:00\n\n04/10/2010 19:45:00 +02:00\n05/10/2010 01:45:00 +08:00\n04/10/2010 13:45:00 -04:00\nEurope/Paris\nAmerica/New_York\n"
  },
  {
    "path": "tests/Fixtures/filters/date_interval.test",
    "content": "--TEST--\n\"date\" filter (interval support)\n--TEMPLATE--\n{{ date1|date }}\n{{ date1|date('%d days %h hours') }}\n{{ date1|date('%d days %h hours', timezone1) }}\n--DATA--\ndate_default_timezone_set('UTC');\nreturn [\n    'date1' => new \\DateInterval('P2D'),\n    // This should have no effect on \\DateInterval formatting\n    'timezone1' => new \\DateTimeZone('America/New_York'),\n]\n--EXPECT--\n2 days\n2 days 0 hours\n2 days 0 hours\n"
  },
  {
    "path": "tests/Fixtures/filters/date_modify.test",
    "content": "--TEST--\n\"date_modify\" filter\n--TEMPLATE--\n{{ date1|date_modify('-1day')|date('Y-m-d H:i:s') }}\n{{ date2|date_modify('-1day')|date('Y-m-d H:i:s') }}\n--DATA--\ndate_default_timezone_set('UTC');\nreturn [\n    'date1' => '2010-10-04 13:45',\n    'date2' => new \\DateTime('2010-10-04 13:45'),\n]\n--EXPECT--\n2010-10-03 13:45:00\n2010-10-03 13:45:00\n"
  },
  {
    "path": "tests/Fixtures/filters/date_namedargs.test",
    "content": "--TEST--\n\"date\" filter\n--TEMPLATE--\n{{ date|date(format='d/m/Y H:i:s P', timezone='America/Chicago') }}\n{{ date|date(timezone='America/Chicago', format='d/m/Y H:i:s P') }}\n{{ date|date('d/m/Y H:i:s P', timezone='America/Chicago') }}\n--DATA--\ndate_default_timezone_set('UTC');\nreturn ['date' => mktime(13, 45, 0, 10, 4, 2010)]\n--EXPECT--\n04/10/2010 08:45:00 -05:00\n04/10/2010 08:45:00 -05:00\n04/10/2010 08:45:00 -05:00\n"
  },
  {
    "path": "tests/Fixtures/filters/date_time_zone_conversion.test",
    "content": "--TEST--\n\"date\" filter with time zone conversion\n--TEMPLATE--\n{{ date1|date }}\n{{ date1|date('d/m/Y') }}\n{{ date1|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }}\n{{ date1|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}\n{{ date1|date('d/m/Y H:i:s P', 'America/Chicago') }}\n{{ date1|date('e') }}\n{{ date1|date('d/m/Y H:i:s') }}\n\n{{ date2|date }}\n{{ date2|date('d/m/Y') }}\n{{ date2|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }}\n{{ date2|date('d/m/Y H:i:s', timezone1) }}\n{{ date2|date('d/m/Y H:i:s') }}\n\n{{ date3|date }}\n{{ date3|date('d/m/Y') }}\n\n{{ date4|date }}\n{{ date4|date('d/m/Y') }}\n\n{{ date5|date }}\n{{ date5|date('d/m/Y') }}\n\n{{ date6|date('d/m/Y H:i:s P', 'Europe/Paris') }}\n{{ date6|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}\n{{ date6|date('d/m/Y H:i:s P', false) }}\n{{ date6|date('e', 'Europe/Paris') }}\n{{ date6|date('e', false) }}\n\n{{ date7|date }}\n{{ date7|date(timezone='Europe/Paris') }}\n{{ date7|date(timezone='Asia/Hong_Kong') }}\n{{ date7|date(timezone=false) }}\n{{ date7|date(timezone='Indian/Mauritius') }}\n\n{{ '2010-01-28 15:00:00'|date(timezone=\"Europe/Paris\") }}\n{{ '2010-01-28 15:00:00'|date(timezone=\"Asia/Hong_Kong\") }}\n--DATA--\ndate_default_timezone_set('Europe/Paris');\n$twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setTimezone('UTC');\nreturn [\n    'date1' => mktime(13, 45, 0, 10, 4, 2010),\n    'date2' => new \\DateTime('2010-10-04 13:45'),\n    'date3' => '2010-10-04 13:45',\n    'date4' => 1286199900, // \\DateTime::createFromFormat('Y-m-d H:i', '2010-10-04 13:45', new \\DateTimeZone('UTC'))->getTimestamp() -- A unixtimestamp is always GMT\n    'date5' => -189291360, // \\DateTime::createFromFormat('Y-m-d H:i', '1964-01-02 03:04', new \\DateTimeZone('UTC'))->getTimestamp(),\n    'date6' => new \\DateTime('2010-10-04 13:45', new \\DateTimeZone('America/New_York')),\n    'date7' => '2010-01-28T15:00:00+04:00',\n    'timezone1' => new \\DateTimeZone('America/New_York'),\n]\n--EXPECT--\nOctober 4, 2010 11:45\n04/10/2010\n04/10/2010 19:45:00\n04/10/2010 19:45:00 +08:00\n04/10/2010 06:45:00 -05:00\nUTC\n04/10/2010 11:45:00\n\nOctober 4, 2010 11:45\n04/10/2010\n04/10/2010 19:45:00\n04/10/2010 07:45:00\n04/10/2010 11:45:00\n\nOctober 4, 2010 11:45\n04/10/2010\n\nOctober 4, 2010 13:45\n04/10/2010\n\nJanuary 2, 1964 03:04\n02/01/1964\n\n04/10/2010 19:45:00 +02:00\n05/10/2010 01:45:00 +08:00\n04/10/2010 13:45:00 -04:00\nEurope/Paris\nAmerica/New_York\n\nJanuary 28, 2010 11:00\nJanuary 28, 2010 12:00\nJanuary 28, 2010 19:00\nJanuary 28, 2010 15:00\nJanuary 28, 2010 15:00\n\nJanuary 28, 2010 15:00\nJanuary 28, 2010 22:00\n"
  },
  {
    "path": "tests/Fixtures/filters/default.test",
    "content": "--TEST--\n\"default\" filter\n--TEMPLATE--\nVariable:\n{{ definedVar                  |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ zeroVar                     |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ emptyVar                    |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ nullVar                     |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ undefinedVar                |default('default') is same as('default') ? 'ok' : 'ko' }}\nArray access:\n{{ nested.definedVar           |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ nested['definedVar']        |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ nested.zeroVar              |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ nested.emptyVar             |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ nested.nullVar              |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ nested.undefinedVar         |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ nested['undefinedVar']      |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ undefined['undefined']      |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ undefinedVar.foo            |default('default') is same as('default') ? 'ok' : 'ko' }}\nPlain values:\n{{ 'defined'                   |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ 0                           |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ ''                          |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ null                        |default('default') is same as('default') ? 'ok' : 'ko' }}\nPrecedence:\n{{ 'o' ~ nullVar               |default('k') }}\n{{ 'o' ~ nested.nullVar        |default('k') }}\nObject methods:\n{{ object.foo                  |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ object.undefinedMethod      |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ object.getFoo()             |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ object.getFoo('a')          |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ object.undefinedMethod()    |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ object.undefinedMethod('a') |default('default') is same as('default') ? 'ok' : 'ko' }}\nDeep nested:\n{{ nested.undefinedVar.foo.bar |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ nested.definedArray.0       |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ nested['definedArray'][0]   |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ nested['undefinedVar'][0]   |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ undefined['undefined'][0]   |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ object.self.foo             |default('default') is same as('default') ? 'ko' : 'ok' }}\n{{ object.self.undefinedMethod |default('default') is same as('default') ? 'ok' : 'ko' }}\n{{ object.undefinedMethod.self |default('default') is same as('default') ? 'ok' : 'ko' }}\n--DATA--\nreturn [\n    'definedVar' => 'defined',\n    'zeroVar'    => 0,\n    'emptyVar'   => '',\n    'nullVar'    => null,\n    'nested'     => [\n        'definedVar'   => 'defined',\n        'zeroVar'      => 0,\n        'emptyVar'     => '',\n        'nullVar'      => null,\n        'definedArray' => [0],\n    ],\n    'object' => new Twig\\Tests\\TwigTestFoo(),\n]\n--CONFIG--\nreturn ['strict_variables' => false]\n--EXPECT--\nVariable:\nok\nok\nok\nok\nok\nArray access:\nok\nok\nok\nok\nok\nok\nok\nok\nok\nPlain values:\nok\nok\nok\nok\nPrecedence:\nok\nok\nObject methods:\nok\nok\nok\nok\nok\nok\nDeep nested:\nok\nok\nok\nok\nok\nok\nok\nok\n--DATA--\nreturn [\n    'definedVar' => 'defined',\n    'zeroVar'    => 0,\n    'emptyVar'   => '',\n    'nullVar'    => null,\n    'nested'     => [\n        'definedVar'   => 'defined',\n        'zeroVar'      => 0,\n        'emptyVar'     => '',\n        'nullVar'      => null,\n        'definedArray' => [0],\n    ],\n    'object' => new Twig\\Tests\\TwigTestFoo(),\n]\n--CONFIG--\nreturn ['strict_variables' => true]\n--EXPECT--\nVariable:\nok\nok\nok\nok\nok\nArray access:\nok\nok\nok\nok\nok\nok\nok\nok\nok\nPlain values:\nok\nok\nok\nok\nPrecedence:\nok\nok\nObject methods:\nok\nok\nok\nok\nok\nok\nDeep nested:\nok\nok\nok\nok\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/filters/dynamic_filter.test",
    "content": "--TEST--\ndynamic filter\n--TEMPLATE--\n{{ 'bar'|foo_path }}\n{{ 'bar'|bar_path }}\n{{ 'bar'|a_foo_b_bar }}\n--DATA--\nreturn []\n--EXPECT--\nfoo/bar\nbar/bar\na/b/bar\n"
  },
  {
    "path": "tests/Fixtures/filters/escape.test",
    "content": "--TEST--\n\"escape\" filter\n--TEMPLATE--\n{{ \"foo <br />\"|e }}\n*{{ \"\"|e }}*\n*{{ null|e }}*\n--DATA--\nreturn []\n--EXPECT--\nfoo &lt;br /&gt;\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/escape_html_attr.test",
    "content": "--TEST--\n\"escape\" filter does not escape with the html strategy when using the html_attr strategy\n--TEMPLATE--\n{{ '<br />'|escape('html_attr') }}\n--DATA--\nreturn []\n--EXPECT--\n&lt;br&#x20;&#x2F;&gt;\n"
  },
  {
    "path": "tests/Fixtures/filters/escape_html_attr_relaxed.test",
    "content": "--TEST--\n\"escape\" filter does not additionally apply the html strategy when the html_attr_relaxed strategy has been applied\n\"escape\" filter does not additionally apply the html_attr_relaxed strategy when the html_attr strategy has been applied\n--TEMPLATE--\n{% autoescape 'html' %}\n{{ 'v:bind@click=\"foo\"'|escape('html_attr_relaxed') }}\n{% endautoescape %}\n{% autoescape 'html_attr_relaxed' %}\n{{ 'v:bind@click=\"foo\"' | escape('html_attr') }}\n{% endautoescape %}\n--DATA--\nreturn []\n--EXPECT--\nv:bind@click&#x3D;&quot;foo&quot;\nv&#x3A;bind&#x40;click&#x3D;&quot;foo&quot;\n"
  },
  {
    "path": "tests/Fixtures/filters/escape_javascript.test",
    "content": "--TEST--\n\"escape\" filter\n--TEMPLATE--\n{{ \"é ♜ 𝌆\"|e('js') }}\n--DATA--\nreturn []\n--EXPECT--\n\\u00E9\\u0020\\u265C\\u0020\\uD834\\uDF06\n"
  },
  {
    "path": "tests/Fixtures/filters/escape_non_supported_charset.test",
    "content": "--TEST--\n\"escape\" filter\n--TEMPLATE--\n{{ \"愛していますか？ <br />\"|e }}\n--DATA--\nreturn []\n--EXPECT--\n愛していますか？ &lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/filters/filter.test",
    "content": "--TEST--\n\"filter\" filter\n--TEMPLATE--\n{% set offset = 3 %}\n\n{% for k, v in [1, 5, 3, 4, 5]|filter((v) => v > offset) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in {a: 1, b: 2, c: 5, d: 8}|filter(v => v > offset) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in {a: 1, b: 2, c: 5, d: 8}|filter((v, k) => (v > offset) and (k != \"d\")) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in [1, 5, 3, 4, 5]|filter(v => v > offset) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in it|filter((v) => v > offset) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in ita|filter(v => v > offset) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in xml|filter(x => true) %}\n{{ k }}/{{ v }}\n{% endfor %}\n\n{# we can iterate more than once #}\n{% for k, v in xml|filter(x => true) %}\n{{ k }}/{{ v }}\n{% endfor %}\n\n{% set coll = ['a', 'b']|filter(v => v is same as('a')) %}\n{% if coll|length > 0 %}\n    {{- coll|join(', ') }}\n{% endif %}\n--DATA--\nreturn [\n    'it' => new \\ArrayIterator(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8]),\n    'ita' => new Twig\\Tests\\IteratorAggregateStub(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8]),\n    'xml' => new \\SimpleXMLElement('<?xml version=\"1.0\" encoding=\"UTF-8\"?><doc><elem>foo</elem><elem>bar</elem><elem>baz</elem></doc>'),\n]\n--EXPECT--\n1 = 5\n3 = 4\n4 = 5\n\nc = 5\nd = 8\n\nc = 5\n\n1 = 5\n3 = 4\n4 = 5\n\nc = 5\nd = 8\n\nc = 5\nd = 8\n\nelem/foo\nelem/bar\nelem/baz\n\nelem/foo\nelem/bar\nelem/baz\n\na\n"
  },
  {
    "path": "tests/Fixtures/filters/find.test",
    "content": "--TEST--\n\"filter\" filter\n--TEMPLATE--\n\n{{ [1, 2]|find((v) => v > 3) }}\n\n{{ [1, 5, 3, 4, 5]|find((v) => v > 3) }}\n\n{{ {a: 1, b: 2, c: 5, d: 8}|find(v => v > 3) }}\n\n{{ {a: 1, b: 2, c: 5, d: 8}|find((v, k) => (v > 3) and (k != \"c\")) }}\n\n{{ [1, 5, 3, 4, 5]|find(v => v > 3) }}\n\n{{ it|find((v) => v > 3) }}\n\n{{ ita|find(v => v > 3) }}\n\n{{ xml|find(x => true) }}\n\n--DATA--\nreturn [\n    'it' => new \\ArrayIterator(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8]),\n    'ita' => new Twig\\Tests\\IteratorAggregateStub(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8]),\n    'xml' => new \\SimpleXMLElement('<?xml version=\"1.0\" encoding=\"UTF-8\"?><doc><elem>foo</elem><elem>bar</elem><elem>baz</elem></doc>'),\n]\n--EXPECT--\n\n\n5\n\n5\n\n8\n\n5\n\n5\n\n5\n\nfoo\n"
  },
  {
    "path": "tests/Fixtures/filters/first.test",
    "content": "--TEST--\n\"first\" filter\n--TEMPLATE--\n{{ [1, 2, 3, 4]|first }}\n{{ {a: 1, b: 2, c: 3, d: 4}|first }}\n{{ '1234'|first }}\n{{ arr|first }}\n{{ 'Ä€é'|first }}\n*{{ ''|first }}*\n*{{ null|first }}*\n--DATA--\nreturn ['arr' => new \\ArrayObject([1, 2, 3, 4])]\n--EXPECT--\n1\n1\n1\n1\nÄ\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/force_escape.test",
    "content": "--TEST--\n\"escape\" filter\n--TEMPLATE--\n{% set foo %}\n    foo<br />\n{% endset %}\n\n{{ foo|e('html') -}}\n{{ foo|e('js') }}\n{% autoescape true %}\n    {{ foo }}\n{% endautoescape %}\n--DATA--\nreturn []\n--EXPECT--\n    foo&lt;br /&gt;\n\\u0020\\u0020\\u0020\\u0020foo\\u003Cbr\\u0020\\/\\u003E\\n\n        foo<br />\n"
  },
  {
    "path": "tests/Fixtures/filters/format.test",
    "content": "--TEST--\n\"format\" filter\n--TEMPLATE--\n{{ string|format(foo, 3) }}\n*{{ \"\"|format(foo, 3) }}*\n*{{ null|format(foo, 3) }}*\n--DATA--\nreturn ['string' => '%s/%d', 'foo' => 'bar']\n--EXPECT--\nbar/3\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/invoke.test",
    "content": "--TEST--\n\"invoke\" filter\n--TEMPLATE--\n{% set func = x => 'Hello '~x %}\n{{ func|invoke('World') }}\n{% set func2 = (x, y) => x+y %}\n{{ func2|invoke(3, 2) }}\n--DATA--\nreturn []\n--CONFIG--\nreturn []\n--EXPECT--\nHello World\n5\n"
  },
  {
    "path": "tests/Fixtures/filters/join.test",
    "content": "--TEST--\n\"join\" filter\n--TEMPLATE--\n{{ [\"foo\", \"bar\"]|join(', ') }}\n{{ foo|join(', ') }}\n{{ bar|join(', ') }}\n\n{{ [\"foo\", \"bar\"]|join(', ', ' and ') }}\n{{ foo|join(', ', ' and ') }}\n{{ bar|join(', ', ' and ') }}\n{{ [\"one\", \"two\", \"three\"]|join(', ', ' and ') }}\n{{ [\"a\", \"b\", \"c\"]|join('','-') }}\n{{ [\"a\", \"b\", \"c\"]|join('-','-') }}\n{{ [\"a\", \"b\", \"c\"]|join('-','') }}\n{{ [\"hello\"]|join('|','-') }}\n\n{{ {\"a\": \"w\", \"b\": \"x\", \"c\": \"y\", \"d\": \"z\"}|join }}\n{{ {\"a\": \"w\", \"b\": \"x\", \"c\": \"y\", \"d\": \"z\"}|join(',') }}\n{{ {\"a\": \"w\", \"b\": \"x\", \"c\": \"y\", \"d\": \"z\"}|join(',','-') }}\n--DATA--\nreturn ['foo' => new Twig\\Tests\\TwigTestFoo(), 'bar' => new \\ArrayObject([3, 4])]\n--EXPECT--\nfoo, bar\n1, 2\n3, 4\n\nfoo and bar\n1 and 2\n3 and 4\none, two and three\nab-c\na-b-c\na-bc\nhello\n\nwxyz\nw,x,y,z\nw,x,y-z\n"
  },
  {
    "path": "tests/Fixtures/filters/json_encode.test",
    "content": "--TEST--\n\"json_encode\" filter\n--TEMPLATE--\n{{ \"foo\"|json_encode|raw }}\n{{ foo|json_encode|raw }}\n{{ [foo, \"foo\"]|json_encode|raw }}\n--DATA--\nreturn ['foo' => new \\Twig\\Markup('foo', 'UTF-8')]\n--EXPECT--\n\"foo\"\n\"foo\"\n[\"foo\",\"foo\"]\n"
  },
  {
    "path": "tests/Fixtures/filters/last.test",
    "content": "--TEST--\n\"last\" filter\n--TEMPLATE--\n{{ [1, 2, 3, 4]|last }}\n{{ {a: 1, b: 2, c: 3, d: 4}|last }}\n{{ '1234'|last }}\n{{ arr|last }}\n{{ 'Ä€é'|last }}\n*{{ ''|last }}*\n*{{ null|last }}*\n--DATA--\nreturn ['arr' => new \\ArrayObject([1, 2, 3, 4])]\n--EXPECT--\n4\n4\n4\n4\né\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/length.test",
    "content": "--TEST--\n\"length\" filter\n--TEMPLATE--\n{{ array|length }}\n{{ string|length }}\n{{ number|length }}\n{{ to_string_able|length }}\n{{ countable|length }}\n{{ iterator_aggregate|length }}\n{{ \"\"|length }}\n{{ null|length }}\n{{ magic|length }}\n{{ non_countable|length }}\n{{ simple_xml_element|length }}\n{{ iterator|length }}\n--DATA--\nreturn [\n    'array' => [1, 4],\n    'string' => 'foo',\n    'number' => 1000,\n    'to_string_able' => new Twig\\Tests\\ToStringStub('foobar'),\n    'countable' => new Twig\\Tests\\CountableStub(42),       /* also asserts we do *not* call __toString() */\n    'iterator_aggregate' => new Twig\\Tests\\IteratorAggregateStub(['a', 'b', 'c']),   /* also asserts we do *not* call __toString() */\n    'null'          => null,\n    'magic'         => new Twig\\Tests\\MagicCallStub(),     /* used to assert we do *not* call __call */\n    'non_countable' => new \\StdClass(),\n    'simple_xml_element' => new \\SimpleXMLElement('<?xml version=\"1.0\" encoding=\"UTF-8\"?><doc><elem/><elem/></doc>'),\n    'iterator' => new Twig\\Tests\\SimpleIteratorForTesting()\n]\n--EXPECT--\n2\n3\n4\n6\n42\n3\n0\n0\n1\n1\n2\n7\n"
  },
  {
    "path": "tests/Fixtures/filters/length_utf8.test",
    "content": "--TEST--\n\"length\" filter\n--TEMPLATE--\n{{ string|length }}\n{{ markup|length }}\n--DATA--\nreturn ['string' => 'été', 'markup' => new \\Twig\\Markup('foo', 'UTF-8')]\n--EXPECT--\n3\n3\n"
  },
  {
    "path": "tests/Fixtures/filters/lower.test",
    "content": "--TEST--\n\"lower\" filter\n--TEMPLATE--\n{{ \"I like Twig.\"|lower }}\n*{{ \"\"|lower }}*\n*{{ null|lower }}*\n--DATA--\nreturn []\n--EXPECT--\ni like twig.\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/map.test",
    "content": "--TEST--\n\"map\" filter\n--TEMPLATE--\n{% set offset = 3 %}\n\n{% for k, v in [1, 2]|map((item) => item + 2 ) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in {a: 1, b: 2}|map((item) => item ~ \"*\" ) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in {a: 1, b: 2}|map((item, k) => item ~ \"*\" ~ k ) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in [1, 2]|map(item => item + 2 ) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% for k, v in it|map(item => item + 2 ) -%}\n    {{ k }} = {{ v }}\n{% endfor %}\n\n{% macro local_lower(string) %}\n    {{- string|lower }}\n{% endmacro %}\n{{ ['A']|map(val => _self.local_lower(val))|join }}\n\n{%- from _self import local_lower as renamed_lower %}\n{{ ['A']|map(val => renamed_lower(val))|join }}\n--DATA--\nreturn ['it' => new \\ArrayIterator([1, 2])]\n--EXPECT--\n0 = 3\n1 = 4\n\na = 1*\nb = 2*\n\na = 1*a\nb = 2*b\n\n0 = 3\n1 = 4\n\n0 = 3\n1 = 4\n\na\na\n"
  },
  {
    "path": "tests/Fixtures/filters/merge.test",
    "content": "--TEST--\n\"merge\" filter\n--TEMPLATE--\n{{ items|merge({'bar': 'foo'})|join }}\n{{ items|merge({'bar': 'foo'})|keys|join }}\n{{ {'bar': 'foo'}|merge(items)|join }}\n{{ {'bar': 'foo'}|merge(items)|keys|join }}\n{{ numerics|merge([4, 5, 6])|join }}\n{{ traversable.a|merge(traversable.b)|join }}\n--DATA--\nreturn [\n    'items' => ['foo' => 'bar'],\n    'numerics' => [1, 2, 3],\n    'traversable' => [\n        'a' => new \\ArrayObject([0 => 1, 1 => 2, 2 => 3]),\n        'b' => new \\ArrayObject(['a' => 'b'])\n    ]\n]\n--EXPECT--\nbarfoo\nfoobar\nfoobar\nbarfoo\n123456\n123b\n"
  },
  {
    "path": "tests/Fixtures/filters/nl2br.test",
    "content": "--TEST--\n\"nl2br\" filter\n--TEMPLATE--\n{{ \"I like Twig.\\nYou will like it too.\\n\\nEverybody like it!\"|nl2br }}\n{{ text|nl2br }}\n*{{ ''|nl2br }}*\n*{{ null|nl2br }}*\n--DATA--\nreturn ['text' => \"If you have some <strong>HTML</strong>\\nit will be escaped.\"]\n--EXPECT--\nI like Twig.<br />\nYou will like it too.<br />\n<br />\nEverybody like it!\nIf you have some &lt;strong&gt;HTML&lt;/strong&gt;<br />\nit will be escaped.\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/number_format.test",
    "content": "--TEST--\n\"number_format\" filter\n--TEMPLATE--\n{{ 20|number_format }}\n{{ 20.25|number_format }}\n{{ 20.25|number_format(2) }}\n{{ 20.25|number_format(2, ',') }}\n{{ 1020.25|number_format(2, ',') }}\n{{ 1020.25|number_format(2, ',', '.') }}\n{{ '1020.25'|number_format(2, ',', '.') }}\n{{ ''|number_format(2, ',', '.') }}\n{{ null|number_format(2, ',', '.') }}\n--DATA--\nreturn []\n--EXPECT--\n20\n20\n20.25\n20,25\n1,020,25\n1.020,25\n1.020,25\n0,00\n0,00\n"
  },
  {
    "path": "tests/Fixtures/filters/number_format_default.test",
    "content": "--TEST--\n\"number_format\" filter with defaults.\n--TEMPLATE--\n{{ 20|number_format }}\n{{ 20.25|number_format }}\n{{ 20.25|number_format(1) }}\n{{ 20.25|number_format(2, ',') }}\n{{ 1020.25|number_format }}\n{{ 1020.25|number_format(2, ',') }}\n{{ 1020.25|number_format(2, ',', '.') }}\n--DATA--\n$twig->getExtension(\\Twig\\Extension\\CoreExtension::class)->setNumberFormat(2, '!', '=');\nreturn []\n--EXPECT--\n20!00\n20!25\n20!3\n20,25\n1=020!25\n1=020,25\n1.020,25\n"
  },
  {
    "path": "tests/Fixtures/filters/raw.test",
    "content": "--TEST--\n\"raw\" filter excludes a variable from being escaped\n--TEMPLATE--\n{{ br|raw }}\n--DATA--\nreturn ['br' => '<br>']\n--EXPECT--\n<br>\n"
  },
  {
    "path": "tests/Fixtures/filters/reduce.test",
    "content": "--TEST--\n\"reduce\" filter\n--TEMPLATE--\n{% set offset = 3 %}\n\n{{ [1, -1, 4]|reduce((carry, item) => carry + item + offset, 10) }}\n\n{{ it|reduce((carry, item) => carry + item + offset, 10) }}\n--DATA--\nreturn ['it' => new \\ArrayIterator([1, -1, 4])]\n--EXPECT--\n23\n\n23\n"
  },
  {
    "path": "tests/Fixtures/filters/reduce_key.test",
    "content": "--TEST--\n\"reduce\" filter passes iterable key to callback\n--TEMPLATE--\n{% set status_classes = {\n    'success': 200,\n    'warning': 400,\n    'error': 500,\n} %}\n\n{{ status_classes|reduce((carry, v, k) => status_code >= v ? k : carry, '') }}\n--DATA--\nreturn ['status_code' => 404]\n--EXPECT--\nwarning\n"
  },
  {
    "path": "tests/Fixtures/filters/replace.test",
    "content": "--TEST--\n\"replace\" filter\n--TEMPLATE--\n{{ \"I liké %this% and %that%.\"|replace({'%this%': \"foo\", '%that%': \"bar\"}) }}\n{{ 'I like single replace operation only %that%'|replace({'%that%' : '%that%1'}) }}\n{{ 'I like %this% and %that%.'|replace(traversable) }}\n*{{ ''|replace(traversable) }}*\n*{{ null|replace(traversable) }}*\n--DATA--\nreturn ['traversable' => new \\ArrayObject(['%this%' => 'foo', '%that%' => 'bar'])]\n--EXPECT--\nI liké foo and bar.\nI like single replace operation only %that%1\nI like foo and bar.\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/replace_invalid_arg.test",
    "content": "--TEST--\nException for invalid argument type in replace call\n--TEMPLATE--\n{{ 'test %foo%'|replace(stdClass) }}\n--DATA--\nreturn ['stdClass' => new \\stdClass()]\n--EXCEPTION--\nTwig\\Error\\RuntimeError: The \"replace\" filter expects a sequence or a mapping, got \"stdClass\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/filters/reverse.test",
    "content": "--TEST--\n\"reverse\" filter\n--TEMPLATE--\n{{ [1, 2, 3, 4]|reverse|join('') }}\n{{ '1234évènement'|reverse }}\n{{ arr|reverse|join('') }}\n{{ {'a': 'c', 'b': 'a'}|reverse()|join(',') }}\n{{ {'a': 'c', 'b': 'a'}|reverse(preserveKeys=true)|join(glue=',') }}\n{{ {'a': 'c', 'b': 'a'}|reverse(preserve_keys=true)|join(glue=',') }}\n*{{ ''|reverse }}*\n*{{ null|reverse }}*\n--DATA--\nreturn ['arr' => new \\ArrayObject([1, 2, 3, 4])]\n--EXPECT--\n4321\ntnemenèvé4321\n4321\na,c\na,c\na,c\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/round.test",
    "content": "--TEST--\n\"round\" filter\n--TEMPLATE--\n{{ 2.7|round }}\n{{ 2.1|round }}\n{{ 2.1234|round(3, 'floor') }}\n{{ 2.1|round(0, 'ceil') }}\n\n{{ 21.3|round(-1)}}\n{{ 21.3|round(-1, 'ceil')}}\n{{ 21.3|round(-1, 'floor')}}\n{{ '21.3'|round(-1, 'floor')}}\n\n{{ ''|round(-1, 'floor')}}\n{{ null|round(-1, 'floor')}}\n{{ null|round }}\n{{ null|round(2, 'ceil') }}\n--DATA--\nreturn []\n--EXPECT--\n3\n2\n2.123\n3\n\n20\n30\n20\n20\n\n0\n0\n0\n0\n"
  },
  {
    "path": "tests/Fixtures/filters/shuffle.test",
    "content": "--TEST--\n\"shuffle\" filter\n--TEMPLATE--\n{% set test = 'ok'|shuffle %}{{ 'ok' is same as test or 'ko' is same as test ? 'ok' : 'ko' }}\n{% set test = [3, 1]|shuffle %}{{ [3, 1] is same as test or [1, 3] is same as test ? 'ok' : 'ko' }}\n{% set test = ['foo', 'bar']|shuffle %}{{ ['foo', 'bar'] is same as test or ['bar', 'foo'] is same as test ? 'ok' : 'ko' }}\n{% set test = {'a': 'd', 'b': 'e'}|shuffle %}{{ ['d', 'e'] is same as test or ['e', 'd'] is same as test ? 'ok' : 'ko' }}\n{% set test = traversable|shuffle %}{{ [3, 1] is same as test or [1, 3] is same as test ? 'ok' : 'ko' }}\n--DATA--\nreturn ['traversable' => new \\ArrayObject([0 => 3, 1 => 1])]\n--EXPECT--\nok\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/filters/slice.test",
    "content": "--TEST--\n\"slice\" filter\n--TEMPLATE--\n{{ [1, 2, 3, 4][1:2]|join('') }}\n{{ {a: 1, b: 2, c: 3, d: 4}[1:2]|join('') }}\n{{ [1, 2, 3, 4][start:length]|join('') }}\n{{ [1, 2, 3, 4]|slice(1, 2)|join('') }}\n{{ [1, 2, 3, 4]|slice(1, 2)|keys|join('') }}\n{{ [1, 2, 3, 4]|slice(1, 2, true)|keys|join('') }}\n{{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|join('') }}\n{{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|keys|join('') }}\n{{ '1234'|slice(1, 2) }}\n{{ '1234'[1:2] }}\n{{ arr|slice(1, 2)|join('') }}\n{{ arr[1:2]|join('') }}\n{{ arr[4:1]|join('') }}\n{{ arr[3:2]|join('') }}\n\n{{ [1, 2, 3, 4]|slice(1)|join('') }}\n{{ [1, 2, 3, 4][1:]|join('') }}\n{{ '1234'|slice(1) }}\n{{ '1234'[1:] }}\n{{ '1234'[:1] }}\n\n{{ arr|slice(3)|join('') }}\n{{ arr[2:]|join('') }}\n{{ xml|slice(1)|join('')}}\n--DATA--\nreturn ['start' => 1, 'length' => 2, 'arr' => new \\ArrayObject([1, 2, 3, 4]), 'xml' => new \\SimpleXMLElement('<items><item>1</item><item>2</item></items>')]\n--EXPECT--\n23\n23\n23\n23\n01\n12\n23\nbc\n23\n23\n23\n23\n\n4\n\n234\n234\n234\n234\n1\n\n4\n34\n2\n"
  },
  {
    "path": "tests/Fixtures/filters/sort.test",
    "content": "--TEST--\n\"sort\" filter\n--TEMPLATE--\n{{ array1|sort|join }}\n{{ array2|sort|join }}\n{{ traversable|sort|join }}\n--DATA--\nreturn ['array1' => [4, 1], 'array2' => ['foo', 'bar'], 'traversable' => new \\ArrayObject([0 => 3, 1 => 2, 2 => 1])]\n--EXPECT--\n14\nbarfoo\n123\n"
  },
  {
    "path": "tests/Fixtures/filters/sort_with_arrow.test",
    "content": "--TEST--\n\"sort\" filter\n--TEMPLATE--\n{{ fruits|sort((a, b) => a.quantity == b.quantity ? 0 : (a.quantity > b.quantity ? 1 : -1))|column('name')|join(', ') }}\n{{ fruits|sort((a, b) => a.quantity <=> b.quantity)|column('name')|join(', ') }}\n--DATA--\nreturn [\n    'fruits' => [\n        [ 'name' => 'Apples', 'quantity' => 5 ],\n        [ 'name' => 'Oranges', 'quantity' => 2 ],\n        [ 'name' => 'Grapes', 'quantity' => 4 ],\n    ],\n]\n--EXPECT--\nOranges, Grapes, Apples\nOranges, Grapes, Apples\n"
  },
  {
    "path": "tests/Fixtures/filters/spaceless.legacy.test",
    "content": "--TEST--\n\"spaceless\" filter\n--DEPRECATION--\nSince twig/twig 3.12: Twig Filter \"spaceless\" is deprecated in index.twig at line 2.\nSince twig/twig 3.12: Twig Filter \"spaceless\" is deprecated in index.twig at line 3.\nSince twig/twig 3.12: Twig Filter \"spaceless\" is deprecated in index.twig at line 4.\n--TEMPLATE--\n{{ \"    <div>   <div>   foo   </div>   </div>\"|spaceless }}\n*{{ \"\"|spaceless }}*\n*{{ null|spaceless }}*\n--DATA--\nreturn []\n--EXPECT--\n<div><div>   foo   </div></div>\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/special_chars.test",
    "content": "--TEST--\n\"§\" custom filter\n--TEMPLATE--\n{{ 'foo'|§ }}\n--DATA--\nreturn []\n--EXPECT--\n§foo§\n"
  },
  {
    "path": "tests/Fixtures/filters/split.test",
    "content": "--TEST--\n\"split\" filter\n--TEMPLATE--\n{{ \"one,two,three,four,five\"|split(',')|join('-') }}\n{{ foo|split(',')|join('-') }}\n{{ foo|split(',', 3)|join('-') }}\n{{ baz|split('')|join('-') }}\n{{ baz|split('', 1)|join('-') }}\n{{ baz|split('', 2)|join('-') }}\n{{ foo|split(',', -2)|join('-') }}\n{{ \"hello0world\"|split('0')|join('-') }}\n*{{ \"\"|split(',')|join('-') }}*\n*{{ null|split(',')|join('-') }}*\n--DATA--\nreturn ['foo' => \"one,two,three,four,five\", 'baz' => '12345',]\n--EXPECT--\none-two-three-four-five\none-two-three-four-five\none-two-three,four,five\n1-2-3-4-5\n1-2-3-4-5\n12-34-5\none-two-three\nhello-world\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/split_utf8.test",
    "content": "--TEST--\n\"split\" filter\n--TEMPLATE--\n{{ \"é\"|split('', 10)|join('-') }}\n{{ foo|split(',')|join('-') }}\n{{ foo|split(',', 1)|join('-') }}\n{{ foo|split(',', 2)|join('-') }}\n{{ foo|split(',', 3)|join('-') }}\n{{ baz|split('')|join('-') }}\n{{ baz|split('', 1)|join('-') }}\n{{ baz|split('', 2)|join('-') }}\n--DATA--\nreturn ['foo' => 'Ä,é,Äほ', 'baz' => 'éÄßごa',]\n--EXPECT--\né\nÄ-é-Äほ\nÄ,é,Äほ\nÄ-é,Äほ\nÄ-é-Äほ\né-Ä-ß-ご-a\né-Ä-ß-ご-a\néÄ-ßご-a"
  },
  {
    "path": "tests/Fixtures/filters/static_calls.test",
    "content": "--TEST--\nFilters as static method calls\n--TEMPLATE--\n{{ 'foo'|static_call_string }}\n{{ 'foo'|static_call_array }}\n--DATA--\nreturn ['foo' => 'foo']\n--EXPECT--\n*foo*\n*foo*\n"
  },
  {
    "path": "tests/Fixtures/filters/striptags.test",
    "content": "--TEST--\n\"striptags\" filter\n--TEMPLATE--\n{{ \"Hello, <strong>World</strong>!\"|striptags }}\n{{ text|striptags }}\n{{ text|striptags('<strong>')|raw }}\n*{{ ''|striptags }}*\n*{{ null|striptags }}*\n--DATA--\nreturn ['text' => \"<p>Hello, <strong>World</strong>!</p>\"]\n--EXPECT--\nHello, World!\nHello, World!\nHello, <strong>World</strong>!\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/title.test",
    "content": "--TEST--\n\"title\" filter\n--TEMPLATE--\n{{ \"I like Twig.\"|title }}\n*{{ \"\"|title }}*\n*{{ null|title }}*\n--DATA--\nreturn []\n--EXPECT--\nI Like Twig.\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/trailing_commas.test",
    "content": "--TEST--\nfilters allow trailing commas in their argument list\n--TEMPLATE--\n{{ 42.55|round(1, 'floor',) }}\n--DATA--\nreturn []\n--EXPECT--\n42.5\n"
  },
  {
    "path": "tests/Fixtures/filters/trim.test",
    "content": "--TEST--\n\"trim\" filter\n--TEMPLATE--\n{{ \"  I like Twig.  \"|trim }}\n{{ text|trim }}\n{{ \"  foo/\"|trim(\"/\") }}\n{{ \"xxxI like Twig.xxx\"|trim(character_mask: \"x\", side: \"left\") }}\n{{ \"xxxI like Twig.xxx\"|trim(side: \"right\", character_mask: \"x\") }}\n{{ \"xxxI like Twig.xxx\"|trim(\"x\", \"right\") }}\n{{ \"/  foo/\"|trim(\"/\", \"left\") }}\n{{ \"/  foo/\"|trim(character_mask: \"/\", side: \"left\") }}\n{{ \"  do nothing.  \"|trim(\"\", \"right\") }}\n*{{ \"\"|trim }}*\n*{{ \"\"|trim(\"\", \"left\") }}*\n*{{ \"\"|trim(\"\", \"right\") }}*\n*{{ null|trim }}*\n*{{ null|trim(\"\", \"left\") }}*\n*{{ null|trim(\"\", \"right\") }}*\n\n{% set myhtml %}\n  Here is<br>my HTML  \n{% endset %}\n{% set myunsafestring = \" I <3 u \" %}\n{{ myhtml | trim }}\n{{ myunsafestring | trim }}\n{{ myhtml | trim(character_mask: \"f\") }}\n--DATA--\nreturn ['text' => \"  If you have some <strong>HTML</strong> it will be escaped.  \"]\n--EXPECT--\nI like Twig.\nIf you have some &lt;strong&gt;HTML&lt;/strong&gt; it will be escaped.\n  foo\nI like Twig.xxx\nxxxI like Twig.\nxxxI like Twig.\n  foo/\n  foo/\n  do nothing.  \n**\n**\n**\n**\n**\n**\n\nHere is<br>my HTML\nI &lt;3 u\n  Here is&lt;br&gt;my HTML\n"
  },
  {
    "path": "tests/Fixtures/filters/upper.test",
    "content": "--TEST--\n\"upper\" filter\n--TEMPLATE--\n{{ \"I like Twig.\"|upper }}\n*{{ \"\"|upper }}*\n*{{ null|upper }}*\n--DATA--\nreturn []\n--EXPECT--\nI LIKE TWIG.\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/filters/urlencode.test",
    "content": "--TEST--\n\"url_encode\" filter\n--TEMPLATE--\n{{ {foo: \"bar\", number: 3, \"spéßi%l\": \"e%c0d@d\", \"spa ce\": \"\"}|url_encode }}\n{{ {foo: \"bar\", number: 3, \"spéßi%l\": \"e%c0d@d\", \"spa ce\": \"\"}|url_encode|raw }}\n{{ {}|url_encode|default(\"default\") }}\n{{ 'spéßi%le%c0d@dspa ce'|url_encode }}\n*{{ ''|url_encode }}*\n*{{ null|url_encode }}*\n--DATA--\nreturn []\n--EXPECT--\nfoo=bar&amp;number=3&amp;sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&amp;spa%20ce=\nfoo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce=\ndefault\nsp%C3%A9%C3%9Fi%25le%25c0d%40dspa%20ce\n**\n**\n"
  },
  {
    "path": "tests/Fixtures/functions/attribute.legacy.test",
    "content": "--TEST--\n\"attribute\" function\n--TEMPLATE--\n{{ attribute(obj, method) }}\n{{ attribute(variable=obj, attribute=method) }}\n{{ attribute(variable: obj, attribute: method) }}\n{{ attribute(array, item) }}\n{{ attribute(obj, \"bar\", [\"a\", \"b\"]) }}\n{{ attribute(obj, \"bar\", arguments) }}\n{{ attribute(variable=obj, attribute=\"bar\", arguments=arguments) }}\n{{ attribute(variable: obj, attribute: \"bar\", arguments: arguments) }}\n{{ attribute(obj, method) is defined ? 'ok' : 'ko' }}\n{{ attribute(obj, nonmethod) is defined ? 'ok' : 'ko' }}\n--DATA--\nreturn ['obj' => new Twig\\Tests\\TwigTestFoo(), 'method' => 'foo', 'array' => ['foo' => 'bar'], 'item' => 'foo', 'nonmethod' => 'xxx', 'arguments' => ['a', 'b']]\n--EXPECT--\nfoo\nfoo\nfoo\nbar\nbar_a-b\nbar_a-b\nbar_a-b\nbar_a-b\nok\nko\n"
  },
  {
    "path": "tests/Fixtures/functions/attribute_with_wrong_args.legacy.test",
    "content": "--TEST--\n\"attribute\" function\n--TEMPLATE--\n{{ attribute(var=var, template=\"tpl\") }}\n--DATA--\nreturn ['var' => null]\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Value for argument \"variable\" is required for function \"attribute\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/block.test",
    "content": "--TEST--\n\"block\" function\n--TEMPLATE--\n{% extends 'base.twig' %}\n{% block bar %}BAR{% endblock %}\n--TEMPLATE(base.twig)--\n{% block foo %}{{ block('bar') }}{% endblock %}\n{% block baz %}{{ block(name='bar') }}{% endblock %}\n{% block bar %}BAR_BASE{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nBARBARBAR\n"
  },
  {
    "path": "tests/Fixtures/functions/block_with_template.test",
    "content": "--TEST--\n\"block\" function with a template argument\n--TEMPLATE--\n{{ block('foo', 'included.twig') }}\n{{ block('foo', included_loaded) }}\n{{ block('foo', included_loaded_internal) }}\n{% set output = block('foo', 'included.twig') %}\n{{ output }}\n{% set output = block(name='foo', template='included.twig') %}\n{{ output }}\n{% set output = block(template='included.twig', name='foo') %}\n{{ output }}\n{% block foo %}NOT FOO{% endblock %}\n--TEMPLATE(included.twig)--\n{% block foo %}FOO{% endblock %}\n--DATA--\nreturn [\n    'included_loaded' => $twig->load('included.twig'),\n    'included_loaded_internal' => $twig->load('included.twig'),\n]\n--EXPECT--\nFOO\nFOO\nFOO\nFOO\nFOO\nFOO\nNOT FOO\n"
  },
  {
    "path": "tests/Fixtures/functions/block_without_name.test",
    "content": "--TEST--\n\"block\" function without arguments\n--TEMPLATE--\n{% extends 'base.twig' %}\n{% block bar %}BAR{% endblock %}\n--TEMPLATE(base.twig)--\n{% block foo %}{{ block() }}{% endblock %}\n{% block bar %}BAR_BASE{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Value for argument \"name\" is required for function \"block\" in \"base.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/block_without_parent.test",
    "content": "--TEST--\n\"block\" calling parent() with no definition in parent template\n--TEMPLATE--\n{% extends \"parent.twig\" %}\n{% block label %}{{ parent() }}{% endblock %}\n--TEMPLATE(parent.twig)--\n{{ block('label') }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Block \"label\" should not call parent() in \"index.twig\" as the block does not exist in the parent template \"parent.twig\" in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/functions/constant.test",
    "content": "--TEST--\n\"constant\" function\n--TEMPLATE--\n{{ constant('DATE_W3C') == expect ? 'true' : 'false' }}\n{{ constant('ARRAY_AS_PROPS', object) }}\n{{ constant('class', object) }}\n{{ constant('ARRAY_AS_PROPS', object) ?? 'KO' }}\n--DATA--\nreturn ['expect' => DATE_W3C, 'object' => new \\ArrayObject(['hi'])]\n--EXPECT--\ntrue\n2\nArrayObject\n2\n"
  },
  {
    "path": "tests/Fixtures/functions/cycle.test",
    "content": "--TEST--\n\"cycle\" function\n--TEMPLATE--\n{% for i in 0..6 %}\n{{ cycle(array1, i) }}-{{ cycle(array2, i) }}-{{ cycle(array3, i) }}\n{% endfor %}\n--DATA--\nreturn [\n    'array1' => ['odd', 'even'],\n    'array2' => ['apple', 'orange', 'citrus'],\n    'array3' => [1, 2, false, null],\n];\n--EXPECT--\nodd-apple-1\neven-orange-2\nodd-citrus-\neven-apple-\nodd-orange-1\neven-citrus-2\nodd-apple-\n"
  },
  {
    "path": "tests/Fixtures/functions/cycle_empty_mapping.test",
    "content": "--TEST--\n\"cycle\" function returns an error on empty mappings\n--TEMPLATE--\n{{ cycle({}, 0) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: The \"cycle\" function expects a non-empty sequence in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/cycle_empty_sequence.test",
    "content": "--TEST--\n\"cycle\" function returns an error on empty sequences\n--TEMPLATE--\n{{ cycle([], 0) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: The \"cycle\" function expects a non-empty sequence in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/cycle_without_enough_args.test",
    "content": "--TEST--\n\"cycle\" function without enough args and a named argument\n--TEMPLATE--\n{{ cycle(position=2) }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Value for argument \"values\" is required for function \"cycle\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/date.test",
    "content": "--TEST--\n\"date\" function\n--TEMPLATE--\n{{ date().format('r') == date('now').format('r') ? 'OK' : 'KO' }}\n{{ date(date1) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}\n{{ date(date2) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}\n{{ date(date3) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}\n{{ date(date4) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}\n{{ date(date5) == date('1964-01-02 03:04') ? 'OK' : 'KO' }}\n{{ date() > date('-1day') ? 'OK' : 'KO' }}\n--DATA--\ndate_default_timezone_set('UTC');\nreturn [\n    'date1' => mktime(13, 45, 0, 10, 4, 2010),\n    'date2' => new \\DateTime('2010-10-04 13:45'),\n    'date3' => '2010-10-04 13:45',\n    'date4' => 1286199900, // \\DateTime::createFromFormat('Y-m-d H:i', '2010-10-04 13:45', new \\DateTimeZone('UTC'))->getTimestamp() -- A unixtimestamp is always GMT\n    'date5' => -189291360, // \\DateTime::createFromFormat('Y-m-d H:i', '1964-01-02 03:04', new \\DateTimeZone('UTC'))->getTimestamp(),\n]\n--EXPECT--\nOK\nOK\nOK\nOK\nOK\nOK\nOK\n"
  },
  {
    "path": "tests/Fixtures/functions/date_namedargs.test",
    "content": "--TEST--\n\"date\" function\n--TEMPLATE--\n{{ date(date, \"America/New_York\")|date('d/m/Y H:i:s P', false) }}\n{{ date(timezone=\"America/New_York\", date=date)|date('d/m/Y H:i:s P', false) }}\n{{ date(timezone: \"America/New_York\", date: date)|date('d/m/Y H:i:s P', false) }}\n--DATA--\ndate_default_timezone_set('UTC');\nreturn ['date' => mktime(13, 45, 0, 10, 4, 2010)]\n--EXPECT--\n04/10/2010 09:45:00 -04:00\n04/10/2010 09:45:00 -04:00\n04/10/2010 09:45:00 -04:00\n"
  },
  {
    "path": "tests/Fixtures/functions/deprecated.test",
    "content": "--TEST--\nFunctions can be deprecated_function\n--DEPRECATION--\nSince foo/bar 1.1: Twig Function \"deprecated_function\" is deprecated; use \"not_deprecated_function\" instead in index.twig at line 2.\nSince foo/bar 1.1: Twig Function \"deprecated_function\" is deprecated; use \"not_deprecated_function\" instead in index.twig at line 4.\n--TEMPLATE--\n{{ deprecated_function() }}\n\n{{ deprecated_function() }}\n--DATA--\nreturn []\n--EXPECT--\nfoo\n\nfoo\n--DATA--\nreturn []\n--EXPECT--\nfoo\n\nfoo\n"
  },
  {
    "path": "tests/Fixtures/functions/dump.test",
    "content": "--TEST--\n\"dump\" function\n--CONDITION--\n!extension_loaded('xdebug')\n--TEMPLATE--\n{{ dump('foo') }}\n{{ dump('foo', 'bar') }}\n--DATA--\nreturn ['foo' => 'foo', 'bar' => 'bar']\n--CONFIG--\nreturn ['debug' => true, 'autoescape' => false]\n--EXPECT--\nstring(3) \"foo\"\n\nstring(3) \"foo\"\nstring(3) \"bar\"\n"
  },
  {
    "path": "tests/Fixtures/functions/dump_array.test",
    "content": "--TEST--\n\"dump\" function, xdebug is not loaded or xdebug <2.2-dev is loaded\n--CONDITION--\n!extension_loaded('xdebug') || (($r = new \\ReflectionExtension('xdebug')) && version_compare($r->getVersion(), '2.2-dev', '<'))\n--TEMPLATE--\n{{ dump() }}\n--DATA--\nreturn ['foo' => 'foo', 'bar' => 'bar']\n--CONFIG--\nreturn ['debug' => true, 'autoescape' => false]\n--EXPECT--\narray(3) {\n  [\"foo\"]=>\n  string(3) \"foo\"\n  [\"bar\"]=>\n  string(3) \"bar\"\n  [\"global\"]=>\n  string(6) \"global\"\n}\n"
  },
  {
    "path": "tests/Fixtures/functions/dynamic_function.test",
    "content": "--TEST--\ndynamic function\n--TEMPLATE--\n{{ foo_path('bar') }}\n{{ bar_path('bar') }}\n{{ a_foo_b_bar('bar') }}\n--DATA--\nreturn []\n--EXPECT--\nfoo/bar\nbar/bar\na/b/bar\n"
  },
  {
    "path": "tests/Fixtures/functions/enum/invalid_dynamic_enum.test",
    "content": "--TEST--\n\"enum\" function with invalid dynamic enum class\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% set from_variable = 'Twig\\\\Tests\\\\NonExistentEnum' %}\n{% for c in enum(from_variable).cases() %}\n    {{~ c.name }}\n{% endfor %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: \"Twig\\Tests\\NonExistentEnum\" is not an enum in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/functions/enum/invalid_enum.test",
    "content": "--TEST--\n\"enum\" function with invalid enum class\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% for c in enum('Twig\\\\Tests\\\\NonExistentEnum').cases() %}\n    {{~ c.name }}\n{% endfor %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The first argument of the \"enum\" function must be the name of an enum, \"Twig\\Tests\\NonExistentEnum\" given in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/enum/invalid_enum_escaping.test",
    "content": "--TEST--\n\"enum\" function with missing \\ escaping\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% for c in enum('Twig\\Tests\\DummyBackedEnum').cases() %}\n    {{~ c.name }}\n{% endfor %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The first argument of the \"enum\" function must be the name of an enum, \"TwigTestsDummyBackedEnum\" given in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/enum/invalid_literal_type.test",
    "content": "--TEST--\n\"enum\" function with invalid literal type\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% for c in enum(13).cases() %}\n    {{~ c.name }}\n{% endfor %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The first argument of the \"enum\" function must be a string in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/enum/valid.test",
    "content": "--TEST--\n\"enum\" function\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{{ enum('Twig\\\\Tests\\\\DummyBackedEnum').FOO.value }}\n{% for c in enum('Twig\\\\Tests\\\\DummyBackedEnum').cases() %}\n    {{~ c.name }}: {{ c.value }}\n{% endfor %}\n{{ enum('Twig\\\\Tests\\\\DummyUnitEnum').BAR.name }}\n{% for c in enum('Twig\\\\Tests\\\\DummyUnitEnum').cases() %}\n    {{~ c.name }}\n{% endfor %}\n{% set from_variable='Twig\\\\Tests\\\\DummyUnitEnum' %}\n{{ enum(from_variable).BAR.name }}\n{% for c in enum(from_variable).cases() %}\n    {{~ c.name }}\n{% endfor %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\nFOO: foo\nBAR: bar\nBAR\nBAR\nBAZ\nBAR\nBAR\nBAZ\n"
  },
  {
    "path": "tests/Fixtures/functions/enum_cases/invalid_dynamic_enum.test",
    "content": "--TEST--\n\"enum_cases\" function with invalid dynamic enum class\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% set from_variable = 'Twig\\\\Tests\\\\NonExistentEnum' %}\n{% for c in enum_cases(from_variable) %}\n    {{~ c.name }}\n{% endfor %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Enum \"Twig\\Tests\\NonExistentEnum\" does not exist in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/functions/enum_cases/invalid_enum.test",
    "content": "--TEST--\n\"enum_cases\" function with invalid enum class\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% for c in enum_cases('Twig\\\\Tests\\\\NonExistentEnum') %}\n    {{~ c.name }}\n{% endfor %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The first argument of the \"enum_cases\" function must be the name of an enum, \"Twig\\Tests\\NonExistentEnum\" given in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/enum_cases/invalid_enum_escaping.test",
    "content": "--TEST--\n\"enum_cases\" function with missing \\ escaping\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% for c in enum_cases('Twig\\Tests\\DummyBackedEnum') %}\n    {{~ c.name }}\n{% endfor %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The first argument of the \"enum_cases\" function must be the name of an enum, \"TwigTestsDummyBackedEnum\" given in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/enum_cases/invalid_literal_type.test",
    "content": "--TEST--\n\"enum_cases\" function with invalid literal type\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% for c in enum_cases(13) %}\n    {{~ c.name }}\n{% endfor %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The first argument of the \"enum_cases\" function must be a string in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/enum_cases/valid.test",
    "content": "--TEST--\n\"enum_cases\" function\n--CONDITION--\n\\PHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% for c in enum_cases('Twig\\\\Tests\\\\DummyBackedEnum') %}\n    {{~ c.name }}: {{ c.value }}\n{% endfor %}\n{% for c in enum_cases('Twig\\\\Tests\\\\DummyUnitEnum') %}\n    {{~ c.name }}\n{% endfor %}\n{% set from_variable='Twig\\\\Tests\\\\DummyUnitEnum' %}\n{% for c in enum_cases(from_variable) %}\n    {{~ c.name }}\n{% endfor %}\n--DATA--\nreturn []\n--EXPECT--\nFOO: foo\nBAR: bar\nBAR\nBAZ\nBAR\nBAZ\n"
  },
  {
    "path": "tests/Fixtures/functions/include/assignment.test",
    "content": "--TEST--\n\"include\" function\n--TEMPLATE--\n{% set tmp = include(\"foo.twig\") %}\n\nFOO{{ tmp }}BAR\n--TEMPLATE(foo.twig)--\nFOOBAR\n--DATA--\nreturn []\n--EXPECT--\nFOO\nFOOBARBAR\n"
  },
  {
    "path": "tests/Fixtures/functions/include/autoescaping.test",
    "content": "--TEST--\n\"include\" function is safe for auto-escaping\n--TEMPLATE--\n{{ include(\"foo.twig\") }}\n--TEMPLATE(foo.twig)--\n<p>Test</p>\n--DATA--\nreturn []\n--EXPECT--\n<p>Test</p>\n"
  },
  {
    "path": "tests/Fixtures/functions/include/basic.test",
    "content": "--TEST--\n\"include\" function\n--TEMPLATE--\nFOO\n{{ include(\"foo.twig\") }}\n\nBAR\n--TEMPLATE(foo.twig)--\nFOOBAR\n--DATA--\nreturn []\n--EXPECT--\nFOO\n\nFOOBAR\n\nBAR\n"
  },
  {
    "path": "tests/Fixtures/functions/include/expression.test",
    "content": "--TEST--\n\"include\" function allows expressions for the template to include\n--TEMPLATE--\nFOO\n{{ include(foo) }}\n\nBAR\n--TEMPLATE(foo.twig)--\nFOOBAR\n--DATA--\nreturn ['foo' => 'foo.twig']\n--EXPECT--\nFOO\n\nFOOBAR\n\nBAR\n"
  },
  {
    "path": "tests/Fixtures/functions/include/ignore_missing.test",
    "content": "--TEST--\n\"include\" function\n--TEMPLATE--\n{{ include([\"foo.twig\", \"bar.twig\"], ignore_missing = true) }}\n{{ include(\"foo.twig\", ignore_missing = true) }}\n{{ include(\"foo.twig\", ignore_missing = true, variables = {}) }}\n{{ include(\"foo.twig\", ignore_missing = true, variables = {}, with_context = true) }}\n--DATA--\nreturn []\n--EXPECT--\n"
  },
  {
    "path": "tests/Fixtures/functions/include/ignore_missing_exists.test",
    "content": "--TEST--\n\"include\" function\n--TEMPLATE--\n{{ include(\"included.twig\", ignore_missing = true) }}\nNOT DISPLAYED\n--TEMPLATE(included.twig)--\n{{ include(\"DOES NOT EXIST\") }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"DOES NOT EXIST\" is not defined in \"included.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/include/include_missing_extends.test",
    "content": "--TEST--\n\"include\" function\n--TEMPLATE--\n{{ include(['bad.twig', 'good.twig'], ignore_missing = true) }}\nNOT DISPLAYED\n--TEMPLATE(bad.twig)--\n{% extends 'DOES NOT EXIST' %}\n--TEMPLATE(good.twig)--\nNOT DISPLAYED\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"DOES NOT EXIST\" is not defined in \"bad.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/include/missing.test",
    "content": "--TEST--\n\"include\" function\n--TEMPLATE--\n{{ include(\"foo.twig\") }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"foo.twig\" is not defined in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/include/missing_nested.test",
    "content": "--TEST--\n\"include\" function\n--TEMPLATE--\n{% extends \"base.twig\" %}\n\n{% block content %}\n    {{ parent() }}\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% block content %}\n    {{ include(\"foo.twig\") }}\n{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"foo.twig\" is not defined in \"base.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/functions/include/sandbox.test",
    "content": "--TEST--\n\"include\" tag sandboxed\n--TEMPLATE--\n{{ include(\"foo.twig\", sandboxed = true) }}\n--TEMPLATE(foo.twig)--\n\n\n{{ 'foo'|e }}\n{{ 'foo'|e }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Sandbox\\SecurityNotAllowedFilterError: Filter \"e\" is not allowed in \"foo.twig\" at line 4.\n"
  },
  {
    "path": "tests/Fixtures/functions/include/sandbox_disabling.test",
    "content": "--TEST--\n\"include\" tag sandboxed\n--TEMPLATE--\n{{ include(\"foo.twig\", sandboxed = true) }}\n{{ include(\"bar.twig\") }}\n--TEMPLATE(foo.twig)--\nfoo\n--TEMPLATE(bar.twig)--\n{{ foo|e }}\n--DATA--\nreturn ['foo' => 'bar<br />']\n--EXPECT--\nfoo\n\n\nbar&lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test",
    "content": "--TEST--\n\"include\" tag sandboxed\n--TEMPLATE--\n{{ include(\"unknown.twig\", sandboxed = true, ignore_missing = true) }}\n{{ include(\"bar.twig\") }}\n--TEMPLATE(bar.twig)--\n{{ foo|e }}\n--DATA--\nreturn ['foo' => 'bar<br />']\n--EXPECT--\n\n\nbar&lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/functions/include/template_instance.test",
    "content": "--TEST--\n\"include\" function accepts Twig\\Template instance\n--TEMPLATE--\n{{ include(foo) }} FOO\n--TEMPLATE(foo.twig)--\nBAR\n--DATA--\nreturn ['foo' => $twig->load('foo.twig')]\n--EXPECT--\nBAR FOO\n"
  },
  {
    "path": "tests/Fixtures/functions/include/templates_as_array.test",
    "content": "--TEST--\n\"include\" function\n--TEMPLATE--\n{{ include([\"foo.twig\", \"bar.twig\"]) }}\n{{- include([\"bar.twig\", \"foo.twig\"]) }}\n--TEMPLATE(foo.twig)--\nfoo\n--DATA--\nreturn []\n--EXPECT--\nfoo\nfoo\n"
  },
  {
    "path": "tests/Fixtures/functions/include/with_context.test",
    "content": "--TEST--\n\"include\" function accept variables and with_context\n--TEMPLATE--\n{{ include(\"foo.twig\") }}\n{{- include(\"foo.twig\", with_context = false) }}\n{{- include(\"foo.twig\", {'foo1': 'bar'}) }}\n{{- include(\"foo.twig\", {'foo1': 'bar'}, with_context = false) }}\n--TEMPLATE(foo.twig)--\n{% for k, v in _context %}{{ k }},{% endfor %}\n--DATA--\nreturn ['foo' => 'bar']\n--EXPECT--\nfoo,global,_parent,\nglobal,_parent,\nfoo,global,foo1,_parent,\nfoo1,global,_parent,\n"
  },
  {
    "path": "tests/Fixtures/functions/include/with_variables.test",
    "content": "--TEST--\n\"include\" function accept variables\n--TEMPLATE--\n{{ include(\"foo.twig\", {'foo': 'bar'}) }}\n{{- include(\"foo.twig\", vars) }}\n--TEMPLATE(foo.twig)--\n{{ foo }}\n--DATA--\nreturn ['vars' => ['foo' => 'bar']]\n--EXPECT--\nbar\nbar\n"
  },
  {
    "path": "tests/Fixtures/functions/include_template_from_string.test",
    "content": "--TEST--\n\"template_from_string\" function works in an \"include\"\n--TEMPLATE--\n{% set embed = '{% embed \"embed.twig\" %}{% endembed %}' %}\n{{ include(template_from_string(embed)) }}\n--TEMPLATE(embed.twig)--\nCool\n--DATA--\nreturn []\n--EXPECT--\nCool\n"
  },
  {
    "path": "tests/Fixtures/functions/magic_call.test",
    "content": "--TEST--\n__call calls\n--TEMPLATE--\n{{ 'foo'|magic_call }}\n{{ 'foo'|magic_call_closure }}\n--DATA--\nreturn []\n--EXPECT--\nmagic_foo\nmagic_foo\n"
  },
  {
    "path": "tests/Fixtures/functions/magic_static_call.test",
    "content": "--TEST--\n__staticCall calls\n--TEMPLATE--\n{{ 'foo'|magic_call_string }}\n{{ 'foo'|magic_call_array }}\n--DATA--\nreturn []\n--EXPECT--\nstatic_magic_foo\nstatic_magic_foo\n"
  },
  {
    "path": "tests/Fixtures/functions/max.test",
    "content": "--TEST--\n\"max\" function\n--TEMPLATE--\n{{ max([2, 1, 3, 5, 4]) }}\n{{ max(2, 1, 3, 5, 4) }}\n{{ max({2:\"two\", 1:\"one\", 3:\"three\", 5:\"five\", 4:\"for\"}) }}\n--DATA--\nreturn []\n--EXPECT--\n5\n5\ntwo\n"
  },
  {
    "path": "tests/Fixtures/functions/max_without_args.test",
    "content": "--TEST--\n\"max\" function without an argument throws a compile time exception\n--TEMPLATE--\n{{ max() }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Value for argument \"value\" is required for function \"max\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/functions/min.test",
    "content": "--TEST--\n\"min\" function\n--TEMPLATE--\n{{ min(2, 1, 3, 5, 4) }}\n{{ min([2, 1, 3, 5, 4]) }}\n{{ min({2:\"two\", 1:\"one\", 3:\"three\", 5:\"five\", 4:\"for\"}) }}\n--DATA--\nreturn []\n--EXPECT--\n1\n1\nfive\n"
  },
  {
    "path": "tests/Fixtures/functions/parent_in_condition.test",
    "content": "--TEST--\n\"block\" calling parent() in a conditional expression\n--TEMPLATE--\n{% extends \"parent.twig\" %}\n{% block label %}{{ parent() ?: 'foo' }}{% endblock %}\n--TEMPLATE(parent.twig)--\n{% block label %}PARENT_LABEL{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nPARENT_LABEL\n"
  },
  {
    "path": "tests/Fixtures/functions/parent_outside_of_a_block.test",
    "content": "--TEST--\n\"parent\" cannot be called outside of a block\n--TEMPLATE--\n{% extends \"parent.twig\" %}\n{{ parent() }}\n--TEMPLATE(parent.twig)--\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Calling the \"parent\" function outside of a block is forbidden in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/functions/range.test",
    "content": "--TEST--\n\"range\" function\n--TEMPLATE--\n{{ range(low=0+1, high=10+0, step=2)|join(',') }}\n--DATA--\nreturn []\n--EXPECT--\n1,3,5,7,9\n"
  },
  {
    "path": "tests/Fixtures/functions/recursive_block_with_inheritance.test",
    "content": "--TEST--\n\"block\" function recursively called in a parent template\n--TEMPLATE--\n{% extends \"ordered_menu.twig\" %}\n{% block label %}\"{{ parent() }}\"{% endblock %}\n{% block list %}{% set class = 'b' %}{{ parent() }}{% endblock %}\n--TEMPLATE(ordered_menu.twig)--\n{% extends \"menu.twig\" %}\n{% block list %}{% set class = class|default('a') %}<ol class=\"{{ class }}\">{{ block('children') }}</ol>{% endblock %}\n--TEMPLATE(menu.twig)--\n{% extends \"base.twig\" %}\n{% block list %}<ul>{{ block('children') }}</ul>{% endblock %}\n{% block children %}{% set currentItem = item %}{% for item in currentItem %}{{ block('item') }}{% endfor %}{% set item = currentItem %}{% endblock %}\n{% block item %}<li>{% if item is not iterable %}{{ block('label') }}{% else %}{{ block('list') }}{% endif %}</li>{% endblock %}\n{% block label %}{{ item }}{% endblock %}\n--TEMPLATE(base.twig)--\n{{ block('list') }}\n--DATA--\nreturn ['item' => ['1', '2', ['3.1', ['3.2.1', '3.2.2'], '3.4']]]\n--EXPECT--\n<ol class=\"b\"><li>\"1\"</li><li>\"2\"</li><li><ol class=\"b\"><li>\"3.1\"</li><li><ol class=\"b\"><li>\"3.2.1\"</li><li>\"3.2.2\"</li></ol></li><li>\"3.4\"</li></ol></li></ol>\n"
  },
  {
    "path": "tests/Fixtures/functions/source.test",
    "content": "--TEST--\n\"source\" function\n--TEMPLATE--\nFOO\n{{ source(\"foo.twig\") }}\n\nBAR\n--TEMPLATE(foo.twig)--\n{{ foo }}<br />\n--DATA--\nreturn []\n--EXPECT--\nFOO\n\n{{ foo }}<br />\n\nBAR\n"
  },
  {
    "path": "tests/Fixtures/functions/special_chars.test",
    "content": "--TEST--\n\"§\" custom function\n--TEMPLATE--\n{{ §('foo') }}\n--DATA--\nreturn []\n--EXPECT--\n§foo§\n"
  },
  {
    "path": "tests/Fixtures/functions/static_calls.test",
    "content": "--TEST--\nFunctions as static method calls\n--TEMPLATE--\n{{ static_call_string('foo') }}\n{{ static_call_array('foo') }}\n--DATA--\nreturn ['foo' => 'foo']\n--EXPECT--\n*foo*\n*foo*\n"
  },
  {
    "path": "tests/Fixtures/functions/template_from_string.test",
    "content": "--TEST--\n\"template_from_string\" function\n--TEMPLATE--\n{% include template_from_string(template) %}\n\n{% include template_from_string(\"Hello {{ name }}\") %}\n{% include template_from_string('{% extends \"parent.twig\" %}{% block content %}Hello {{ name }}{% endblock %}') %}\n--TEMPLATE(parent.twig)--\n{% block content %}{% endblock %}\n--DATA--\nreturn ['name' => 'Fabien', 'template' => \"Hello {{ name }}\"]\n--EXPECT--\nHello Fabien\nHello Fabien\nHello Fabien\n"
  },
  {
    "path": "tests/Fixtures/functions/template_from_string_error.test",
    "content": "--TEST--\n\"template_from_string\" function\n--CONDITION--\nPHP_VERSION_ID >= 80100\n--TEMPLATE--\n{% include template_from_string(\"{{ not a Twig template \", \"foo.twig\") %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unclosed \"variable\" in \"foo.twig (string template 85e7b092afbbcd36f11981c2ef8f1569)\" at line 1.\n"
  },
  {
    "path": "tests/Fixtures/functions/template_from_string_error_php80.test",
    "content": "--TEST--\n\"template_from_string\" function\n--CONDITION--\nPHP_VERSION_ID < 80100\n--TEMPLATE--\n{% include template_from_string(\"{{ not a Twig template \", \"foo.twig\") %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unclosed \"variable\" in \"foo.twig (string template 4900163d56b1af4b704c6b0afee7f98ba53418ce7a93d37a3af1882735baf9cd)\" at line 1.\n"
  },
  {
    "path": "tests/Fixtures/functions/trailing_commas.test",
    "content": "--TEST--\nfunctions allow trailing commas in their argument list\n--TEMPLATE--\n{{ max(1, 2, 3,) }}\n--DATA--\nreturn []\n--EXPECT--\n3\n"
  },
  {
    "path": "tests/Fixtures/functions/undefined_block.test",
    "content": "--TEST--\n\"block\" function with undefined block\n--TEMPLATE--\n{% extends \"base.twig\" %}\n{% block foo %}\n    {{ parent() }}\n    {{ block('unknown') }}\n    {{ block('bar') }}\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% block foo %}Foo{% endblock %}\n{% block bar %}Bar{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Block \"unknown\" on template \"base.twig\" does not exist in \"index.twig\" at line 5.\n"
  },
  {
    "path": "tests/Fixtures/functions/undefined_block_deep.test",
    "content": "--TEST--\n\"block\" function with undefined block on deep inheritance\n--TEMPLATE--\n{% extends \"base.twig\" %}\n{% block foo %}\n    {{ parent() }}\n    {{ block('unknown') }}\n    {{ block('bar') }}\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% extends \"layout.twig\" %}\n{% block foo %}Foo{% endblock %}\n{% block bar %}Bar{% endblock %}\n--TEMPLATE(layout.twig)--\n{% block foo %}Foo{% endblock %}\n{% block bar %}Bar{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Block \"unknown\" on template \"layout.twig\" does not exist in \"index.twig\" at line 5.\n"
  },
  {
    "path": "tests/Fixtures/macros/arrow_as_arg.test",
    "content": "--TEST--\nmacro\n--TEMPLATE--\n{% set people = [\n    {first: \"Bob\", last: \"Smith\"},\n    {first: \"Alice\", last: \"Dupond\"},\n] %}\n\n{% set first_name_fn = (p) => p.first %}\n\n{{ _self.display_people(people, first_name_fn) }}\n\n{% macro display_people(people, fn) %}\n    {{ people|map(fn)|join(', ') }}\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nBob, Alice\n"
  },
  {
    "path": "tests/Fixtures/macros/default_values.test",
    "content": "--TEST--\nmacro\n--TEMPLATE--\n{% from _self import test %}\n\n{% macro test(a, b = 'bar') -%}\n{{ a }}{{ b }}\n{%- endmacro %}\n\n{{ test('foo') }}\n{{ test('bar', 'foo') }}\n--DATA--\nreturn []\n--EXPECT--\nfoobar\nbarfoo\n"
  },
  {
    "path": "tests/Fixtures/macros/macro_with_capture.test",
    "content": "--TEST--\nmacro\n--TEMPLATE--\n{{ _self.some_macro() }}\n\n{% macro some_macro() %}\n    {% apply upper %}\n        {% if true %}foo{% endif %}\n    {% endapply %}\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nFOO\n"
  },
  {
    "path": "tests/Fixtures/macros/nested_calls.test",
    "content": "--TEST--\nmacro\n--TEMPLATE--\n{% import _self as macros %}\n\n{% macro foo(data) %}\n    {{ data }}\n{% endmacro %}\n\n{% macro bar() %}\n    <br />\n{% endmacro %}\n\n{{ macros.foo(macros.bar()) }}\n--DATA--\nreturn []\n--EXPECT--\n<br />\n"
  },
  {
    "path": "tests/Fixtures/macros/reserved_variables.test",
    "content": "--TEST--\nmacro\n--TEMPLATE--\n{% from _self import test %}\n\n{% macro test(this) -%}\n    {{ this }}\n{%- endmacro %}\n\n{{ test(this) }}\n--DATA--\nreturn ['this' => 'foo']\n--EXPECT--\nfoo\n"
  },
  {
    "path": "tests/Fixtures/macros/simple.test",
    "content": "--TEST--\nmacro\n--TEMPLATE--\n{% import _self as test %}\n{% from _self import test %}\n\n{% macro test(a, b) -%}\n    {{ a|default('a') }}<br />\n    {{- b|default('b') }}<br />\n{%- endmacro %}\n\n{{ test.test() }}\n{{ test() }}\n{{ test.test(1, \"c\") }}\n{{ test(1, \"c\") }}\n--DATA--\nreturn []\n--EXPECT--\na<br />b<br />\na<br />b<br />\n1<br />c<br />\n1<br />c<br />\n"
  },
  {
    "path": "tests/Fixtures/macros/trailing_commas.test",
    "content": "--TEST--\nmacros allow trailing commas in their argument and parameter list\n--TEMPLATE--\n{% import _self as test %}\n\n{% macro test(a, b,) -%}\n    {{ a|default('a') }}<br />\n    {{- b|default('b') }}<br />\n{%- endmacro %}\n{% macro test2(a, b) -%}\n    {{ a|default('a') }}<br />\n    {{- b|default('b') }}<br />\n{%- endmacro %}\n\n{{ test.test(1, 2,) }}\n{{ test.test(3, 4) }}\n{{ test.test2(5, 6,) }}\n{{ test.test2(7, 8) }}\n--DATA--\nreturn []\n--EXPECT--\n1<br />2<br />\n3<br />4<br />\n5<br />6<br />\n7<br />8<br />\n"
  },
  {
    "path": "tests/Fixtures/macros/unknown_macro.test",
    "content": "--TEST--\nmacro\n--TEMPLATE--\n{% import _self as macros %}\n\n{{ macros.unknown() }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Macro \"unknown\" is not defined in template \"index.twig\" in \"index.twig\" at line 4.\n"
  },
  {
    "path": "tests/Fixtures/macros/unknown_macro_different_template.test",
    "content": "--TEST--\nException for unknown macro in different template\n--TEMPLATE--\n{% import foo_template as macros %}\n{{ macros.foo() }}\n--TEMPLATE(foo.twig)--\nfoo\n--DATA--\nreturn array('foo_template' => 'foo.twig')\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Macro \"foo\" is not defined in template \"foo.twig\" in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/macros/varargs.test",
    "content": "--TEST--\nmacro with arbitrary arguments\n--TEMPLATE--\n{% from _self import test1, test2 %}\n\n{% macro test1(var) %}\n    {{- var }}: {{ varargs|join(\", \") }}\n{% endmacro %}\n\n{% macro test2() %}\n    {{- varargs|join(\", \") }}\n{% endmacro %}\n\n{{ test1(\"foo\", \"bar\", \"foobar\") }}\n{{ test2(\"foo\", \"bar\", \"foobar\") }}\n--DATA--\nreturn []\n--EXPECT--\nfoo: bar, foobar\n\nfoo, bar, foobar\n"
  },
  {
    "path": "tests/Fixtures/macros/varargs_argument.test",
    "content": "--TEST--\nmacro with varargs argument\n--TEMPLATE--\n{% macro test(varargs) %}\n{% endmacro %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The argument \"varargs\" in macro \"test\" cannot be defined because the variable \"varargs\" is reserved for arbitrary arguments in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/macros/with_filters.test",
    "content": "--TEST--\nmacro with a filter\n--TEMPLATE--\n{% import _self as test %}\n\n{% macro test() %}\n    {% apply escape %}foo<br />{% endapply %}\n{% endmacro %}\n\n{{ test.test() }}\n--DATA--\nreturn []\n--EXPECT--\nfoo&lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/operators/concat_vs_add_sub.test",
    "content": "--TEST--\n+/- will have a higher precedence over ~ in Twig 4.0\n--TEMPLATE--\n{{ 1 + 41 }}\n{{ '42==' ~ '42' }}\n{{ '42==' ~ (1 + 41) }}\n{{ '42==' ~ (43 - 1) }}\n{{ ('42' ~ 43) - 1 }}\n--DATA--\nreturn []\n--EXPECT--\n42\n42==42\n42==42\n42==42\n4242\n"
  },
  {
    "path": "tests/Fixtures/operators/contat_vs_add_sub.legacy.test",
    "content": "--TEST--\n+/- will have a higher precedence over ~ in Twig 4.0\n--DEPRECATION--\nSince twig/twig 3.15: As the \"~\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 2.\nSince twig/twig 3.15: As the \"~\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 3.\n--TEMPLATE--\n{{ '42' ~ 1 + 41 }}\n{{ '42' ~ 43 - 1 }}\n--DATA--\nreturn []\n--EXPECT--\n462\n4242\n"
  },
  {
    "path": "tests/Fixtures/operators/minus_vs_pipe.legacy.test",
    "content": "--TEST--\n| will have a higher precedence over + and - in Twig 4.0\n--DEPRECATION--\nSince twig/twig 3.21: As the \"|\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 2.\n--TEMPLATE--\n{{ -1|abs }}\n--DATA--\nreturn []\n--EXPECT--\n-1\n"
  },
  {
    "path": "tests/Fixtures/operators/not_precedence.legacy.test",
    "content": "--TEST--\n*, /, //, and % will have a higher precedence over not in Twig 4.0\n--DEPRECATION--\nSince twig/twig 3.15: As the \"not\" prefix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 2.\n--TEMPLATE--\n{{ not 1 * 2 }}\n--DATA--\nreturn []\n--EXPECT--\n"
  },
  {
    "path": "tests/Fixtures/operators/not_precedence.test",
    "content": "--TEST--\n*, /, //, and % will have a higher precedence over not in Twig 4.0\n--TEMPLATE--\n{{ (not 1) * 2 }}\n{{ not (1 * 2) }}\n--DATA--\nreturn []\n--EXPECT--\n0\n"
  },
  {
    "path": "tests/Fixtures/regression/4029-iterator_to_array.test",
    "content": "--TEST--\n#4029 When use_yield is true, CaptureNode fall in iterator_to_array pitfall regarding index overwrite\n--TEMPLATE--\n{%- set tmp -%}\n  {%- block foo 'foo' -%}\n  {%- block bar 'bar' -%}\n{%- endset -%}\n{{ tmp }}\n--DATA--\nreturn []\n--CONFIG--\nreturn ['use_yield' => true]\n--EXPECT--\nfoobar"
  },
  {
    "path": "tests/Fixtures/regression/4033-missing-unwrap.test",
    "content": "--TEST--\nCall to undefined method Twig\\\\TemplateWrapper::yieldBlock()\n--TEMPLATE--\n{% extends 'parent' %}\n{%- block content -%}\n    {{ parent() }}\n    child\n{%- endblock -%}\n--TEMPLATE(parent)--\n{% extends ['unknowngrandparent', 'grandparent'] %}\n--TEMPLATE(grandparent)--\n{%- block content -%}\n    grandparent\n{%- endblock -%}\n--DATA--\nreturn []\n--EXPECT--\n    grandparent\n    child"
  },
  {
    "path": "tests/Fixtures/regression/4701-block-inheritance-issue.test",
    "content": "--TEST--\n#4701 Accessing arrays with stringable objects as key\n--TEMPLATE--\n{% set hash = {\n  'foo': 'FOO',\n  'bar': 'BAR',\n} %}\n\n{{ hash[key] }}\n--DATA--\nclass MyObj {\n    public function __toString() {\n        return 'foo';\n    }\n}\n\nreturn [\n    'key' => new MyObj(),\n];\n--EXPECT--\nFOO\n"
  },
  {
    "path": "tests/Fixtures/regression/block_names_unicity.test",
    "content": "--TEST--\nBlock names are unique per template\n--TEMPLATE--\n{% extends 'layout' %}\n{% block content -%}\n    {% apply title -%}\n        second\n    {% endapply %}\n{% endblock %}\n--TEMPLATE(layout)--\n{% apply title -%}\n    first\n{% endapply %}\n{% block content %}{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nFirst\nSecond\n"
  },
  {
    "path": "tests/Fixtures/regression/combined_debug_info.test",
    "content": "--TEST--\nException with bad line number\n--TEMPLATE--\n{% block content %}\n    {{ foo }}\n    {{ include(\"foo\") }}\n{% endblock %}\nindex\n--TEMPLATE(foo)--\nfoo\n{{ foo.bar }}\n--DATA--\nreturn ['foo' => 'foo']\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Impossible to access an attribute (\"bar\") on a string variable (\"foo\") in \"foo\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/regression/empty_token.test",
    "content": "--TEST--\nTwig outputs 0 nodes correctly\n--TEMPLATE--\n{{ foo }}0{{ foo }}\n--DATA--\nreturn ['foo' => 'foo']\n--EXPECT--\nfoo0foo\n"
  },
  {
    "path": "tests/Fixtures/regression/markup_test.test",
    "content": "--TEST--\nTwig outputs 0 nodes correctly\n--TEMPLATE--\n{{ empty|trim ? 'KO' : 'ok' }}\n{{ spaces|trim ? 'KO' : 'ok' }}\n{% if empty %}KO{% else %}ok{% endif %}\n\n{% if spaces|trim %}KO{% else %}ok{% endif %}\n\n{% set bar %}    {% endset %}{{ bar|trim ? 'KO' : 'ok' }}\n--DATA--\nreturn ['spaces' => new Twig\\Markup('    ', 'UTF-8'), 'empty' => new Twig\\Markup('', 'UTF-8')]\n--EXPECT--\nok\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/regression/multi_word_tests.test",
    "content": "--TEST--\nTwig allows multi-word tests without a custom node class\n--TEMPLATE--\n{{ 'foo' is multi word ? 'yes' : 'no' }}\n{{ 'foo bar' is multi word ? 'yes' : 'no' }}\n--DATA--\nreturn []\n--EXPECT--\nno\nyes\n"
  },
  {
    "path": "tests/Fixtures/regression/simple_xml_element.test",
    "content": "--TEST--\nTwig is able to deal with SimpleXMLElement instances as variables\n--CONDITION--\nversion_compare(phpversion(), '8.0', '<')\n--TEMPLATE--\nHello '{{ images.image.0.group }}'!\n{{ images.image.0.group.attributes.myattr }}\n{{ images.children().image.count() }}\n{% for image in images %}\n    - {{ image.group }}\n{% endfor %}\n--DATA--\nreturn ['images' => new \\SimpleXMLElement('<images><image><group myattr=\"example\">foo</group></image><image><group>bar</group></image></images>')]\n--EXPECT--\nHello 'foo'!\nexample\n2\n    - foo\n    - bar\n"
  },
  {
    "path": "tests/Fixtures/regression/strings_like_numbers.test",
    "content": "--TEST--\nTwig does not confuse strings with integers in getAttribute()\n--TEMPLATE--\n{{ mapping['2e2'] }}\n--DATA--\nreturn ['mapping' => ['2e2' => 'works']]\n--EXPECT--\nworks\n"
  },
  {
    "path": "tests/Fixtures/tags/apply/basic.test",
    "content": "--TEST--\n\"apply\" tag applies a filter on its children\n--TEMPLATE--\n{% apply upper %}\nSome text with a {{ var }}\n{% endapply %}\n--DATA--\nreturn ['var' => 'var']\n--EXPECT--\nSOME TEXT WITH A VAR\n"
  },
  {
    "path": "tests/Fixtures/tags/apply/json_encode.test",
    "content": "--TEST--\n\"apply\" tag applies a filter on its children\n--TEMPLATE--\n{% apply json_encode|raw %}test{% endapply %}\n--DATA--\nreturn []\n--EXPECT--\n\"test\"\n"
  },
  {
    "path": "tests/Fixtures/tags/apply/multiple.test",
    "content": "--TEST--\n\"apply\" tags accept multiple chained filters\n--TEMPLATE--\n{% apply lower|title %}\n  {{ var }}\n{% endapply %}\n--DATA--\nreturn ['var' => 'VAR']\n--EXPECT--\n    Var\n"
  },
  {
    "path": "tests/Fixtures/tags/apply/nested.test",
    "content": "--TEST--\n\"apply\" tags can be nested at will\n--TEMPLATE--\n{% apply lower|title %}\n  {{ var }}\n  {% apply upper %}\n    {{ var }}\n  {% endapply %}\n  {{ var }}\n{% endapply %}\n--DATA--\nreturn ['var' => 'var']\n--EXPECT--\n  Var\n      Var\n    Var\n"
  },
  {
    "path": "tests/Fixtures/tags/apply/scope.test",
    "content": "--TEST--\n\"apply\" tag does not create a new scope\n--TEMPLATE--\n{% set foo = 'baz' %}\n{% apply upper %}\n    {% set foo = 'foo' %}\n    {% set bar = 'bar' %}\n{% endapply %}\n{{ 'foo' == foo ? 'OK ' ~ foo : 'KO' }}\n{{ 'bar' == bar ? 'OK ' ~ bar : 'KO' }}\n--DATA--\nreturn []\n--EXPECT--\nOK foo\nOK bar\n"
  },
  {
    "path": "tests/Fixtures/tags/apply/with_for_tag.test",
    "content": "--TEST--\n\"apply\" tag applies the filter on \"for\" tags\n--TEMPLATE--\n{% apply upper %}\n{% for item in items %}\n{{ item }}\n{% endfor %}\n{% endapply %}\n--DATA--\nreturn ['items' => ['a', 'b']]\n--EXPECT--\nA\nB\n"
  },
  {
    "path": "tests/Fixtures/tags/apply/with_if_tag.test",
    "content": "--TEST--\n\"apply\" tag applies the filter on \"if\" tags\n--TEMPLATE--\n{% apply upper %}\n{% if items %}\n{{ items|join(', ') }}\n{% endif %}\n\n{% if items.3 is defined %}\nFOO\n{% else %}\n{{ items.1 }}\n{% endif %}\n\n{% if items.3 is defined %}\nFOO\n{% elseif items.1 %}\n{{ items.0 }}\n{% endif %}\n\n{% endapply %}\n--DATA--\nreturn ['items' => ['a', 'b']]\n--EXPECT--\nA, B\n\nB\n\nA\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/basic.test",
    "content": "--TEST--\n\"autoescape\" tag applies escaping on its children\n--TEMPLATE--\n{% autoescape %}\n{{ var }}<br />\n{% endautoescape %}\n{% autoescape 'html' %}\n{{ var }}<br />\n{% endautoescape %}\n{% autoescape false %}\n{{ var }}<br />\n{% endautoescape %}\n{% autoescape false %}\n{{ var }}<br />\n{% endautoescape %}\n--DATA--\nreturn ['var' => '<br />']\n--EXPECT--\n&lt;br /&gt;<br />\n&lt;br /&gt;<br />\n<br /><br />\n<br /><br />\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/blocks.test",
    "content": "--TEST--\n\"autoescape\" tag applies escaping on embedded blocks\n--TEMPLATE--\n{% autoescape 'html' %}\n  {% block foo %}\n    {{ var }}\n  {% endblock %}\n{% endautoescape %}\n--DATA--\nreturn ['var' => '<br />']\n--EXPECT--\n&lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/double_escaping.test",
    "content": "--TEST--\n\"autoescape\" tag does not double-escape\n--TEMPLATE--\n{% autoescape 'html' %}\n{{ var|escape }}\n{% endautoescape %}\n--DATA--\nreturn ['var' => '<br />']\n--EXPECT--\n&lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/functions.test",
    "content": "--TEST--\n\"autoescape\" tag applies escaping after calling functions\n--TEMPLATE--\n\nautoescape false\n{% autoescape false %}\n\nsafe_br\n{{ safe_br() }}\n\nunsafe_br\n{{ unsafe_br() }}\n\n{% endautoescape %}\n\nautoescape 'html'\n{% autoescape 'html' %}\n\nsafe_br\n{{ safe_br() }}\n\nunsafe_br\n{{ unsafe_br() }}\n\nunsafe_br()|raw\n{{ (unsafe_br())|raw }}\n\nsafe_br()|escape\n{{ (safe_br())|escape }}\n\nsafe_br()|raw\n{{ (safe_br())|raw }}\n\nunsafe_br()|escape\n{{ (unsafe_br())|escape }}\n\n{% endautoescape %}\n\nautoescape js\n{% autoescape 'js' %}\n\nsafe_br\n{{ safe_br() }}\n\n{% endautoescape %}\n--DATA--\nreturn []\n--EXPECT--\n\nautoescape false\n\nsafe_br\n<br />\n\nunsafe_br\n<br />\n\n\nautoescape 'html'\n\nsafe_br\n<br />\n\nunsafe_br\n&lt;br /&gt;\n\nunsafe_br()|raw\n<br />\n\nsafe_br()|escape\n&lt;br /&gt;\n\nsafe_br()|raw\n<br />\n\nunsafe_br()|escape\n&lt;br /&gt;\n\n\nautoescape js\n\nsafe_br\n\\u003Cbr\\u0020\\/\\u003E\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/literal.test",
    "content": "--TEST--\n\"autoescape\" tag does not apply escaping on literals\n--TEMPLATE--\n{% autoescape 'html' %}\n\n1. Simple literal\n{{ \"<br />\" }}\n\n2. Conditional expression with only literals\n{{ true ? \"<br />\" : \"<br>\" }}\n\n3. Conditional expression with a variable\n{{ true ? \"<br />\" : someVar }}\n{{ false ? \"<br />\" : someVar }}\n{{ true ? someVar : \"<br />\" }}\n{{ false ? someVar : \"<br />\" }}\n\n4. Nested conditionals with only literals\n{{ true ? (true ? \"<br />\" : \"<br>\") : \"\\n\" }}\n\n5. Nested conditionals with a variable\n{{ true ? (true ? \"<br />\" : someVar) : \"\\n\" }}\n{{ true ? (false ? \"<br />\" : someVar) : \"\\n\" }}\n{{ true ? (true ? someVar : \"<br />\") : \"\\n\" }}\n{{ true ? (false ? someVar : \"<br />\") : \"\\n\" }}\n{{ false ? \"\\n\" : (true ? someVar : \"<br />\") }}\n{{ false ? \"\\n\" : (false ? someVar : \"<br />\") }}\n\n6. Nested conditionals with a variable marked safe\n{{ true ? (true ? \"<br />\" : someVar|raw) : \"\\n\" }}\n{{ true ? (false ? \"<br />\" : someVar|raw) : \"\\n\" }}\n{{ true ? (true ? someVar|raw : \"<br />\") : \"\\n\" }}\n{{ true ? (false ? someVar|raw : \"<br />\") : \"\\n\" }}\n{{ false ? \"\\n\" : (true ? someVar|raw : \"<br />\") }}\n{{ false ? \"\\n\" : (false ? someVar|raw : \"<br />\") }}\n\n7. Without then clause\n{{ \"<br />\" ?: someVar }}\n{{ someFalseVar ?: \"<br />\" }}\n\n8. NullCoalesce\n{{ aaaa ?? \"<br />\" }}\n{{ \"<br />\" ?? someVar }}\n\n{% endautoescape %}\n--DATA--\nreturn ['someVar' => '<br />', 'someFalseVar' => false]\n--EXPECT--\n\n1. Simple literal\n<br />\n\n2. Conditional expression with only literals\n<br />\n\n3. Conditional expression with a variable\n<br />\n&lt;br /&gt;\n&lt;br /&gt;\n<br />\n\n4. Nested conditionals with only literals\n<br />\n\n5. Nested conditionals with a variable\n<br />\n&lt;br /&gt;\n&lt;br /&gt;\n<br />\n&lt;br /&gt;\n<br />\n\n6. Nested conditionals with a variable marked safe\n<br />\n<br />\n<br />\n<br />\n<br />\n<br />\n\n7. Without then clause\n<br />\n<br />\n\n8. NullCoalesce\n<br />\n<br />\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/nested.test",
    "content": "--TEST--\n\"autoescape\" tags can be nested at will\n--TEMPLATE--\n{{ var }}\n{% autoescape 'html' %}\n  {{ var }}\n  {% autoescape false %}\n    {{ var }}\n    {% autoescape 'html' %}\n      {{ var }}\n    {% endautoescape %}\n    {{ var }}\n  {% endautoescape %}\n  {{ var }}\n{% endautoescape %}\n{{ var }}\n--DATA--\nreturn ['var' => '<br />']\n--EXPECT--\n&lt;br /&gt;\n  &lt;br /&gt;\n      <br />\n          &lt;br /&gt;\n        <br />\n    &lt;br /&gt;\n&lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/objects.test",
    "content": "--TEST--\n\"autoescape\" tag applies escaping to object method calls\n--TEMPLATE--\n{% autoescape 'html' %}\n{{ user.name }}\n{{ user.name|lower }}\n{{ user }}\n{% endautoescape %}\n--DATA--\nclass UserForAutoEscapeTest\n{\n  public function getName()\n  {\n    return 'Fabien<br />';\n  }\n\n  public function __toString()\n  {\n     return 'Fabien<br />';\n  }\n}\nreturn ['user' => new UserForAutoEscapeTest()]\n--EXPECT--\nFabien&lt;br /&gt;\nfabien&lt;br /&gt;\nFabien&lt;br /&gt;\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/raw.test",
    "content": "--TEST--\n\"autoescape\" tag does not escape when raw is used as a filter\n--TEMPLATE--\n{% autoescape 'html' %}\n{{ var|raw }}\n{% endautoescape %}\n--DATA--\nreturn ['var' => '<br />']\n--EXPECT--\n<br />\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/strategy.test",
    "content": "--TEST--\n\"autoescape\" tag accepts an escaping strategy\n--TEMPLATE--\n{% autoescape 'js' %}{{ var }}{% endautoescape %}\n\n{% autoescape 'html' %}{{ var }}{% endautoescape %}\n--DATA--\nreturn ['var' => '<br />\"']\n--EXPECT--\n\\u003Cbr\\u0020\\/\\u003E\\u0022\n&lt;br /&gt;&quot;\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/type.test",
    "content": "--TEST--\nescape types\n--TEMPLATE--\n\n1. autoescape 'html' |escape('js')\n\n{% autoescape 'html' %}\n<a onclick=\"alert(&quot;{{ msg|escape('js') }}&quot;)\"></a>\n{% endautoescape %}\n\n2. autoescape 'html' |escape('js')\n\n{% autoescape 'html' %}\n<a onclick=\"alert(&quot;{{ msg|escape('js') }}&quot;)\"></a>\n{% endautoescape %}\n\n3. autoescape 'js' |escape('js')\n\n{% autoescape 'js' %}\n<a onclick=\"alert(&quot;{{ msg|escape('js') }}&quot;)\"></a>\n{% endautoescape %}\n\n4. no escape\n\n{% autoescape false %}\n<a onclick=\"alert(&quot;{{ msg }}&quot;)\"></a>\n{% endautoescape %}\n\n5. |escape('js')|escape('html')\n\n{% autoescape false %}\n<a onclick=\"alert(&quot;{{ msg|escape('js')|escape('html') }}&quot;)\"></a>\n{% endautoescape %}\n\n6. autoescape 'html' |escape('js')|escape('html')\n\n{% autoescape 'html' %}\n<a onclick=\"alert(&quot;{{ msg|escape('js')|escape('html') }}&quot;)\"></a>\n{% endautoescape %}\n\n--DATA--\nreturn ['msg' => \"<>\\n'\\\"\"]\n--EXPECT--\n\n1. autoescape 'html' |escape('js')\n\n<a onclick=\"alert(&quot;\\u003C\\u003E\\n\\u0027\\u0022&quot;)\"></a>\n\n2. autoescape 'html' |escape('js')\n\n<a onclick=\"alert(&quot;\\u003C\\u003E\\n\\u0027\\u0022&quot;)\"></a>\n\n3. autoescape 'js' |escape('js')\n\n<a onclick=\"alert(&quot;\\u003C\\u003E\\n\\u0027\\u0022&quot;)\"></a>\n\n4. no escape\n\n<a onclick=\"alert(&quot;<>\n'\"&quot;)\"></a>\n\n5. |escape('js')|escape('html')\n\n<a onclick=\"alert(&quot;\\u003C\\u003E\\n\\u0027\\u0022&quot;)\"></a>\n\n6. autoescape 'html' |escape('js')|escape('html')\n\n<a onclick=\"alert(&quot;\\u003C\\u003E\\n\\u0027\\u0022&quot;)\"></a>\n\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/with_filters.test",
    "content": "--TEST--\n\"autoescape\" tag applies escaping after calling filters\n--TEMPLATE--\n{% autoescape 'html' %}\n\n(escape_and_nl2br is an escaper filter)\n\n1. Don't escape escaper filter output\n( var is escaped by |escape_and_nl2br, line-breaks are added, \n  the output is not escaped )\n{{ var|escape_and_nl2br }}\n\n2. Don't escape escaper filter output\n( var is escaped by |escape_and_nl2br, line-breaks are added, \n  the output is not escaped, |raw is redundant )\n{{ var|escape_and_nl2br|raw }}\n\n3. Explicit escape\n( var is escaped by |escape_and_nl2br, line-breaks are added,\n  the output is explicitly escaped by |escape )\n{{ var|escape_and_nl2br|escape }}\n\n4. Escape non-escaper filter output\n( var is upper-cased by |upper,\n  the output is auto-escaped )\n{{ var|upper }}\n\n5. Escape if last filter is not an escaper\n( var is escaped by |escape_and_nl2br, line-breaks are added,\n  the output is upper-cased by |upper,\n  the output is auto-escaped as |upper is not an escaper )\n{{ var|escape_and_nl2br|upper }}\n\n6. Don't escape escaper filter output\n( var is upper cased by upper,\n  the output is escaped by |escape_and_nl2br, line-breaks are added,\n  the output is not escaped as |escape_and_nl2br is an escaper )\n{{ var|upper|escape_and_nl2br }}\n\n7. Escape if last filter is not an escaper\n( the output of |format is \"<b>\" ~ var ~ \"</b>\",\n  the output is auto-escaped )\n{{ \"<b>%s</b>\"|format(var) }}\n\n8. Escape if last filter is not an escaper\n( the output of |format is \"<b>\" ~ var ~ \"</b>\",\n  |raw is redundant,\n  the output is auto-escaped )\n{{ \"<b>%s</b>\"|raw|format(var) }}\n\n9. Don't escape escaper filter output\n( the output of |format is \"<b>\" ~ var ~ \"</b>\",\n  the output is not escaped due to |raw filter at the end )\n{{ \"<b>%s</b>\"|format(var)|raw }}\n\n10. Don't escape escaper filter output\n( the output of |format is \"<b>\" ~ var ~ \"</b>\",\n  the output is not escaped due to |raw filter at the end,\n  the |raw filter on var is redundant )\n{{ \"<b>%s</b>\"|format(var|raw)|raw }}\n\n{% endautoescape %}\n--DATA--\nreturn ['var' => \"<Fabien>\\nTwig\"]\n--EXPECT--\n\n(escape_and_nl2br is an escaper filter)\n\n1. Don't escape escaper filter output\n( var is escaped by |escape_and_nl2br, line-breaks are added, \n  the output is not escaped )\n&lt;Fabien&gt;<br />\nTwig\n\n2. Don't escape escaper filter output\n( var is escaped by |escape_and_nl2br, line-breaks are added, \n  the output is not escaped, |raw is redundant )\n&lt;Fabien&gt;<br />\nTwig\n\n3. Explicit escape\n( var is escaped by |escape_and_nl2br, line-breaks are added,\n  the output is explicitly escaped by |escape )\n&amp;lt;Fabien&amp;gt;&lt;br /&gt;\nTwig\n\n4. Escape non-escaper filter output\n( var is upper-cased by |upper,\n  the output is auto-escaped )\n&lt;FABIEN&gt;\nTWIG\n\n5. Escape if last filter is not an escaper\n( var is escaped by |escape_and_nl2br, line-breaks are added,\n  the output is upper-cased by |upper,\n  the output is auto-escaped as |upper is not an escaper )\n&amp;LT;FABIEN&amp;GT;&lt;BR /&gt;\nTWIG\n\n6. Don't escape escaper filter output\n( var is upper cased by upper,\n  the output is escaped by |escape_and_nl2br, line-breaks are added,\n  the output is not escaped as |escape_and_nl2br is an escaper )\n&lt;FABIEN&gt;<br />\nTWIG\n\n7. Escape if last filter is not an escaper\n( the output of |format is \"<b>\" ~ var ~ \"</b>\",\n  the output is auto-escaped )\n&lt;b&gt;&lt;Fabien&gt;\nTwig&lt;/b&gt;\n\n8. Escape if last filter is not an escaper\n( the output of |format is \"<b>\" ~ var ~ \"</b>\",\n  |raw is redundant,\n  the output is auto-escaped )\n&lt;b&gt;&lt;Fabien&gt;\nTwig&lt;/b&gt;\n\n9. Don't escape escaper filter output\n( the output of |format is \"<b>\" ~ var ~ \"</b>\",\n  the output is not escaped due to |raw filter at the end )\n<b><Fabien>\nTwig</b>\n\n10. Don't escape escaper filter output\n( the output of |format is \"<b>\" ~ var ~ \"</b>\",\n  the output is not escaped due to |raw filter at the end,\n  the |raw filter on var is redundant )\n<b><Fabien>\nTwig</b>\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/with_filters_arguments.test",
    "content": "--TEST--\n\"autoescape\" tag do not applies escaping on filter arguments\n--TEMPLATE--\n{% autoescape 'html' %}\n{{ var|nl2br(\"<br />\") }}\n{{ var|nl2br(\"<br />\"|escape) }}\n{{ var|nl2br(sep) }}\n{{ var|nl2br(sep|raw) }}\n{{ var|nl2br(sep|escape) }}\n{% endautoescape %}\n--DATA--\nreturn ['var' => \"<Fabien>\\nTwig\", 'sep' => '<br />']\n--EXPECT--\n&lt;Fabien&gt;<br />\nTwig\n&lt;Fabien&gt;&lt;br /&gt;\nTwig\n&lt;Fabien&gt;<br />\nTwig\n&lt;Fabien&gt;<br />\nTwig\n&lt;Fabien&gt;&lt;br /&gt;\nTwig\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/with_pre_escape_filters.test",
    "content": "--TEST--\n\"autoescape\" tag applies escaping after calling filters, and before calling pre_escape filters\n--TEMPLATE--\n{% autoescape 'html' %}\n\n(nl2br is pre_escaped for \"html\" and declared safe for \"html\")\n\n1. Pre-escape and don't post-escape\n( var|escape|nl2br )\n{{ var|nl2br }}\n\n2. Don't double-pre-escape\n( var|escape|nl2br )\n{{ var|escape|nl2br }}\n\n3. Don't escape safe values\n( var|raw|nl2br )\n{{ var|raw|nl2br }}\n\n4. Don't escape safe values\n( var|escape|nl2br|nl2br )\n{{ var|nl2br|nl2br }}\n\n5. Re-escape values that are escaped for an other contexts\n( var|escape_something|escape|nl2br )\n{{ var|escape_something|nl2br }}\n\n6. Still escape when using filters not declared safe\n( var|escape|nl2br|upper|escape )\n{{ var|nl2br|upper }}\n\n{% endautoescape %}\n--DATA--\nreturn ['var' => \"<Fabien>\\nTwig\"]\n--EXPECT--\n\n(nl2br is pre_escaped for \"html\" and declared safe for \"html\")\n\n1. Pre-escape and don't post-escape\n( var|escape|nl2br )\n&lt;Fabien&gt;<br />\nTwig\n\n2. Don't double-pre-escape\n( var|escape|nl2br )\n&lt;Fabien&gt;<br />\nTwig\n\n3. Don't escape safe values\n( var|raw|nl2br )\n<Fabien><br />\nTwig\n\n4. Don't escape safe values\n( var|escape|nl2br|nl2br )\n&lt;Fabien&gt;<br /><br />\nTwig\n\n5. Re-escape values that are escaped for an other contexts\n( var|escape_something|escape|nl2br )\n&lt;FABIEN&gt;<br />\nTWIG\n\n6. Still escape when using filters not declared safe\n( var|escape|nl2br|upper|escape )\n&amp;LT;FABIEN&amp;GT;&lt;BR /&gt;\nTWIG\n\n"
  },
  {
    "path": "tests/Fixtures/tags/autoescape/with_preserves_safety_filters.test",
    "content": "--TEST--\n\"autoescape\" tag handles filters preserving the safety\n--TEMPLATE--\n{% autoescape 'html' %}\n\n(preserves_safety is preserving safety for \"html\")\n\n1. Unsafe values are still unsafe\n( var|preserves_safety|escape )\n{{ var|preserves_safety }}\n\n2. Safe values are still safe\n( var|escape|preserves_safety )\n{{ var|escape|preserves_safety }}\n\n3. Re-escape values that are escaped for an other contexts\n( var|escape_something|preserves_safety|escape )\n{{ var|escape_something|preserves_safety }}\n\n4. Still escape when using filters not declared safe\n( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape )\n{{ var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'}) }}\n\n{% endautoescape %}\n--DATA--\nreturn ['var' => \"<Fabien>\\nTwig\"]\n--EXPECT--\n\n(preserves_safety is preserving safety for \"html\")\n\n1. Unsafe values are still unsafe\n( var|preserves_safety|escape )\n&lt;FABIEN&gt;\nTWIG\n\n2. Safe values are still safe\n( var|escape|preserves_safety )\n&LT;FABIEN&GT;\nTWIG\n\n3. Re-escape values that are escaped for an other contexts\n( var|escape_something|preserves_safety|escape )\n&lt;FABIEN&gt;\nTWIG\n\n4. Still escape when using filters not declared safe\n( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape )\n&amp;LT;FABPOT&amp;GT;\nTWIG\n\n"
  },
  {
    "path": "tests/Fixtures/tags/block/basic.test",
    "content": "--TEST--\n\"block\" tag\n--TEMPLATE--\n{% block title1 %}FOO{% endblock %}\n{% block title2 foo|lower %}\n--TEMPLATE(foo.twig)--\n{% block content %}{% endblock %}\n--DATA--\nreturn ['foo' => 'bar']\n--EXPECT--\nFOObar\n"
  },
  {
    "path": "tests/Fixtures/tags/block/block_unique_name.test",
    "content": "--TEST--\n\"block\" tag\n--TEMPLATE--\n{% block content %}\n    {% block content %}\n    {% endblock %}\n{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The block 'content' has already been defined line 2 in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/tags/block/conditional_block.test",
    "content": "--TEST--\nconditional \"block\" tag\n--TEMPLATE--\n{% if false %}{% block foo %}FOO{% endblock %}{% endif %}\n{% if true %}{% block bar %}BAR{% endblock %}{% endif %}\n--DATA--\nreturn []\n--EXPECT--\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/block/special_chars.test",
    "content": "--TEST--\n\"§\" special chars in a block name\n--TEMPLATE--\n{% block § %}\n§\n{% endblock § %}\n--DATA--\nreturn []\n--EXPECT--\n§\n"
  },
  {
    "path": "tests/Fixtures/tags/deprecated/block.legacy.test",
    "content": "--TEST--\nDeprecating a block with \"deprecated\" tag\n--TEMPLATE--\n{% use 'greeting.twig' %}\n\n{{ block('welcome') }}\n\n--TEMPLATE(greeting.twig)--\n{% block welcome %}\n  {% deprecated 'The \"welcome\" block is deprecated, use \"hello\" instead.' %}\n  {{ block('hello') }}\n{% endblock %}\n\n{% block hello %}\nHello Fabien\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\n  Hello Fabien\n"
  },
  {
    "path": "tests/Fixtures/tags/deprecated/macro.legacy.test",
    "content": "--TEST--\nDeprecating a macro with \"deprecated\" tag\n--TEMPLATE--\n{% import 'greeting.twig' as greeting %}\n\n{{ greeting.welcome('Fabien') }}\n\n--TEMPLATE(greeting.twig)--\n{% macro welcome(name) %}\n  {% deprecated 'The \"welcome\" macro is deprecated, use \"hello\" instead.' %}\n  {% import _self as self %}\n  {{ self.hello(name) }}\n{% endmacro %}\n\n{% macro hello(name) %}\nHello {{ name }}\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n  Hello Fabien\n"
  },
  {
    "path": "tests/Fixtures/tags/deprecated/template.legacy.test",
    "content": "--TEST--\nDeprecating a template with \"deprecated\" tag\n--TEMPLATE--\n{% extends 'greeting.twig' %}\n\n{% deprecated 'The \"index.twig\" template is deprecated, use \"greeting.twig\" instead.' %}\n--TEMPLATE(greeting.twig)--\nHello Fabien\n--DATA--\nreturn []\n--EXPECT--\nHello Fabien\n"
  },
  {
    "path": "tests/Fixtures/tags/deprecated/with_package.legacy.test",
    "content": "--TEST--\nDeprecating a template with \"deprecated\" tag\n--TEMPLATE--\n{% deprecated 'The \"index.twig\" template is deprecated, use \"greeting.twig\" instead.' package=\"foo/bar\" %}\n\nHello Fabien\n--DATA--\nreturn []\n--EXPECT--\nHello Fabien\n"
  },
  {
    "path": "tests/Fixtures/tags/deprecated/with_package_version.legacy.test",
    "content": "--TEST--\nDeprecating a template with \"deprecated\" tag\n--TEMPLATE--\n{% deprecated 'The \"index.twig\" template is deprecated, use \"greeting.twig\" instead.' package=\"foo/bar\" version=1.1 %}\n\nHello Fabien\n--DATA--\nreturn []\n--EXPECT--\nHello Fabien\n"
  },
  {
    "path": "tests/Fixtures/tags/embed/basic.test",
    "content": "--TEST--\n\"embed\" tag\n--TEMPLATE--\nFOO\n{% embed \"foo.twig\" %}\n    {% block c1 %}\n        {{ parent() }}\n        block1extended\n    {% endblock %}\n{% endembed %}\n\nBAR\n--TEMPLATE(foo.twig)--\nA\n{% block c1 %}\n    block1\n{% endblock %}\nB\n{% block c2 %}\n    block2\n{% endblock %}\nC\n--DATA--\nreturn []\n--EXPECT--\nFOO\n\nA\n            block1\n\n        block1extended\n    B\n    block2\nC\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/embed/complex_dynamic_parent.test",
    "content": "--TEST--\n\"embed\" tag\n--TEMPLATE--\nFOO\n{% embed foo ~ \".twig\" %}\n    {% block c1 %}\n        {{ parent() }}\n        block1extended\n    {% endblock %}\n{% endembed %}\n\nBAR\n--TEMPLATE(foo.twig)--\nA\n{% block c1 %}\n    block1\n{% endblock %}\nB\n{% block c2 %}\n    block2\n{% endblock %}\nC\n--DATA--\nreturn ['foo' => 'foo']\n--EXPECT--\nFOO\n\nA\n            block1\n\n        block1extended\n    B\n    block2\nC\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/embed/dynamic_parent.test",
    "content": "--TEST--\n\"embed\" tag\n--TEMPLATE--\nFOO\n{% embed foo %}\n    {% block c1 %}\n        {{ parent() }}\n        block1extended\n    {% endblock %}\n{% endembed %}\n\nBAR\n--TEMPLATE(foo.twig)--\nA\n{% block c1 %}\n    block1\n{% endblock %}\nB\n{% block c2 %}\n    block2\n{% endblock %}\nC\n--DATA--\nreturn ['foo' => 'foo.twig']\n--EXPECT--\nFOO\n\nA\n            block1\n\n        block1extended\n    B\n    block2\nC\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/embed/embed_ignore_missing.test",
    "content": "--TEST--\n\"embed\" tag\n--TEMPLATE--\n{% set x = 'bad' %}\n{% embed x ~ 'ger.twig' ignore missing %}{% endembed %}\nHERE\n--DATA--\nreturn []\n--EXPECT--\nHERE\n"
  },
  {
    "path": "tests/Fixtures/tags/embed/error_line.test",
    "content": "--TEST--\n\"embed\" tag\n--TEMPLATE(index.twig)--\nFOO\n{% embed \"foo.twig\" %}\n    {% block c1 %}\n        {{ nothing }}\n    {% endblock %}\n{% endembed %}\nBAR\n--TEMPLATE(foo.twig)--\n{% block c1 %}{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"nothing\" does not exist in \"index.twig\" at line 5.\n"
  },
  {
    "path": "tests/Fixtures/tags/embed/multiple.test",
    "content": "--TEST--\n\"embed\" tag\n--TEMPLATE--\nFOO\n{% embed \"foo.twig\" %}\n    {% block c1 %}\n        {{ parent() }}\n        block1extended\n    {% endblock %}\n{% endembed %}\n\n{% embed \"foo.twig\" %}\n    {% block c1 %}\n        {{ parent() }}\n        block1extended\n    {% endblock %}\n{% endembed %}\n\nBAR\n--TEMPLATE(foo.twig)--\nA\n{% block c1 %}\n    block1\n{% endblock %}\nB\n{% block c2 %}\n    block2\n{% endblock %}\nC\n--DATA--\nreturn []\n--EXPECT--\nFOO\n\nA\n            block1\n\n        block1extended\n    B\n    block2\nC\n\nA\n            block1\n\n        block1extended\n    B\n    block2\nC\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/embed/nested.test",
    "content": "--TEST--\n\"embed\" tag\n--TEMPLATE--\n{% embed \"foo.twig\" %}\n    {% block c1 %}\n        {{ parent() }}\n        {% embed \"foo.twig\" %}\n            {% block c1 %}\n                {{ parent() }}\n                block1extended\n            {% endblock %}\n        {% endembed %}\n\n    {% endblock %}\n{% endembed %}\n--TEMPLATE(foo.twig)--\nA\n{% block c1 %}\n    block1\n{% endblock %}\nB\n{% block c2 %}\n    block2\n{% endblock %}\nC\n--DATA--\nreturn []\n--EXPECT--\nA\n            block1\n\n        \nA\n                    block1\n\n                block1extended\n            B\n    block2\nC\n    B\n    block2\nC\n"
  },
  {
    "path": "tests/Fixtures/tags/embed/with_extends.test",
    "content": "--TEST--\n\"embed\" tag\n--TEMPLATE--\n{% extends \"base.twig\" %}\n\n{% block c1 %}\n    {{ parent() }}\n    blockc1baseextended\n{% endblock %}\n\n{% block c2 %}\n    {{ parent() }}\n\n    {% embed \"foo.twig\" %}\n        {% block c1 %}\n            {{ parent() }}\n            block1extended\n        {% endblock %}\n    {% endembed %}\n    {{ parent() }}\n{% endblock %}\n--TEMPLATE(base.twig)--\nA\n{% block c1 %}\n    blockc1base\n{% endblock %}\n{% block c2 %}\n    blockc2base\n{% endblock %}\nB\n--TEMPLATE(foo.twig)--\nA\n{% block c1 %}\n    block1\n{% endblock %}\nB\n{% block c2 %}\n    block2\n{% endblock %}\nC\n--DATA--\nreturn []\n--EXPECT--\nA\n        blockc1base\n\n    blockc1baseextended\n        blockc2base\n\n\n    \nA\n                block1\n\n            block1extended\n        B\n    block2\nC        blockc2base\n\nB"
  },
  {
    "path": "tests/Fixtures/tags/for/context.test",
    "content": "--TEST--\n\"for\" tag keeps the context safe\n--TEMPLATE--\n{% for item in items %}\n  {% for item in items %}\n    * {{ item }}\n  {% endfor %}\n  * {{ item }}\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b']]\n--EXPECT--\n      * a\n      * b\n    * a\n      * a\n      * b\n    * b\n"
  },
  {
    "path": "tests/Fixtures/tags/for/else.test",
    "content": "--TEST--\n\"for\" tag can use an \"else\" clause\n--TEMPLATE--\n{% for item in items %}\n  * {{ item }}\n{% else %}\n  no item\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b']]\n--EXPECT--\n  * a\n  * b\n--DATA--\nreturn ['items' => []]\n--EXPECT--\n  no item\n--DATA--\nreturn []\n--CONFIG--\nreturn ['strict_variables' => false]\n--EXPECT--\n  no item\n"
  },
  {
    "path": "tests/Fixtures/tags/for/for_on_strings.test",
    "content": "--TEST--\n\"for\" tag can iterate over a string via the \"split\" filter\n--TEMPLATE--\n{% set jp = \"諺 / ことわざ\" %}\n\n{% for letter in jp|split('') -%}\n    -{{- letter }}\n    {{- loop.last ? '.' }}\n{%- endfor %}\n--DATA--\nreturn []\n--EXPECT--\n-諺- -/- -こ-と-わ-ざ.\n"
  },
  {
    "path": "tests/Fixtures/tags/for/inner_variables.test",
    "content": "--TEST--\n\"for\" tag does not reset inner variables\n--TEMPLATE--\n{% for i in 1..2 %}\n  {% for j in 0..2 %}\n    {{k}}{% set k = k+1 %} {{ loop.parent.loop.index }}\n  {% endfor %}\n{% endfor %}\n--DATA--\nreturn ['k' => 0]\n--EXPECT--\n      0 1\n      1 1\n      2 1\n        3 2\n      4 2\n      5 2\n"
  },
  {
    "path": "tests/Fixtures/tags/for/keys.test",
    "content": "--TEST--\n\"for\" tag can iterate over keys\n--TEMPLATE--\n{% for key in items|keys %}\n  * {{ key }}\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b']]\n--EXPECT--\n  * 0\n  * 1\n"
  },
  {
    "path": "tests/Fixtures/tags/for/keys_and_values.test",
    "content": "--TEST--\n\"for\" tag can iterate over keys and values\n--TEMPLATE--\n{% for key, item in items %}\n  * {{ key }}/{{ item }}\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b']]\n--EXPECT--\n  * 0/a\n  * 1/b\n"
  },
  {
    "path": "tests/Fixtures/tags/for/loop_context.test",
    "content": "--TEST--\n\"for\" tag adds a loop variable to the context\n--TEMPLATE--\n{% for item in items %}\n  * {{ loop.index }}/{{ loop.index0 }}\n  * {{ loop.revindex }}/{{ loop.revindex0 }}\n  * {{ loop.first }}/{{ loop.last }}/{{ loop.length }}\n\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b']]\n--EXPECT--\n  * 1/0\n  * 2/1\n  * 1//2\n\n  * 2/1\n  * 1/0\n  * /1/2\n"
  },
  {
    "path": "tests/Fixtures/tags/for/loop_context_local.test",
    "content": "--TEST--\n\"for\" tag adds a loop variable to the context locally\n--TEMPLATE--\n{% for item in items %}\n{% endfor %}\n{% if loop is not defined %}WORKS{% endif %}\n--DATA--\nreturn ['items' => []]\n--EXPECT--\nWORKS\n"
  },
  {
    "path": "tests/Fixtures/tags/for/nested_else.test",
    "content": "--TEST--\n\"for\" tag can use an \"else\" clause\n--TEMPLATE--\n{% for item in items %}\n  {% for item in items1 %}\n    * {{ item }}\n  {% else %}\n    no {{ item }}\n  {% endfor %}\n{% else %}\n  no item1\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b'], 'items1' => []]\n--EXPECT--\nno a\n        no b\n"
  },
  {
    "path": "tests/Fixtures/tags/for/objects.test",
    "content": "--TEST--\n\"for\" tag iterates over iterable objects\n--TEMPLATE--\n{% for item in items %}\n  * {{ item }}\n  * {{ loop.index }}/{{ loop.index0 }}\n  * {{ loop.first }}\n\n{% endfor %}\n\n{% for key, value in items %}\n  * {{ key }}/{{ value }}\n{% endfor %}\n\n{% for key in items|keys %}\n  * {{ key }}\n{% endfor %}\n--DATA--\nclass ItemsIterator implements \\Iterator\n{\n  protected $values = ['foo' => 'bar', 'bar' => 'foo'];\n  #[\\ReturnTypeWillChange]\n  public function current() { return current($this->values); }\n  #[\\ReturnTypeWillChange]\n  public function key() { return key($this->values); }\n  public function next(): void { next($this->values); }\n  public function rewind(): void { reset($this->values); }\n  public function valid(): bool { return false !== current($this->values); }\n}\nreturn ['items' => new ItemsIterator()]\n--EXPECT--\n  * bar\n  * 1/0\n  * 1\n\n  * foo\n  * 2/1\n  * \n\n\n  * foo/bar\n  * bar/foo\n\n  * foo\n  * bar\n"
  },
  {
    "path": "tests/Fixtures/tags/for/objects_countable.test",
    "content": "--TEST--\n\"for\" tag iterates over iterable and countable objects\n--TEMPLATE--\n{% for item in items %}\n  * {{ item }}\n  * {{ loop.index }}/{{ loop.index0 }}\n  * {{ loop.revindex }}/{{ loop.revindex0 }}\n  * {{ loop.first }}/{{ loop.last }}/{{ loop.length }}\n\n{% endfor %}\n\n{% for key, value in items %}\n  * {{ key }}/{{ value }}\n{% endfor %}\n\n{% for key in items|keys %}\n  * {{ key }}\n{% endfor %}\n--DATA--\nclass ItemsIteratorCountable implements \\Iterator, \\Countable\n{\n  protected $values = ['foo' => 'bar', 'bar' => 'foo'];\n  #[\\ReturnTypeWillChange]\n  public function current() { return current($this->values); }\n  #[\\ReturnTypeWillChange]\n  public function key() { return key($this->values); }\n  public function next(): void { next($this->values); }\n  public function rewind(): void { reset($this->values); }\n  public function valid(): bool { return false !== current($this->values); }\n  public function count(): int { return count($this->values); }\n}\nreturn ['items' => new ItemsIteratorCountable()]\n--EXPECT--\n  * bar\n  * 1/0\n  * 2/1\n  * 1//2\n\n  * foo\n  * 2/1\n  * 1/0\n  * /1/2\n\n\n  * foo/bar\n  * bar/foo\n\n  * foo\n  * bar\n"
  },
  {
    "path": "tests/Fixtures/tags/for/recursive.test",
    "content": "--TEST--\n\"for\" tags can be nested\n--TEMPLATE--\n{% for key, item in items %}\n* {{ key }} ({{ loop.length }}):\n{% for value in item %}\n  * {{ value }} ({{ loop.length }})\n{% endfor %}\n{% endfor %}\n--DATA--\nreturn ['items' => ['a' => ['a1', 'a2', 'a3'], 'b' => ['b1']]]\n--EXPECT--\n* a (2):\n  * a1 (3)\n  * a2 (3)\n  * a3 (3)\n* b (2):\n  * b1 (1)\n"
  },
  {
    "path": "tests/Fixtures/tags/for/reserved_names.test",
    "content": "--TEST--\n\"for\" tag\n--TEMPLATE--\n{% for true in [1, 2] %}\n{% endfor %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: You cannot assign a value to \"true\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/for/values.test",
    "content": "--TEST--\n\"for\" tag iterates over item values\n--TEMPLATE--\n{% for item in items %}\n  * {{ item }}\n{% endfor %}\n--DATA--\nreturn ['items' => ['a', 'b']]\n--EXPECT--\n  * a\n  * b\n"
  },
  {
    "path": "tests/Fixtures/tags/from.test",
    "content": "--TEST--\nglobal variables\n--TEMPLATE--\n{% include \"included.twig\" %}\n{% from \"included.twig\" import foobar %}\n{{ foobar() }}\n--TEMPLATE(included.twig)--\n{% macro foobar() %}\ncalled foobar\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\ncalled foobar\n"
  },
  {
    "path": "tests/Fixtures/tags/guard/basic.test",
    "content": "--TEST--\n\"guard\" creates a compilation time condition on Twig callables availability\n--TEMPLATE--\n{% guard filter foobar %}\n    NEVER\n    {{ 'a'|foobar }}\n{% else -%}\n    The foobar filter doesn't exist\n{% endguard %}\n\n{% guard function constant -%}\n    The constant function does exist\n{% else %}\n    NEVER\n{% endguard %}\n\n{% guard test foobar %}\n    NEVER\n    {{ 'a'|foobar }}\n{% else -%}\n    The foobar test doesn't exist\n{% endguard %}\n\n{% guard test divisible by -%}\n    The divisible by function does exist\n{% else %}\n    NEVER\n{% endguard %}\n--DATA--\nreturn []\n--EXPECT--\nThe foobar filter doesn't exist\n\nThe constant function does exist\n\nThe foobar test doesn't exist\n\nThe divisible by function does exist\n"
  },
  {
    "path": "tests/Fixtures/tags/guard/exception.test",
    "content": "--TEST--\n\"guard\" creates a compilation time condition on Twig callables availability\n--TEMPLATE--\n{% guard function foobar %}\n    {{ foobar() }}\n{% else %}\n    {{ foobar() }}\n{% endguard %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unknown \"foobar\" function in \"index.twig\" at line 5.\n"
  },
  {
    "path": "tests/Fixtures/tags/guard/nested.test",
    "content": "--TEST--\n\"guard\" creates a compilation time condition on Twig callables availability\n--TEMPLATE--\n{% guard function constant %}\n    {% guard filter foobar %}\n        NEVER\n        {{ 'a'|foobar }}\n    {% else %}\n        The constant function does exist, but the foobar filter does not\n    {%- endguard %}\n{% else %}\n    NEVER\n{% endguard %}\n\n{% guard function constant -%}\n    {% guard filter upper -%}\n        The constant function does exist, and the upper filter as well\n    {%- else %}\n        NEVER\n    {% endguard %}\n{% else %}\n    NEVER\n{% endguard %}\n\n{% guard filter foobar %}\n    NEVER\n    {{ 'a'|foobar }}\n{% else -%}\n    {% guard function barfoo %}\n        NEVER\n    {% else -%}\n        The foobar filter does not exist, and the barfoo function does not exist\n    {%- endguard %}\n{% endguard %}\n\n{% guard filter foobar %}\n    NEVER\n    {{ 'a'|foobar }}\n{% else %}\n    {%- guard function constant -%}\n        The foobar filter does not exist, but the constant function exists\n    {% else -%}\n        NEVER\n    {% endguard %}\n{% endguard %}\n\n{% guard function first %}\n    {% guard function second %}\n        NEVER\n        {{ second() }}\n    {% endguard %}\n    {{ first() }}\n{% endguard %}\n--DATA--\nreturn []\n--EXPECT--\nThe constant function does exist, but the foobar filter does not\nThe constant function does exist, and the upper filter as well\nThe foobar filter does not exist, and the barfoo function does not exist\nThe foobar filter does not exist, but the constant function exists\n"
  },
  {
    "path": "tests/Fixtures/tags/guard/throwing_handler.test",
    "content": "--TEST--\n\"guard\" creates a compilation time condition on Twig callables availability\n--TEMPLATE--\n{% guard filter throwing_undefined_filter %}\n    NEVER\n    {{ 'a'|throwing_undefined_filter }}\n{% else -%}\n    The throwing_undefined_filter filter doesn't exist\n{% endguard %}\n\n{% guard function throwing_undefined_function -%}\n    NEVER\n    {{ throwing_undefined_function() }}\n{% else -%}\n    The throwing_undefined_function function doesn't exist\n{% endguard %}\n\n{% guard test throwing_undefined_test -%}\n    NEVER\n    {% if 'a' is throwing_undefined_test('b') %}{% endif %}\n{% else -%}\n    The throwing_undefined_test test doesn't exist\n{% endguard %}\n\n{% guard test throwing_undefined_two words_test -%}\n    NEVER\n    {% if 'a' is throwing_undefined_test words_test('b') %}{% endif %}\n{% else -%}\n    The throwing_undefined_two words_test test doesn't exist\n{% endguard %}\n--DATA--\nreturn []\n--EXPECT--\nThe throwing_undefined_filter filter doesn't exist\n\nThe throwing_undefined_function function doesn't exist\n\nThe throwing_undefined_test test doesn't exist\n\nThe throwing_undefined_two words_test test doesn't exist\n"
  },
  {
    "path": "tests/Fixtures/tags/if/basic.test",
    "content": "--TEST--\n\"if\" creates a condition\n--TEMPLATE--\n{% if a is defined %}\n  {{ a }}\n{% elseif b is defined %}\n  {{ b }}\n{% else %}\n  NOTHING\n{% endif %}\n--DATA--\nreturn ['a' => 'a']\n--EXPECT--\n  a\n--DATA--\nreturn ['b' => 'b']\n--EXPECT--\n  b\n--DATA--\nreturn []\n--EXPECT--\n  NOTHING\n"
  },
  {
    "path": "tests/Fixtures/tags/if/empty_body.test",
    "content": "--TEST--\nempty \"if\" body in child template\n--TEMPLATE--\n{% extends 'base.twig' %}\n\n{% set foo = '' %}\n\n{% if a is defined %}\n\n{% else %}\n    {% set foo = 'NOTHING' %}\n{% endif %}\n\n{% if a is defined %}\n\n{% endif %}\n\n{% if a is defined %}\n    {% set foo = 'NOTHING' %}\n{% else %}\n\n{% endif %}\n\n{% block content %}\n    {{ foo }}\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% block content %}{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\n  NOTHING\n"
  },
  {
    "path": "tests/Fixtures/tags/if/expression.test",
    "content": "--TEST--\n\"if\" takes an expression as a test\n--TEMPLATE--\n{% if a < 2 %}\n  A1\n{% elseif a > 10 %}\n  A2\n{% else %}\n  A3\n{% endif %}\n--DATA--\nreturn ['a' => 1]\n--EXPECT--\n  A1\n--DATA--\nreturn ['a' => 12]\n--EXPECT--\n  A2\n--DATA--\nreturn ['a' => 7]\n--EXPECT--\n  A3\n"
  },
  {
    "path": "tests/Fixtures/tags/include/basic.test",
    "content": "--TEST--\n\"include\" tag\n--TEMPLATE--\nFOO\n{% include \"foo.twig\" %}\n\nBAR\n--TEMPLATE(foo.twig)--\nFOOBAR\n--DATA--\nreturn []\n--EXPECT--\nFOO\n\nFOOBAR\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/include/expression.test",
    "content": "--TEST--\n\"include\" tag allows expressions for the template to include\n--TEMPLATE--\nFOO\n{% include foo %}\n\nBAR\n--TEMPLATE(foo.twig)--\nFOOBAR\n--DATA--\nreturn ['foo' => 'foo.twig']\n--EXPECT--\nFOO\n\nFOOBAR\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/include/ignore_missing.test",
    "content": "--TEST--\n\"include\" tag\n--TEMPLATE--\n{% include [\"foo.twig\", \"bar.twig\"] ignore missing %}\n{% include \"foo.twig\" ignore missing %}\n{% include \"foo.twig\" ignore missing with {} %}\n{% include \"foo.twig\" ignore missing with {} only %}\n--DATA--\nreturn []\n--EXPECT--\n"
  },
  {
    "path": "tests/Fixtures/tags/include/ignore_missing_exists.test",
    "content": "--TEST--\n\"include\" tag\n--TEMPLATE--\n{% include \"included.twig\" ignore missing %}\nNOT DISPLAYED\n--TEMPLATE(included.twig)--\n{% include \"DOES NOT EXIST\" %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"DOES NOT EXIST\" is not defined in \"included.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/include/include_missing_extends.test",
    "content": "--TEST--\n\"include\" tag\n--TEMPLATE--\n{% include ['bad.twig', 'good.twig'] ignore missing %}\nNOT DISPLAYED\n--TEMPLATE(bad.twig)--\n{% extends 'DOES NOT EXIST' %}\n--TEMPLATE(good.twig)--\nNOT DISPLAYED\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"DOES NOT EXIST\" is not defined in \"bad.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/include/missing.test",
    "content": "--TEST--\n\"include\" tag\n--TEMPLATE--\n{% include \"foo.twig\" %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"foo.twig\" is not defined in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/include/missing_nested.test",
    "content": "--TEST--\n\"include\" tag\n--TEMPLATE--\n{% extends \"base.twig\" %}\n\n{% block content %}\n    {{ parent() }}\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% block content %}\n    {% include \"foo.twig\" %}\n{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"foo.twig\" is not defined in \"base.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/tags/include/only.test",
    "content": "--TEST--\n\"include\" tag accept variables and only\n--TEMPLATE--\n{% include \"foo.twig\" %}\n{% include \"foo.twig\" only %}\n{% include \"foo.twig\" with vars1 %}\n{% include \"foo.twig\" with vars1 only %}\n{% include \"foo.twig\" with vars2 %}\n{% include \"foo.twig\" with vars2 only %}\n--TEMPLATE(foo.twig)--\n{% for k, v in _context %}{{ k }},{% endfor %}\n--DATA--\nreturn ['vars1' => ['foo1' => 'bar'], 'vars2' => new ArrayObject(['foo2' => 'bar'])]\n--EXPECT--\nvars1,vars2,global,_parent,\nglobal,_parent,\nvars1,vars2,global,foo1,_parent,\nfoo1,global,_parent,\nvars1,vars2,global,foo2,_parent,\nfoo2,global,_parent,\n"
  },
  {
    "path": "tests/Fixtures/tags/include/template_instance.test",
    "content": "--TEST--\n\"include\" tag accepts \\Twig\\TemplateWrapper instance\n--TEMPLATE--\n{% include foo %} FOO\n--TEMPLATE(foo.twig)--\nBAR\n--DATA--\nreturn ['foo' => $twig->load('foo.twig')]\n--EXPECT--\nBAR FOO\n"
  },
  {
    "path": "tests/Fixtures/tags/include/templates_as_array.test",
    "content": "--TEST--\n\"include\" tag\n--TEMPLATE--\n{% include [\"foo.twig\", \"bar.twig\"] %}\n{% include [\"bar.twig\", \"foo.twig\"] %}\n--TEMPLATE(foo.twig)--\nfoo\n--DATA--\nreturn []\n--EXPECT--\nfoo\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/include/with_variables.test",
    "content": "--TEST--\n\"include\" tag accept variables\n--TEMPLATE--\n{% include \"foo.twig\" with {'foo': 'bar'} %}\n{% include \"foo.twig\" with vars1 %}\n{% include \"foo.twig\" with vars2 %}\n--TEMPLATE(foo.twig)--\n{{ foo }}\n--DATA--\nreturn ['vars1' => ['foo' => 'bar'], 'vars2' => new ArrayObject(['foo' => 'bar'])]\n--EXPECT--\nbar\nbar\nbar\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/basic.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends \"foo.twig\" %}\n\n{% block content %}\nFOO\n{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nFOO\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/block_expr.test",
    "content": "--TEST--\nblock_expr\n--TEMPLATE--\n{% extends \"base.twig\" %}\n\n{% block element -%}\n    Element:\n    {{- parent() -}}\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% block element -%}\n    <div>\n        {%- if item.children is defined %}\n            {%- for item in item.children %}\n                {{- block('element') -}}\n            {% endfor %}\n        {%- endif -%}\n    </div>\n{%- endblock %}\n--DATA--\nreturn [\n    'item' => [\n        'children' => [\n            null,\n            null,\n        ]\n    ]\n]\n--EXPECT--\nElement:<div>Element:<div></div>Element:<div></div></div>\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/block_expr2.test",
    "content": "--TEST--\nblock_expr2\n--TEMPLATE--\n{% extends \"base2.twig\" %}\n\n{% block element -%}\n    Element:\n    {{- parent() -}}\n{% endblock %}\n--TEMPLATE(base2.twig)--\n{% extends \"base.twig\" %}\n--TEMPLATE(base.twig)--\n{% block element -%}\n    <div>\n        {%- if item.children is defined %}\n            {%- for item in item.children %}\n                {{- block('element') -}}\n            {% endfor %}\n        {%- endif -%}\n    </div>\n{%- endblock %}\n--DATA--\nreturn [\n    'item' => [\n        'children' => [\n            null,\n            null,\n        ]\n    ]\n]\n--EXPECT--\nElement:<div>Element:<div></div>Element:<div></div></div>\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/capturing_block.test",
    "content": "--TEST--\ncapturing \"block\" tag with \"extends\" tag\n--TEMPLATE--\n{% extends \"layout.twig\" %}\n\n{% set foo %}\n    {%- block content %}FOO{% endblock %}\n{% endset %}\n\n{% block content1 %}BAR{{ foo }}{% endblock %}\n--TEMPLATE(layout.twig)--\n{% block content %}{% endblock %}\n{% block content1 %}{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nFOOBARFOO\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/conditional.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends standalone ? foo : 'bar.twig' %}\n\n{% block content %}{{ parent() }}FOO{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}FOO{% endblock %}\n--TEMPLATE(bar.twig)--\n{% block content %}BAR{% endblock %}\n--DATA--\nreturn ['foo' => 'foo.twig', 'standalone' => true]\n--EXPECT--\nFOOFOO\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/conditional_block.test",
    "content": "--TEST--\nconditional \"block\" tag with \"extends\" tag\n--TEMPLATE--\n{% extends \"layout.twig\" %}\n\n{% if false %}\n    {% block content %}FOO{% endblock %}\n{% endif %}\n--TEMPLATE(layout.twig)--\n{% block content %}{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: A block definition cannot be nested under non-capturing nodes in \"index.twig\" at line 5.\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/conditional_block_nested.test",
    "content": "--TEST--\nconditional \"block\" tag with \"extends\" tag (nested)\n--TEMPLATE--\n{% extends \"layout.twig\" %}\n\n{% block content_base %}\n    {{ parent() -}}\n    index\n{% endblock %}\n\n{% block content_layout -%}\n    {{ parent() -}}\n    nested_index\n{% endblock %}\n--TEMPLATE(layout.twig)--\n{% extends \"base.twig\" %}\n\n{% block content_base %}\n    {{ parent() -}}\n    layout\n    {% if true -%}\n        {% block content_layout -%}\n            nested_layout\n        {% endblock -%}\n    {% endif %}\n{% endblock %}\n--TEMPLATE(base.twig)--\n{% block content_base %}\n    base\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nbase\nlayout\n    nested_layout\n        nested_index\nindex\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/dynamic.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends foo %}\n\n{% block content %}\nFOO\n{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}{% endblock %}\n--DATA--\nreturn ['foo' => 'foo.twig']\n--EXPECT--\nFOO\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/dynamic_parent_from_include.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{{ include('included.twig') }}\n\n--TEMPLATE(included.twig)--\n\n\n\n{% extends dynamic %}\n--DATA--\nreturn ['dynamic' => 'unknown.twig']\n--EXCEPTION--\nTwig\\Error\\LoaderError: Template \"unknown.twig\" is not defined in \"included.twig\" at line 5.\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/empty.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends \"foo.twig\" %}\n--TEMPLATE(foo.twig)--\n{% block content %}FOO{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nFOO\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/extends_as_array.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends [\"foo.twig\", \"bar.twig\"] %}\n--TEMPLATE(bar.twig)--\n{% block content %}\nfoo\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/extends_as_array_with_empty_name.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends [\"\", \"bar.twig\"] %}\n--TEMPLATE(bar.twig)--\n{% block content %}\nfoo\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/extends_as_array_with_nested_blocks.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends [\"parent.twig\"] %}\n\n{% block outer %}\n    outer wrap start\n    {{~ parent() }}\n    outer wrap end\n{% endblock %}\n\n{% block inner -%}\n    inner actual\n{% endblock %}\n--TEMPLATE(parent.twig)--\n{% block outer %}\n    outer start\n    {% block inner %}\n        inner default\n    {% endblock %}\n    outer end\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\n    outer wrap start\n    outer start\n    inner actual\n    outer end\n\n    outer wrap end\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/extends_in_block.test",
    "content": "--TEST--\n\"extends\" tag in a block\n--TEMPLATE--\n{% block foo %}\n    {% extends \"foo.twig\" %}\n{% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Cannot use \"extend\" in a block in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/extends_in_macro.test",
    "content": "--TEST--\n\"extends\" tag in a macro\n--TEMPLATE--\n{% macro foo() %}\n    {% extends \"foo.twig\" %}\n{% endmacro %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Cannot use \"extend\" in a macro in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/extends_with_nested_blocks.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends \"parent.twig\" %}\n\n{% block outer %}\n    outer wrap start\n    {{~ parent() }}\n    outer wrap end\n{% endblock %}\n\n{% block inner -%}\n    inner actual\n{% endblock %}\n--TEMPLATE(parent.twig)--\n{% block outer %}\n    outer start\n    {% block inner %}\n        inner default\n    {% endblock %}\n    outer end\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\n    outer wrap start\n    outer start\n    inner actual\n    outer end\n\n    outer wrap end\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/multiple.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends \"layout.twig\" %}{% block content %}{{ parent() }}index {% endblock %}\n--TEMPLATE(layout.twig)--\n{% extends \"base.twig\" %}{% block content %}{{ parent() }}layout {% endblock %}\n--TEMPLATE(base.twig)--\n{% block content %}base {% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nbase layout index\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/multiple_dynamic.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% set foo = 1 %}\n{{ include('parent.twig') }}\n{{ include('parent.twig') }}\n{% set foo = 2 %}\n{{ include('parent.twig') }}\n--TEMPLATE(parent.twig)--\n{% extends foo~'_parent.twig' %}{% block content %}{{ parent() }} parent{% endblock %}\n--TEMPLATE(1_parent.twig)--\n{% block content %}1{% endblock %}\n--TEMPLATE(2_parent.twig)--\n{% block content %}2{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\n1 parent\n\n1 parent\n\n2 parent\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/nested_blocks.test",
    "content": "--TEST--\n\"block\" tag\n--TEMPLATE--\n{% extends \"foo.twig\" %}\n\n{% block content %}\n    {% block subcontent %}\n        {% block subsubcontent %}\n            SUBSUBCONTENT\n        {% endblock %}\n    {% endblock %}\n{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}\n    {% block subcontent %}\n        SUBCONTENT\n    {% endblock %}\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nSUBSUBCONTENT\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/nested_blocks_parent_only.test",
    "content": "--TEST--\n\"block\" tag\n--TEMPLATE--\n{% block content %}\n    CONTENT\n    {%- block subcontent -%}\n        SUBCONTENT\n    {%- endblock -%}\n    ENDCONTENT\n{% endblock %}\n--TEMPLATE(foo.twig)--\n--DATA--\nreturn []\n--EXPECT--\nCONTENTSUBCONTENTENDCONTENT\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/nested_inheritance.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends \"layout.twig\" %}\n{% block inside %}INSIDE{% endblock inside %}\n--TEMPLATE(layout.twig)--\n{% extends \"base.twig\" %}\n{% block body %}\n    {% block inside '' %}\n{% endblock body %}\n--TEMPLATE(base.twig)--\n{% block body '' %}\n--DATA--\nreturn []\n--EXPECT--\nINSIDE\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/parent.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends \"foo.twig\" %}\n\n{% block content %}{{ parent() }}FOO{{ parent() }}{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}BAR{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nBARFOOBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/parent_as_template_wrapper.test",
    "content": "--TEST--\n\"extends\" tag with a parent as a Twig\\TemplateWrapper instance\n--TEMPLATE--\n{% extends foo %}\n\n{% block content %}New{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}Default{% endblock %}\n--DATA--\nreturn ['foo' => $twig->load('foo.twig')]\n--EXPECT--\nNew\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/parent_change.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends foo ? 'foo.twig' : 'bar.twig' %}\n--TEMPLATE(foo.twig)--\nFOO\n--TEMPLATE(bar.twig)--\nBAR\n--DATA--\nreturn ['foo' => true]\n--EXPECT--\nFOO\n--DATA--\nreturn ['foo' => false]\n--EXPECT--\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/parent_isolation.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends \"base.twig\" %}\n{% block content %}{% include \"included.twig\" %}{% endblock %}\n\n{% block footer %}Footer{% endblock %}\n--TEMPLATE(included.twig)--\n{% extends \"base.twig\" %}\n{% block content %}Included Content{% endblock %}\n--TEMPLATE(base.twig)--\n{% block content %}Default Content{% endblock %}\n\n{% block footer %}Default Footer{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nIncluded Content\nDefault Footer\nFooter\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/parent_nested.test",
    "content": "--TEST--\n\"extends\" tag\n--TEMPLATE--\n{% extends \"foo.twig\" %}\n\n{% block content %}\n  {% block inside %}\n    INSIDE OVERRIDDEN\n  {% endblock %}\n\n  BEFORE\n  {{ parent() }}\n  AFTER\n{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}\n  BAR\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\n\nINSIDE OVERRIDDEN\n  \n  BEFORE\n    BAR\n\n  AFTER\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/parent_without_extends.test",
    "content": "--TEST--\n\"parent\" tag\n--TEMPLATE--\n{% block content %}\n    {{ parent() }}\n{% endblock %}\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Calling the \"parent\" function on a template that does not call \"extends\" or \"use\" is forbidden in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/parent_without_extends_but_traits.test",
    "content": "--TEST--\n\"parent\" tag\n--TEMPLATE--\n{% use 'foo.twig' %}\n\n{% block content %}\n    {{ parent() }}\n{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}BAR{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nBAR\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/template_instance.test",
    "content": "--TEST--\n\"extends\" tag accepts Twig\\Template instance\n--TEMPLATE--\n{% extends foo %}\n\n{% block content %}\n{{ parent() }}FOO\n{% endblock %}\n--TEMPLATE(foo.twig)--\n{% block content %}BAR{% endblock %}\n--DATA--\nreturn ['foo' => $twig->load('foo.twig')]\n--EXPECT--\nBARFOO\n"
  },
  {
    "path": "tests/Fixtures/tags/inheritance/use.test",
    "content": "--TEST--\n\"parent\" function\n--TEMPLATE--\n{% extends \"parent.twig\" %}\n\n{% use \"use1.twig\" %}\n{% use \"use2.twig\" %}\n\n{% block content_parent %}\n    {{ parent() }}\n{% endblock %}\n\n{% block content_use1 %}\n    {{ parent() }}\n{% endblock %}\n\n{% block content_use2 %}\n    {{ parent() }}\n{% endblock %}\n\n{% block content %}\n    {{ block('content_use1_only') }}\n    {{ block('content_use2_only') }}\n{% endblock %}\n--TEMPLATE(parent.twig)--\n{% block content_parent 'content_parent' %}\n{% block content_use1 'content_parent' %}\n{% block content_use2 'content_parent' %}\n{% block content '' %}\n--TEMPLATE(use1.twig)--\n{% block content_use1 'content_use1' %}\n{% block content_use2 'content_use1' %}\n{% block content_use1_only 'content_use1_only' %}\n--TEMPLATE(use2.twig)--\n{% block content_use2 'content_use2' %}\n{% block content_use2_only 'content_use2_only' %}\n--DATA--\nreturn []\n--EXPECT--\n    content_parent\n    content_use1\n    content_use2\n    content_use1_only\n    content_use2_only\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/argument_reserved_names.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import _self as macros %}\n\n{% macro input(true, false, null) %}\n    {{ true }}\n{% endmacro %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: You cannot assign a value to \"true\" in \"index.twig\" at line 4.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/auto_import.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{{ _self.hello('Fabien') }}\n\n{% macro hello(name) -%}\n    Hello {{ _self.up(name) }}\n{% endmacro %}\n\n{% macro up(name) -%}\n    {{ name|upper }}\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nHello FABIEN\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/auto_import_blocks.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% block content %}\n    {{ _self.hello('Fabien') }}\n{% endblock %}\n\n{% macro hello(name) -%}\n    Hello {{ _self.up(name) }}\n{% endmacro %}\n\n{% macro up(name) -%}\n    {{ name|upper }}\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nHello FABIEN\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/auto_import_without_blocks.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import 'macros' as macro %}\n{{ macro.foo() }}\n--TEMPLATE(macros)--\n{% macro foo() %}\n    foo\n    {{- _self.bar() }}\n{% endmacro %}\n\n{% macro bar() -%}\n    bar\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nfoobar\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/basic.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import _self as macros %}\n\n{{ macros.input('username') }}\n{{ macros.input('password', null, 'password', 1) }}\n\n{% macro input(name, value, type, size) %}\n  <input type=\"{{ type|default(\"text\") }}\" name=\"{{ name }}\" value=\"{{ value|e|default('') }}\" size=\"{{ size|default(20) }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n  <input type=\"text\" name=\"username\" value=\"\" size=\"20\">\n\n  <input type=\"password\" name=\"password\" value=\"\" size=\"1\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/colon_not_supported_as_default_separator.test",
    "content": "--TEST--\n\"macro\" tag does not support : as a separator in definition, only = is supported\n--TEMPLATE--\n{% macro test(foo: \"foo\") -%}\n    {{ foo }}\n{%- endmacro %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Arguments must be separated by a comma. Unexpected token \"punctuation\" of value \":\" (\"punctuation\" expected with value \",\") in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/endmacro_name.test",
    "content": "--TEST--\n\"macro\" tag supports name for endmacro\n--TEMPLATE--\n{% import _self as macros %}\n\n{{ macros.foo() }}\n{{ macros.bar() }}\n\n{% macro foo() %}foo{% endmacro %}\n{% macro bar() %}bar{% endmacro bar %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\nbar\n\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/external.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import 'forms.twig' as forms %}\n\n{{ forms.input('username') }}\n{{ forms.input('password', null, 'password', 1) }}\n--TEMPLATE(forms.twig)--\n{% macro input(name, value, type, size) %}\n  <input type=\"{{ type|default(\"text\") }}\" name=\"{{ name }}\" value=\"{{ value|e|default('') }}\" size=\"{{ size|default(20) }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n  <input type=\"text\" name=\"username\" value=\"\" size=\"20\">\n\n  <input type=\"password\" name=\"password\" value=\"\" size=\"1\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% from 'forms.twig' import foo %}\n{% from 'forms.twig' import foo as foobar, bar %}\n\n{{ foo('foo') }}\n{{ foobar('foo') }}\n{{ bar('foo') }}\n--TEMPLATE(forms.twig)--\n{% macro foo(name) %}foo{{ name }}{% endmacro %}\n{% macro bar(name) %}bar{{ name }}{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nfoofoo\nfoofoo\nbarfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_embed_with_global_macro.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% from _self import input %}\n\n{% embed 'embed' %}\n    {% block foo %}\n        {{ input(\"username\") }}\n    {% endblock %}\n{% endembed %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--TEMPLATE(embed)--\n    {% block foo %}\n    {% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unknown \"input\" function in \"index.twig\" at line 6.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_in_block_is_local.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% block foo %}\n    {%- from _self import input as linput %}\n{% endblock %}\n\n{% block bar %}\n    {{- linput('username') }}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unknown \"linput\" function in \"index.twig\" at line 7.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_local_override.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{%- from _self import input %}\n\n{% block foo %}\n    {%- from \"macros\" import input %}\n    {{- input('username') }}\n{% endblock %}\n\n{% block bar %}\n    {{- input('username') }}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--TEMPLATE(macros)--\n{% macro input(name) %}\n    <input name=\"{{ name }}\" value=\"local\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n<input name=\"username\" value=\"local\">\n\n\n<input name=\"username\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_macro_in_a_macro.test",
    "content": "--TEST--\n\"from\" tag with syntax error\n--TEMPLATE--\n{% from _self import another, foo %}\n\n{{ foo() }}\n\n{% macro foo() %}\n    {{ another() }}\n{% endmacro %}\n\n{% macro another() %}\n    OK\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nOK\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_macros_in_parent.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% from \"macros\" import hello %}\n\n{{ hello() }}\n--TEMPLATE(macros)--\n{% extends \"parent\" %}\n--TEMPLATE(parent)--\n{% macro hello() %}\n    Test\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nTest\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_nested_blocks.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% block foo %}\n    {%- from _self import input as linput %}\n\n    {% block bar %}\n        {{- linput('username') }}\n    {% endblock %}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unknown \"linput\" function in \"index.twig\" at line 6.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{%- from _self import input %}\n\n{% block foo %}\n    {% block bar %}\n        {{- input('username') }}\n    {% endblock %}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n<input name=\"username\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_recursive.test",
    "content": "--TEST--\n\"import\" tag\n--TEMPLATE--\n{% from _self import recursive_macro %}\n\n{{ recursive_macro(10) }}\n\n{% macro recursive_macro(n) %}\n    {% if n > 0 %}\n        {{- recursive_macro(n - 1) -}}\n    {% endif %}\n    {{- n }}\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_reserved_names.test",
    "content": "--TEST--\n\"from\" tag\n--TEMPLATE--\n{% from _self import input as true %}\n\n{{ true('username') }}\n\n{% macro input(name) -%}\n{% endmacro %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: You cannot assign a value to \"true\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_self_parent.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% extends \"parent\" %}\n\n{% block test %}\n    {{ _self.hello() }}\n{% endblock test %}\n--TEMPLATE(parent)--\n{% block test %}\nHello\n{% endblock test %}\n\n{% macro hello() %}\n    Test\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nTest\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/from_syntax_error.test",
    "content": "--TEST--\n\"from\" tag with syntax error\n--TEMPLATE--\n{% from 'forms.twig' %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unexpected token \"end of statement block\" (\"name\" expected with value \"import\") in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/global.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% from 'forms.twig' import foo %}\n\n{{ foo('foo') }}\n{{ foo() }}\n--TEMPLATE(forms.twig)--\n{% macro foo(name) %}{{ name|default('foo') }}{{ global }}{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nfooglobal\nfooglobal\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_and_blocks.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import _self as macros %}\n{% from _self import input %}\n\n{% block foo %}\n    {{- macros.input('username') }}\n    {{- input('username') }}\n\n    {%- import _self as lmacros %}\n    {%- from _self import input as linput %}\n\n    {{- lmacros.input('username') }}\n    {{- linput('username') }}\n{% endblock %}\n\n{% block bar %}\n    {{- macros.input('username') }}\n    {{- input('username') }}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n<input name=\"username\">\n<input name=\"username\">\n<input name=\"username\">\n<input name=\"username\">\n\n\n<input name=\"username\">\n<input name=\"username\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_embed_with_global_macro.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import _self as macros %}\n\n{% embed 'embed' %}\n    {% block foo %}\n        {{ macros.input(\"username\") }}\n    {% endblock %}\n{% endembed %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--TEMPLATE(embed)--\n    {% block foo %}\n    {% endblock %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"macros\" does not exist in \"index.twig\" at line 6.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_from_string_template.test",
    "content": "--TEST--\n\"import\" tag with a template as string\n--TEMPLATE--\n{% import template_from_string(\"{% macro test() %}ok{% endmacro %}\") as m %}\n{{ m.test() }}\n--TEMPLATE(forms.twig)--\n--DATA--\nreturn []\n--EXPECT--\nok\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_in_block_is_local.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% block foo %}\n    {%- import _self as lmacros %}\n{% endblock %}\n\n{% block bar %}\n    {{- lmacros.input('username') }}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"lmacros\" does not exist in \"index.twig\" at line 7.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_local_override.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{%- import _self as macros %}\n\n{% block foo %}\n    {%- import \"macros\" as macros %}\n    {{- macros.input('username') }}\n{% endblock %}\n\n{% block bar %}\n    {{- macros.input('username') }}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--TEMPLATE(macros)--\n{% macro input(name) %}\n    <input name=\"{{ name }}\" value=\"local\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n<input name=\"username\" value=\"local\">\n\n\n<input name=\"username\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_macro_in_a_macro.test",
    "content": "--TEST--\n\"import\" tag with syntax error\n--TEMPLATE--\n{% import _self as foo %}\n\n{{ foo.foo() }}\n\n{% macro foo() %}\n    {{ foo.another() }}\n{% endmacro %}\n\n{% macro another() %}\n    OK\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nOK\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_macros_in_parent.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import \"macros\" as m %}\n\n{{ m.hello() }}\n--TEMPLATE(macros)--\n{% extends \"parent\" %}\n--TEMPLATE(parent)--\n{% macro hello() %}\n    Test\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nTest\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_nested_blocks.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% block foo %}\n    {%- import _self as lmacros %}\n\n    {% block bar %}\n        {{- lmacros.input('username') }}\n    {% endblock %}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"lmacros\" does not exist in \"index.twig\" at line 6.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{%- import _self as macros %}\n\n{% block foo %}\n    {% block bar %}\n        {{- macros.input('username') }}\n    {% endblock %}\n{% endblock %}\n\n{% macro input(name) -%}\n    <input name=\"{{ name }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n<input name=\"username\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_reserved_names.test",
    "content": "--TEST--\n\"import\" tag\n--TEMPLATE--\n{% import _self as true %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: You cannot assign a value to \"true\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_same_parent_and_child.test",
    "content": "--TEST--\n\"import\" tag\n--TEMPLATE--\n{% extends \"parent\" %}\n\n{% macro anotherThing() -%}\n    Do it too\n{% endmacro %}\n\n{% import _self as macros %}\n{% block content %}\n    {{ parent() }}\n    {{ macros.anotherThing() }}\n{% endblock %}\n--TEMPLATE(parent)--\n{% macro thing() %}\n    Do it\n{% endmacro %}\n\n{% import _self as macros %}\n{% block content %}\n    {{ macros.thing() }}\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nDo it\n\n\n    Do it too\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_self_parent.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% extends \"parent\" %}\n{% import _self as me %}\n\n{% block test %}\n    {{ me.hello() }}\n{% endblock test %}\n--TEMPLATE(parent)--\n{% import _self as me %}\n\n{% block test %}\nHello\n{% endblock test %}\n\n{% macro hello() %}\n    Test\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nTest\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/import_syntax_error.test",
    "content": "--TEST--\n\"import\" tag with reserved name\n--TEMPLATE--\n{% import 'forms.twig' %}\n\n{{ macros.parent() }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Unexpected token \"end of statement block\" (\"name\" expected with value \"as\") in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/named_arguments.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import _self as forms %}\n\n{{ forms.input(size: 10, name: 'username') }}\n\n{% macro input(name, value, type, size) %}\n  <input type=\"{{ type|default(\"text\") }}\" name=\"{{ name }}\" value=\"{{ value|e|default('') }}\" size=\"{{ size|default(20) }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n  <input type=\"text\" name=\"username\" value=\"\" size=\"10\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/self_import.test",
    "content": "--TEST--\n\"macro\" tag\n--TEMPLATE--\n{% import _self as forms %}\n\n{{ forms.input('username') }}\n{{ forms.input('password', null, 'password', 1) }}\n\n{% macro input(name, value, type, size) %}\n  <input type=\"{{ type|default(\"text\") }}\" name=\"{{ name }}\" value=\"{{ value|e|default('') }}\" size=\"{{ size|default(20) }}\">\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n  <input type=\"text\" name=\"username\" value=\"\" size=\"20\">\n\n  <input type=\"password\" name=\"password\" value=\"\" size=\"1\">\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/special_chars.test",
    "content": "--TEST--\n\"§\" as a macro name\n--TEMPLATE--\n{% import _self as macros %}\n\n{{ macros.§('foo') }}\n\n{% macro §(foo) %}\n  §{{ foo }}§\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\n§foo§\n"
  },
  {
    "path": "tests/Fixtures/tags/macro/super_globals.test",
    "content": "--TEST--\nSuper globals as macro arguments\n--TEMPLATE--\n{% import _self as macros %}\n\n{{ macros.foo('foo') }}\n\n{% macro foo(GET) %}\n    {{ GET }}\n{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/sandbox/array.legacy.test",
    "content": "--TEST--\nsandbox tag\n--DEPRECATION--\nSince twig/twig 3.15: The \"sandbox\" tag is deprecated in \"index.twig\" at line 2.\n--TEMPLATE--\n{%- sandbox %}\n    {%- include \"foo.twig\" %}\n{%- endsandbox %}\n--TEMPLATE(foo.twig)--\n{{ [a][0] }}\n{{ dump([a][0]) }}\n--DATA--\nreturn ['a' => 'b']\n--CONFIG--\nreturn ['autoescape' => false, 'debug' => true]\n--EXPECT--\nb\nstring(1) \"b\"\n"
  },
  {
    "path": "tests/Fixtures/tags/sandbox/not_valid1.legacy.test",
    "content": "--TEST--\nsandbox tag\n--TEMPLATE--\n{%- sandbox %}\n    {%- include \"foo.twig\" %}\n    a\n{%- endsandbox %}\n--TEMPLATE(foo.twig)--\nfoo\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Only \"include\" tags are allowed within a \"sandbox\" section in \"index.twig\" at line 4.\n"
  },
  {
    "path": "tests/Fixtures/tags/sandbox/not_valid2.legacy.test",
    "content": "--TEST--\nsandbox tag\n--TEMPLATE--\n{%- sandbox %}\n    {%- include \"foo.twig\" %}\n\n    {% if 1 %}\n        {%- include \"foo.twig\" %}\n    {% endif %}\n{%- endsandbox %}\n--TEMPLATE(foo.twig)--\nfoo\n--EXCEPTION--\nTwig\\Error\\SyntaxError: Only \"include\" tags are allowed within a \"sandbox\" section in \"index.twig\" at line 5.\n"
  },
  {
    "path": "tests/Fixtures/tags/sandbox/simple.legacy.test",
    "content": "--TEST--\nsandbox tag\n--DEPRECATION--\nSince twig/twig 3.15: The \"sandbox\" tag is deprecated in \"index.twig\" at line 2.\nSince twig/twig 3.15: The \"sandbox\" tag is deprecated in \"index.twig\" at line 6.\nSince twig/twig 3.15: The \"sandbox\" tag is deprecated in \"index.twig\" at line 11.\n--TEMPLATE--\n{%- sandbox %}\n    {%- include \"foo.twig\" %}\n{%- endsandbox %}\n\n{%- sandbox %}\n    {%- include \"foo.twig\" %}\n    {%- include \"foo.twig\" %}\n{%- endsandbox %}\n\n{%- sandbox %}{% include \"foo.twig\" %}{% endsandbox %}\n--TEMPLATE(foo.twig)--\nfoo\n--DATA--\nreturn []\n--EXPECT--\nfoo\nfoo\nfoo\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/set/basic.test",
    "content": "--TEST--\n\"set\" tag\n--TEMPLATE--\n{% set foo = 'foo' %}\n{% set bar = 'foo<br />' %}\n\n{{ foo }}\n{{ bar }}\n\n{% set foo, bar = 'foo', 'bar' %}\n\n{{ foo }}{{ bar }}\n--DATA--\nreturn []\n--EXPECT--\nfoo\nfoo&lt;br /&gt;\n\n\nfoobar\n"
  },
  {
    "path": "tests/Fixtures/tags/set/capture-empty.test",
    "content": "--TEST--\n\"set\" tag block empty capture\n--TEMPLATE--\n{% set foo %}{% endset %}\n\n{% if foo %}FAIL{% endif %}\n--DATA--\nreturn []\n--EXPECT--\n"
  },
  {
    "path": "tests/Fixtures/tags/set/capture.test",
    "content": "--TEST--\n\"set\" tag block capture\n--TEMPLATE--\n{% set foo %}f<br />o<br />o{% endset %}\n\n{{ foo }}\n--DATA--\nreturn []\n--EXPECT--\nf<br />o<br />o\n"
  },
  {
    "path": "tests/Fixtures/tags/set/capture_scope.test",
    "content": "--TEST--\n\"set\" tag block capture\n--TEMPLATE--\n{% set foo %}{{ foo }}{% endset %}\n\n{{ foo }}\n--DATA--\nreturn ['foo' => 'foo']\n--EXPECT--\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/set/expression.test",
    "content": "--TEST--\n\"set\" tag\n--TEMPLATE--\n{% set foo, bar = 'foo' ~ 'bar', 'bar' ~ 'foo' %}\n\n{{ foo }}\n{{ bar }}\n--DATA--\nreturn []\n--EXPECT--\nfoobar\nbarfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/set/inheritance.test",
    "content": "--TEST--\n\"set\" tag with inheritance\n--TEMPLATE--\n{% extends \"layout.twig\" %}\n\n{% set bar %}bar{% endset %}\n\n{% block var_from_child %}\n    {{- bar -}}\n{% endblock %}\n--TEMPLATE(layout.twig)--\n{% set foo %}foo{% endset %}\n\n{% block var_from_layout %}\n    {{- foo -}}\n{% endblock %}\n\n{% block var_from_child %}\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\nbar\n"
  },
  {
    "path": "tests/Fixtures/tags/set/inheritance_overriding.test",
    "content": "--TEST--\n\"set\" tag with inheritance\n--TEMPLATE--\n{% extends \"layout.twig\" %}\n\n{% set foo %}bar{% endset %}\n\n{% block var_from_child %}\n    {{- foo -}}\n{% endblock %}\n--TEMPLATE(layout.twig)--\n{% set foo %}foo{% endset %}\n\n{% block var_from_layout %}\n    {{- foo -}}\n{% endblock %}\n\n{% block var_from_child %}\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/set/mutating.test",
    "content": "--TEST--\n\"set\" tag\n--TEMPLATE--\n{% set foo = \"foo\" %}\n\n{% set bar %}\n    {%- set foo = \"bar\" -%}\n    bar\n{% endset %}\n\n{{ foo }}\n{{ bar }}\n--DATA--\nreturn []\n--EXPECT--\nbar\nbar\n"
  },
  {
    "path": "tests/Fixtures/tags/set/reserved_names.test",
    "content": "--TEST--\n\"set\" tag\n--TEMPLATE--\n{% set true = 'foo' %}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: You cannot assign a value to \"true\" in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/special_chars.test",
    "content": "--TEST--\n\"§\" custom tag\n--TEMPLATE--\n{% § %}\n--DATA--\nreturn []\n--EXPECT--\n§\n"
  },
  {
    "path": "tests/Fixtures/tags/use/aliases.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use \"blocks.twig\" with content as foo %}\n\n{{ block('foo') }}\n--TEMPLATE(blocks.twig)--\n{% block content 'foo' %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/use/basic.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use \"blocks.twig\" %}\n\n{{ block('content') }}\n--TEMPLATE(blocks.twig)--\n{% block content 'foo' %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/use/deep.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use \"foo.twig\" %}\n\n{{ block('content') }}\n{{ block('foo') }}\n{{ block('bar') }}\n--TEMPLATE(foo.twig)--\n{% use \"bar.twig\" %}\n\n{% block content 'foo' %}\n{% block foo 'foo' %}\n--TEMPLATE(bar.twig)--\n{% block content 'bar' %}\n{% block bar 'bar' %}\n--DATA--\nreturn []\n--EXPECT--\nfoo\nfoo\nbar\n"
  },
  {
    "path": "tests/Fixtures/tags/use/deep_empty.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use \"foo.twig\" %}\n--TEMPLATE(foo.twig)--\n{% use \"bar.twig\" %}\n--TEMPLATE(bar.twig)--\n--DATA--\nreturn []\n--EXPECT--\n"
  },
  {
    "path": "tests/Fixtures/tags/use/inheritance.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use \"parent.twig\" %}\n\n{{ block('container') }}\n--TEMPLATE(parent.twig)--\n{% use \"ancestor.twig\" %}\n\n{% block sub_container %}\n    <div class=\"overridden_sub_container\">overridden sub_container</div>\n{% endblock %}\n--TEMPLATE(ancestor.twig)--\n{% block container %}\n    <div class=\"container\">{{ block('sub_container') }}</div>\n{% endblock %}\n\n{% block sub_container %}\n    <div class=\"sub_container\">sub_container</div>\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\n<div class=\"container\">    <div class=\"overridden_sub_container\">overridden sub_container</div>\n</div>\n"
  },
  {
    "path": "tests/Fixtures/tags/use/inheritance2.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use \"ancestor.twig\" %}\n{% use \"parent.twig\" %}\n\n{{ block('container') }}\n--TEMPLATE(parent.twig)--\n{% block sub_container %}\n    <div class=\"overridden_sub_container\">overridden sub_container</div>\n{% endblock %}\n--TEMPLATE(ancestor.twig)--\n{% block container %}\n    <div class=\"container\">{{ block('sub_container') }}</div>\n{% endblock %}\n\n{% block sub_container %}\n    <div class=\"sub_container\">sub_container</div>\n{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\n<div class=\"container\">    <div class=\"overridden_sub_container\">overridden sub_container</div>\n</div>\n"
  },
  {
    "path": "tests/Fixtures/tags/use/multiple.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use \"foo.twig\" %}\n{% use \"bar.twig\" %}\n\n{{ block('content') }}\n{{ block('foo') }}\n{{ block('bar') }}\n--TEMPLATE(foo.twig)--\n{% block content 'foo' %}\n{% block foo 'foo' %}\n--TEMPLATE(bar.twig)--\n{% block content 'bar' %}\n{% block bar 'bar' %}\n--DATA--\nreturn []\n--EXPECT--\nbar\nfoo\nbar\n"
  },
  {
    "path": "tests/Fixtures/tags/use/multiple_aliases.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use \"foo.twig\" with content as foo_content %}\n{% use \"bar.twig\" %}\n\n{{ block('content') }}\n{{ block('foo') }}\n{{ block('bar') }}\n{{ block('foo_content') }}\n--TEMPLATE(foo.twig)--\n{% block content 'foo' %}\n{% block foo 'foo' %}\n--TEMPLATE(bar.twig)--\n{% block content 'bar' %}\n{% block bar 'bar' %}\n--DATA--\nreturn []\n--EXPECT--\nbar\nfoo\nbar\nfoo\n"
  },
  {
    "path": "tests/Fixtures/tags/use/parent_block.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use 'file2.html.twig' with foobar as base_base_foobar %}\n{% block foobar %}\n    {{- block('base_base_foobar') -}}\n    Content of block (second override)\n{% endblock foobar %}\n--TEMPLATE(file2.html.twig)--\n{% use 'file1.html.twig' with foobar as base_foobar %}\n{% block foobar %}\n    {{- block('base_foobar') -}}\n    Content of block (first override)\n{% endblock foobar %}\n--TEMPLATE(file1.html.twig)--\n{% block foobar -%}\n    Content of block\n{% endblock foobar %}\n--DATA--\nreturn []\n--EXPECT--\nContent of block\nContent of block (first override)\nContent of block (second override)\n"
  },
  {
    "path": "tests/Fixtures/tags/use/parent_block2.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use 'file2.html.twig'%}\n{% block foobar %}\n    {{- parent() -}}\n    Content of block (second override)\n{% endblock foobar %}\n--TEMPLATE(file2.html.twig)--\n{% use 'file1.html.twig' %}\n{% block foobar %}\n    {{- parent() -}}\n    Content of block (first override)\n{% endblock foobar %}\n--TEMPLATE(file1.html.twig)--\n{% block foobar -%}\n    Content of block\n{% endblock foobar %}\n--DATA--\nreturn []\n--EXPECT--\nContent of block\nContent of block (first override)\nContent of block (second override)\n"
  },
  {
    "path": "tests/Fixtures/tags/use/parent_block3.test",
    "content": "--TEST--\n\"use\" tag\n--TEMPLATE--\n{% use 'file2.html.twig' %}\n{% use 'file1.html.twig' with foo %}\n{% block foo %}\n    {{- parent() -}}\n    Content of foo (second override)\n{% endblock foo %}\n{% block bar %}\n    {{- parent() -}}\n    Content of bar (second override)\n{% endblock bar %}\n--TEMPLATE(file2.html.twig)--\n{% use 'file1.html.twig' %}\n{% block foo %}\n    {{- parent() -}}\n    Content of foo (first override)\n{% endblock foo %}\n{% block bar %}\n    {{- parent() -}}\n    Content of bar (first override)\n{% endblock bar %}\n--TEMPLATE(file1.html.twig)--\n{% block foo -%}\n    Content of foo\n{% endblock foo %}\n{% block bar -%}\n    Content of bar\n{% endblock bar %}\n--DATA--\nreturn []\n--EXPECT--\nContent of foo\nContent of foo (first override)\nContent of foo (second override)\nContent of bar\nContent of bar (second override)\n"
  },
  {
    "path": "tests/Fixtures/tags/use/use_aliased_block_overridden.test",
    "content": "--TEST--\n\"use\" tag with an overridden block that is aliased\n--TEMPLATE--\n{% use \"blocks.twig\" with bar as baz %}\n\n{% block foo %}{{ parent() }}+{% endblock %}\n\n{% block baz %}{{ parent() }}+{% endblock %}\n\n{{ block('foo') }}\n{{ block('baz') }}\n--TEMPLATE(blocks.twig)--\n{% block foo %}Foo{% endblock %}\n{% block bar %}Bar{% endblock %}\n--DATA--\nreturn []\n--EXPECT--\nFoo+\nBar+\nFoo+\nBar+\n"
  },
  {
    "path": "tests/Fixtures/tags/use/use_with_parent.test",
    "content": "--TEST--\n\"use\" tag with a parent block\n--TEMPLATE--\n{% extends \"parent.twig\" %}\n\n{% use 'blocks.twig' %}\n\n{% block body %}\n    {{ parent() -}}\n    CHILD\n    {{ block('content') }}\n{% endblock %}\n--TEMPLATE(parent.twig)--\n{% block body %}\n    PARENT\n{% endblock %}\n--TEMPLATE(blocks.twig)--\n{% block content 'BLOCK' %}\n--DATA--\nreturn []\n--EXPECT--\nPARENT\nCHILD\n    BLOCK\n"
  },
  {
    "path": "tests/Fixtures/tags/verbatim/basic.test",
    "content": "--TEST--\n\"verbatim\" tag\n--TEMPLATE--\n{% verbatim %}\n{{ foo }}\n{% endverbatim %}\n--DATA--\nreturn []\n--EXPECT--\n{{ foo }}\n"
  },
  {
    "path": "tests/Fixtures/tags/verbatim/whitespace_control.test",
    "content": "--TEST--\n\"verbatim\" tag\n--TEMPLATE--\n1***\n\n{%- verbatim %}\n    {{ 'bla' }}\n{% endverbatim %}\n\n1***\n2***\n\n{%- verbatim -%}\n    {{ 'bla' }}\n{% endverbatim %}\n\n2***\n3***\n\n{%- verbatim -%}\n    {{ 'bla' }}\n{% endverbatim -%}\n\n3***\n4***\n\n{%- verbatim -%}\n    {{ 'bla' }}\n{%- endverbatim %}\n\n4***\n5***\n\n{%- verbatim -%}\n    {{ 'bla' }}\n{%- endverbatim -%}\n\n5***\n--DATA--\nreturn []\n--EXPECT--\n1***\n    {{ 'bla' }}\n\n\n1***\n2***{{ 'bla' }}\n\n\n2***\n3***{{ 'bla' }}\n3***\n4***{{ 'bla' }}\n\n4***\n5***{{ 'bla' }}5***\n"
  },
  {
    "path": "tests/Fixtures/tags/with/basic.test",
    "content": "--TEST--\n\"with\" tag\n--TEMPLATE--\n{% with %}\n    {% set bar = 'BAZ' %}\n    {{ foo }}{{ bar }}\n{% endwith %}\n{{ foo }}{{ bar }}\n--DATA--\nreturn ['foo' => 'foo', 'bar' => 'bar']\n--EXPECT--\nfooBAZ\nfoobar\n"
  },
  {
    "path": "tests/Fixtures/tags/with/expression.test",
    "content": "--TEST--\n\"with\" tag with expression\n--TEMPLATE--\n{% with {foo: 'foo', bar: 'BAZ'} %}\n    {{ foo }}{{ bar }}\n{% endwith %}\n--DATA--\nreturn ['foo' => 'baz']\n--EXPECT--\nfooBAZ\n"
  },
  {
    "path": "tests/Fixtures/tags/with/globals.test",
    "content": "--TEST--\n\"with\" tag\n--TEMPLATE--\n{% with [] only %}\n    {{ global }}\n{% endwith %}\n--DATA--\nreturn []\n--EXPECT--\nglobal\n"
  },
  {
    "path": "tests/Fixtures/tags/with/iterable.test",
    "content": "--TEST--\n\"with\" tag with an iterable expression\n--TEMPLATE--\n{% with vars %}\n    {{ foo }}{{ bar }}\n{% endwith %}\n--DATA--\nreturn ['vars' => new ArrayObject(['foo' => 'baz', 'bar' => 'qux'])]\n--EXPECT--\nbazqux\n"
  },
  {
    "path": "tests/Fixtures/tags/with/nested.test",
    "content": "--TEST--\nnested \"with\" tags\n--TEMPLATE--\n{% set foo, bar = 'foo', 'bar' %}\n{% with {bar: 'BAZ'} %}\n    {% with {foo: 'FOO'} %}\n        {{ foo }}{{ bar }}\n    {% endwith %}\n{% endwith %}\n{{ foo }}{{ bar }}\n--DATA--\nreturn []\n--EXPECT--\nFOOBAZ\n    foobar\n"
  },
  {
    "path": "tests/Fixtures/tags/with/with_no_mapping.test",
    "content": "--TEST--\n\"with\" tag with an expression that is not a mapping\n--TEMPLATE--\n{% with vars %}\n    {{ foo }}{{ bar }}\n{% endwith %}\n--DATA--\nreturn ['vars' => 'no-mapping']\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variables passed to the \"with\" tag must be a mapping in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tags/with/with_only.test",
    "content": "--TEST--\n\"with\" tag with expression and only\n--TEMPLATE--\n{% with {foo: 'foo', bar: 'BAZ'} only %}\n    {{ foo }}{{ bar }}{{ baz }}\n{% endwith %}\n--DATA--\nreturn ['foo' => 'baz', 'baz' => 'baz']\n--EXCEPTION--\nTwig\\Error\\RuntimeError: Variable \"baz\" does not exist in \"index.twig\" at line 3.\n"
  },
  {
    "path": "tests/Fixtures/tests/array.test",
    "content": "--TEST--\narray index test\n--TEMPLATE--\n{% for key, value in days %}\n{{ key }}\n{% endfor %}\n--DATA--\nreturn ['days' => [\n    1  => ['money' => 9],\n    2  => ['money' => 21],\n    3  => ['money' => 38],\n    4  => ['money' => 6],\n    18 => ['money' => 6],\n    19 => ['money' => 3],\n    31 => ['money' => 11],\n]]\n--EXPECT--\n1\n2\n3\n4\n18\n19\n31\n"
  },
  {
    "path": "tests/Fixtures/tests/constant.test",
    "content": "--TEST--\n\"const\" test\n--TEMPLATE--\n{{ 8 is constant('E_NOTICE') ? 'ok' : 'no' }}\n{{ 'bar' is constant('Twig\\\\Tests\\\\TwigTestFoo::BAR_NAME') ? 'ok' : 'no' }}\n{{ value is constant('Twig\\\\Tests\\\\TwigTestFoo::BAR_NAME') ? 'ok' : 'no' }}\n{{ 2 is constant('ARRAY_AS_PROPS', object) ? 'ok' : 'no' }}\n--DATA--\nreturn ['value' => 'bar', 'object' => new \\ArrayObject(['hi'])]\n--EXPECT--\nok\nok\nok\nok"
  },
  {
    "path": "tests/Fixtures/tests/defined.test",
    "content": "--TEST--\n\"defined\" test\n--TEMPLATE--\n{{ definedVar                     is     defined ? 'ok' : 'ko' }}\n{{ definedVar                     is not defined ? 'ko' : 'ok' }}\n{{ undefinedVar                   is     defined ? 'ko' : 'ok' }}\n{{ undefinedVar                   is not defined ? 'ok' : 'ko' }}\n{{ zeroVar                        is     defined ? 'ok' : 'ko' }}\n{{ nullVar                        is     defined ? 'ok' : 'ko' }}\n{{ nested.definedVar              is     defined ? 'ok' : 'ko' }}\n{{ nested['definedVar']           is     defined ? 'ok' : 'ko' }}\n{{ nested.definedVar              is not defined ? 'ko' : 'ok' }}\n{{ nested.undefinedVar            is     defined ? 'ko' : 'ok' }}\n{{ nested['undefinedVar']         is     defined ? 'ko' : 'ok' }}\n{{ nested.undefinedVar            is not defined ? 'ok' : 'ko' }}\n{{ nested.zeroVar                 is     defined ? 'ok' : 'ko' }}\n{{ nested.nullVar                 is     defined ? 'ok' : 'ko' }}\n{{ nested.definedArray.0          is     defined ? 'ok' : 'ko' }}\n{{ nested['definedArray'][0]      is     defined ? 'ok' : 'ko' }}\n{{ object.foo                     is     defined ? 'ok' : 'ko' }}\n{{ object.undefinedMethod         is     defined ? 'ko' : 'ok' }}\n{{ object.getFoo()                is     defined ? 'ok' : 'ko' }}\n{{ object.getFoo('a')             is     defined ? 'ok' : 'ko' }}\n{{ object.undefinedMethod()       is     defined ? 'ko' : 'ok' }}\n{{ object.undefinedMethod('a')    is     defined ? 'ko' : 'ok' }}\n{{ object.self.foo                is     defined ? 'ok' : 'ko' }}\n{{ object.self.undefinedMethod    is     defined ? 'ko' : 'ok' }}\n{{ object.undefinedMethod.self    is     defined ? 'ko' : 'ok' }}\n{{ 0                              is     defined ? 'ok' : 'ko' }}\n{{ \"foo\"                          is     defined ? 'ok' : 'ko' }}\n{{ true                           is     defined ? 'ok' : 'ko' }}\n{{ false                          is     defined ? 'ok' : 'ko' }}\n{{ null                           is     defined ? 'ok' : 'ko' }}\n{{ [1, 2]                         is     defined ? 'ok' : 'ko' }}\n{{ { foo: \"bar\" }                 is     defined ? 'ok' : 'ko' }}\n--DATA--\nreturn [\n    'definedVar' => 'defined',\n    'zeroVar'    => 0,\n    'nullVar'    => null,\n    'nested'      => [\n        'definedVar'   => 'defined',\n        'zeroVar'      => 0,\n        'nullVar'      => null,\n        'definedArray' => [0],\n    ],\n    'object' => new Twig\\Tests\\TwigTestFoo(),\n]\n--EXPECT--\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\n--DATA--\nreturn [\n    'definedVar' => 'defined',\n    'zeroVar'    => 0,\n    'nullVar'    => null,\n    'nested'      => [\n        'definedVar'   => 'defined',\n        'zeroVar'      => 0,\n        'nullVar'      => null,\n        'definedArray' => [0],\n    ],\n    'object' => new Twig\\Tests\\TwigTestFoo(),\n]\n--CONFIG--\nreturn ['strict_variables' => false]\n--EXPECT--\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/tests/defined_for_attribute.legacy.test",
    "content": "--TEST--\n\"defined\" support for attribute\n--TEMPLATE--\n{{ attribute(nested, \"definedVar\")     is     defined ? 'ok' : 'ko' }}\n{{ attribute(nested, \"undefinedVar\")   is not defined ? 'ok' : 'ko' }}\n{{ attribute(nested, definedVarName)   is     defined ? 'ok' : 'ko' }}\n{{ attribute(nested, undefinedVarName) is not defined ? 'ok' : 'ko' }}\n--DATA--\nreturn [\n    'nested' => [\n        'definedVar' => 'defined',\n    ],\n    'definedVarName' => 'definedVar',\n    'undefinedVarName' => 'undefinedVar',\n]\n--EXPECT--\nok\nok\nok\nok\n--DATA--\nreturn [\n    'nested' => [\n        'definedVar' => 'defined',\n    ],\n    'definedVarName' => 'definedVar',\n    'undefinedVarName' => 'undefinedVar',\n]\n--CONFIG--\nreturn ['strict_variables' => false]\n--EXPECT--\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/tests/defined_for_attribute.test",
    "content": "--TEST--\n\"defined\" support for dynamic attribute\n--TEMPLATE--\n{{ nested.(\"definedVar\")     is     defined ? 'ok' : 'ko' }}\n{{ nested.(\"undefinedVar\")   is not defined ? 'ok' : 'ko' }}\n{{ nested.(definedVarName)   is     defined ? 'ok' : 'ko' }}\n{{ nested.(undefinedVarName) is not defined ? 'ok' : 'ko' }}\n--DATA--\nreturn [\n    'nested' => [\n        'definedVar' => 'defined',\n    ],\n    'definedVarName' => 'definedVar',\n    'undefinedVarName' => 'undefinedVar',\n]\n--EXPECT--\nok\nok\nok\nok\n--DATA--\nreturn [\n    'nested' => [\n        'definedVar' => 'defined',\n    ],\n    'definedVarName' => 'definedVar',\n    'undefinedVarName' => 'undefinedVar',\n]\n--CONFIG--\nreturn ['strict_variables' => false]\n--EXPECT--\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/tests/defined_for_blocks.test",
    "content": "--TEST--\n\"defined\" support for blocks\n--TEMPLATE--\n{% extends 'parent' %}\n{% block icon %}icon{% endblock %}\n{% block body %}\n    {{ parent() }}\n    {{ block('foo') is defined ? 'ok' : 'ko' }}\n    {{ block('footer') is defined ? 'ok' : 'ko' }}\n    {{ block('icon') is defined ? 'ok' : 'ko' }}\n    {{ block('block1') is defined ? 'ok' : 'ko' }}\n    {%- embed 'embed' %}\n        {% block content %}content{% endblock %}\n    {% endembed %}\n{% endblock %}\n{% use 'blocks' %}\n--TEMPLATE(parent)--\n{% block body %}\n  {{ block('icon') is defined ? 'ok' : 'ko' -}}\n{% endblock %}\n{% block footer %}{% endblock %}\n--TEMPLATE(embed)--\n{{ block('icon') is defined ? 'ok' : 'ko' }}\n{{ block('content') is defined ? 'ok' : 'ko' }}\n{{ block('block1') is defined ? 'ok' : 'ko' }}\n--TEMPLATE(blocks)--\n{% block block1 %}{%endblock %}\n--DATA--\nreturn []\n--EXPECT--\nok\n    ko\n    ok\n    ok\n    ok\nko\nok\nko\n"
  },
  {
    "path": "tests/Fixtures/tests/defined_for_blocks_with_template.test",
    "content": "--TEST--\n\"defined\" support for blocks with a template argument\n--TEMPLATE--\n{{ block('foo', 'included.twig') is defined ? 'ok' : 'ko' }}\n{{ block('foo', included_loaded) is defined ? 'ok' : 'ko' }}\n{{ block('foo', included_loaded_internal) is defined ? 'ok' : 'ko' }}\n--TEMPLATE(included.twig)--\n{% block foo %}FOO{% endblock %}\n--DATA--\nreturn [\n    'included_loaded' => $twig->load('included.twig'),\n    'included_loaded_internal' => $twig->load('included.twig'),\n]\n--EXPECT--\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/tests/defined_for_constants.test",
    "content": "--TEST--\n\"defined\" support for constants\n--TEMPLATE--\n{{ constant('DATE_W3C') is defined ? 'ok' : 'ko' }}\n{{ constant('ARRAY_AS_PROPS', object) is defined ? 'ok' : 'ko' }}\n{{ constant('FOOBAR') is not defined ? 'ok' : 'ko' }}\n{{ constant('FOOBAR', object) is not defined ? 'ok' : 'ko' }}\n--DATA--\nreturn ['expect' => DATE_W3C, 'object' => new \\ArrayObject(['hi'])]\n--EXPECT--\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/tests/defined_for_macros.test",
    "content": "--TEST--\n\"defined\" support for macros\n--TEMPLATE--\n{% extends 'macros.twig' %}\n\n{% import 'macros.twig' as macros_ext %}\n{% from 'macros.twig' import lol, baz %}\n{% import _self as macros %}\n{% from _self import hello, bar %}\n\n{% block content %}\n    {{~ macros.hello is defined ? 'OK' : 'KO' }}\n    {{~ macros.hello() is defined ? 'OK' : 'KO' }}\n\n    {{~ macros_ext.lol is defined ? 'OK' : 'KO' }}\n    {{~ macros_ext.lol() is defined ? 'OK' : 'KO' }}\n\n    {{~ macros.foo is not defined ? 'OK' : 'KO' }}\n    {{~ macros.foo() is not defined ? 'OK' : 'KO' }}\n\n    {{~ macros_ext.hello is not defined ? 'OK' : 'KO' }}\n    {{~ macros_ext.hello() is not defined ? 'OK' : 'KO' }}\n\n    {{~ hello is defined ? 'OK' : 'KO' }}\n    {{~ hello() is defined ? 'OK' : 'KO' }}\n\n    {{~ lol is defined ? 'OK' : 'KO' }}\n    {{~ lol() is defined ? 'OK' : 'KO' }}\n\n    {{~ baz is not defined ? 'OK' : 'KO' }}\n    {{~ baz() is not defined ? 'OK' : 'KO' }}\n\n    {{~ _self.hello is defined ? 'OK' : 'KO' }}\n    {{~ _self.hello() is defined ? 'OK' : 'KO' }}\n\n    {{~ _self.bar is not defined ? 'OK' : 'KO' }}\n    {{~ _self.bar() is not defined ? 'OK' : 'KO' }}\n\n    {{~ _self.lol is defined ? 'OK' : 'KO' }}\n    {{~ _self.lol() is defined ? 'OK' : 'KO' }}\n{% endblock %}\n\n{% macro hello(name) %}{% endmacro %}\n--TEMPLATE(macros.twig)--\n{% block content %}\n{% endblock %}\n\n{% macro lol(name) -%}{% endmacro %}\n--DATA--\nreturn []\n--EXPECT--\nOK\nOK\n\nOK\nOK\n\nOK\nOK\n\nOK\nOK\n\nOK\nOK\n\nOK\nOK\n\nOK\nOK\n\nOK\nOK\n\nOK\nOK\n\nOK\nOK\n"
  },
  {
    "path": "tests/Fixtures/tests/defined_on_complex_expr.test",
    "content": "--TEST--\n\"defined\" support for \"complex\" expressions\n--TEMPLATE--\n{{ (1 + 2) is defined ? 'ok' : 'ko' }}\n--DATA--\nreturn []\n--EXCEPTION--\nTwig\\Error\\SyntaxError: The \"defined\" test only works with simple variables in \"index.twig\" at line 2.\n"
  },
  {
    "path": "tests/Fixtures/tests/dynamic_test.test",
    "content": "--TEST--\ndynamic test\n--TEMPLATE--\n{{ 'bar' is test_bar ? '1' :'0' }}\n{{ 'foo' is test_foo ? '1' :'0' }}\n{{ 'bar' is test_foo ? '1' :'0' }}\n{{ 'foo' is test_bar ? '1' :'0' }}\n--DATA--\nreturn []\n--EXPECT--\n1\n1\n0\n0\n"
  },
  {
    "path": "tests/Fixtures/tests/empty.test",
    "content": "--TEST--\n\"empty\" test\n--TEMPLATE--\n{{ string_empty is empty ? 'ok' : 'ko' }}\n{{ string_zero is empty ? 'ko' : 'ok' }}\n{{ value_null is empty ? 'ok' : 'ko' }}\n{{ value_false is empty ? 'ok' : 'ko' }}\n{{ value_int_zero is empty ? 'ko' : 'ok' }}\n{{ array_empty is empty ? 'ok' : 'ko' }}\n{{ array_not_empty is empty ? 'ko' : 'ok' }}\n{{ magically_callable is empty ? 'ko' : 'ok' }}\n{{ countable_empty is empty ? 'ok' : 'ko' }}\n{{ countable_not_empty is empty ? 'ko' : 'ok' }}\n{{ tostring_empty is empty ? 'ok' : 'ko' }}\n{{ tostring_not_empty is empty ? 'ko' : 'ok' }}\n{{ markup_empty is empty ? 'ok' : 'ko' }}\n{{ markup_not_empty is empty ? 'ko' : 'ok' }}\n{{ iterator is empty ? 'ko' : 'ok' }}\n{{ empty_iterator is empty ? 'ok' : 'ko' }}\n{{ callback_iterator is empty ? 'ko' : 'ok' }}\n{{ empty_callback_iterator is empty ? 'ok' : 'ko' }}\n--DATA--\nreturn [\n    'string_empty' => '', 'string_zero' => '0',\n    'value_null' => null, 'value_false' => false, 'value_int_zero' => 0,\n    'array_empty' => [], 'array_not_empty' => [1, 2],\n    'magically_callable' => new \\Twig\\Tests\\MagicCallStub(),\n    'countable_empty' => new \\Twig\\Tests\\CountableStub(0), 'countable_not_empty' => new \\Twig\\Tests\\CountableStub(2),\n    'tostring_empty' => new \\Twig\\Tests\\ToStringStub(''), 'tostring_not_empty' => new \\Twig\\Tests\\ToStringStub('0' /* edge case of using \"0\" as the string */),\n    'markup_empty' => new \\Twig\\Markup('', 'UTF-8'), 'markup_not_empty' => new \\Twig\\Markup('test', 'UTF-8'),\n    'iterator' => $iter = new \\ArrayIterator(['bar', 'foo']),\n    'empty_iterator' => new \\ArrayIterator(),\n    'callback_iterator' => new \\CallbackFilterIterator($iter, function ($el) { return true; }),\n    'empty_callback_iterator' => new \\CallbackFilterIterator($iter, function ($el) { return false; }),\n]\n--EXPECT--\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/tests/even.test",
    "content": "--TEST--\n\"even\" test\n--TEMPLATE--\n{{ 1 is even ? 'ko' : 'ok' }}\n{{ 2 is even ? 'ok' : 'ko' }}\n{{ 1 is not even ? 'ok' : 'ko' }}\n{{ 2 is not even ? 'ko' : 'ok' }}\n--DATA--\nreturn []\n--EXPECT--\nok\nok\nok\nok\n"
  },
  {
    "path": "tests/Fixtures/tests/in.test",
    "content": "--TEST--\nTwig supports the in operator\n--TEMPLATE--\n{{ bar in foo ? 'OK' : 'KO' }}\n{{ not (bar in foo) ? 'KO' : 'OK' }}\n{{ bar not in foo ? 'KO' : 'OK' }}\n{{ 'a' in bar ? 'OK' : 'KO' }}\n{{ 'c' not in bar ? 'OK' : 'KO' }}\n{{ '' in bar ? 'OK' : 'KO' }}\n{{ '' in '' ? 'OK' : 'KO' }}\n{{ '0' not in '' ? 'OK' : 'KO' }}\n{{ 'a' not in '0' ? 'OK' : 'KO' }}\n{{ '0' in '0' ? 'OK' : 'KO' }}\n\n{{ false in [0, 1] ? 'OK' : 'KO' }}\n{{ true in [0, 1] ? 'OK' : 'KO' }}\n{{ '0' in [0, 1] ? 'OK' : 'KO' }}\n{{ '0' in [1, 0] ? 'OK' : 'KO' }}\n{{ '' in [0, 1] ? 'KO' : 'OK' }}\n{{ '' in [1, 0] ? 'KO' : 'OK' }}\n{{ 0 in ['', 1] ? 'KO' : 'OK' }}\n{{ 0 in [1, ''] ? 'KO' : 'OK' }}\n\n{{ '' in 'foo' ? 'OK' : 'KO' }}\n{{ 0 in 'foo' ? 'KO' : 'OK' }}\n{{ false in 'foo' ? 'KO' : 'OK' }}\n{{ false in '100' ? 'KO' : 'OK' }}\n{{ true in '100' ? 'KO' : 'OK' }}\n\n{{ [] in [true, false] ? 'OK' : 'KO' }}\n{{ [] in [true, ''] ? 'KO' : 'OK' }}\n{{ [] in [true, []] ? 'OK' : 'KO' }}\n\n{{ resource ? 'OK' : 'KO' }}\n{{ resource in 'foo'~resource ? 'KO' : 'OK' }}\n{{ object in 'stdClass' ? 'KO' : 'OK' }}\n{{ [] in 'Array' ? 'KO' : 'OK' }}\n{{ dir_object in 'foo'~dir_object ? 'KO' : 'OK' }}\n\n{{ ''~resource in resource ? 'KO' : 'OK' }}\n{{ 'stdClass' in object ? 'KO' : 'OK' }}\n{{ 'Array' in [] ? 'KO' : 'OK' }}\n{{ ''~dir_object in dir_object ? 'KO' : 'OK' }}\n\n{{ resource in [''~resource] ? 'KO' : 'OK' }}\n{{ dir_object in [''~dir_object] ? 'KO' : 'OK' }}\n\n{{ 5 in 125 ? 'KO' : 'OK' }}\n{{ 5 in '125' ? 'OK' : 'KO' }}\n{{ '5' in 125 ? 'KO' : 'OK' }}\n{{ '5' in '125' ? 'OK' : 'KO' }}\n\n{{ 5.5 in 125.5 ? 'KO' : 'OK' }}\n{{ 5.5 in '125.5' ? 'OK' : 'KO' }}\n{{ '5.5' in 125.5 ? 'KO' : 'OK' }}\n\n{{ safe in ['foo', 'bar'] ? 'OK' : 'KO' }}\n{{ 'fo' in safe ? 'OK' : 'KO' }}\n\n{{ foo.not in ['not'] ? 'OK' : 'KO' }}\n{{ 'value'|not in ['not value'] ? 'OK' : 'KO' }}\n{{ foo.not not in ['not'] ? 'KO' : 'OK' }}\n{{ 'value'|not not in ['not value'] ? 'KO' : 'OK' }}\n{{ 'value'not in['not value'] ? 'OK' : 'KO' }}\n--DATA--\nreturn ['bar' => 'bar', 'foo' => ['bar' => 'bar', 'not' => 'not'], 'dir_object' => new \\SplFileInfo(__DIR__), 'object' => new \\stdClass(), 'resource' => opendir(__DIR__), 'safe' => new \\Twig\\Markup('foo', 'UTF-8')]\n--EXPECT--\nOK\nOK\nOK\nOK\nOK\nOK\nOK\nOK\nOK\nOK\n\nOK\nOK\nOK\nOK\nOK\nOK\nOK\nOK\n\nOK\nOK\nOK\nOK\nOK\n\nOK\nOK\nOK\n\nOK\nOK\nOK\nOK\nOK\n\nOK\nOK\nOK\nOK\n\nOK\nOK\n\nOK\nOK\nOK\nOK\n\nOK\nOK\nOK\n\nOK\nOK\n\nOK\nOK\nOK\nOK\nOK\n"
  },
  {
    "path": "tests/Fixtures/tests/in_with_iterator.test",
    "content": "--TEST--\nTwig supports the in operator when using iterators\n--TEMPLATE--\n{{ foo in iter ? 'OK' : 'KO' }}\n--DATA--\n$foo = new Twig\\Tests\\TwigTestFoo();\n$bar = new Twig\\Tests\\TwigTestFoo();\n\n$foo->position = $bar;\n$bar->position = $foo;\n\nreturn ['foo' => $foo, 'iter' => new \\ArrayIterator([$bar, $foo])]\n--EXPECT--\nOK\n"
  },
  {
    "path": "tests/Fixtures/tests/in_with_objects.test",
    "content": "--TEST--\nTwig supports the in operator when using objects\n--TEMPLATE--\n{% if object in object_list %}\nTRUE\n{% endif %}\n--DATA--\n$foo = new Twig\\Tests\\TwigTestFoo();\n$foo1 = new Twig\\Tests\\TwigTestFoo();\n\n$foo->position = $foo1;\n$foo1->position = $foo;\n\nreturn [\n    'object'      => $foo,\n    'object_list' => [$foo1, $foo],\n]\n--EXPECT--\nTRUE\n"
  },
  {
    "path": "tests/Fixtures/tests/iterable.test",
    "content": "--TEST--\n\"iterable\" test\n--TEMPLATE--\n{{ foo is iterable ? 'ok' : 'ko' }}\n{{ traversable is iterable ? 'ok' : 'ko' }}\n{{ obj is iterable ? 'ok' : 'ko' }}\n{{ val is iterable ? 'ok' : 'ko' }}\n--DATA--\nreturn [\n    'foo' => [],\n    'traversable' => new \\ArrayIterator([]),\n    'obj' => new \\stdClass(),\n    'val' => 'test',\n]\n--EXPECT--\nok\nok\nko\nko"
  },
  {
    "path": "tests/Fixtures/tests/mapping.test",
    "content": "--TEST--\n\"mapping\" test\n--TEMPLATE--\n{{ empty is mapping ? 'ok' : 'ko' }}\n{{ sequence is mapping ? 'ok' : 'ko' }}\n{{ empty_array_obj is mapping ? 'ok' : 'ko' }}\n{{ sequence_array_obj is mapping ? 'ok' : 'ko' }}\n{{ mapping_array_obj is mapping ? 'ok' : 'ko' }}\n{{ obj is mapping ? 'ok' : 'ko' }}\n{{ mapping is mapping ? 'ok' : 'ko' }}\n{{ string is mapping ? 'ok' : 'ko' }}\n--DATA--\nreturn [\n    'empty' => [],\n    'sequence' => [\n        'foo',\n        'bar',\n        'baz'\n    ],\n    'empty_array_obj' => new \\ArrayObject(),\n    'sequence_array_obj' => new \\ArrayObject(['foo', 'bar']),\n    'mapping_array_obj' => new \\ArrayObject(['foo' => 'bar']),\n    'obj' => new \\stdClass(),\n    'mapping' => [\n        'foo' => 'bar',\n        'bar' => 'foo'\n    ],\n    'string' => 'test',\n]\n--EXPECT--\nko\nko\nko\nko\nok\nok\nok\nko\n"
  },
  {
    "path": "tests/Fixtures/tests/null_coalesce.legacy.test",
    "content": "--TEST--\nTwig supports the ?? operator\n--DEPRECATION--\nSince twig/twig 3.15: As the \"??\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 4.\nSince twig/twig 3.15: As the \"??\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 5.\nSince twig/twig 3.15: As the \"??\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 6.\nSince twig/twig 3.15: As the \"??\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 7.\nSince twig/twig 3.15: As the \"??\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 10.\nSince twig/twig 3.15: As the \"~\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 9.\nSince twig/twig 3.15: As the \"~\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 11.\nSince twig/twig 3.15: As the \"??\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 16.\nSince twig/twig 3.15: As the \"~\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 15.\nSince twig/twig 3.15: As the \"~\" infix operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in \"index.twig\" at line 17.\n--TEMPLATE--\n{{ nope ?? nada ?? 'OK' -}} {# no deprecation as the operators have the same precedence #}\n\n{{ 1 + nope ?? nada ?? 2 }}\n{{ 1 + nope ??\n   3 + nada ?? 2 }}\n{{ 1 ~ 'notnull' ?? 'foo' ~ '_bar' }}\n{{\n  1 ~ 2 + 3\n  ??\n  1 ~\n  2 + 4\n}}\n{{ (\n  1 ~ 2 + 3\n  ??\n  1 ~\n  2 + 4\n  )\n}}\n--DATA--\nreturn []\n--EXPECT--\nOK\n3\n6\n1notnull_bar\n48\n48\n"
  },
  {
    "path": "tests/Fixtures/tests/null_coalesce.test",
    "content": "--TEST--\nTwig supports the ?? operator\n--TEMPLATE--\n{{ 'OK' ?? 'KO' }}\n{{ null ?? 'OK' }}\n{{ bar ?? 'KO' }}\n{{ baz ?? 'OK' }}\n{{ foo.bar ?? 'KO' }}\n{{ foo.missing ?? 'OK' }}\n{{ foo.bar.baz.missing ?? 'OK' }}\n{{ foo['bar'] ?? 'KO' }}\n{{ foo['missing'] ?? 'OK' }}\n{{ nope ?? (nada ?? 'OK') }}\n{{ 1 + (nope ?? (nada ?? 2)) }}\n{{ 1 + (nope ?? 3) + (nada ?? 2) }}\n{{ obj.null() ?? 'OK' }}\n{{ obj.empty() ?? 'KO' }}\n{{ tag ?? 'KO' }}\n--DATA--\nreturn ['bar' => 'OK', 'foo' => ['bar' => 'OK'], 'obj' => new Twig\\Tests\\TwigTestFoo(), 'tag' => '<br>']\n--EXPECT--\nOK\nOK\nOK\nOK\nOK\nOK\nOK\nOK\nOK\nOK\n3\n6\nOK\n\n&lt;br&gt;\n"
  },
  {
    "path": "tests/Fixtures/tests/null_coalesce_block.test",
    "content": "--TEST--\nTwig supports the ?? operator with blocks\n--TEMPLATE--\n{% block foo %}OK{% endblock %}\n{{ block('foo') ?? 'KO' }}\n--DATA--\nreturn []\n--EXPECT--\nOKOK\n"
  },
  {
    "path": "tests/Fixtures/tests/odd.test",
    "content": "--TEST--\n\"odd\" test\n--TEMPLATE--\n{{ 1 is odd ? 'ok' : 'ko' }}\n{{ 2 is odd ? 'ko' : 'ok' }}\n{{ -1 is odd ? 'ok' : 'ko' }}\n--DATA--\nreturn []\n--EXPECT--\nok\nok\nok"
  },
  {
    "path": "tests/Fixtures/tests/sequence.test",
    "content": "--TEST--\n\"sequence\" test\n--TEMPLATE--\n{{ empty is sequence ? 'ok' : 'ko' }}\n{{ sequence is sequence ? 'ok' : 'ko' }}\n{{ empty_array_obj is sequence ? 'ok' : 'ko' }}\n{{ sequence_array_obj is sequence ? 'ok' : 'ko' }}\n{{ mapping_array_obj is sequence ? 'ok' : 'ko' }}\n{{ obj is sequence ? 'ok' : 'ko' }}\n{{ mapping is sequence ? 'ok' : 'ko' }}\n{{ string is sequence ? 'ok' : 'ko' }}\n--DATA--\nreturn [\n    'empty' => [],\n    'sequence' => [\n        'foo',\n        'bar',\n        'baz'\n    ],\n    'empty_array_obj' => new \\ArrayObject(),\n    'sequence_array_obj' => new \\ArrayObject(['foo', 'bar']),\n    'mapping_array_obj' => new \\ArrayObject(['foo' => 'bar']),\n    'obj' => new \\stdClass(),\n    'mapping' => [\n        'foo' => 'bar',\n        'bar' => 'foo'\n    ],\n    'string' => 'test',\n]\n--EXPECT--\nok\nok\nok\nok\nko\nko\nko\nko\n"
  },
  {
    "path": "tests/Fixtures/whitespace/trim_block.test",
    "content": "--TEST--\nWhitespace trimming on tags.\n--TEMPLATE--\nTrim on control tag:\n{% for i in range(1, 9) -%}\n\t{{ i }}\n{%- endfor %}\n\n\nTrim on output tag:\n{% for i in range(1, 9) %}\n\t{{- i -}}\n{% endfor %}\n\n\nTrim comments:\n      \n{#- Invisible -#}\n       \nAfter the comment.\n\nTrim leading space:\n{% if leading %}\n\n\t\t{{- leading }}\n{% endif %}\n\n{%- if leading %}\n\t{{- leading }}\n\n{%- endif %}\n\n\nTrim trailing space:\n{% if trailing -%}          \n\t{{ trailing -}}\n\n{% endif -%}\n\nCombined:\n\n{%- if both -%}\n<ul>\n\t<li>    {{- both -}}   </li>\n</ul>\n\n{%- endif -%}\n\nend\n--DATA--\nreturn ['leading' => 'leading space', 'trailing' => 'trailing space', 'both' => 'both']\n--EXPECT--\nTrim on control tag:\n123456789\n\nTrim on output tag:\n123456789\n\nTrim comments:After the comment.\n\nTrim leading space:\nleading space\nleading space\n\nTrim trailing space:\ntrailing spaceCombined:<ul>\n\t<li>both</li>\n</ul>end\n"
  },
  {
    "path": "tests/Fixtures/whitespace/trim_delimiter_as_strings.test",
    "content": "--TEST--\nWhitespace trimming as strings.\n--TEMPLATE--\n{{ 5 * '{#-'|length }}\n{{ '{{-'|length * 5 + '{%-'|length }}\n--DATA--\nreturn []\n--EXPECT--\n15\n18\n"
  },
  {
    "path": "tests/Fixtures/whitespace/trim_left.test",
    "content": "--TEST--\nWhitespace trimming on tags (left side).\n--TEMPLATE--\n**{% if true %}\nfoo\n    \n    \t    {%- endif %}**\n\n**\n\n\t    {{- 'foo' }}**\n\n**\n    \n\t\n{#- comment #}**\n\n**{% verbatim %}\nfoo\n    \n    \t    {%- endverbatim %}**\n--DATA--\nreturn []\n--EXPECT--\n**foo**\n\n**foo**\n\n****\n\n**\nfoo**\n"
  },
  {
    "path": "tests/Fixtures/whitespace/trim_line_left.test",
    "content": "--TEST--\nLine whitespace trimming on tags (left side).\n--TEMPLATE--\n**{% if true %}\nfoo\n    \t    {%~ endif %}**\n\n**\n\t    {{~ 'foo' }}**\n\n**\n\t{#~ comment #}**\n\n**{% verbatim %}\nfoo\n    \n    \t    {%~ endverbatim %}**\n--DATA--\nreturn []\n--EXPECT--\n**foo\n**\n\n**\nfoo**\n\n**\n**\n\n**\nfoo\n    \n**\n"
  },
  {
    "path": "tests/Fixtures/whitespace/trim_line_right.test",
    "content": "--TEST--\nLine whitespace trimming on tags (right side).\n--TEMPLATE--\n**{% if true ~%}    \t    \nfoo{% endif %}**\n\n**{{ 'foo' ~}}    \t    \nfoo\n**\n\n**{# comment ~#}\t    \n\tfoo\n**\n\n**{% verbatim ~%}\t    \n    foo{% endverbatim %}**\n--DATA--\nreturn []\n--EXPECT--\n**\nfoo**\n\n**foo\nfoo\n**\n\n**\n\tfoo\n**\n\n**\n    foo**\n"
  },
  {
    "path": "tests/Fixtures/whitespace/trim_right.test",
    "content": "--TEST--\nWhitespace trimming on tags (right side).\n--TEMPLATE--\n**{% if true -%}\n    \n    \t    foo{% endif %}**\n\n**{{ 'foo' -}}\n\t    \n**\n\n**{# comment -#}    \n\t\n**\n\n**{% verbatim -%}    \n    \t    \nfoo{% endverbatim %}**\n--DATA--\nreturn []\n--EXPECT--\n**foo**\n\n**foo**\n\n****\n\n**foo**\n"
  },
  {
    "path": "tests/IntegrationTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\DeprecatedCallableInfo;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\Extension\\DebugExtension;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Extension\\StringLoaderExtension;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Runtime\\EscaperRuntime;\nuse Twig\\Sandbox\\SecurityPolicy;\nuse Twig\\Test\\IntegrationTestCase;\nuse Twig\\Token;\nuse Twig\\TokenParser\\AbstractTokenParser;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\n// This function is defined to check that escaping strategies\n// like html works even if a function with the same name is defined.\nfunction html()\n{\n    return 'foo';\n}\n\nclass IntegrationTest extends IntegrationTestCase\n{\n    public function getExtensions()\n    {\n        $policy = new SecurityPolicy([], [], [], [], ['dump']);\n\n        return [\n            new DebugExtension(),\n            new SandboxExtension($policy, false),\n            new StringLoaderExtension(),\n            new TwigTestExtension(),\n        ];\n    }\n\n    protected function getUndefinedFunctionCallbacks(): array\n    {\n        return [\n            static function (string $name) {\n                if ('throwing_undefined_function' === $name) {\n                    throw new SyntaxError('This function is undefined in the tests.');\n                }\n\n                return false;\n            },\n        ];\n    }\n\n    protected function getUndefinedTestCallbacks(): array\n    {\n        return [\n            static function (string $name) {\n                if ('throwing_undefined_test' === $name) {\n                    throw new SyntaxError('This test is undefined in the tests.');\n                }\n                if ('throwing_undefined_two words_test' === $name) {\n                    throw new SyntaxError('This test is undefined in the tests.');\n                }\n\n                // Ensure this does not conflict with `divisible by` and `same as`.\n                if (\\in_array($name, ['divisible', 'same'], true)) {\n                    return new TwigTest($name, static fn () => '');\n                }\n\n                return false;\n            },\n        ];\n    }\n\n    protected function getUndefinedFilterCallbacks(): array\n    {\n        return [\n            static function (string $name) {\n                if ('throwing_undefined_filter' === $name) {\n                    throw new SyntaxError('This filter is undefined in the tests.');\n                }\n\n                return false;\n            },\n        ];\n    }\n\n    protected static function getFixturesDirectory(): string\n    {\n        return __DIR__.'/Fixtures/';\n    }\n}\n\nfunction test_foo($value = 'foo')\n{\n    return $value;\n}\n\nclass TwigTestFoo implements \\Iterator\n{\n    public const BAR_NAME = 'bar';\n\n    public $position = 0;\n    public $array = [1, 2];\n\n    public static $foo = 'Foo';\n\n    public function bar($param1 = null, $param2 = null)\n    {\n        return 'bar'.($param1 ? '_'.$param1 : '').($param2 ? '-'.$param2 : '');\n    }\n\n    public function getFoo()\n    {\n        return 'foo';\n    }\n\n    public function getEmpty()\n    {\n        return '';\n    }\n\n    public function getNull()\n    {\n        return null;\n    }\n\n    public function getSelf()\n    {\n        return $this;\n    }\n\n    public function is()\n    {\n        return 'is';\n    }\n\n    public function in()\n    {\n        return 'in';\n    }\n\n    public function not()\n    {\n        return 'not';\n    }\n\n    public function strToLower($value)\n    {\n        return strtolower($value);\n    }\n\n    public function rewind(): void\n    {\n        $this->position = 0;\n    }\n\n    #[\\ReturnTypeWillChange]\n    public function current()\n    {\n        return $this->array[$this->position];\n    }\n\n    #[\\ReturnTypeWillChange]\n    public function key()\n    {\n        return 'a';\n    }\n\n    public function next(): void\n    {\n        ++$this->position;\n    }\n\n    public function valid(): bool\n    {\n        return isset($this->array[$this->position]);\n    }\n}\n\nclass TwigTestTokenParser_§ extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);\n\n        return new PrintNode(new ConstantExpression('§', -1), -1);\n    }\n\n    public function getTag(): string\n    {\n        return '§';\n    }\n}\n\nclass TwigTestExtension extends AbstractExtension\n{\n    public function getTokenParsers(): array\n    {\n        return [\n            new TwigTestTokenParser_§(),\n        ];\n    }\n\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('§', [$this, '§Filter']),\n            new TwigFilter('escape_and_nl2br', [$this, 'escape_and_nl2br'], ['needs_environment' => true, 'is_safe' => ['html']]),\n            new TwigFilter('nl2br', [$this, 'nl2br'], ['pre_escape' => 'html', 'is_safe' => ['html']]),\n            new TwigFilter('escape_something', [$this, 'escape_something'], ['is_safe' => ['something']]),\n            new TwigFilter('preserves_safety', [$this, 'preserves_safety'], ['preserves_safety' => ['html']]),\n            new TwigFilter('static_call_string', 'Twig\\Tests\\TwigTestExtension::staticCall'),\n            new TwigFilter('static_call_array', ['Twig\\Tests\\TwigTestExtension', 'staticCall']),\n            new TwigFilter('magic_call', [$this, 'magicCall']),\n            new TwigFilter('magic_call_closure', \\Closure::fromCallable([$this, 'magicCall'])),\n            new TwigFilter('magic_call_string', 'Twig\\Tests\\TwigTestExtension::magicStaticCall'),\n            new TwigFilter('magic_call_array', ['Twig\\Tests\\TwigTestExtension', 'magicStaticCall']),\n            new TwigFilter('*_path', [$this, 'dynamic_path']),\n            new TwigFilter('*_foo_*_bar', [$this, 'dynamic_foo']),\n            new TwigFilter('not', [$this, 'notFilter']),\n            new TwigFilter('anon_foo', static function ($name) { return '*'.$name.'*'; }),\n        ];\n    }\n\n    public function getFunctions(): array\n    {\n        return [\n            new TwigFunction('§', [$this, '§Function']),\n            new TwigFunction('safe_br', [$this, 'br'], ['is_safe' => ['html']]),\n            new TwigFunction('unsafe_br', [$this, 'br']),\n            new TwigFunction('static_call_string', 'Twig\\Tests\\TwigTestExtension::staticCall'),\n            new TwigFunction('static_call_array', ['Twig\\Tests\\TwigTestExtension', 'staticCall']),\n            new TwigFunction('*_path', [$this, 'dynamic_path']),\n            new TwigFunction('*_foo_*_bar', [$this, 'dynamic_foo']),\n            new TwigFunction('anon_foo', static function ($name) { return '*'.$name.'*'; }),\n            new TwigFunction('deprecated_function', static function () { return 'foo'; }, ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.1', 'not_deprecated_function')]),\n        ];\n    }\n\n    public function getTests(): array\n    {\n        return [\n            new TwigTest('multi word', [$this, 'is_multi_word']),\n            new TwigTest('test_*', [$this, 'dynamic_test']),\n        ];\n    }\n\n    public function notFilter($value)\n    {\n        return 'not '.$value;\n    }\n\n    public function §Filter($value)\n    {\n        return \"§{$value}§\";\n    }\n\n    public function §Function($value)\n    {\n        return \"§{$value}§\";\n    }\n\n    /**\n     * nl2br which also escapes, for testing escaper filters.\n     */\n    public function escape_and_nl2br($env, $value, $sep = '<br />')\n    {\n        return $this->nl2br($env->getRuntime(EscaperRuntime::class)->escape($value, 'html'), $sep);\n    }\n\n    /**\n     * nl2br only, for testing filters with pre_escape.\n     */\n    public function nl2br($value, $sep = '<br />')\n    {\n        // not secure if $value contains html tags (not only entities)\n        // don't use\n        return str_replace(\"\\n\", \"$sep\\n\", $value ?? '');\n    }\n\n    public function dynamic_path($element, $item)\n    {\n        return $element.'/'.$item;\n    }\n\n    public function dynamic_foo($foo, $bar, $item)\n    {\n        return $foo.'/'.$bar.'/'.$item;\n    }\n\n    public function dynamic_test($element, $item)\n    {\n        return $element === $item;\n    }\n\n    public function escape_something($value)\n    {\n        return strtoupper($value);\n    }\n\n    public function preserves_safety($value)\n    {\n        return strtoupper($value);\n    }\n\n    public static function staticCall($value)\n    {\n        return \"*$value*\";\n    }\n\n    public function br()\n    {\n        return '<br />';\n    }\n\n    public function is_multi_word($value)\n    {\n        return str_contains($value, ' ');\n    }\n\n    public function __call($method, $arguments)\n    {\n        if ('magicCall' !== $method) {\n            throw new \\BadMethodCallException('Unexpected call to __call.');\n        }\n\n        return 'magic_'.$arguments[0];\n    }\n\n    public static function __callStatic($method, $arguments)\n    {\n        if ('magicStaticCall' !== $method) {\n            throw new \\BadMethodCallException('Unexpected call to __callStatic.');\n        }\n\n        return 'static_magic_'.$arguments[0];\n    }\n}\n\n/**\n * This class is used in tests for the \"length\" filter and \"empty\" test. It asserts that __call is not\n * used to convert such objects to strings.\n */\nclass MagicCallStub\n{\n    public function __call($name, $args)\n    {\n        throw new \\Exception('__call shall not be called.');\n    }\n}\n\nclass ToStringStub\n{\n    /**\n     * @var string\n     */\n    private $string;\n\n    public function __construct($string)\n    {\n        $this->string = $string;\n    }\n\n    public function __toString()\n    {\n        return $this->string;\n    }\n}\n\n/**\n * This class is used in tests for the length filter and empty test to show\n * that when \\Countable is implemented, it is preferred over the __toString()\n * method.\n */\nclass CountableStub implements \\Countable\n{\n    private $count;\n\n    public function __construct($count)\n    {\n        $this->count = $count;\n    }\n\n    public function count(): int\n    {\n        return $this->count;\n    }\n\n    public function __toString()\n    {\n        throw new \\Exception('__toString shall not be called on \\Countables.');\n    }\n}\n\n/**\n * This class is used in tests for the length filter.\n */\nclass IteratorAggregateStub implements \\IteratorAggregate\n{\n    private $data;\n\n    public function __construct(array $data)\n    {\n        $this->data = $data;\n    }\n\n    public function getIterator(): \\Traversable\n    {\n        return new \\ArrayIterator($this->data);\n    }\n}\n\nclass SimpleIteratorForTesting implements \\Iterator\n{\n    private $data = [1, 2, 3, 4, 5, 6, 7];\n    private $key = 0;\n\n    #[\\ReturnTypeWillChange]\n    public function current()\n    {\n        return $this->key;\n    }\n\n    public function next(): void\n    {\n        ++$this->key;\n    }\n\n    #[\\ReturnTypeWillChange]\n    public function key()\n    {\n        return $this->key;\n    }\n\n    public function valid(): bool\n    {\n        return isset($this->data[$this->key]);\n    }\n\n    public function rewind(): void\n    {\n        $this->key = 0;\n    }\n\n    public function __toString()\n    {\n        // for testing, make sure string length returned is not the same as the `iterator_count`\n        return str_repeat('X', iterator_count($this) + 10);\n    }\n}\n"
  },
  {
    "path": "tests/LexerTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Bridge\\PhpUnit\\ExpectDeprecationTrait;\nuse Twig\\Environment;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Lexer;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Source;\nuse Twig\\Token;\n\nclass LexerTest extends TestCase\n{\n    use ExpectDeprecationTrait;\n\n    public function testNameLabelForTag()\n    {\n        $template = '{% § %}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n\n        $stream->expect(Token::BLOCK_START_TYPE);\n        $this->assertSame('§', $stream->expect(Token::NAME_TYPE)->getValue());\n    }\n\n    public function testNameLabelForFunction()\n    {\n        $template = '{{ §() }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n\n        $stream->expect(Token::VAR_START_TYPE);\n        $this->assertSame('§', $stream->expect(Token::NAME_TYPE)->getValue());\n    }\n\n    public function testBracketsNesting()\n    {\n        $template = '{{ {\"a\":{\"b\":\"c\"}} }}';\n\n        $this->assertEquals(2, $this->countToken($template, Token::PUNCTUATION_TYPE, '{'));\n        $this->assertEquals(2, $this->countToken($template, Token::PUNCTUATION_TYPE, '}'));\n    }\n\n    protected function countToken($template, $type, $value = null)\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n\n        $count = 0;\n        while (!$stream->isEOF()) {\n            $token = $stream->next();\n            if ($token->test($type)) {\n                if (null === $value || $value === $token->getValue()) {\n                    ++$count;\n                }\n            }\n        }\n\n        return $count;\n    }\n\n    public function testLineDirective()\n    {\n        $template = \"foo\\n\"\n            .\"bar\\n\"\n            .\"{% line 10 %}\\n\"\n            .\"{{\\n\"\n            .\"baz\\n\"\n            .\"}}\\n\";\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n\n        // foo\\nbar\\n\n        $this->assertSame(1, $stream->expect(Token::TEXT_TYPE)->getLine());\n        // \\n (after {% line %})\n        $this->assertSame(10, $stream->expect(Token::TEXT_TYPE)->getLine());\n        // {{\n        $this->assertSame(11, $stream->expect(Token::VAR_START_TYPE)->getLine());\n        // baz\n        $this->assertSame(12, $stream->expect(Token::NAME_TYPE)->getLine());\n    }\n\n    public function testLineDirectiveInline()\n    {\n        $template = \"foo\\n\"\n            .\"bar{% line 10 %}{{\\n\"\n            .\"baz\\n\"\n            .\"}}\\n\";\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n\n        // foo\\nbar\n        $this->assertSame(1, $stream->expect(Token::TEXT_TYPE)->getLine());\n        // {{\n        $this->assertSame(10, $stream->expect(Token::VAR_START_TYPE)->getLine());\n        // baz\n        $this->assertSame(11, $stream->expect(Token::NAME_TYPE)->getLine());\n    }\n\n    public function testLongComments()\n    {\n        $template = '{# '.str_repeat('*', 100000).' #}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $lexer->tokenize(new Source($template, 'index'));\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testLongVerbatim()\n    {\n        $template = '{% verbatim %}'.str_repeat('*', 100000).'{% endverbatim %}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $lexer->tokenize(new Source($template, 'index'));\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testLongVar()\n    {\n        $template = '{{ '.str_repeat('x', 100000).' }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $lexer->tokenize(new Source($template, 'index'));\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testLongBlock()\n    {\n        $template = '{% '.str_repeat('x', 100000).' %}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $lexer->tokenize(new Source($template, 'index'));\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testBigNumbers()\n    {\n        $template = '{{ 922337203685477580700 }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->next();\n        $node = $stream->next();\n        $this->assertEquals('922337203685477580700', $node->getValue());\n    }\n\n    /**\n     * @dataProvider getStringWithEscapedDelimiter\n     */\n    public function testStringWithEscapedDelimiter(string $template, string $expected)\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $token = $stream->expect(Token::STRING_TYPE);\n        $this->assertSame($expected, $token->getValue());\n    }\n\n    public static function getStringWithEscapedDelimiter()\n    {\n        yield [\n            <<<'EOF'\n            {{ '\\x6' }}\n            EOF,\n            \"\\x6\",\n        ];\n        yield [\n            <<<'EOF'\n            {{ '\\065\\x64' }}\n            EOF,\n            \"\\065\\x64\",\n        ];\n        yield [\n            <<<'EOF'\n            {{ 'App\\\\Test' }}\n            EOF,\n            'App\\\\Test',\n        ];\n        yield [\n            <<<'EOF'\n            {{ \"App\\#{var}\" }}\n            EOF,\n            'App#{var}',\n        ];\n        yield [\n            <<<'EOF'\n            {{ 'foo \\' bar' }}\n            EOF,\n            <<<'EOF'\n            foo ' bar\n            EOF,\n        ];\n        yield [\n            <<<'EOF'\n            {{ \"foo \\\" bar\" }}\n            EOF,\n            'foo \" bar',\n        ];\n        yield [\n            <<<'EOF'\n            {{ '\\f\\n\\r\\t\\v' }}\n            EOF,\n            \"\\f\\n\\r\\t\\v\",\n        ];\n        yield [\n            <<<'EOF'\n            {{ '\\\\f\\\\n\\\\r\\\\t\\\\v' }}\n            EOF,\n            '\\\\f\\\\n\\\\r\\\\t\\\\v',\n        ];\n        yield [\n            <<<'EOF'\n            {{ 'Ymd\\\\THis' }}\n            EOF,\n            <<<'EOF'\n            Ymd\\THis\n            EOF,\n        ];\n    }\n\n    /**\n     * @group legacy\n     *\n     * @dataProvider getStringWithEscapedDelimiterProducingDeprecation\n     */\n    public function testStringWithEscapedDelimiterProducingDeprecation(string $template, string $expected, string $expectedDeprecation)\n    {\n        $this->expectDeprecation($expectedDeprecation);\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, $expected);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public static function getStringWithEscapedDelimiterProducingDeprecation()\n    {\n        yield [\n            <<<'EOF'\n            {{ 'App\\Test' }}\n            EOF,\n            'AppTest',\n            'Since twig/twig 3.12: Character \"T\" should not be escaped; the \"\\\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra \"\\\" character at position 5 in \"index\" at line 1.',\n        ];\n        yield [\n            <<<'EOF'\n            {{ \"foo \\' bar\" }}\n            EOF,\n            <<<'EOF'\n            foo ' bar\n            EOF,\n            'Since twig/twig 3.12: Character \"\\'\" should not be escaped; the \"\\\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra \"\\\" character at position 6 in \"index\" at line 1.',\n        ];\n        yield [\n            <<<'EOF'\n            {{ 'foo \\\" bar' }}\n            EOF,\n            'foo \" bar',\n            'Since twig/twig 3.12: Character \"\"\" should not be escaped; the \"\\\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra \"\\\" character at position 6 in \"index\" at line 1.',\n        ];\n    }\n\n    public function testStringWithInterpolation()\n    {\n        $template = 'foo {{ \"bar #{ baz + 1 }\" }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::TEXT_TYPE, 'foo ');\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, 'bar ');\n        $stream->expect(Token::INTERPOLATION_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'baz');\n        $stream->expect(Token::OPERATOR_TYPE, '+');\n        $stream->expect(Token::NUMBER_TYPE, '1');\n        $stream->expect(Token::INTERPOLATION_END_TYPE);\n        $stream->expect(Token::VAR_END_TYPE);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testStringWithEscapedInterpolation()\n    {\n        $template = '{{ \"bar \\#{baz+1}\" }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, 'bar #{baz+1}');\n        $stream->expect(Token::VAR_END_TYPE);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testStringWithHash()\n    {\n        $template = '{{ \"bar # baz\" }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, 'bar # baz');\n        $stream->expect(Token::VAR_END_TYPE);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testStringWithUnterminatedInterpolation()\n    {\n        $template = '{{ \"bar #{x\" }}';\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unclosed \"\"\"');\n\n        $lexer->tokenize(new Source($template, 'index'));\n    }\n\n    public function testStringWithNestedInterpolations()\n    {\n        $template = '{{ \"bar #{ \"foo#{bar}\" }\" }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, 'bar ');\n        $stream->expect(Token::INTERPOLATION_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, 'foo');\n        $stream->expect(Token::INTERPOLATION_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'bar');\n        $stream->expect(Token::INTERPOLATION_END_TYPE);\n        $stream->expect(Token::INTERPOLATION_END_TYPE);\n        $stream->expect(Token::VAR_END_TYPE);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testStringWithNestedInterpolationsInBlock()\n    {\n        $template = '{% foo \"bar #{ \"foo#{bar}\" }\" %}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::BLOCK_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'foo');\n        $stream->expect(Token::STRING_TYPE, 'bar ');\n        $stream->expect(Token::INTERPOLATION_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, 'foo');\n        $stream->expect(Token::INTERPOLATION_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'bar');\n        $stream->expect(Token::INTERPOLATION_END_TYPE);\n        $stream->expect(Token::INTERPOLATION_END_TYPE);\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testOperatorEndingWithALetterAtTheEndOfALine()\n    {\n        $template = \"{{ 1 and\\n0}}\";\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::NUMBER_TYPE, 1);\n        $stream->expect(Token::OPERATOR_TYPE, 'and');\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public function testFilterAndAttributeNamedAfterOperator()\n    {\n        // Ensure that filters/attributes aren't mistaken for operators when their names conflict\n        // (see https://github.com/twigphp/Twig/issues/4767)\n        $template = '{{ \\'foo\\'|and }}'\n            .'{{ \\'bar\\' | and }}'\n            .'{{ foo.and }}'\n            .'{{ bar . and }}'\n            .'{{ foo and bar }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        foreach (['foo', 'bar'] as $value) {\n            $stream->expect(Token::VAR_START_TYPE);\n            $stream->expect(Token::STRING_TYPE, $value);\n            $stream->expect(Token::OPERATOR_TYPE, '|');\n            $stream->expect(Token::NAME_TYPE, 'and');\n            $stream->expect(Token::VAR_END_TYPE);\n        }\n        foreach (['foo', 'bar'] as $value) {\n            $stream->expect(Token::VAR_START_TYPE);\n            $stream->expect(Token::NAME_TYPE, $value);\n            $stream->expect(Token::OPERATOR_TYPE, '.');\n            $stream->expect(Token::NAME_TYPE, 'and');\n            $stream->expect(Token::VAR_END_TYPE);\n        }\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'foo');\n        $stream->expect(Token::OPERATOR_TYPE, 'and');\n        $stream->expect(Token::NAME_TYPE, 'bar');\n        $stream->expect(Token::VAR_END_TYPE);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n    }\n\n    public function testLiteralIsNotAnOperator()\n    {\n        // \"literal\" is the name of the LiteralExpressionParser but should not be treated as an operator token\n        $template = '{{ literal }}';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'literal');\n        $stream->expect(Token::VAR_END_TYPE);\n\n        $this->addToAssertionCount(1);\n    }\n\n    public function testUnterminatedVariable()\n    {\n        $template = '\n\n{{\n\nbar\n\n\n';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unclosed \"variable\" in \"index\" at line 3');\n        $lexer->tokenize(new Source($template, 'index'));\n    }\n\n    public function testUnterminatedBlock()\n    {\n        $template = '\n\n{%\n\nbar\n\n\n';\n\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unclosed \"block\" in \"index\" at line 3');\n\n        $lexer->tokenize(new Source($template, 'index'));\n    }\n\n    public function testOverridingSyntax()\n    {\n        $template = '[# comment #]{# variable #}/# if true #/true/# endif #/';\n        $lexer = new Lexer(new Environment(new ArrayLoader()), [\n            'tag_comment' => ['[#', '#]'],\n            'tag_block' => ['/#', '#/'],\n            'tag_variable' => ['{#', '#}'],\n        ]);\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'variable');\n        $stream->expect(Token::VAR_END_TYPE);\n        $stream->expect(Token::BLOCK_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'if');\n        $stream->expect(Token::NAME_TYPE, 'true');\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $stream->expect(Token::TEXT_TYPE, 'true');\n        $stream->expect(Token::BLOCK_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'endif');\n        $stream->expect(Token::BLOCK_END_TYPE);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    /**\n     * @dataProvider getTemplateForErrorsAtTheEndOfTheStream\n     */\n    public function testErrorsAtTheEndOfTheStream(string $template)\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        set_error_handler(function () {\n            $this->fail('Lexer should not emit warnings.');\n        });\n        try {\n            $lexer->tokenize(new Source($template, 'index'));\n            $this->addToAssertionCount(1);\n        } finally {\n            restore_error_handler();\n        }\n    }\n\n    public static function getTemplateForErrorsAtTheEndOfTheStream()\n    {\n        yield ['{{ ='];\n        yield ['{{ ..'];\n    }\n\n    /**\n     * @dataProvider getTemplateForStrings\n     */\n    public function testStrings(string $expected)\n    {\n        $template = '{{ \"'.$expected.'\" }}';\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, $expected);\n\n        $template = \"{{ '\".$expected.\"' }}\";\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, $expected);\n\n        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above\n        // can be executed without throwing any exceptions\n        $this->addToAssertionCount(1);\n    }\n\n    public static function getTemplateForStrings()\n    {\n        yield ['日本では、春になると桜の花が咲きます。多くの人々は、公園や川の近くに集まり、お花見を楽しみます。桜の花びらが風に舞い、まるで雪のように見える瞬間は、とても美しいです。'];\n        yield ['في العالم العربي، يُعتبر الخط العربي أحد أجمل أشكال الفن. يُستخدم الخط في تزيين المساجد والكتب والمخطوطات القديمة. يتميز الخط العربي بجماله وتناسقه، ويُعتبر رمزًا للثقافة الإسلامية.'];\n    }\n\n    public function testInlineCommentWithHashInString()\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source('{{ \"me # this is NOT an inline comment\" }}', 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, 'me # this is NOT an inline comment');\n        $stream->expect(Token::VAR_END_TYPE);\n        $this->assertTrue($stream->isEOF());\n    }\n\n    /**\n     * @dataProvider getTemplateForInlineCommentsForVariable\n     */\n    public function testInlineCommentForVariable(string $template)\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::VAR_START_TYPE);\n        $stream->expect(Token::STRING_TYPE, 'me');\n        $stream->expect(Token::VAR_END_TYPE);\n        $this->assertTrue($stream->isEOF());\n    }\n\n    public static function getTemplateForInlineCommentsForVariable()\n    {\n        yield ['{{\n            \"me\"\n            # this is an inline comment\n        }}'];\n        yield ['{{\n            # this is an inline comment\n            \"me\"\n        }}'];\n        yield ['{{\n            \"me\" # this is an inline comment\n        }}'];\n        yield ['{{\n            # this is an inline comment\n            \"me\" # this is an inline comment\n            # this is an inline comment\n        }}'];\n    }\n\n    /**\n     * @dataProvider getTemplateForInlineCommentsForBlock\n     */\n    public function testInlineCommentForBlock(string $template)\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $stream->expect(Token::BLOCK_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'if');\n        $stream->expect(Token::NAME_TYPE, 'true');\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $stream->expect(Token::TEXT_TYPE, 'me');\n        $stream->expect(Token::BLOCK_START_TYPE);\n        $stream->expect(Token::NAME_TYPE, 'endif');\n        $stream->expect(Token::BLOCK_END_TYPE);\n        $this->assertTrue($stream->isEOF());\n    }\n\n    public static function getTemplateForInlineCommentsForBlock()\n    {\n        yield ['{%\n            if true\n            # this is an inline comment\n        %}me{% endif %}'];\n        yield ['{%\n            # this is an inline comment\n            if true\n        %}me{% endif %}'];\n        yield ['{%\n            if true # this is an inline comment\n        %}me{% endif %}'];\n        yield ['{%\n            # this is an inline comment\n            if true # this is an inline comment\n            # this is an inline comment\n        %}me{% endif %}'];\n    }\n\n    /**\n     * @dataProvider getTemplateForInlineCommentsForComment\n     */\n    public function testInlineCommentForComment(string $template)\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n        $this->assertTrue($stream->isEOF());\n    }\n\n    public static function getTemplateForInlineCommentsForComment()\n    {\n        yield ['{#\n            Some regular comment # this is an inline comment\n        #}'];\n    }\n\n    /**\n     * @dataProvider getTemplateForUnclosedBracketInExpression\n     */\n    public function testUnclosedBracketInExpression(string $template, string $bracket)\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage(\\sprintf('Unclosed \"%s\" in \"index\" at line 1.', $bracket));\n\n        $lexer->tokenize(new Source($template, 'index'));\n    }\n\n    public static function getTemplateForUnclosedBracketInExpression()\n    {\n        yield ['{{ (1 + 3 }}', '('];\n        yield ['{{ obj[\"a\" }}', '['];\n        yield ['{{ ({ a: 1) }}', '{'];\n        yield ['{{ (([1]) + 3 }}', '('];\n    }\n\n    /**\n     * @dataProvider getTemplateForUnexpectedBracketInExpression\n     */\n    public function testUnexpectedBracketInExpression(string $template, string $bracket)\n    {\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage(\\sprintf('Unexpected \"%s\" in \"index\" at line 1.', $bracket));\n\n        $lexer->tokenize(new Source($template, 'index'));\n    }\n\n    public static function getTemplateForUnexpectedBracketInExpression()\n    {\n        yield ['{{ 1 + 3) }}', ')'];\n        yield ['{{ obj] }}', ']'];\n        yield ['{{ { a: 1 }}', '}'];\n        yield ['{{ ([1] + 3)) }}', ')'];\n    }\n}\n"
  },
  {
    "path": "tests/Loader/ArrayTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Loader;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Loader\\ArrayLoader;\n\nclass ArrayTest extends TestCase\n{\n    public function testGetSourceContextWhenTemplateDoesNotExist()\n    {\n        $loader = new ArrayLoader();\n\n        $this->expectException(LoaderError::class);\n        $loader->getSourceContext('foo');\n    }\n\n    public function testGetCacheKey()\n    {\n        $loader = new ArrayLoader(['foo' => 'bar']);\n\n        $this->assertEquals('foo:bar', $loader->getCacheKey('foo'));\n    }\n\n    public function testGetCacheKeyWhenTemplateHasDuplicateContent()\n    {\n        $loader = new ArrayLoader([\n            'foo' => 'bar',\n            'baz' => 'bar',\n        ]);\n\n        $this->assertEquals('foo:bar', $loader->getCacheKey('foo'));\n        $this->assertEquals('baz:bar', $loader->getCacheKey('baz'));\n    }\n\n    public function testGetCacheKeyIsProtectedFromEdgeCollisions()\n    {\n        $loader = new ArrayLoader([\n            'foo__' => 'bar',\n            'foo' => '__bar',\n        ]);\n\n        $this->assertEquals('foo__:bar', $loader->getCacheKey('foo__'));\n        $this->assertEquals('foo:__bar', $loader->getCacheKey('foo'));\n    }\n\n    public function testGetCacheKeyWhenTemplateDoesNotExist()\n    {\n        $loader = new ArrayLoader();\n\n        $this->expectException(LoaderError::class);\n        $loader->getCacheKey('foo');\n    }\n\n    public function testSetTemplate()\n    {\n        $loader = new ArrayLoader();\n        $loader->setTemplate('foo', 'bar');\n\n        $this->assertEquals('bar', $loader->getSourceContext('foo')->getCode());\n    }\n\n    public function testIsFresh()\n    {\n        $loader = new ArrayLoader(['foo' => 'bar']);\n        $this->assertTrue($loader->isFresh('foo', time()));\n    }\n\n    public function testIsFreshWhenTemplateDoesNotExist()\n    {\n        $loader = new ArrayLoader();\n\n        $this->expectException(LoaderError::class);\n        $loader->isFresh('foo', time());\n    }\n}\n"
  },
  {
    "path": "tests/Loader/ChainTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Loader;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Loader\\ChainLoader;\nuse Twig\\Loader\\FilesystemLoader;\nuse Twig\\Loader\\LoaderInterface;\n\nclass ChainTest extends TestCase\n{\n    public function testGetSourceContext()\n    {\n        $path = __DIR__.'/../Fixtures';\n        $loader = new ChainLoader([\n            new ArrayLoader(['foo' => 'bar']),\n            new ArrayLoader(['errors/index.html' => 'baz']),\n            new FilesystemLoader([$path]),\n        ]);\n\n        $this->assertEquals('foo', $loader->getSourceContext('foo')->getName());\n        $this->assertSame('', $loader->getSourceContext('foo')->getPath());\n\n        $this->assertEquals('errors/index.html', $loader->getSourceContext('errors/index.html')->getName());\n        $this->assertSame('', $loader->getSourceContext('errors/index.html')->getPath());\n        $this->assertEquals('baz', $loader->getSourceContext('errors/index.html')->getCode());\n\n        $this->assertEquals('errors/base.html', $loader->getSourceContext('errors/base.html')->getName());\n        $this->assertEquals(realpath($path.'/errors/base.html'), realpath($loader->getSourceContext('errors/base.html')->getPath()));\n        $this->assertNotEquals('baz', $loader->getSourceContext('errors/base.html')->getCode());\n    }\n\n    public function testGetSourceContextWhenTemplateDoesNotExist()\n    {\n        $loader = new ChainLoader([]);\n\n        $this->expectException(LoaderError::class);\n        $loader->getSourceContext('foo');\n    }\n\n    public function testGetCacheKey()\n    {\n        $loader = new ChainLoader([\n            new ArrayLoader(['foo' => 'bar']),\n            new ArrayLoader(['foo' => 'foobar', 'bar' => 'foo']),\n        ]);\n\n        $this->assertEquals('foo:bar', $loader->getCacheKey('foo'));\n        $this->assertEquals('bar:foo', $loader->getCacheKey('bar'));\n    }\n\n    public function testGetCacheKeyWhenTemplateDoesNotExist()\n    {\n        $loader = new ChainLoader([]);\n\n        $this->expectException(LoaderError::class);\n        $loader->getCacheKey('foo');\n    }\n\n    public function testAddLoader()\n    {\n        $fooLoader = new ArrayLoader(['foo' => 'foo:code']);\n        $barLoader = new ArrayLoader(['bar' => 'bar:code']);\n        $bazLoader = new ArrayLoader(['baz' => 'baz:code']);\n        $quxLoader = new ArrayLoader(['qux' => 'qux:code']);\n\n        $loader = new ChainLoader((static function () use ($fooLoader, $barLoader): \\Generator {\n            yield $fooLoader;\n            yield $barLoader;\n        })());\n\n        $loader->addLoader($bazLoader);\n        $loader->addLoader($quxLoader);\n\n        $this->assertEquals('foo:code', $loader->getSourceContext('foo')->getCode());\n        $this->assertEquals('bar:code', $loader->getSourceContext('bar')->getCode());\n        $this->assertEquals('baz:code', $loader->getSourceContext('baz')->getCode());\n        $this->assertEquals('qux:code', $loader->getSourceContext('qux')->getCode());\n\n        $this->assertEquals([\n            $fooLoader,\n            $barLoader,\n            $bazLoader,\n            $quxLoader,\n        ], $loader->getLoaders());\n    }\n\n    public function testExists()\n    {\n        $loader1 = $this->createMock(LoaderInterface::class);\n        $loader1->expects($this->once())->method('exists')->willReturn(false);\n        $loader1->expects($this->never())->method('getSourceContext');\n\n        $loader2 = $this->createMock(LoaderInterface::class);\n        $loader2->expects($this->once())->method('exists')->willReturn(true);\n        $loader2->expects($this->never())->method('getSourceContext');\n\n        $loader = new ChainLoader();\n        $loader->addLoader($loader1);\n        $loader->addLoader($loader2);\n\n        $this->assertTrue($loader->exists('foo'));\n    }\n}\n"
  },
  {
    "path": "tests/Loader/FilesystemTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Loader;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Loader\\FilesystemLoader;\n\nclass FilesystemTest extends TestCase\n{\n    public function testGetSourceContext()\n    {\n        $path = __DIR__.'/../Fixtures';\n        $loader = new FilesystemLoader([$path]);\n        $this->assertEquals('errors/index.html', $loader->getSourceContext('errors/index.html')->getName());\n        $this->assertEquals(realpath($path.'/errors/index.html'), realpath($loader->getSourceContext('errors/index.html')->getPath()));\n    }\n\n    /**\n     * @dataProvider getSecurityTests\n     */\n    public function testSecurity($template)\n    {\n        $loader = new FilesystemLoader([__DIR__.'/../Fixtures']);\n        $loader->addPath(__DIR__.'/../Fixtures', 'foo');\n\n        try {\n            $loader->getCacheKey($template);\n            $this->fail();\n        } catch (LoaderError $e) {\n            $this->assertStringNotContainsString('Unable to find template', $e->getMessage());\n        }\n    }\n\n    public static function getSecurityTests()\n    {\n        return [\n            [\"AutoloaderTest\\0.php\"],\n            ['..\\\\AutoloaderTest.php'],\n            ['..\\\\\\\\\\\\AutoloaderTest.php'],\n            ['../AutoloaderTest.php'],\n            ['..////AutoloaderTest.php'],\n            ['./../AutoloaderTest.php'],\n            ['.\\\\..\\\\AutoloaderTest.php'],\n            ['././././././../AutoloaderTest.php'],\n            ['.\\\\./.\\\\./.\\\\./../AutoloaderTest.php'],\n            ['foo/../../AutoloaderTest.php'],\n            ['foo\\\\..\\\\..\\\\AutoloaderTest.php'],\n            ['foo/../bar/../../AutoloaderTest.php'],\n            ['foo/bar/../../../AutoloaderTest.php'],\n            ['filters/../../AutoloaderTest.php'],\n            ['filters//..//..//AutoloaderTest.php'],\n            ['filters\\\\..\\\\..\\\\AutoloaderTest.php'],\n            ['filters\\\\\\\\..\\\\\\\\..\\\\\\\\AutoloaderTest.php'],\n            ['filters\\\\//../\\\\/\\\\..\\\\AutoloaderTest.php'],\n            ['/../AutoloaderTest.php'],\n            ['@__main__/../AutoloaderTest.php'],\n            ['@foo/../AutoloaderTest.php'],\n            ['@__main__/../../AutoloaderTest.php'],\n            ['@foo/../../AutoloaderTest.php'],\n        ];\n    }\n\n    /**\n     * @dataProvider getBasePaths\n     */\n    public function testPaths($basePath, $cacheKey, $rootPath)\n    {\n        $loader = new FilesystemLoader([$basePath.'/normal', $basePath.'/normal_bis'], $rootPath);\n        $loader->setPaths([$basePath.'/named', $basePath.'/named_bis'], 'named');\n        $loader->addPath($basePath.'/named_ter', 'named');\n        $loader->addPath($basePath.'/normal_ter');\n        $loader->prependPath($basePath.'/normal_final');\n        $loader->prependPath($basePath.'/named/../named_quater', 'named');\n        $loader->prependPath($basePath.'/named_final', 'named');\n\n        $this->assertEquals([\n            $basePath.'/normal_final',\n            $basePath.'/normal',\n            $basePath.'/normal_bis',\n            $basePath.'/normal_ter',\n        ], $loader->getPaths());\n        $this->assertEquals([\n            $basePath.'/named_final',\n            $basePath.'/named/../named_quater',\n            $basePath.'/named',\n            $basePath.'/named_bis',\n            $basePath.'/named_ter',\n        ], $loader->getPaths('named'));\n\n        // do not use realpath here as it would make the test unuseful\n        $this->assertEquals($cacheKey, str_replace('\\\\', '/', $loader->getCacheKey('@named/named_absolute.html')));\n        $this->assertEquals(\"path (final)\\n\", $loader->getSourceContext('index.html')->getCode());\n        $this->assertEquals(\"path (final)\\n\", $loader->getSourceContext('@__main__/index.html')->getCode());\n        $this->assertEquals(\"named path (final)\\n\", $loader->getSourceContext('@named/index.html')->getCode());\n    }\n\n    public static function getBasePaths()\n    {\n        return [\n            [\n                __DIR__.'/Fixtures',\n                'tests/Loader/Fixtures/named_quater/named_absolute.html',\n                null,\n            ],\n            [\n                __DIR__.'/Fixtures/../Fixtures',\n                'tests/Loader/Fixtures/named_quater/named_absolute.html',\n                null,\n            ],\n            [\n                'tests/Loader/Fixtures',\n                'tests/Loader/Fixtures/named_quater/named_absolute.html',\n                getcwd(),\n            ],\n            [\n                'Fixtures',\n                'Fixtures/named_quater/named_absolute.html',\n                getcwd().'/tests/Loader',\n            ],\n            [\n                'Fixtures',\n                'Fixtures/named_quater/named_absolute.html',\n                getcwd().'/tests/../tests/Loader',\n            ],\n        ];\n    }\n\n    public function testEmptyConstructor()\n    {\n        $loader = new FilesystemLoader();\n        $this->assertEquals([], $loader->getPaths());\n    }\n\n    public function testGetNamespaces()\n    {\n        $loader = new FilesystemLoader(sys_get_temp_dir());\n        $this->assertEquals([FilesystemLoader::MAIN_NAMESPACE], $loader->getNamespaces());\n\n        $loader->addPath(sys_get_temp_dir(), 'named');\n        $this->assertEquals([FilesystemLoader::MAIN_NAMESPACE, 'named'], $loader->getNamespaces());\n    }\n\n    public function testFindTemplateExceptionNamespace()\n    {\n        $basePath = __DIR__.'/Fixtures';\n\n        $loader = new FilesystemLoader([$basePath.'/normal']);\n        $loader->addPath($basePath.'/named', 'named');\n\n        try {\n            $loader->getSourceContext('@named/nowhere.html');\n        } catch (\\Exception $e) {\n            $this->assertInstanceOf(LoaderError::class, $e);\n            $this->assertStringContainsString('Unable to find template \"@named/nowhere.html\"', $e->getMessage());\n        }\n    }\n\n    public function testFindTemplateWithCache()\n    {\n        $basePath = __DIR__.'/Fixtures';\n\n        $loader = new FilesystemLoader([$basePath.'/normal']);\n        $loader->addPath($basePath.'/named', 'named');\n\n        // prime the cache for index.html in the named namespace\n        $namedSource = $loader->getSourceContext('@named/index.html')->getCode();\n        $this->assertEquals(\"named path\\n\", $namedSource);\n\n        // get index.html from the main namespace\n        $this->assertEquals(\"path\\n\", $loader->getSourceContext('index.html')->getCode());\n    }\n\n    public function testLoadTemplateAndRenderBlockWithCache()\n    {\n        $loader = new FilesystemLoader([]);\n        $loader->addPath(__DIR__.'/Fixtures/themes/theme2');\n        $loader->addPath(__DIR__.'/Fixtures/themes/theme1');\n        $loader->addPath(__DIR__.'/Fixtures/themes/theme1', 'default_theme');\n\n        $twig = new Environment($loader);\n\n        $template = $twig->load('blocks.html.twig');\n        $this->assertSame('block from theme 1', $template->renderBlock('b1', []));\n\n        $template = $twig->load('blocks.html.twig');\n        $this->assertSame('block from theme 2', $template->renderBlock('b2', []));\n    }\n\n    public static function getArrayInheritanceTests()\n    {\n        return [\n            'valid array inheritance' => ['array_inheritance_valid_parent.html.twig'],\n            'array inheritance with empty first template' => ['array_inheritance_empty_parent.html.twig'],\n            'array inheritance with non-existent first template' => ['array_inheritance_nonexistent_parent.html.twig'],\n        ];\n    }\n\n    /**\n     * @dataProvider getArrayInheritanceTests\n     */\n    public function testArrayInheritance(string $templateName)\n    {\n        $loader = new FilesystemLoader([]);\n        $loader->addPath(__DIR__.'/Fixtures/inheritance');\n\n        $twig = new Environment($loader);\n\n        $template = $twig->load($templateName);\n        $this->assertSame('VALID Child', $template->renderBlock('body', []));\n    }\n\n    public function testLoadTemplateFromPhar()\n    {\n        $loader = new FilesystemLoader([]);\n        // phar-sample.phar was created with the following script:\n        // $f = new Phar('phar-test.phar');\n        // $f->addFromString('hello.twig', 'hello from phar');\n        $loader->addPath('phar://'.__DIR__.'/Fixtures/phar/phar-sample.phar');\n        $this->assertSame('hello from phar', $loader->getSourceContext('hello.twig')->getCode());\n    }\n\n    public function testTemplateExistsAlwaysReturnsBool()\n    {\n        $loader = new FilesystemLoader([]);\n        $this->assertFalse($loader->exists(\"foo\\0.twig\"));\n        $this->assertFalse($loader->exists('../foo.twig'));\n        $this->assertFalse($loader->exists('@foo'));\n        $this->assertFalse($loader->exists('foo'));\n        $this->assertFalse($loader->exists('@foo/bar.twig'));\n\n        $loader->addPath(__DIR__.'/Fixtures/normal');\n        $this->assertTrue($loader->exists('index.html'));\n        $loader->addPath(__DIR__.'/Fixtures/normal', 'foo');\n        $this->assertTrue($loader->exists('@foo/index.html'));\n    }\n}\n"
  },
  {
    "path": "tests/Loader/Fixtures/inheritance/array_inheritance_empty_parent.html.twig",
    "content": "{% extends ['','parent.html.twig'] %}\n\n{% block body %}{{ parent() }} Child{% endblock %}\n"
  },
  {
    "path": "tests/Loader/Fixtures/inheritance/array_inheritance_nonexistent_parent.html.twig",
    "content": "{% extends ['nonexistent.html.twig','parent.html.twig'] %}\n\n{% block body %}{{ parent() }} Child{% endblock %}\n"
  },
  {
    "path": "tests/Loader/Fixtures/inheritance/array_inheritance_valid_parent.html.twig",
    "content": "{% extends ['parent.html.twig','spare_parent.html.twig'] %}\n\n{% block body %}{{ parent() }} Child{% endblock %}\n"
  },
  {
    "path": "tests/Loader/Fixtures/inheritance/parent.html.twig",
    "content": "{% block body %}VALID{% endblock %}\n"
  },
  {
    "path": "tests/Loader/Fixtures/inheritance/spare_parent.html.twig",
    "content": "{% block body %}SPARE PARENT{% endblock %}\n"
  },
  {
    "path": "tests/Loader/Fixtures/named/index.html",
    "content": "named path\n"
  },
  {
    "path": "tests/Loader/Fixtures/named_bis/index.html",
    "content": "named path (bis)\n"
  },
  {
    "path": "tests/Loader/Fixtures/named_final/index.html",
    "content": "named path (final)\n"
  },
  {
    "path": "tests/Loader/Fixtures/named_quater/named_absolute.html",
    "content": "named path (quater)\n"
  },
  {
    "path": "tests/Loader/Fixtures/named_ter/index.html",
    "content": "named path (ter)\n"
  },
  {
    "path": "tests/Loader/Fixtures/normal/index.html",
    "content": "path\n"
  },
  {
    "path": "tests/Loader/Fixtures/normal_bis/index.html",
    "content": "path (bis)\n"
  },
  {
    "path": "tests/Loader/Fixtures/normal_final/index.html",
    "content": "path (final)\n"
  },
  {
    "path": "tests/Loader/Fixtures/normal_ter/index.html",
    "content": "path (ter)\n"
  },
  {
    "path": "tests/Loader/Fixtures/themes/theme1/blocks.html.twig",
    "content": "{% block b1 %}block from theme 1{% endblock %}\n\n{% block b2 %}block from theme 1{% endblock %}\n"
  },
  {
    "path": "tests/Loader/Fixtures/themes/theme2/blocks.html.twig",
    "content": "{% use '@default_theme/blocks.html.twig' %}\n\n{% block b2 %}block from theme 2{% endblock %}\n"
  },
  {
    "path": "tests/Node/AutoEscapeTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\AutoEscapeNode;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\TextNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass AutoEscapeTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $body = new Nodes([new TextNode('foo', 1)]);\n        $node = new AutoEscapeNode(true, $body, 1);\n\n        $this->assertEquals($body, $node->getNode('body'));\n        $this->assertTrue($node->getAttribute('value'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $body = new Nodes([new TextNode('foo', 1)]);\n        $node = new AutoEscapeNode(true, $body, 1);\n\n        return [\n            [$node, \"// line 1\\nyield \\\"foo\\\";\"],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/BlockReferenceTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\BlockReferenceNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass BlockReferenceTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $node = new BlockReferenceNode('foo', 1);\n\n        $this->assertEquals('foo', $node->getAttribute('name'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        return [\n            [new BlockReferenceNode('foo', 1), <<<'EOF'\n// line 1\nyield from $this->unwrap()->yieldBlock('foo', $context, $blocks);\nEOF\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/BlockTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\BlockNode;\nuse Twig\\Node\\TextNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass BlockTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $body = new TextNode('foo', 1);\n        $node = new BlockNode('foo', $body, 1);\n\n        $this->assertEquals($body, $node->getNode('body'));\n        $this->assertEquals('foo', $node->getAttribute('name'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n        $tests[] = [new BlockNode('foo', new TextNode('foo', 1), 1), <<<EOF\n// line 1\n/**\n * @return iterable<null|scalar|\\Stringable>\n */\npublic function block_foo(array \\$context, array \\$blocks = []): iterable\n{\n    \\$macros = \\$this->macros;\n    yield \"foo\";\n    yield from [];\n}\nEOF, new Environment(new ArrayLoader()),\n        ];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/DeprecatedTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Compiler;\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\DeprecatedNode;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\Node\\IfNode;\nuse Twig\\Node\\Nodes;\nuse Twig\\Source;\nuse Twig\\Test\\NodeTestCase;\nuse Twig\\TwigFunction;\n\nclass DeprecatedTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression('foo', 1);\n        $node = new DeprecatedNode($expr, 1);\n\n        $this->assertEquals($expr, $node->getNode('expr'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $expr = new ConstantExpression('This section is deprecated', 1);\n        $node = new DeprecatedNode($expr, 1);\n        $node->setSourceContext(new Source('', 'foo.twig'));\n        $node->setNode('package', new ConstantExpression('twig/twig', 1));\n        $node->setNode('version', new ConstantExpression('1.1', 1));\n\n        $tests[] = [$node, <<<EOF\n// line 1\ntrigger_deprecation(\"twig/twig\", \"1.1\", \"This section is deprecated\".\" in \\\"foo.twig\\\" at line 1.\");\nEOF\n        ];\n\n        $t = new Nodes([\n            new ConstantExpression(true, 1),\n            $dep = new DeprecatedNode($expr, 2),\n        ], 1);\n        $node = new IfNode($t, null, 1);\n        $node->setSourceContext(new Source('', 'foo.twig'));\n        $dep->setNode('package', new ConstantExpression('twig/twig', 1));\n        $dep->setNode('version', new ConstantExpression('1.1', 1));\n\n        $tests[] = [$node, <<<EOF\n// line 1\nif (true) {\n    // line 2\n    trigger_deprecation(\"twig/twig\", \"1.1\", \"This section is deprecated\".\" in \\\"foo.twig\\\" at line 2.\");\n}\nEOF\n        ];\n\n        $environment = new Environment(new ArrayLoader());\n        $environment->addFunction($function = new TwigFunction('foo', 'Twig\\Tests\\Node\\foo', []));\n\n        $expr = new FunctionExpression($function, new EmptyNode(), 1);\n        $node = new DeprecatedNode($expr, 1);\n        $node->setSourceContext(new Source('', 'foo.twig'));\n        $node->setNode('package', new ConstantExpression('twig/twig', 1));\n        $node->setNode('version', new ConstantExpression('1.1', 1));\n\n        $compiler = new Compiler($environment);\n        $varName = $compiler->getVarName();\n\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$$varName = Twig\\Tests\\Node\\\\foo();\ntrigger_deprecation(\"twig/twig\", \"1.1\", \\$$varName.\" in \\\"foo.twig\\\" at line 1.\");\nEOF, $environment];\n\n        return $tests;\n    }\n}\n\nfunction foo()\n{\n}\n"
  },
  {
    "path": "tests/Node/DoTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\DoNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass DoTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression('foo', 1);\n        $node = new DoNode($expr, 1);\n\n        $this->assertEquals($expr, $node->getNode('expr'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $expr = new ConstantExpression('foo', 1);\n        $node = new DoNode($expr, 1);\n        $tests[] = [$node, \"// line 1\\n\\\"foo\\\";\"];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/EmbedTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\nuse Twig\\Node\\EmbedNode;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass EmbedTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $node = new EmbedNode('foo.twig', 0, null, false, false, 1);\n\n        $this->assertFalse($node->hasNode('variables'));\n        $this->assertEquals('foo.twig', $node->getAttribute('name'));\n        $this->assertEquals(0, $node->getAttribute('index'));\n        $this->assertFalse($node->getAttribute('only'));\n        $this->assertFalse($node->getAttribute('ignore_missing'));\n\n        $vars = new ArrayExpression([new ConstantExpression('foo', 1), new ConstantExpression(true, 1)], 1);\n        $node = new EmbedNode('bar.twig', 1, $vars, true, false, 1);\n        $this->assertEquals($vars, $node->getNode('variables'));\n        $this->assertTrue($node->getAttribute('only'));\n        $this->assertEquals('bar.twig', $node->getAttribute('name'));\n        $this->assertEquals(1, $node->getAttribute('index'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $node = new EmbedNode('foo.twig', 0, null, false, false, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\nyield from $this->load(\"foo.twig\", 1, 0)->unwrap()->yield($context);\nEOF\n        ];\n\n        $node = new EmbedNode('foo.twig', 1, null, false, false, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\nyield from $this->load(\"foo.twig\", 1, 1)->unwrap()->yield($context);\nEOF\n        ];\n\n        $vars = new ArrayExpression([new ConstantExpression('foo', 1), new ConstantExpression(true, 1)], 1);\n        $node = new EmbedNode('foo.twig', 0, $vars, false, false, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\nyield from $this->load(\"foo.twig\", 1, 0)->unwrap()->yield(CoreExtension::merge($context, [\"foo\" => true]));\nEOF\n        ];\n\n        $node = new EmbedNode('foo.twig', 0, $vars, true, false, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\nyield from $this->load(\"foo.twig\", 1, 0)->unwrap()->yield(CoreExtension::toArray([\"foo\" => true]));\nEOF\n        ];\n\n        $node = new EmbedNode('foo.twig', 2, $vars, true, true, 1);\n        $tests[] = [$node, <<<EOF\n// line 1\ntry {\n    \\$_v0 = \\$this->load(\"foo.twig\", 1, 2);\n    \\$_v0->getParent(\\$context);\n;\n} catch (LoaderError \\$e) {\n    // ignore missing template\n    \\$_v0 = null;\n}\nif (\\$_v0) {\n    yield from \\$_v0->unwrap()->yield(CoreExtension::toArray([\"foo\" => true]));\n}\nEOF\n        ];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/ArrayTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass ArrayTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $elements = [new ConstantExpression('foo', 1), $foo = new ConstantExpression('bar', 1)];\n        $node = new ArrayExpression($elements, 1);\n\n        $this->assertEquals($foo, $node->getNode('1'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $elements = [\n            new ConstantExpression('foo', 1),\n            new ConstantExpression('bar', 1),\n\n            new ConstantExpression('bar', 1),\n            new ConstantExpression('foo', 1),\n        ];\n        $node = new ArrayExpression($elements, 1);\n\n        return [\n            [$node, '[\"foo\" => \"bar\", \"bar\" => \"foo\"]'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/AddTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\AddBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass AddTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new AddBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new AddBinary($left, $right, 1);\n\n        return [\n            [$node, '(1 + 2)'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/AndTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\AndBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass AndTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new AndBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new AndBinary($left, $right, 1);\n\n        return [\n            [$node, '(1 && 2)'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/ConcatTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\ConcatBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass ConcatTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new ConcatBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new ConcatBinary($left, $right, 1);\n\n        return [\n            [$node, '(1 . 2)'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/DivTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\DivBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass DivTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new DivBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new DivBinary($left, $right, 1);\n\n        return [\n            [$node, '(1 / 2)'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/FloorDivTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\FloorDivBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass FloorDivTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new FloorDivBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new FloorDivBinary($left, $right, 1);\n\n        return [\n            [$node, '(int) floor((1 / 2))'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/ModTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\ModBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass ModTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new ModBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new ModBinary($left, $right, 1);\n\n        return [\n            [$node, '(1 % 2)'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/MulTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\MulBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass MulTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new MulBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new MulBinary($left, $right, 1);\n\n        return [\n            [$node, '(1 * 2)'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/NullCoalesceTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\NullCoalesceBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Test\\NodeTestCase;\n\nclass NullCoalesceTest extends NodeTestCase\n{\n    public static function provideTests(): iterable\n    {\n        $left = new ContextVariable('foo', 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new NullCoalesceBinary($left, $right, 1);\n\n        return [[$node, \"(((// line 1\\narray_key_exists(\\\"foo\\\", \\$context) &&  !(null === \\$context[\\\"foo\\\"]))) ? (\\$context[\\\"foo\\\"]) : (2))\"]];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/OrTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\OrBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass OrTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new OrBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new OrBinary($left, $right, 1);\n\n        return [\n            [$node, '(1 || 2)'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Binary/SubTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Binary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Binary\\SubBinary;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass SubTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new SubBinary($left, $right, 1);\n\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $left = new ConstantExpression(1, 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new SubBinary($left, $right, 1);\n\n        return [\n            [$node, '(1 - 2)'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/CallTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\TwigFunction;\n\n/**\n * @group legacy\n */\nclass CallTest extends TestCase\n{\n    public function testGetArguments()\n    {\n        $node = $this->createFunctionExpression('date', 'date');\n        $this->assertEquals(['U', null], $this->getArguments($node, ['date', ['format' => 'U', 'timestamp' => null]]));\n    }\n\n    public function testGetArgumentsWhenPositionalArgumentsAfterNamedArguments()\n    {\n        $node = $this->createFunctionExpression('date', 'date');\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Positional arguments cannot be used after named arguments for function \"date\".');\n\n        $this->getArguments($node, ['date', ['timestamp' => 123456, 'Y-m-d']]);\n    }\n\n    public function testGetArgumentsWhenArgumentIsDefinedTwice()\n    {\n        $node = $this->createFunctionExpression('date', 'date');\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Argument \"format\" is defined twice for function \"date\".');\n\n        $this->getArguments($node, ['date', ['Y-m-d', 'format' => 'U']]);\n    }\n\n    public function testGetArgumentsWithWrongNamedArgumentName()\n    {\n        $node = $this->createFunctionExpression('date', 'date');\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown argument \"unknown\" for function \"date(format, timestamp)\".');\n\n        $this->getArguments($node, ['date', ['Y-m-d', 'timestamp' => null, 'unknown' => '']]);\n    }\n\n    public function testGetArgumentsWithWrongNamedArgumentNames()\n    {\n        $node = $this->createFunctionExpression('date', 'date');\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown arguments \"unknown1\", \"unknown2\" for function \"date(format, timestamp)\".');\n\n        $this->getArguments($node, ['date', ['Y-m-d', 'timestamp' => null, 'unknown1' => '', 'unknown2' => '']]);\n    }\n\n    public function testResolveArgumentsWithMissingValueForOptionalArgument()\n    {\n        if (\\PHP_VERSION_ID >= 80000) {\n            $this->markTestSkipped('substr_compare() has a default value in 8.0, so the test does not work anymore, one should find another PHP built-in function for this test to work in PHP 8.');\n        }\n\n        $node = $this->createFunctionExpression('substr_compare', 'substr_compare');\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Argument \"case_sensitivity\" could not be assigned for function \"substr_compare(main_str, str, offset, length, case_sensitivity)\" because it is mapped to an internal PHP function which cannot determine default value for optional argument \"length\".');\n\n        $this->getArguments($node, ['substr_compare', ['abcd', 'bc', 'offset' => 1, 'case_sensitivity' => true]]);\n    }\n\n    public function testResolveArgumentsOnlyNecessaryArgumentsForCustomFunction()\n    {\n        $node = $this->createFunctionExpression('custom_function', [$this, 'customFunction']);\n        $this->assertEquals(['arg1'], $this->getArguments($node, [[$this, 'customFunction'], ['arg1' => 'arg1']]));\n    }\n\n    public function testGetArgumentsForStaticMethod()\n    {\n        $node = $this->createFunctionExpression('custom_static_function', __CLASS__.'::customStaticFunction');\n        $this->assertEquals(['arg1'], $this->getArguments($node, [__CLASS__.'::customStaticFunction', ['arg1' => 'arg1']]));\n    }\n\n    public function testResolveArgumentsWithMissingParameterForArbitraryArguments()\n    {\n        $node = $this->createFunctionExpression('foo', [$this, 'customFunctionWithArbitraryArguments'], true);\n\n        $this->expectException(\\LogicException::class);\n        $this->expectExceptionMessage('The last parameter of \"Twig\\\\Tests\\\\Node\\\\Expression\\\\CallTest::customFunctionWithArbitraryArguments\" for function \"foo\" must be an array with default value, eg. \"array $arg = []\".');\n\n        $this->getArguments($node, [[$this, 'customFunctionWithArbitraryArguments'], []]);\n    }\n\n    public function testGetArgumentsWithInvalidCallable()\n    {\n        $node = $this->createFunctionExpression('foo', '<not-a-callable>', true);\n\n        $this->expectException(\\LogicException::class);\n        $this->expectExceptionMessage('Callback for function \"foo\" is not callable in the current scope.');\n\n        $this->getArguments($node, ['<not-a-callable>', []]);\n    }\n\n    public function testResolveArgumentsWithMissingParameterForArbitraryArgumentsOnFunction()\n    {\n        $node = $this->createFunctionExpression('foo', 'Twig\\Tests\\Node\\Expression\\custom_call_test_function', true);\n\n        $this->expectException(\\LogicException::class);\n        $this->expectExceptionMessageMatches('#^The last parameter of \"Twig\\\\\\\\Tests\\\\\\\\Node\\\\\\\\Expression\\\\\\\\custom_call_test_function\" for function \"foo\" must be an array with default value, eg\\\\. \"array \\\\$arg \\\\= \\\\[\\\\]\"\\\\.$#');\n\n        $this->getArguments($node, ['Twig\\Tests\\Node\\Expression\\custom_call_test_function', []]);\n    }\n\n    public function testResolveArgumentsWithMissingParameterForArbitraryArgumentsOnObject()\n    {\n        $node = $this->createFunctionExpression('foo', new CallableTestClass(), true);\n\n        $this->expectException(\\LogicException::class);\n        $this->expectExceptionMessageMatches('#^The last parameter of \"Twig\\\\\\\\Tests\\\\\\\\Node\\\\\\\\Expression\\\\\\\\CallableTestClass\\\\:\\\\:__invoke\" for function \"foo\" must be an array with default value, eg\\\\. \"array \\\\$arg \\\\= \\\\[\\\\]\"\\\\.$#');\n\n        $this->getArguments($node, [new CallableTestClass(), []]);\n    }\n\n    public static function customStaticFunction($arg1, $arg2 = 'default', $arg3 = [])\n    {\n    }\n\n    public function customFunction($arg1, $arg2 = 'default', $arg3 = [])\n    {\n    }\n\n    public function customFunctionWithArbitraryArguments()\n    {\n    }\n\n    private function getArguments($call, $args)\n    {\n        $m = new \\ReflectionMethod($call, 'getArguments');\n\n        return $m->invokeArgs($call, $args);\n    }\n\n    private function createFunctionExpression($name, $callable, $isVariadic = false): Node_Expression_Call\n    {\n        return new Node_Expression_Call(new TwigFunction($name, $callable, ['is_variadic' => $isVariadic]), new EmptyNode(), 0);\n    }\n}\n\nclass Node_Expression_Call extends FunctionExpression\n{\n}\n\nclass CallableTestClass\n{\n    public function __invoke($required)\n    {\n    }\n}\n\nfunction custom_call_test_function($required)\n{\n}\n"
  },
  {
    "path": "tests/Node/Expression/ConditionalTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConditionalExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\n/**\n * @group legacy\n */\nclass ConditionalTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr1 = new ConstantExpression(1, 1);\n        $expr2 = new ConstantExpression(2, 1);\n        $expr3 = new ConstantExpression(3, 1);\n        $node = new ConditionalExpression($expr1, $expr2, $expr3, 1);\n\n        $this->assertEquals($expr1, $node->getNode('expr1'));\n        $this->assertEquals($expr2, $node->getNode('expr2'));\n        $this->assertEquals($expr3, $node->getNode('expr3'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $expr1 = new ConstantExpression(1, 1);\n        $expr2 = new ConstantExpression(2, 1);\n        $expr3 = new ConstantExpression(3, 1);\n        $node = new ConditionalExpression($expr1, $expr2, $expr3, 1);\n        $tests[] = [$node, '((1) ? (2) : (3))'];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/ConstantTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass ConstantTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $node = new ConstantExpression('foo', 1);\n\n        $this->assertEquals('foo', $node->getAttribute('value'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $node = new ConstantExpression('foo', 1);\n        $tests[] = [$node, '\"foo\"'];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Filter/RawTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Filter;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Filter\\RawFilter;\nuse Twig\\Test\\NodeTestCase;\n\nclass RawTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $filter = new RawFilter($node = new ConstantExpression('foo', 12));\n\n        $this->assertSame(12, $filter->getTemplateLine());\n        $this->assertSame('raw', $filter->getAttribute('name'));\n        $this->assertSame('raw', $filter->getNode('filter', false)->getAttribute('value'));\n        $this->assertSame($node, $filter->getNode('node'));\n        $this->assertCount(0, $filter->getNode('arguments'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $node = new RawFilter(new ConstantExpression('foo', 12));\n\n        return [\n            [$node, '\"foo\"'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/FilterTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FilterExpression;\nuse Twig\\Node\\Nodes;\nuse Twig\\Test\\NodeTestCase;\nuse Twig\\TwigFilter;\n\nclass FilterTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression('foo', 1);\n        $filter = new TwigFilter($name = 'upper');\n        $args = new EmptyNode();\n        $node = new FilterExpression($expr, $filter, $args, 1);\n\n        $this->assertEquals($expr, $node->getNode('node'));\n        $this->assertEquals($name, $node->getAttribute('name'));\n        $this->assertEquals($args, $node->getNode('arguments'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $environment = static::createEnvironment();\n\n        $tests = [];\n\n        $expr = new ConstantExpression('foo', 1);\n        $node = self::createFilter($environment, $expr, 'upper');\n        $node = self::createFilter($environment, $node, 'number_format', [new ConstantExpression(2, 1), new ConstantExpression('.', 1), new ConstantExpression(',', 1)]);\n\n        $tests[] = [$node, '$this->extensions[\\'Twig\\Extension\\CoreExtension\\']->formatNumber(Twig\\Extension\\CoreExtension::upper($this->env->getCharset(), \"foo\"), 2, \".\", \",\")'];\n\n        // named arguments\n        $date = new ConstantExpression(0, 1);\n        $node = self::createFilter($environment, $date, 'date', [\n            'timezone' => new ConstantExpression('America/Chicago', 1),\n            'format' => new ConstantExpression('d/m/Y H:i:s P', 1),\n        ]);\n        $tests[] = [$node, '$this->extensions[\\'Twig\\Extension\\CoreExtension\\']->formatDate(0, \"d/m/Y H:i:s P\", \"America/Chicago\")'];\n\n        // skip an optional argument\n        $date = new ConstantExpression(0, 1);\n        $node = self::createFilter($environment, $date, 'date', [\n            'timezone' => new ConstantExpression('America/Chicago', 1),\n        ]);\n        $tests[] = [$node, '$this->extensions[\\'Twig\\Extension\\CoreExtension\\']->formatDate(0, null, \"America/Chicago\")'];\n\n        // underscores vs camelCase for named arguments\n        $string = new ConstantExpression('abc', 1);\n        $node = self::createFilter($environment, $string, 'reverse', [\n            'preserve_keys' => new ConstantExpression(true, 1),\n        ]);\n        $tests[] = [$node, 'Twig\\Extension\\CoreExtension::reverse($this->env->getCharset(), \"abc\", true)'];\n        $node = self::createFilter($environment, $string, 'reverse', [\n            'preserveKeys' => new ConstantExpression(true, 1),\n        ]);\n        $tests[] = [$node, 'Twig\\Extension\\CoreExtension::reverse($this->env->getCharset(), \"abc\", true)'];\n\n        // filter as an anonymous function\n        $node = self::createFilter($environment, new ConstantExpression('foo', 1), 'anonymous');\n        $tests[] = [$node, '$this->env->getFilter(\\'anonymous\\')->getCallable()(\"foo\")'];\n\n        // needs environment\n        $node = self::createFilter($environment, $string, 'bar');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_filter_dummy($this->env, \"abc\")', $environment];\n\n        $node = self::createFilter($environment, $string, 'bar_closure');\n        $tests[] = [$node, twig_tests_filter_dummy::class.'($this->env, \"abc\")', $environment];\n\n        $node = self::createFilter($environment, $string, 'bar', [new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_filter_dummy($this->env, \"abc\", \"bar\")', $environment];\n\n        // arbitrary named arguments\n        $node = self::createFilter($environment, $string, 'barbar');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_filter_barbar($context, \"abc\")', $environment];\n\n        $node = self::createFilter($environment, $string, 'barbar', ['foo' => new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_filter_barbar($context, \"abc\", null, null, [\"foo\" => \"bar\"])', $environment];\n\n        $node = self::createFilter($environment, $string, 'barbar', ['arg2' => new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_filter_barbar($context, \"abc\", null, \"bar\")', $environment];\n\n        if (\\PHP_VERSION_ID >= 80111) {\n            $node = self::createFilter($environment, $string, 'first_class_callable_static');\n            $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\FilterTestExtension::staticMethod(\"abc\")', $environment];\n\n            $node = self::createFilter($environment, $string, 'first_class_callable_object');\n            $tests[] = [$node, '$this->extensions[\\'Twig\\Tests\\Node\\Expression\\FilterTestExtension\\']->objectMethod(\"abc\")', $environment];\n        }\n\n        $node = self::createFilter($environment, $string, 'barbar', [\n            new ConstantExpression('1', 1),\n            new ConstantExpression('2', 1),\n            new ConstantExpression('3', 1),\n            'foo' => new ConstantExpression('bar', 1),\n        ]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_filter_barbar($context, \"abc\", \"1\", \"2\", [\"3\", \"foo\" => \"bar\"])', $environment];\n\n        // from extension\n        $node = self::createFilter($environment, $string, 'foo');\n        $tests[] = [$node, \\sprintf('$this->extensions[\\'%s\\']->foo(\"abc\")', \\get_class(self::createExtension())), $environment];\n\n        $node = self::createFilter($environment, $string, 'foobar');\n        $tests[] = [$node, '$this->env->getFilter(\\'foobar\\')->getCallable()(\"abc\")', $environment];\n\n        $node = self::createFilter($environment, $string, 'magic_static');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\ChildMagicCallStub::magicStaticCall(\"abc\")', $environment];\n\n        return $tests;\n    }\n\n    public function testCompileWithWrongNamedArgumentName()\n    {\n        $date = new ConstantExpression(0, 1);\n        $node = $this->createFilter($this->getEnvironment(), $date, 'date', [\n            'foobar' => new ConstantExpression('America/Chicago', 1),\n        ]);\n\n        $compiler = $this->getCompiler();\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown argument \"foobar\" for filter \"date(format, timezone)\" at line 1.');\n\n        $compiler->compile($node);\n    }\n\n    public function testCompileWithMissingNamedArgument()\n    {\n        $value = new ConstantExpression(0, 1);\n        $node = $this->createFilter($this->getEnvironment(), $value, 'replace', [\n            'to' => new ConstantExpression('foo', 1),\n        ]);\n\n        $compiler = $this->getCompiler();\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Value for argument \"from\" is required for filter \"replace\" at line 1.');\n\n        $compiler->compile($node);\n    }\n\n    private static function createFilter(Environment $env, $node, $name, array $arguments = []): FilterExpression\n    {\n        return new FilterExpression($node, $env->getFilter($name), new Nodes($arguments), 1);\n    }\n\n    protected static function createEnvironment(): Environment\n    {\n        $env = new Environment(new ArrayLoader());\n        $env->addFilter(new TwigFilter('anonymous', static function () {}));\n        $env->addFilter(new TwigFilter('bar', 'Twig\\Tests\\Node\\Expression\\twig_tests_filter_dummy', ['needs_environment' => true]));\n        $env->addFilter(new TwigFilter('bar_closure', \\Closure::fromCallable(twig_tests_filter_dummy::class), ['needs_environment' => true]));\n        $env->addFilter(new TwigFilter('barbar', 'Twig\\Tests\\Node\\Expression\\twig_tests_filter_barbar', ['needs_context' => true, 'is_variadic' => true]));\n        $env->addFilter(new TwigFilter('magic_static', __NAMESPACE__.'\\ChildMagicCallStub::magicStaticCall'));\n        if (\\PHP_VERSION_ID >= 80111) {\n            $env->addExtension(new FilterTestExtension());\n        }\n        $env->addExtension(self::createExtension());\n\n        return $env;\n    }\n\n    private static function createExtension(): AbstractExtension\n    {\n        return new class extends AbstractExtension {\n            public function getFilters(): array\n            {\n                return [\n                    new TwigFilter('foo', \\Closure::fromCallable([$this, 'foo'])),\n                    new TwigFilter('foobar', \\Closure::fromCallable([$this, 'foobar'])),\n                ];\n            }\n\n            public function foo()\n            {\n            }\n\n            protected function foobar()\n            {\n            }\n        };\n    }\n}\n\nfunction twig_tests_filter_dummy()\n{\n}\n\nfunction twig_tests_filter_barbar($context, $string, $arg1 = null, $arg2 = null, array $args = [])\n{\n}\n\nclass ChildMagicCallStub extends ParentMagicCallStub\n{\n    public static function identifier()\n    {\n        return 'child';\n    }\n}\n\nclass ParentMagicCallStub\n{\n    public static function identifier()\n    {\n        throw new \\Exception('Identifier has not been defined.');\n    }\n\n    public static function __callStatic($method, $arguments)\n    {\n        if ('magicStaticCall' !== $method) {\n            throw new \\BadMethodCallException('Unexpected call to __callStatic.');\n        }\n\n        return 'inherited_static_magic_'.static::identifier().'_'.$arguments[0];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/FilterTestExtension.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\nuse Twig\\Extension\\AbstractExtension;\nuse Twig\\TwigFilter;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nclass FilterTestExtension extends AbstractExtension\n{\n    public function getFilters(): array\n    {\n        return [\n            new TwigFilter('first_class_callable_static', self::staticMethod(...)),\n            new TwigFilter('first_class_callable_object', $this->objectMethod(...)),\n        ];\n    }\n\n    public static function staticMethod()\n    {\n    }\n\n    public function objectMethod()\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/FunctionTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\Node\\Nodes;\nuse Twig\\Test\\NodeTestCase;\nuse Twig\\TwigFunction;\n\nclass FunctionTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $name = 'function';\n        $args = new EmptyNode();\n        $node = new FunctionExpression(new TwigFunction($name), $args, 1);\n\n        $this->assertEquals($name, $node->getAttribute('name'));\n        $this->assertEquals($args, $node->getNode('arguments'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $environment = static::createEnvironment();\n\n        $tests = [];\n\n        $node = self::createFunction($environment, 'foo');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy()', $environment];\n\n        $node = self::createFunction($environment, 'foo_closure');\n        $tests[] = [$node, twig_tests_function_dummy::class.'()', $environment];\n\n        $node = self::createFunction($environment, 'foo', [new ConstantExpression('bar', 1), new ConstantExpression('foobar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy(\"bar\", \"foobar\")', $environment];\n\n        $node = self::createFunction($environment, 'bar');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy($this->env)', $environment];\n\n        $node = self::createFunction($environment, 'bar', [new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy($this->env, \"bar\")', $environment];\n\n        $node = self::createFunction($environment, 'foofoo');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy($context)', $environment];\n\n        $node = self::createFunction($environment, 'foofoo', [new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy($context, \"bar\")', $environment];\n\n        $node = self::createFunction($environment, 'foobar');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy($this->env, $context)', $environment];\n\n        $node = self::createFunction($environment, 'foobar', [new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy($this->env, $context, \"bar\")', $environment];\n\n        // named arguments\n        $node = self::createFunction($environment, 'date', [\n            'timezone' => new ConstantExpression('America/Chicago', 1),\n            'date' => new ConstantExpression(0, 1),\n        ]);\n        $tests[] = [$node, '$this->extensions[\\'Twig\\Extension\\CoreExtension\\']->convertDate(0, \"America/Chicago\")'];\n\n        // arbitrary named arguments\n        $node = self::createFunction($environment, 'barbar');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_barbar()', $environment];\n\n        $node = self::createFunction($environment, 'barbar', ['foo' => new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_barbar(null, null, [\"foo\" => \"bar\"])', $environment];\n\n        $node = self::createFunction($environment, 'barbar', ['arg2' => new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_barbar(null, \"bar\")', $environment];\n\n        $node = self::createFunction($environment, 'barbar', [\n            new ConstantExpression('1', 1),\n            new ConstantExpression('2', 1),\n            new ConstantExpression('3', 1),\n            'foo' => new ConstantExpression('bar', 1),\n        ]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_function_barbar(\"1\", \"2\", [\"3\", \"foo\" => \"bar\"])', $environment];\n\n        // function as an anonymous function\n        $node = self::createFunction($environment, 'anonymous', [new ConstantExpression('foo', 1)]);\n        $tests[] = [$node, '$this->env->getFunction(\\'anonymous\\')->getCallable()(\"foo\")'];\n\n        return $tests;\n    }\n\n    private static function createFunction(Environment $env, $name, array $arguments = []): FunctionExpression\n    {\n        return new FunctionExpression($env->getFunction($name), new Nodes($arguments), 1);\n    }\n\n    protected static function createEnvironment(): Environment\n    {\n        $env = new Environment(new ArrayLoader());\n        $env->addFunction(new TwigFunction('anonymous', static function () {}));\n        $env->addFunction(new TwigFunction('foo', 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy', []));\n        $env->addFunction(new TwigFunction('foo_closure', \\Closure::fromCallable(twig_tests_function_dummy::class), []));\n        $env->addFunction(new TwigFunction('bar', 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy', ['needs_environment' => true]));\n        $env->addFunction(new TwigFunction('foofoo', 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy', ['needs_context' => true]));\n        $env->addFunction(new TwigFunction('foobar', 'Twig\\Tests\\Node\\Expression\\twig_tests_function_dummy', ['needs_environment' => true, 'needs_context' => true]));\n        $env->addFunction(new TwigFunction('barbar', 'Twig\\Tests\\Node\\Expression\\twig_tests_function_barbar', ['is_variadic' => true]));\n\n        return $env;\n    }\n}\n\nfunction twig_tests_function_dummy()\n{\n}\n\nfunction twig_tests_function_barbar($arg1 = null, $arg2 = null, array $args = [])\n{\n}\n"
  },
  {
    "path": "tests/Node/Expression/GetAttrTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Template;\nuse Twig\\Test\\NodeTestCase;\n\nclass GetAttrTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ContextVariable('foo', 1);\n        $attr = new ConstantExpression('bar', 1);\n        $args = new ArrayExpression([], 1);\n        $args->addElement(new ContextVariable('foo', 1));\n        $args->addElement(new ConstantExpression('bar', 1));\n        $node = new GetAttrExpression($expr, $attr, $args, Template::ARRAY_CALL, 1);\n\n        $this->assertEquals($expr, $node->getNode('node'));\n        $this->assertEquals($attr, $node->getNode('attribute'));\n        $this->assertEquals($args, $node->getNode('arguments'));\n        $this->assertEquals(Template::ARRAY_CALL, $node->getAttribute('type'));\n        $this->assertFalse($node->getAttribute('null_safe'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $expr = new ContextVariable('foo', 1);\n        $attr = new ConstantExpression('bar', 1);\n        $attr2 = new ConstantExpression('baz', 1);\n        $attr3 = new ConstantExpression('qux', 1);\n        $attr4 = new ConstantExpression('corge', 1);\n        $args = new ArrayExpression([], 1);\n\n        $node = new GetAttrExpression($expr, $attr, $args, Template::ANY_CALL, 1);\n        $tests[] = [$node, \\sprintf('%s%s, \"bar\", [], \"any\", false, false, false, 1)', self::createAttributeGetter(), self::createVariableGetter('foo', 1))];\n\n        $node = new GetAttrExpression($expr, $attr, $args, Template::ANY_CALL, 1, true);\n        $tests[] = [$node, '((null === ($_v%s = // line 1'.\"\\n\".'($context[\"foo\"] ?? null))) ? null : '.self::createAttributeGetter().'$_v%s, \"bar\", [], \"any\", false, false, false, 1))', null, true];\n\n        $node = new GetAttrExpression($expr, $attr, $args, Template::ANY_CALL, 1, true);\n        $node = new GetAttrExpression($node, $attr2, $args, Template::METHOD_CALL, 1);\n        $tests[] = [$node, '((null === ($_v%s = // line 1'.\"\\n\".'($context[\"foo\"] ?? null))) ? null : '.self::createAttributeGetter().self::createAttributeGetter().'$_v%s, \"bar\", [], \"any\", false, false, false, 1), \"baz\", [], \"method\", false, false, false, 1))', null, true];\n\n        $node = new GetAttrExpression($expr, $attr, $args, Template::ANY_CALL, 1, true);\n        $node = new GetAttrExpression($node, $attr2, $args, Template::ANY_CALL, 1);\n        $node = new GetAttrExpression($node, $attr3, $args, Template::METHOD_CALL, 1, true);\n        $node = new GetAttrExpression($node, $attr4, $args, Template::ANY_CALL, 1);\n        $tests[] = [$node, '((null === ($_v0 = ((null === ($_v1 = // line 1'.\"\\n\".'($context[\"foo\"] ?? null))) ? null : '.self::createAttributeGetter().self::createAttributeGetter().'$_v1, \"bar\", [], \"any\", false, false, false, 1), \"baz\", [], \"any\", false, false, false, 1)))) ? null : '.self::createAttributeGetter().self::createAttributeGetter().'$_v0, \"qux\", [], \"method\", false, false, false, 1), \"corge\", [], \"any\", false, false, false, 1))', null];\n\n        $node = new GetAttrExpression($expr, $attr, $args, Template::ARRAY_CALL, 1);\n        $tests[] = [$node, '(($_v%s = // line 1'.\"\\n\".\n            '($context[\"foo\"] ?? null)) && is_array($_v%s) || $_v%s instanceof ArrayAccess ? ($_v%s[\"bar\"] ?? null) : null)', null, true, ];\n\n        $args = new ArrayExpression([], 1);\n        $args->addElement(new ContextVariable('foo', 1));\n        $args->addElement(new ConstantExpression('bar', 1));\n        $node = new GetAttrExpression($expr, $attr, $args, Template::METHOD_CALL, 1);\n        $tests[] = [$node, \\sprintf('%s%s, \"bar\", [%s, \"bar\"], \"method\", false, false, false, 1)', self::createAttributeGetter(), self::createVariableGetter('foo', 1), self::createVariableGetter('foo'))];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/NullCoalesceTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\NullCoalesceExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Test\\NodeTestCase;\n\n/**\n * @group legacy\n */\nclass NullCoalesceTest extends NodeTestCase\n{\n    public static function provideTests(): iterable\n    {\n        $left = new ContextVariable('foo', 1);\n        $right = new ConstantExpression(2, 1);\n        $node = new NullCoalesceExpression($left, $right, 1);\n\n        return [[$node, \"((// line 1\\n\\$context[\\\"foo\\\"]) ?? (2))\"]];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/ParentTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ParentExpression;\nuse Twig\\Test\\NodeTestCase;\n\nclass ParentTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $node = new ParentExpression('foo', 1);\n\n        $this->assertEquals('foo', $node->getAttribute('name'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n        $tests[] = [new ParentExpression('foo', 1), '$this->renderParentBlock(\"foo\", $context, $blocks)'];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Ternary/ConditionalTernaryTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Ternary\\ConditionalTernary;\nuse Twig\\Test\\NodeTestCase;\n\nclass ConditionalTernaryTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $test = new ConstantExpression(1, 1);\n        $left = new ConstantExpression(2, 1);\n        $right = new ConstantExpression(3, 1);\n        $node = new ConditionalTernary($test, $left, $right, 1);\n\n        $this->assertEquals($test, $node->getNode('test'));\n        $this->assertEquals($left, $node->getNode('left'));\n        $this->assertEquals($right, $node->getNode('right'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $test = new ConstantExpression(1, 1);\n        $left = new ConstantExpression(2, 1);\n        $right = new ConstantExpression(3, 1);\n        $node = new ConditionalTernary($test, $left, $right, 1);\n        $tests[] = [$node, '((1) ? (2) : (3))'];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/TestTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Test\\NullTest;\nuse Twig\\Node\\Expression\\TestExpression;\nuse Twig\\Node\\Nodes;\nuse Twig\\Test\\NodeTestCase;\nuse Twig\\TwigTest;\n\nclass TestTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression('foo', 1);\n        $name = 'test_name';\n        $args = new EmptyNode();\n        $node = new TestExpression($expr, new TwigTest($name), $args, 1);\n\n        $this->assertEquals($expr, $node->getNode('node'));\n        $this->assertEquals($args, $node->getNode('arguments'));\n        $this->assertEquals($name, $node->getAttribute('name'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $environment = static::createEnvironment();\n\n        $tests = [];\n\n        $expr = new ConstantExpression('foo', 1);\n        $node = new NullTest($expr, $environment->getTest('null'), new EmptyNode(), 1);\n        $tests[] = [$node, '(null === \"foo\")'];\n\n        // test as an anonymous function\n        $node = self::createTest($environment, new ConstantExpression('foo', 1), 'anonymous', [new ConstantExpression('foo', 1)]);\n        $tests[] = [$node, '$this->env->getTest(\\'anonymous\\')->getCallable()(\"foo\", \"foo\")'];\n\n        // arbitrary named arguments\n        $string = new ConstantExpression('abc', 1);\n        $node = self::createTest($environment, $string, 'barbar');\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_test_barbar(\"abc\")', $environment];\n\n        $node = self::createTest($environment, $string, 'barbar', ['foo' => new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_test_barbar(\"abc\", null, null, [\"foo\" => \"bar\"])', $environment];\n\n        $node = self::createTest($environment, $string, 'barbar', ['arg2' => new ConstantExpression('bar', 1)]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_test_barbar(\"abc\", null, \"bar\")', $environment];\n\n        $node = self::createTest($environment, $string, 'barbar', [\n            new ConstantExpression('1', 1),\n            new ConstantExpression('2', 1),\n            new ConstantExpression('3', 1),\n            'foo' => new ConstantExpression('bar', 1),\n        ]);\n        $tests[] = [$node, 'Twig\\Tests\\Node\\Expression\\twig_tests_test_barbar(\"abc\", \"1\", \"2\", [\"3\", \"foo\" => \"bar\"])', $environment];\n\n        return $tests;\n    }\n\n    private static function createTest(Environment $env, $node, $name, array $arguments = []): TestExpression\n    {\n        return new TestExpression($node, $env->getTest($name), new Nodes($arguments), 1);\n    }\n\n    protected static function createEnvironment(): Environment\n    {\n        $env = new Environment(new ArrayLoader());\n        $env->addTest(new TwigTest('anonymous', static function () {}));\n        $env->addTest(new TwigTest('barbar', 'Twig\\Tests\\Node\\Expression\\twig_tests_test_barbar', ['is_variadic' => true, 'need_context' => true]));\n\n        return $env;\n    }\n}\n\nfunction twig_tests_test_barbar($string, $arg1 = null, $arg2 = null, array $args = [])\n{\n}\n"
  },
  {
    "path": "tests/Node/Expression/Unary/NegTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Unary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Unary\\NegUnary;\nuse Twig\\Test\\NodeTestCase;\n\nclass NegTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression(1, 1);\n        $node = new NegUnary($expr, 1);\n\n        $this->assertEquals($expr, $node->getNode('node'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $node = new ConstantExpression(1, 1);\n        $node = new NegUnary($node, 1);\n\n        return [\n            [$node, '-1'],\n            [new NegUnary($node, 1), '- -1'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Unary/NotTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Unary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Unary\\NotUnary;\nuse Twig\\Test\\NodeTestCase;\n\nclass NotTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression(1, 1);\n        $node = new NotUnary($expr, 1);\n\n        $this->assertEquals($expr, $node->getNode('node'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $node = new ConstantExpression(1, 1);\n        $node = new NotUnary($node, 1);\n\n        return [\n            [$node, '!1'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Unary/PosTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression\\Unary;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Unary\\PosUnary;\nuse Twig\\Test\\NodeTestCase;\n\nclass PosTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression(1, 1);\n        $node = new PosUnary($expr, 1);\n\n        $this->assertEquals($expr, $node->getNode('node'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $node = new ConstantExpression(1, 1);\n        $node = new PosUnary($node, 1);\n\n        return [\n            [$node, '+1'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Variable/AssignContextVariableTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Test\\NodeTestCase;\n\nclass AssignContextVariableTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $node = new AssignContextVariable('foo', 1);\n\n        $this->assertEquals('foo', $node->getAttribute('name'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $node = new AssignContextVariable('foo', 1);\n\n        return [\n            [$node, '$context[\"foo\"]'],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/Expression/Variable/ContextVariableTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node\\Expression;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Test\\NodeTestCase;\n\nclass ContextVariableTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $node = new ContextVariable('foo', 1);\n\n        $this->assertEquals('foo', $node->getAttribute('name'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        // special variables\n        foreach (['_self' => '$this->getTemplateName()', '_context' => '$context', '_charset' => '$this->env->getCharset()'] as $special => $compiled) {\n            $node = new ContextVariable($special, 1);\n            yield $special => [$node, \"// line 1\\n$compiled\"];\n            $node = new ContextVariable($special, 1);\n            $node->enableDefinedTest();\n            yield $special.'_defined_test' => [$node, \"// line 1\\ntrue\"];\n        }\n\n        $env = new Environment(new ArrayLoader(), ['strict_variables' => false]);\n        $envStrict = new Environment(new ArrayLoader(), ['strict_variables' => true]);\n\n        // regular\n        $node = new ContextVariable('foo', 1);\n        $output = '(isset($context[\"foo\"]) || array_key_exists(\"foo\", $context) ? $context[\"foo\"] : (function () { throw new RuntimeError(\\'Variable \"foo\" does not exist.\\', 1, $this->source); })())';\n        yield 'strict' => [$node, \"// line 1\\n\".$output, $envStrict];\n        yield 'non_strict' => [$node, self::createVariableGetter('foo', 1), $env];\n\n        // ignore strict check\n        $node = new ContextVariable('foo', 1);\n        $node->setAttribute('ignore_strict_check', true);\n        yield 'ignore_strict_check_strict' => [$node, \"// line 1\\n(\\$context[\\\"foo\\\"] ?? null)\", $envStrict];\n        yield 'ignore_strict_check_non_strict' => [$node, \"// line 1\\n(\\$context[\\\"foo\\\"] ?? null)\", $env];\n\n        // always defined\n        $node = new ContextVariable('foo', 1);\n        $node->setAttribute('always_defined', true);\n        yield 'always_defined_strict' => [$node, \"// line 1\\n\\$context[\\\"foo\\\"]\", $envStrict];\n        yield 'always_defined_non_strict' => [$node, \"// line 1\\n\\$context[\\\"foo\\\"]\", $env];\n\n        // is defined test\n        $node = new ContextVariable('foo', 1);\n        $node->enableDefinedTest();\n        yield 'is_defined_test_strict' => [$node, \"// line 1\\narray_key_exists(\\\"foo\\\", \\$context)\", $envStrict];\n        yield 'is_defined_test_non_strict' => [$node, \"// line 1\\narray_key_exists(\\\"foo\\\", \\$context)\", $env];\n\n        // is defined test // always defined\n        $node = new ContextVariable('foo', 1);\n        $node->enableDefinedTest();\n        $node->setAttribute('always_defined', true);\n        yield 'is_defined_test_always_defined_strict' => [$node, \"// line 1\\ntrue\", $envStrict];\n        yield 'is_defined_test_always_defined_non_strict' => [$node, \"// line 1\\ntrue\", $env];\n    }\n}\n"
  },
  {
    "path": "tests/Node/ForTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\ForElseNode;\nuse Twig\\Node\\ForNode;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass ForTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $keyTarget = new AssignContextVariable('key', 1);\n        $valueTarget = new AssignContextVariable('item', 1);\n        $seq = new ContextVariable('items', 1);\n        $body = new Nodes([new PrintNode(new ContextVariable('foo', 1), 1)], 1);\n        $else = null;\n        $node = new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, 1);\n        $node->setAttribute('with_loop', false);\n\n        $this->assertEquals($keyTarget, $node->getNode('key_target'));\n        $this->assertEquals($valueTarget, $node->getNode('value_target'));\n        $this->assertEquals($seq, $node->getNode('seq'));\n        $this->assertEquals($body, $node->getNode('body')->getNode('0'));\n        $this->assertFalse($node->hasNode('else'));\n\n        $else = new ForElseNode(new PrintNode(new ContextVariable('foo', 1), 1), 5);\n        $node = new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, 1);\n        $node->setAttribute('with_loop', false);\n        $this->assertEquals($else, $node->getNode('else'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $keyTarget = new AssignContextVariable('key', 1);\n        $valueTarget = new AssignContextVariable('item', 1);\n        $seq = new ContextVariable('items', 1);\n        $body = new Nodes([new PrintNode(new ContextVariable('foo', 1), 1)], 1);\n        $else = null;\n        $node = new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, 1);\n        $node->setAttribute('with_loop', false);\n\n        $itemsGetter = self::createVariableGetter('items');\n        $fooGetter = self::createVariableGetter('foo');\n        $valuesGetter = self::createVariableGetter('values');\n\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context['_parent'] = \\$context;\n\\$context['_seq'] = CoreExtension::ensureTraversable($itemsGetter);\nforeach (\\$context['_seq'] as \\$context[\"key\"] => \\$context[\"item\"]) {\n    yield $fooGetter;\n}\n\\$_parent = \\$context['_parent'];\nunset(\\$context['_seq'], \\$context['key'], \\$context['item'], \\$context['_parent']);\n\\$context = array_intersect_key(\\$context, \\$_parent) + \\$_parent;\nEOF\n        ];\n\n        $keyTarget = new AssignContextVariable('k', 1);\n        $valueTarget = new AssignContextVariable('v', 1);\n        $seq = new ContextVariable('values', 1);\n        $body = new Nodes([new PrintNode(new ContextVariable('foo', 1), 1)], 1);\n        $else = null;\n        $node = new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, 1);\n        $node->setAttribute('with_loop', true);\n\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context['_parent'] = \\$context;\n\\$context['_seq'] = CoreExtension::ensureTraversable($valuesGetter);\n\\$context['loop'] = [\n  'parent' => \\$context['_parent'],\n  'index0' => 0,\n  'index'  => 1,\n  'first'  => true,\n];\nif (is_array(\\$context['_seq']) || (is_object(\\$context['_seq']) && \\$context['_seq'] instanceof \\Countable)) {\n    \\$length = count(\\$context['_seq']);\n    \\$context['loop']['revindex0'] = \\$length - 1;\n    \\$context['loop']['revindex'] = \\$length;\n    \\$context['loop']['length'] = \\$length;\n    \\$context['loop']['last'] = 1 === \\$length;\n}\nforeach (\\$context['_seq'] as \\$context[\"k\"] => \\$context[\"v\"]) {\n    yield $fooGetter;\n    ++\\$context['loop']['index0'];\n    ++\\$context['loop']['index'];\n    \\$context['loop']['first'] = false;\n    if (isset(\\$context['loop']['revindex0'], \\$context['loop']['revindex'])) {\n        --\\$context['loop']['revindex0'];\n        --\\$context['loop']['revindex'];\n        \\$context['loop']['last'] = 0 === \\$context['loop']['revindex0'];\n    }\n}\n\\$_parent = \\$context['_parent'];\nunset(\\$context['_seq'], \\$context['k'], \\$context['v'], \\$context['_parent'], \\$context['loop']);\n\\$context = array_intersect_key(\\$context, \\$_parent) + \\$_parent;\nEOF\n        ];\n\n        $keyTarget = new AssignContextVariable('k', 1);\n        $valueTarget = new AssignContextVariable('v', 1);\n        $seq = new ContextVariable('values', 1);\n        $body = new Nodes([new PrintNode(new ContextVariable('foo', 1), 1)], 1);\n        $else = null;\n        $node = new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, 1);\n        $node->setAttribute('with_loop', true);\n\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context['_parent'] = \\$context;\n\\$context['_seq'] = CoreExtension::ensureTraversable($valuesGetter);\n\\$context['loop'] = [\n  'parent' => \\$context['_parent'],\n  'index0' => 0,\n  'index'  => 1,\n  'first'  => true,\n];\nif (is_array(\\$context['_seq']) || (is_object(\\$context['_seq']) && \\$context['_seq'] instanceof \\Countable)) {\n    \\$length = count(\\$context['_seq']);\n    \\$context['loop']['revindex0'] = \\$length - 1;\n    \\$context['loop']['revindex'] = \\$length;\n    \\$context['loop']['length'] = \\$length;\n    \\$context['loop']['last'] = 1 === \\$length;\n}\nforeach (\\$context['_seq'] as \\$context[\"k\"] => \\$context[\"v\"]) {\n    yield $fooGetter;\n    ++\\$context['loop']['index0'];\n    ++\\$context['loop']['index'];\n    \\$context['loop']['first'] = false;\n    if (isset(\\$context['loop']['revindex0'], \\$context['loop']['revindex'])) {\n        --\\$context['loop']['revindex0'];\n        --\\$context['loop']['revindex'];\n        \\$context['loop']['last'] = 0 === \\$context['loop']['revindex0'];\n    }\n}\n\\$_parent = \\$context['_parent'];\nunset(\\$context['_seq'], \\$context['k'], \\$context['v'], \\$context['_parent'], \\$context['loop']);\n\\$context = array_intersect_key(\\$context, \\$_parent) + \\$_parent;\nEOF\n        ];\n\n        $keyTarget = new AssignContextVariable('k', 1);\n        $valueTarget = new AssignContextVariable('v', 1);\n        $seq = new ContextVariable('values', 1);\n        $body = new Nodes([new PrintNode(new ContextVariable('foo', 1), 1)], 1);\n        $else = new ForElseNode(new PrintNode(new ContextVariable('foo', 6), 6), 5);\n        $node = new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, 1);\n        $node->setAttribute('with_loop', true);\n\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context['_parent'] = \\$context;\n\\$context['_seq'] = CoreExtension::ensureTraversable($valuesGetter);\n\\$context['_iterated'] = false;\n\\$context['loop'] = [\n  'parent' => \\$context['_parent'],\n  'index0' => 0,\n  'index'  => 1,\n  'first'  => true,\n];\nif (is_array(\\$context['_seq']) || (is_object(\\$context['_seq']) && \\$context['_seq'] instanceof \\Countable)) {\n    \\$length = count(\\$context['_seq']);\n    \\$context['loop']['revindex0'] = \\$length - 1;\n    \\$context['loop']['revindex'] = \\$length;\n    \\$context['loop']['length'] = \\$length;\n    \\$context['loop']['last'] = 1 === \\$length;\n}\nforeach (\\$context['_seq'] as \\$context[\"k\"] => \\$context[\"v\"]) {\n    yield $fooGetter;\n    \\$context['_iterated'] = true;\n    ++\\$context['loop']['index0'];\n    ++\\$context['loop']['index'];\n    \\$context['loop']['first'] = false;\n    if (isset(\\$context['loop']['revindex0'], \\$context['loop']['revindex'])) {\n        --\\$context['loop']['revindex0'];\n        --\\$context['loop']['revindex'];\n        \\$context['loop']['last'] = 0 === \\$context['loop']['revindex0'];\n    }\n}\n// line 5\nif (!\\$context['_iterated']) {\n    // line 6\n    yield $fooGetter;\n}\n\\$_parent = \\$context['_parent'];\nunset(\\$context['_seq'], \\$context['k'], \\$context['v'], \\$context['_parent'], \\$context['_iterated'], \\$context['loop']);\n\\$context = array_intersect_key(\\$context, \\$_parent) + \\$_parent;\nEOF\n        ];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/IfTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\IfNode;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass IfTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $t = new Nodes([\n            new ConstantExpression(true, 1),\n            new PrintNode(new ContextVariable('foo', 1), 1),\n        ], 1);\n        $else = null;\n        $node = new IfNode($t, $else, 1);\n\n        $this->assertEquals($t, $node->getNode('tests'));\n        $this->assertFalse($node->hasNode('else'));\n\n        $else = new PrintNode(new ContextVariable('bar', 1), 1);\n        $node = new IfNode($t, $else, 1);\n        $this->assertEquals($else, $node->getNode('else'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $t = new Nodes([\n            new ConstantExpression(true, 1),\n            new PrintNode(new ContextVariable('foo', 1), 1),\n        ], 1);\n        $else = null;\n        $node = new IfNode($t, $else, 1);\n\n        $fooGetter = self::createVariableGetter('foo');\n        $barGetter = self::createVariableGetter('bar');\n\n        $tests[] = [$node, <<<EOF\n// line 1\nif (true) {\n    yield $fooGetter;\n}\nEOF\n        ];\n\n        $t = new Nodes([\n            new ConstantExpression(true, 1),\n            new PrintNode(new ContextVariable('foo', 1), 1),\n            new ConstantExpression(false, 1),\n            new PrintNode(new ContextVariable('bar', 1), 1),\n        ], 1);\n        $else = null;\n        $node = new IfNode($t, $else, 1);\n\n        $tests[] = [$node, <<<EOF\n// line 1\nif (true) {\n    yield $fooGetter;\n} elseif (false) {\n    yield $barGetter;\n}\nEOF\n        ];\n\n        $t = new Nodes([\n            new ConstantExpression(true, 1),\n            new PrintNode(new ContextVariable('foo', 1), 1),\n        ], 1);\n        $else = new PrintNode(new ContextVariable('bar', 1), 1);\n        $node = new IfNode($t, $else, 1);\n\n        $tests[] = [$node, <<<EOF\n// line 1\nif (true) {\n    yield $fooGetter;\n} else {\n    yield $barGetter;\n}\nEOF\n        ];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/ImportTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Variable\\AssignTemplateVariable;\nuse Twig\\Node\\Expression\\Variable\\TemplateVariable;\nuse Twig\\Node\\ImportNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass ImportTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $macro = new ConstantExpression('foo.twig', 1);\n        $node = new ImportNode($macro, new AssignTemplateVariable(new TemplateVariable('macro', 1), true), 1);\n\n        $this->assertEquals($macro, $node->getNode('expr'));\n        $this->assertEquals('macro', $node->getNode('var')->getNode('var')->getAttribute('name'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $macro = new ConstantExpression('foo.twig', 1);\n        $node = new ImportNode($macro, new AssignTemplateVariable(new TemplateVariable('macro', 1), true), 1);\n\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$macros[\"macro\"] = \\$this->macros[\"macro\"] = \\$this->load(\"foo.twig\", 1)->unwrap();\nEOF\n        ];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/IncludeTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Ternary\\ConditionalTernary;\nuse Twig\\Node\\IncludeNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass IncludeTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression('foo.twig', 1);\n        $node = new IncludeNode($expr, null, false, false, 1);\n\n        $this->assertFalse($node->hasNode('variables'));\n        $this->assertEquals($expr, $node->getNode('expr'));\n        $this->assertFalse($node->getAttribute('only'));\n\n        $vars = new ArrayExpression([new ConstantExpression('foo', 1), new ConstantExpression(true, 1)], 1);\n        $node = new IncludeNode($expr, $vars, true, false, 1);\n        $this->assertEquals($vars, $node->getNode('variables'));\n        $this->assertTrue($node->getAttribute('only'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $expr = new ConstantExpression('foo.twig', 1);\n        $node = new IncludeNode($expr, null, false, false, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\nyield from $this->load(\"foo.twig\", 1)->unwrap()->yield($context);\nEOF\n        ];\n\n        $expr = new ConditionalTernary(\n            new ConstantExpression(true, 1),\n            new ConstantExpression('foo', 1),\n            new ConstantExpression('foo', 1),\n            0\n        );\n        $node = new IncludeNode($expr, null, false, false, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\nyield from $this->load(((true) ? (\"foo\") : (\"foo\")), 1)->unwrap()->yield($context);\nEOF\n        ];\n\n        $expr = new ConstantExpression('foo.twig', 1);\n        $vars = new ArrayExpression([new ConstantExpression('foo', 1), new ConstantExpression(true, 1)], 1);\n        $node = new IncludeNode($expr, $vars, false, false, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\nyield from $this->load(\"foo.twig\", 1)->unwrap()->yield(CoreExtension::merge($context, [\"foo\" => true]));\nEOF\n        ];\n\n        $node = new IncludeNode($expr, $vars, true, false, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\nyield from $this->load(\"foo.twig\", 1)->unwrap()->yield(CoreExtension::toArray([\"foo\" => true]));\nEOF\n        ];\n\n        $node = new IncludeNode($expr, $vars, true, true, 1);\n        $tests[] = [$node, <<<EOF\n// line 1\ntry {\n    \\$_v%s = \\$this->load(\"foo.twig\", 1);\n} catch (LoaderError \\$e) {\n    // ignore missing template\n    \\$_v%s = null;\n}\nif (\\$_v%s) {\n    yield from \\$_v%s->unwrap()->yield(CoreExtension::toArray([\"foo\" => true]));\n}\nEOF, null, true];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/MacroTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\BodyNode;\nuse Twig\\Node\\Expression\\ArrayExpression;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Expression\\Variable\\LocalVariable;\nuse Twig\\Node\\MacroNode;\nuse Twig\\Node\\TextNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass MacroTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $body = new BodyNode([new TextNode('foo', 1)]);\n        $arguments = new ArrayExpression([new ContextVariable('foo', 1), new ConstantExpression(null, 1)], 1);\n        $node = new MacroNode('foo', $body, $arguments, 1);\n\n        $this->assertEquals($body, $node->getNode('body'));\n        $this->assertEquals($arguments, $node->getNode('arguments'));\n        $this->assertEquals('foo', $node->getAttribute('name'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $arguments = new ArrayExpression([\n            new LocalVariable('foo', 1),\n            new ConstantExpression(null, 1),\n            new LocalVariable('bar', 1),\n            new ConstantExpression('Foo', 1),\n            new LocalVariable('_underscore', 1),\n            new ConstantExpression(null, 1),\n        ], 1);\n\n        $body = new BodyNode([new TextNode('foo', 1)]);\n        $node = new MacroNode('foo', $body, $arguments, 1);\n\n        yield 'with use_yield = true' => [$node, <<<EOF\n// line 1\npublic function macro_foo(\\$foo = null, \\$bar = \"Foo\", \\$_underscore = null, ...\\$varargs): string|Markup\n{\n    \\$macros = \\$this->macros;\n    \\$context = [\n        \"foo\" => \\$foo,\n        \"bar\" => \\$bar,\n        \"_underscore\" => \\$_underscore,\n        \"varargs\" => \\$varargs,\n    ] + \\$this->env->getGlobals();\n\n    \\$blocks = [];\n\n    return ('' === \\$tmp = implode('', iterator_to_array((function () use (&\\$context, \\$macros, \\$blocks) {\n        yield \"foo\";\n        yield from [];\n    })(), false))) ? '' : new Markup(\\$tmp, \\$this->env->getCharset());\n}\nEOF, new Environment(new ArrayLoader(), ['use_yield' => true]),\n        ];\n\n        yield 'with use_yield = false' => [$node, <<<EOF\n// line 1\npublic function macro_foo(\\$foo = null, \\$bar = \"Foo\", \\$_underscore = null, ...\\$varargs): string|Markup\n{\n    \\$macros = \\$this->macros;\n    \\$context = [\n        \"foo\" => \\$foo,\n        \"bar\" => \\$bar,\n        \"_underscore\" => \\$_underscore,\n        \"varargs\" => \\$varargs,\n    ] + \\$this->env->getGlobals();\n\n    \\$blocks = [];\n\n    return ('' === \\$tmp = \\\\Twig\\\\Extension\\\\CoreExtension::captureOutput((function () use (&\\$context, \\$macros, \\$blocks) {\n        yield \"foo\";\n        yield from [];\n    })())) ? '' : new Markup(\\$tmp, \\$this->env->getCharset());\n}\nEOF, new Environment(new ArrayLoader(), ['use_yield' => false]),\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Node/ModuleTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\BodyNode;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Ternary\\ConditionalTernary;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\AssignTemplateVariable;\nuse Twig\\Node\\Expression\\Variable\\TemplateVariable;\nuse Twig\\Node\\ImportNode;\nuse Twig\\Node\\ModuleNode;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\SetNode;\nuse Twig\\Node\\TextNode;\nuse Twig\\Source;\nuse Twig\\Test\\NodeTestCase;\n\nclass ModuleTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $body = new BodyNode([new TextNode('foo', 1)]);\n        $parent = new ConstantExpression('layout.twig', 1);\n        $blocks = new EmptyNode();\n        $macros = new EmptyNode();\n        $traits = new EmptyNode();\n        $source = new Source('{{ foo }}', 'foo.twig');\n        $node = new ModuleNode($body, $parent, $blocks, $macros, $traits, new EmptyNode(), $source);\n\n        $this->assertEquals($body, $node->getNode('body'));\n        $this->assertEquals($blocks, $node->getNode('blocks'));\n        $this->assertEquals($macros, $node->getNode('macros'));\n        $this->assertEquals($parent, $node->getNode('parent'));\n        $this->assertEquals($source->getName(), $node->getTemplateName());\n    }\n\n    public static function provideTests(): iterable\n    {\n        $twig = new Environment(new ArrayLoader(['foo.twig' => '{{ foo }}']));\n\n        $tests = [];\n\n        $body = new BodyNode([new TextNode('foo', 1)]);\n        $extends = null;\n        $blocks = new EmptyNode();\n        $macros = new EmptyNode();\n        $traits = new EmptyNode();\n        $source = new Source('{{ foo }}', 'foo.twig');\n\n        $node = new ModuleNode($body, $extends, $blocks, $macros, $traits, new EmptyNode(), $source);\n        $tests[] = [$node, <<<EOF\n<?php\n\nuse Twig\\Environment;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Markup;\nuse Twig\\Sandbox\\SecurityError;\nuse Twig\\Sandbox\\SecurityNotAllowedTagError;\nuse Twig\\Sandbox\\SecurityNotAllowedFilterError;\nuse Twig\\Sandbox\\SecurityNotAllowedFunctionError;\nuse Twig\\Source;\nuse Twig\\Template;\nuse Twig\\TemplateWrapper;\n\n/* foo.twig */\nclass __TwigTemplate_%x extends Template\n{\n    private Source \\$source;\n    /**\n     * @var array<string, Template>\n     */\n    private array \\$macros = [];\n\n    public function __construct(Environment \\$env)\n    {\n        parent::__construct(\\$env);\n\n        \\$this->source = \\$this->getSourceContext();\n\n        \\$this->parent = false;\n\n        \\$this->blocks = [\n        ];\n    }\n\n    protected function doDisplay(array \\$context, array \\$blocks = []): iterable\n    {\n        \\$macros = \\$this->macros;\n        // line 1\n        yield \"foo\";\n        yield from [];\n    }\n\n    /**\n     * @codeCoverageIgnore\n     */\n    public function getTemplateName(): string\n    {\n        return \"foo.twig\";\n    }\n\n    /**\n     * @codeCoverageIgnore\n     */\n    public function getDebugInfo(): array\n    {\n        return array (  42 => 1,);\n    }\n\n    public function getSourceContext(): Source\n    {\n        return new Source(\"\", \"foo.twig\", \"\");\n    }\n}\nEOF, $twig, true];\n\n        $import = new ImportNode(new ConstantExpression('foo.twig', 1), new AssignTemplateVariable(new TemplateVariable('macro', 2), true), 2);\n\n        $body = new BodyNode([$import]);\n        $extends = new ConstantExpression('layout.twig', 1);\n\n        $node = new ModuleNode($body, $extends, $blocks, $macros, $traits, new EmptyNode(), $source);\n        $tests[] = [$node, <<<EOF\n<?php\n\nuse Twig\\Environment;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Markup;\nuse Twig\\Sandbox\\SecurityError;\nuse Twig\\Sandbox\\SecurityNotAllowedTagError;\nuse Twig\\Sandbox\\SecurityNotAllowedFilterError;\nuse Twig\\Sandbox\\SecurityNotAllowedFunctionError;\nuse Twig\\Source;\nuse Twig\\Template;\nuse Twig\\TemplateWrapper;\n\n/* foo.twig */\nclass __TwigTemplate_%x extends Template\n{\n    private Source \\$source;\n    /**\n     * @var array<string, Template>\n     */\n    private array \\$macros = [];\n\n    public function __construct(Environment \\$env)\n    {\n        parent::__construct(\\$env);\n\n        \\$this->source = \\$this->getSourceContext();\n\n        \\$this->blocks = [\n        ];\n    }\n\n    protected function doGetParent(array \\$context): bool|string|Template|TemplateWrapper\n    {\n        // line 1\n        return \"layout.twig\";\n    }\n\n    protected function doDisplay(array \\$context, array \\$blocks = []): iterable\n    {\n        \\$macros = \\$this->macros;\n        // line 2\n        \\$macros[\"macro\"] = \\$this->macros[\"macro\"] = \\$this->load(\"foo.twig\", 2)->unwrap();\n        // line 1\n        \\$this->parent = \\$this->load(\"layout.twig\", 1);\n        yield from \\$this->parent->unwrap()->yield(\\$context, array_merge(\\$this->blocks, \\$blocks));\n    }\n\n    /**\n     * @codeCoverageIgnore\n     */\n    public function getTemplateName(): string\n    {\n        return \"foo.twig\";\n    }\n\n    /**\n     * @codeCoverageIgnore\n     */\n    public function isTraitable(): bool\n    {\n        return false;\n    }\n\n    /**\n     * @codeCoverageIgnore\n     */\n    public function getDebugInfo(): array\n    {\n        return array (  48 => 1,  46 => 2,  39 => 1,);\n    }\n\n    public function getSourceContext(): Source\n    {\n        return new Source(\"\", \"foo.twig\", \"\");\n    }\n}\nEOF, $twig, true];\n\n        $set = new SetNode(false, new Nodes([new AssignContextVariable('foo', 4)]), new Nodes([new ConstantExpression('foo', 4)]), 4);\n        $body = new BodyNode([$set]);\n        $extends = new ConditionalTernary(\n            new ConstantExpression(true, 2),\n            new ConstantExpression('foo', 2),\n            new ConstantExpression('foo', 2),\n            2\n        );\n\n        $twig = new Environment(new ArrayLoader(['foo.twig' => '{{ foo }}']), ['debug' => true]);\n        $node = new ModuleNode($body, $extends, $blocks, $macros, $traits, new EmptyNode(), $source);\n        $tests[] = [$node, <<<EOF\n<?php\n\nuse Twig\\Environment;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Markup;\nuse Twig\\Sandbox\\SecurityError;\nuse Twig\\Sandbox\\SecurityNotAllowedTagError;\nuse Twig\\Sandbox\\SecurityNotAllowedFilterError;\nuse Twig\\Sandbox\\SecurityNotAllowedFunctionError;\nuse Twig\\Source;\nuse Twig\\Template;\nuse Twig\\TemplateWrapper;\n\n/* foo.twig */\nclass __TwigTemplate_%x extends Template\n{\n    private Source \\$source;\n    /**\n     * @var array<string, Template>\n     */\n    private array \\$macros = [];\n\n    public function __construct(Environment \\$env)\n    {\n        parent::__construct(\\$env);\n\n        \\$this->source = \\$this->getSourceContext();\n\n        \\$this->blocks = [\n        ];\n    }\n\n    protected function doGetParent(array \\$context): bool|string|Template|TemplateWrapper\n    {\n        // line 2\n        return \\$this->load(((true) ? (\"foo\") : (\"foo\")), 2);\n    }\n\n    protected function doDisplay(array \\$context, array \\$blocks = []): iterable\n    {\n        \\$macros = \\$this->macros;\n        // line 4\n        \\$context[\"foo\"] = \"foo\";\n        // line 2\n        yield from \\$this->getParent(\\$context)->unwrap()->yield(\\$context, array_merge(\\$this->blocks, \\$blocks));\n    }\n\n    /**\n     * @codeCoverageIgnore\n     */\n    public function getTemplateName(): string\n    {\n        return \"foo.twig\";\n    }\n\n    /**\n     * @codeCoverageIgnore\n     */\n    public function isTraitable(): bool\n    {\n        return false;\n    }\n\n    /**\n     * @codeCoverageIgnore\n     */\n    public function getDebugInfo(): array\n    {\n        return array (  48 => 2,  46 => 4,  39 => 2,);\n    }\n\n    public function getSourceContext(): Source\n    {\n        return new Source(\"{{ foo }}\", \"foo.twig\", \"\");\n    }\n}\nEOF, $twig, true];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/NodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Bridge\\PhpUnit\\ExpectDeprecationTrait;\nuse Twig\\Node\\NameDeprecation;\nuse Twig\\Node\\Node;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\nuse Twig\\TwigTest;\n\nclass NodeTest extends TestCase\n{\n    use ExpectDeprecationTrait;\n\n    public function testToString()\n    {\n        // callable is not a supported type for a Node attribute, but Drupal uses some apparently\n        $node = new NodeForTest([], ['value' => static function () { return '1'; }], 1);\n\n        $this->assertEquals(<<<EOF\nTwig\\Tests\\Node\\NodeForTest\n  attributes:\n    value: \\Closure\nEOF, (string) $node\n        );\n    }\n\n    public function testToStringWithTwigCallables()\n    {\n        $node = new NodeForTest([], [\n            'function' => new TwigFunction('a_function'),\n            'filter' => new TwigFilter('a_filter'),\n            'test' => new TwigTest('a_test'),\n        ], 1);\n\n        $this->assertEquals(<<<EOF\nTwig\\Tests\\Node\\NodeForTest\n  attributes:\n    function: Twig\\TwigFunction(a_function)\n    filter: Twig\\TwigFilter(a_filter)\n    test: Twig\\TwigTest(a_test)\nEOF, (string) $node);\n    }\n\n    public function testToStringWithTag()\n    {\n        $node = new NodeForTest();\n        $node->setNodeTag('tag');\n\n        $this->assertEquals(<<<EOF\nTwig\\Tests\\Node\\NodeForTest\n  tag: tag\nEOF, (string) $node);\n    }\n\n    public function testAttributeDeprecationIgnore()\n    {\n        $node = new NodeForTest([], ['foo' => false]);\n        $node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));\n\n        $this->assertFalse($node->getAttribute('foo', false));\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testAttributeDeprecationWithoutAlternative()\n    {\n        $node = new NodeForTest([], ['foo' => false]);\n        $node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0'));\n\n        $this->expectDeprecation('Since foo/bar 2.0: Getting attribute \"foo\" on a \"Twig\\Tests\\Node\\NodeForTest\" class is deprecated.');\n        $this->assertFalse($node->getAttribute('foo'));\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testAttributeDeprecationWithAlternative()\n    {\n        $node = new NodeForTest([], ['foo' => false]);\n        $node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));\n\n        $this->expectDeprecation('Since foo/bar 2.0: Getting attribute \"foo\" on a \"Twig\\Tests\\Node\\NodeForTest\" class is deprecated, get the \"bar\" attribute instead.');\n        $this->assertFalse($node->getAttribute('foo'));\n    }\n\n    public function testNodeDeprecationIgnore()\n    {\n        $node = new NodeForTest(['foo' => $foo = new NodeForTest()]);\n        $node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0'));\n\n        $this->assertSame($foo, $node->getNode('foo', false));\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testNodeDeprecationWithoutAlternative()\n    {\n        $node = new NodeForTest(['foo' => $foo = new NodeForTest()]);\n        $node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0'));\n\n        $this->expectDeprecation('Since foo/bar 2.0: Getting node \"foo\" on a \"Twig\\Tests\\Node\\NodeForTest\" class is deprecated.');\n        $this->assertSame($foo, $node->getNode('foo'));\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testNodeAttributeDeprecationWithAlternative()\n    {\n        $node = new NodeForTest(['foo' => $foo = new NodeForTest()]);\n        $node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));\n\n        $this->expectDeprecation('Since foo/bar 2.0: Getting node \"foo\" on a \"Twig\\Tests\\Node\\NodeForTest\" class is deprecated, get the \"bar\" node instead.');\n        $this->assertSame($foo, $node->getNode('foo'));\n    }\n}\n\nclass NodeForTest extends Node\n{\n}\n"
  },
  {
    "path": "tests/Node/PrintTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\GetAttrExpression;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Template;\nuse Twig\\Test\\NodeTestCase;\n\nclass PrintTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $expr = new ConstantExpression('foo', 1);\n        $node = new PrintNode($expr, 1);\n\n        $this->assertEquals($expr, $node->getNode('expr'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n        $tests[] = [new PrintNode(new ConstantExpression('foo', 1), 1), \"// line 1\\nyield \\\"foo\\\";\"];\n\n        $expr = new ContextVariable('foo', 1);\n        $attr = new ConstantExpression('bar', 1);\n        $node = new GetAttrExpression($expr, $attr, null, Template::METHOD_CALL, 1);\n        $node->setAttribute('is_generator', true);\n        $tests[] = [new PrintNode($node, 1), \"// line 1\\nyield from CoreExtension::getAttribute(\\$this->env, \\$this->source, (\\$context[\\\"foo\\\"] ?? null), \\\"bar\\\", [], \\\"method\\\", false, false, false, 1);\"];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/SandboxTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\SandboxNode;\nuse Twig\\Node\\TextNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass SandboxTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $body = new TextNode('foo', 1);\n        $node = new SandboxNode($body, 1);\n\n        $this->assertEquals($body, $node->getNode('body'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $body = new TextNode('foo', 1);\n        $node = new SandboxNode($body, 1);\n\n        $tests[] = [$node, <<<EOF\n// line 1\nif (!\\$alreadySandboxed = \\$this->sandbox->isSandboxed()) {\n    \\$this->sandbox->enableSandbox();\n}\ntry {\n    yield \"foo\";\n} finally {\n    if (!\\$alreadySandboxed) {\n        \\$this->sandbox->disableSandbox();\n    }\n}\nEOF\n        ];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/SetTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\Variable\\AssignContextVariable;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\PrintNode;\nuse Twig\\Node\\SetNode;\nuse Twig\\Node\\TextNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass SetTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $names = new Nodes([new AssignContextVariable('foo', 1)], 1);\n        $values = new Nodes([new ConstantExpression('foo', 1)], 1);\n        $node = new SetNode(false, $names, $values, 1);\n\n        $this->assertEquals($names, $node->getNode('names'));\n        $this->assertEquals($values, $node->getNode('values'));\n        $this->assertFalse($node->getAttribute('capture'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n\n        $names = new Nodes([new AssignContextVariable('foo', 1)], 1);\n        $values = new Nodes([new ConstantExpression('foo', 1)], 1);\n        $node = new SetNode(false, $names, $values, 1);\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context[\"foo\"] = \"foo\";\nEOF\n        ];\n\n        $names = new Nodes([new AssignContextVariable('foo', 1)], 1);\n        $values = new Nodes([new PrintNode(new ConstantExpression('foo', 1), 1)], 1);\n        $node = new SetNode(true, $names, $values, 1);\n\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context[\"foo\"] = ('' === \\$tmp = implode('', iterator_to_array((function () use (&\\$context, \\$macros, \\$blocks) {\n    yield \"foo\";\n    yield from [];\n})(), false))) ? '' : new Markup(\\$tmp, \\$this->env->getCharset());\nEOF, new Environment(new ArrayLoader(), ['use_yield' => true]),\n        ];\n\n        $tests[] = [$node, <<<'EOF'\n// line 1\n$context[\"foo\"] = ('' === $tmp = \\Twig\\Extension\\CoreExtension::captureOutput((function () use (&$context, $macros, $blocks) {\n    yield \"foo\";\n    yield from [];\n})())) ? '' : new Markup($tmp, $this->env->getCharset());\nEOF, new Environment(new ArrayLoader(), ['use_yield' => false]),\n        ];\n\n        $names = new Nodes([new AssignContextVariable('foo', 1)], 1);\n        $values = new TextNode('foo', 1);\n        $node = new SetNode(true, $names, $values, 1);\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context[\"foo\"] = new Markup(\"foo\", \\$this->env->getCharset());\nEOF\n        ];\n\n        $names = new Nodes([new AssignContextVariable('foo', 1)], 1);\n        $values = new TextNode('', 1);\n        $node = new SetNode(true, $names, $values, 1);\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context[\"foo\"] = \"\";\nEOF\n        ];\n\n        $names = new Nodes([new AssignContextVariable('foo', 1)], 1);\n        $values = new PrintNode(new ConstantExpression('foo', 1), 1);\n        $node = new SetNode(true, $names, $values, 1);\n        $tests[] = [$node, <<<EOF\n// line 1\n\\$context[\"foo\"] = new Markup(\"foo\", \\$this->env->getCharset());\nEOF\n        ];\n\n        $names = new Nodes([new AssignContextVariable('foo', 1), new AssignContextVariable('bar', 1)], 1);\n        $values = new Nodes([new ConstantExpression('foo', 1), new ContextVariable('bar', 1)], 1);\n        $node = new SetNode(false, $names, $values, 1);\n        $tests[] = [$node, <<<'EOF'\n// line 1\n[$context[\"foo\"], $context[\"bar\"]] = [\"foo\", ($context[\"bar\"] ?? null)];\nEOF\n        ];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/TextTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Node\\TextNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass TextTest extends NodeTestCase\n{\n    public function testConstructor()\n    {\n        $node = new TextNode('foo', 1);\n\n        $this->assertEquals('foo', $node->getAttribute('data'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        $tests = [];\n        $tests[] = [new TextNode('foo', 1), \"// line 1\\nyield \\\"foo\\\";\"];\n\n        return $tests;\n    }\n}\n"
  },
  {
    "path": "tests/Node/TypesTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Node;\n\nuse Twig\\Node\\TypesNode;\nuse Twig\\Test\\NodeTestCase;\n\nclass TypesTest extends NodeTestCase\n{\n    private static function getValidMapping(): array\n    {\n        // {foo: 'string', bar?: 'number'}\n        return [\n            'foo' => [\n                'type' => 'string',\n                'optional' => false,\n            ],\n            'bar' => [\n                'type' => 'number',\n                'optional' => true,\n            ],\n        ];\n    }\n\n    public function testConstructor()\n    {\n        $types = self::getValidMapping();\n        $node = new TypesNode($types, 1);\n\n        $this->assertEquals($types, $node->getAttribute('mapping'));\n    }\n\n    public static function provideTests(): iterable\n    {\n        return [\n            // 1st test: Node shouldn't compile at all\n            [\n                new TypesNode(self::getValidMapping(), 1),\n                '',\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/NodeVisitor/OptimizerTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\NodeVisitor;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\Expression\\BlockReferenceExpression;\nuse Twig\\Node\\Expression\\NameExpression;\nuse Twig\\Node\\Expression\\ParentExpression;\nuse Twig\\Node\\ForNode;\nuse Twig\\Node\\Node;\nuse Twig\\NodeVisitor\\OptimizerNodeVisitor;\nuse Twig\\Source;\n\nclass OptimizerTest extends TestCase\n{\n    public function testConstructor()\n    {\n        $this->expectNotToPerformAssertions();\n\n        new OptimizerNodeVisitor(OptimizerNodeVisitor::OPTIMIZE_FOR);\n    }\n\n    public function testRenderBlockOptimizer()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n\n        $stream = $env->parse($env->tokenize(new Source('{{ block(\"foo\") }}', 'index')));\n\n        $node = $stream->getNode('body')->getNode('0');\n\n        $this->assertInstanceOf(BlockReferenceExpression::class, $node);\n        $this->assertTrue($node->getAttribute('output'));\n    }\n\n    public function testRenderParentBlockOptimizer()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n\n        $stream = $env->parse($env->tokenize(new Source('{% extends \"foo\" %}{% block content %}{{ parent() }}{% endblock %}', 'index')));\n\n        $node = $stream->getNode('blocks')->getNode('content')->getNode('0')->getNode('body');\n\n        $this->assertInstanceOf(ParentExpression::class, $node);\n        $this->assertTrue($node->getAttribute('output'));\n    }\n\n    public function testForVarOptimizer()\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n\n        $template = '{% for i, j in foo %}{{ loop.index }}{{ i }}{{ j }}{% endfor %}';\n        $stream = $env->parse($env->tokenize(new Source($template, 'index')));\n\n        foreach (['loop', 'i', 'j'] as $target) {\n            $this->checkForVarConfiguration($stream, $target);\n        }\n    }\n\n    public function checkForVarConfiguration(Node $node, $target)\n    {\n        foreach ($node as $n) {\n            if (NameExpression::class === $n::class && $target === $n->getAttribute('name')) {\n                $this->assertTrue($n->getAttribute('always_defined'));\n            } else {\n                $this->checkForVarConfiguration($n, $target);\n            }\n        }\n    }\n\n    /**\n     * @dataProvider getTestsForForLoopOptimizer\n     */\n    public function testForLoopOptimizer($template, $expected)\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false]);\n\n        $stream = $env->parse($env->tokenize(new Source($template, 'index')));\n\n        foreach ($expected as $target => $withLoop) {\n            $this->assertTrue($this->checkForLoopConfiguration($stream, $target, $withLoop), \\sprintf('variable %s is %soptimized', $target, $withLoop ? 'not ' : ''));\n        }\n    }\n\n    public static function getTestsForForLoopOptimizer()\n    {\n        return [\n            ['{% for i in foo %}{% endfor %}', ['i' => false]],\n\n            ['{% for i in foo %}{{ loop.index }}{% endfor %}', ['i' => true]],\n\n            ['{% for i in foo %}{% for j in foo %}{% endfor %}{% endfor %}', ['i' => false, 'j' => false]],\n\n            ['{% for i in foo %}{% include \"foo\" %}{% endfor %}', ['i' => true]],\n\n            ['{% for i in foo %}{% include \"foo\" only %}{% endfor %}', ['i' => false]],\n\n            ['{% for i in foo %}{% include \"foo\" with { \"foo\": \"bar\" } only %}{% endfor %}', ['i' => false]],\n\n            ['{% for i in foo %}{% include \"foo\" with { \"foo\": loop.index } only %}{% endfor %}', ['i' => true]],\n\n            ['{% for i in foo %}{% for j in foo %}{{ loop.index }}{% endfor %}{% endfor %}', ['i' => false, 'j' => true]],\n\n            ['{% for i in foo %}{% for j in foo %}{{ loop.parent.loop.index }}{% endfor %}{% endfor %}', ['i' => true, 'j' => true]],\n\n            ['{% for i in foo %}{% set l = loop %}{% for j in foo %}{{ l.index }}{% endfor %}{% endfor %}', ['i' => true, 'j' => false]],\n\n            ['{% for i in foo %}{% for j in foo %}{{ foo.parent.loop.index }}{% endfor %}{% endfor %}', ['i' => false, 'j' => false]],\n\n            ['{% for i in foo %}{% for j in foo %}{{ loop[\"parent\"].loop.index }}{% endfor %}{% endfor %}', ['i' => true, 'j' => true]],\n\n            ['{% for i in foo %}{{ include(\"foo\") }}{% endfor %}', ['i' => true]],\n\n            ['{% for i in foo %}{{ include(\"foo\", with_context = false) }}{% endfor %}', ['i' => false]],\n\n            ['{% for i in foo %}{{ include(\"foo\", with_context = true) }}{% endfor %}', ['i' => true]],\n\n            ['{% for i in foo %}{{ include(\"foo\", { \"foo\": \"bar\" }, with_context = false) }}{% endfor %}', ['i' => false]],\n\n            ['{% for i in foo %}{{ include(\"foo\", { \"foo\": loop.index }, with_context = false) }}{% endfor %}', ['i' => true]],\n        ];\n    }\n\n    public function checkForLoopConfiguration(Node $node, $target, $withLoop)\n    {\n        foreach ($node as $n) {\n            if ($n instanceof ForNode) {\n                if ($target === $n->getNode('value_target')->getAttribute('name')) {\n                    return $withLoop == $n->getAttribute('with_loop');\n                }\n            }\n\n            $ret = $this->checkForLoopConfiguration($n, $target, $withLoop);\n            if (null !== $ret) {\n                return $ret;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tests/NodeVisitor/SandboxTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\NodeVisitor;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\BodyNode;\nuse Twig\\Node\\CheckToStringNode;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\Variable\\ContextVariable;\nuse Twig\\Node\\ModuleNode;\nuse Twig\\Node\\PrintNode;\nuse Twig\\NodeTraverser;\nuse Twig\\NodeVisitor\\SandboxNodeVisitor;\nuse Twig\\Source;\n\nclass SandboxTest extends TestCase\n{\n    public function testGeneratorExpression()\n    {\n        $env = new Environment(new ArrayLoader());\n        $expr = new ContextVariable('foo', 1);\n        $expr->setAttribute('is_generator', true);\n        $node = new ModuleNode(new BodyNode([new PrintNode($expr, 1)]), null, new EmptyNode(), new EmptyNode(), new EmptyNode(), new EmptyNode(), new Source('foo', 'foo'));\n        $traverser = new NodeTraverser($env, [new SandboxNodeVisitor($env)]);\n        $node = $traverser->traverse($node);\n\n        $this->assertNotInstanceOf(CheckToStringNode::class, $node->getNode('body')->getNode(0)->getNode('expr'));\n        $this->assertSame(\"// line 1\\nyield from (\\$context[\\\"foo\\\"] ?? null);\\n\", $env->compile($node->getNode('body')));\n    }\n}\n"
  },
  {
    "path": "tests/ParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Lexer;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Node;\nuse Twig\\Node\\Nodes;\nuse Twig\\Node\\SetNode;\nuse Twig\\Node\\TextNode;\nuse Twig\\Parser;\nuse Twig\\Source;\nuse Twig\\Token;\nuse Twig\\TokenParser\\AbstractTokenParser;\nuse Twig\\TokenStream;\n\nclass ParserTest extends TestCase\n{\n    public function testUnknownTag()\n    {\n        $stream = new TokenStream([\n            new Token(Token::BLOCK_START_TYPE, '', 1),\n            new Token(Token::NAME_TYPE, 'foo', 1),\n            new Token(Token::BLOCK_END_TYPE, '', 1),\n            new Token(Token::EOF_TYPE, '', 1),\n        ], new Source('', ''));\n        $parser = new Parser(new Environment(new ArrayLoader()));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown \"foo\" tag. Did you mean \"for\" at line 1?');\n\n        $parser->parse($stream);\n    }\n\n    public function testUnknownTagWithoutSuggestions()\n    {\n        $stream = new TokenStream([\n            new Token(Token::BLOCK_START_TYPE, '', 1),\n            new Token(Token::NAME_TYPE, 'foobar', 1),\n            new Token(Token::BLOCK_END_TYPE, '', 1),\n            new Token(Token::EOF_TYPE, '', 1),\n        ], new Source('', ''));\n        $parser = new Parser(new Environment(new ArrayLoader()));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown \"foobar\" tag at line 1.');\n\n        $parser->parse($stream);\n    }\n\n    /**\n     * @dataProvider getFilterBodyNodesData\n     */\n    public function testFilterBodyNodes($input, $expected)\n    {\n        $parser = $this->getParser();\n        $m = new \\ReflectionMethod($parser, 'filterBodyNodes');\n\n        $this->assertEquals($expected, $m->invoke($parser, $input));\n    }\n\n    public static function getFilterBodyNodesData()\n    {\n        return [\n            [\n                new Nodes([new TextNode('   ', 1)]),\n                new Nodes([]),\n            ],\n            [\n                $input = new Nodes([new SetNode(false, new EmptyNode(), new EmptyNode(), 1)]),\n                $input,\n            ],\n            [\n                $input = new Nodes([new SetNode(true, new EmptyNode(), new Nodes([new Nodes([new TextNode('foo', 1)])]), 1)]),\n                $input,\n            ],\n        ];\n    }\n\n    /**\n     * @dataProvider getFilterBodyNodesDataThrowsException\n     */\n    public function testFilterBodyNodesThrowsException($input)\n    {\n        $parser = $this->getParser();\n\n        $m = new \\ReflectionMethod($parser, 'filterBodyNodes');\n\n        $this->expectException(SyntaxError::class);\n        $m->invoke($parser, $input);\n    }\n\n    public static function getFilterBodyNodesDataThrowsException()\n    {\n        return [\n            [new TextNode('foo', 1)],\n            [new Nodes([new Nodes([new TextNode('foo', 1)])])],\n        ];\n    }\n\n    /**\n     * @dataProvider getFilterBodyNodesWithBOMData\n     */\n    public function testFilterBodyNodesWithBOM($emptyNode)\n    {\n        $parser = $this->getParser();\n\n        $m = new \\ReflectionMethod($parser, 'filterBodyNodes');\n        $this->assertNull($m->invoke($parser, new TextNode(\\chr(0xEF).\\chr(0xBB).\\chr(0xBF).$emptyNode, 1)));\n    }\n\n    public static function getFilterBodyNodesWithBOMData()\n    {\n        return [\n            [' '],\n            [\"\\t\"],\n            [\"\\n\"],\n            [\"\\n\\t\\n   \"],\n        ];\n    }\n\n    public function testParseIsReentrant()\n    {\n        $twig = new Environment(new ArrayLoader(), [\n            'autoescape' => false,\n            'optimizations' => 0,\n        ]);\n        $twig->addTokenParser(new TestTokenParser());\n\n        $parser = new Parser($twig);\n\n        $parser->parse(new TokenStream([\n            new Token(Token::BLOCK_START_TYPE, '', 1),\n            new Token(Token::NAME_TYPE, 'test', 1),\n            new Token(Token::BLOCK_END_TYPE, '', 1),\n            new Token(Token::VAR_START_TYPE, '', 1),\n            new Token(Token::NAME_TYPE, 'foo', 1),\n            new Token(Token::VAR_END_TYPE, '', 1),\n            new Token(Token::EOF_TYPE, '', 1),\n        ], new Source('', '')));\n\n        $p = new \\ReflectionProperty($parser, 'parent');\n        $this->assertNull($p->getValue($parser));\n    }\n\n    public function testGetVarName()\n    {\n        $twig = new Environment(new ArrayLoader(), [\n            'autoescape' => false,\n            'optimizations' => 0,\n        ]);\n\n        $twig->parse($twig->tokenize(new Source(<<<EOF\n{% from _self import foo %}\n\n{% macro foo() %}\n    {{ foo }}\n{% endmacro %}\nEOF, 'index')));\n\n        // The getVarName() must not depend on the template loaders,\n        // If this test does not throw any exception, that's good.\n        $this->addToAssertionCount(1);\n    }\n\n    public function testImplicitMacroArgumentDefaultValues()\n    {\n        $template = '{% macro marco (po, lo = true) %}{% endmacro %}';\n        $lexer = new Lexer(new Environment(new ArrayLoader()));\n        $stream = $lexer->tokenize(new Source($template, 'index'));\n\n        $argumentNodes = $this->getParser()\n            ->parse($stream)\n            ->getNode('macros')\n            ->getNode('marco')\n            ->getNode('arguments')\n        ;\n\n        $this->assertTrue($argumentNodes->getNode(1)->hasAttribute('is_implicit'));\n        $this->assertNull($argumentNodes->getNode(1)->getAttribute('value'));\n\n        $this->assertFalse($argumentNodes->getNode(3)->hasAttribute('is_implicit'));\n        $this->assertTrue($argumentNodes->getNode(3)->getAttribute('value'));\n    }\n\n    protected function getParser()\n    {\n        $parser = new Parser(new Environment(new ArrayLoader()));\n        $parser->setParent(new ConstantExpression('base.html', 1));\n\n        $p = new \\ReflectionProperty($parser, 'stream');\n        $p->setValue($parser, new TokenStream([], new Source('', '')));\n\n        return $parser;\n    }\n}\n\nclass TestTokenParser extends AbstractTokenParser\n{\n    public function parse(Token $token): Node\n    {\n        // simulate the parsing of another template right in the middle of the parsing of the current template\n        $this->parser->parse(new TokenStream([\n            new Token(Token::BLOCK_START_TYPE, '', 1),\n            new Token(Token::NAME_TYPE, 'extends', 1),\n            new Token(Token::STRING_TYPE, 'base', 1),\n            new Token(Token::BLOCK_END_TYPE, '', 1),\n            new Token(Token::EOF_TYPE, '', 1),\n        ], new Source('', '')));\n\n        $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);\n\n        return new EmptyNode(1);\n    }\n\n    public function getTag(): string\n    {\n        return 'test';\n    }\n}\n"
  },
  {
    "path": "tests/Profiler/Dumper/BlackfireTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Profiler\\Dumper;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Profiler\\Dumper\\BlackfireDumper;\n\nclass BlackfireTest extends ProfilerTestCase\n{\n    public function testDump()\n    {\n        $dumper = new BlackfireDumper();\n\n        $this->assertStringMatchesFormat(<<<EOF\nfile-format: BlackfireProbe\ncost-dimensions: wt mu pmu\nrequest-start: %d.%d\n\nmain()//1 %d %d %d\nmain()==>index.twig//1 %d %d %d\nindex.twig==>embedded.twig::block(body)//1 %d %d 0\nindex.twig==>embedded.twig//2 %d %d %d\nembedded.twig==>included.twig//2 %d %d %d\nindex.twig==>index.twig::macro(foo)//1 %d %d %d\nEOF, $dumper->dump($this->getProfile()));\n    }\n}\n"
  },
  {
    "path": "tests/Profiler/Dumper/HtmlTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Profiler\\Dumper;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Profiler\\Dumper\\HtmlDumper;\n\nclass HtmlTest extends ProfilerTestCase\n{\n    public function testDump()\n    {\n        $dumper = new HtmlDumper();\n        $this->assertStringMatchesFormat(<<<EOF\n<pre>main <span style=\"color: #d44\">%d.%dms/%d%</span>\n└ <span style=\"background-color: #ffd\">index.twig</span> <span style=\"color: #d44\">%d.%dms/%d%</span>\n  └ embedded.twig::block(<span style=\"background-color: #dfd\">body</span>)\n  └ <span style=\"background-color: #ffd\">embedded.twig</span>\n  │ └ <span style=\"background-color: #ffd\">included.twig</span>\n  └ index.twig::macro(<span style=\"background-color: #ddf\">foo</span>)\n  └ <span style=\"background-color: #ffd\">embedded.twig</span>\n    └ <span style=\"background-color: #ffd\">included.twig</span>\n</pre>\nEOF, $dumper->dump($this->getProfile()));\n    }\n}\n"
  },
  {
    "path": "tests/Profiler/Dumper/ProfilerTestCase.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Profiler\\Dumper;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Profiler\\Profile;\n\nabstract class ProfilerTestCase extends TestCase\n{\n    protected function getProfile()\n    {\n        $profile = new Profile('main');\n        $subProfiles = [\n            $this->getIndexProfile(\n                [\n                    $this->getEmbeddedBlockProfile(),\n                    $this->getEmbeddedTemplateProfile(\n                        [\n                            $this->getIncludedTemplateProfile(),\n                        ]\n                    ),\n                    $this->getMacroProfile(),\n                    $this->getEmbeddedTemplateProfile(\n                        [\n                            $this->getIncludedTemplateProfile(),\n                        ]\n                    ),\n                ]\n            ),\n        ];\n\n        $p = new \\ReflectionProperty($profile, 'profiles');\n        $p->setValue($profile, $subProfiles);\n\n        return $profile;\n    }\n\n    private function getIndexProfile(array $subProfiles = [])\n    {\n        return $this->generateProfile('main', 1, 'template', 'index.twig', $subProfiles);\n    }\n\n    private function getEmbeddedBlockProfile(array $subProfiles = [])\n    {\n        return $this->generateProfile('body', 0.0001, 'block', 'embedded.twig', $subProfiles);\n    }\n\n    private function getEmbeddedTemplateProfile(array $subProfiles = [])\n    {\n        return $this->generateProfile('main', 0.0001, 'template', 'embedded.twig', $subProfiles);\n    }\n\n    private function getIncludedTemplateProfile(array $subProfiles = [])\n    {\n        return $this->generateProfile('main', 0.0001, 'template', 'included.twig', $subProfiles);\n    }\n\n    private function getMacroProfile(array $subProfiles = [])\n    {\n        return $this->generateProfile('foo', 0.0001, 'macro', 'index.twig', $subProfiles);\n    }\n\n    /**\n     * @param string $name\n     * @param float  $duration\n     * @param string $type\n     * @param string $templateName\n     *\n     * @return Profile\n     */\n    private function generateProfile($name, $duration, $type, $templateName, array $subProfiles = [])\n    {\n        $profile = new Profile($templateName, $type, $name);\n\n        $p = new \\ReflectionProperty($profile, 'profiles');\n        $p->setValue($profile, $subProfiles);\n\n        $starts = new \\ReflectionProperty($profile, 'starts');\n        $starts->setValue($profile, [\n            'wt' => 0,\n            'mu' => 0,\n            'pmu' => 0,\n        ]);\n        $ends = new \\ReflectionProperty($profile, 'ends');\n        $ends->setValue($profile, [\n            'wt' => $duration,\n            'mu' => 0,\n            'pmu' => 0,\n        ]);\n\n        return $profile;\n    }\n}\n"
  },
  {
    "path": "tests/Profiler/Dumper/TextTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Profiler\\Dumper;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse Twig\\Profiler\\Dumper\\TextDumper;\n\nclass TextTest extends ProfilerTestCase\n{\n    public function testDump()\n    {\n        $dumper = new TextDumper();\n        $this->assertStringMatchesFormat(<<<EOF\nmain %d.%dms/%d%\n└ index.twig %d.%dms/%d%\n  └ embedded.twig::block(body)\n  └ embedded.twig\n  │ └ included.twig\n  └ index.twig::macro(foo)\n  └ embedded.twig\n    └ included.twig\n\nEOF, $dumper->dump($this->getProfile()));\n    }\n}\n"
  },
  {
    "path": "tests/Profiler/ProfileTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Profiler;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Profiler\\Profile;\n\nclass ProfileTest extends TestCase\n{\n    public function testConstructor()\n    {\n        $profile = new Profile('template', 'type', 'name');\n\n        $this->assertEquals('template', $profile->getTemplate());\n        $this->assertEquals('type', $profile->getType());\n        $this->assertEquals('name', $profile->getName());\n    }\n\n    public function testIsRoot()\n    {\n        $profile = new Profile('template', Profile::ROOT);\n        $this->assertTrue($profile->isRoot());\n\n        $profile = new Profile('template', Profile::TEMPLATE);\n        $this->assertFalse($profile->isRoot());\n    }\n\n    public function testIsTemplate()\n    {\n        $profile = new Profile('template', Profile::TEMPLATE);\n        $this->assertTrue($profile->isTemplate());\n\n        $profile = new Profile('template', Profile::ROOT);\n        $this->assertFalse($profile->isTemplate());\n    }\n\n    public function testIsBlock()\n    {\n        $profile = new Profile('template', Profile::BLOCK);\n        $this->assertTrue($profile->isBlock());\n\n        $profile = new Profile('template', Profile::ROOT);\n        $this->assertFalse($profile->isBlock());\n    }\n\n    public function testIsMacro()\n    {\n        $profile = new Profile('template', Profile::MACRO);\n        $this->assertTrue($profile->isMacro());\n\n        $profile = new Profile('template', Profile::ROOT);\n        $this->assertFalse($profile->isMacro());\n    }\n\n    public function testGetAddProfile()\n    {\n        $profile = new Profile();\n        $profile->addProfile($a = new Profile());\n        $profile->addProfile($b = new Profile());\n\n        $this->assertSame([$a, $b], $profile->getProfiles());\n        $this->assertSame([$a, $b], iterator_to_array($profile));\n    }\n\n    public function testGetDuration()\n    {\n        $profile = new Profile();\n        usleep(1);\n        $profile->leave();\n\n        $this->assertTrue($profile->getDuration() > 0, \\sprintf('Expected duration > 0, got: %f', $profile->getDuration()));\n    }\n\n    public function testTimeAccessors()\n    {\n        $current = microtime(true);\n        $profile = new Profile();\n\n        $this->assertEqualsWithDelta($current, $profile->getStartTime(), 1);\n        $this->assertSame(0.0, $profile->getEndTime());\n    }\n\n    public function testSerialize()\n    {\n        $profile = new Profile('template', 'type', 'name');\n        $profile1 = new Profile('template1', 'type1', 'name1');\n        $profile->addProfile($profile1);\n        $profile->leave();\n        $profile1->leave();\n\n        $profile2 = unserialize(serialize($profile));\n        $profiles = $profile->getProfiles();\n        $this->assertCount(1, $profiles);\n        $profile3 = $profiles[0];\n\n        $this->assertEquals($profile->getTemplate(), $profile2->getTemplate());\n        $this->assertEquals($profile->getType(), $profile2->getType());\n        $this->assertEquals($profile->getName(), $profile2->getName());\n        $this->assertEquals($profile->getDuration(), $profile2->getDuration());\n\n        $this->assertEquals($profile1->getTemplate(), $profile3->getTemplate());\n        $this->assertEquals($profile1->getType(), $profile3->getType());\n        $this->assertEquals($profile1->getName(), $profile3->getName());\n    }\n\n    public function testReset()\n    {\n        $profile = new Profile();\n        usleep(1);\n        $profile->leave();\n        $profile->reset();\n\n        $this->assertEquals(0, $profile->getDuration());\n    }\n}\n"
  },
  {
    "path": "tests/Runtime/EscaperRuntimeTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Runtime;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Runtime\\EscaperRuntime;\n\nclass EscaperRuntimeTest extends TestCase\n{\n    /**\n     * All character encodings supported by htmlspecialchars().\n     */\n    protected $htmlSpecialChars = [\n        '\\'' => '&#039;',\n        '\"' => '&quot;',\n        '<' => '&lt;',\n        '>' => '&gt;',\n        '&' => '&amp;',\n    ];\n\n    protected $htmlAttrSpecialChars = [\n        '\\'' => '&#x27;',\n        /* Characters beyond ASCII value 255 to unicode escape */\n        'Ā' => '&#x0100;',\n        '😀' => '&#x1F600;',\n        /* Immune chars excluded */\n        ',' => ',',\n        '.' => '.',\n        '-' => '-',\n        '_' => '_',\n        /* Basic alnums excluded */\n        'a' => 'a',\n        'A' => 'A',\n        'z' => 'z',\n        'Z' => 'Z',\n        '0' => '0',\n        '9' => '9',\n        /* Basic control characters and null */\n        \"\\r\" => '&#x0D;',\n        \"\\n\" => '&#x0A;',\n        \"\\t\" => '&#x09;',\n        \"\\0\" => '&#xFFFD;', // should use Unicode replacement char\n        /* Encode chars as named entities where possible */\n        '<' => '&lt;',\n        '>' => '&gt;',\n        '&' => '&amp;',\n        '\"' => '&quot;',\n        /* Encode spaces for quoteless attribute protection */\n        ' ' => '&#x20;',\n    ];\n\n    protected $jsSpecialChars = [\n        /* HTML special chars - escape without exception to hex */\n        '<' => '\\\\u003C',\n        '>' => '\\\\u003E',\n        '\\'' => '\\\\u0027',\n        '\"' => '\\\\u0022',\n        '&' => '\\\\u0026',\n        '/' => '\\\\/',\n        /* Characters beyond ASCII value 255 to unicode escape */\n        'Ā' => '\\\\u0100',\n        '😀' => '\\\\uD83D\\\\uDE00',\n        /* Immune chars excluded */\n        ',' => ',',\n        '.' => '.',\n        '_' => '_',\n        /* Basic alnums excluded */\n        'a' => 'a',\n        'A' => 'A',\n        'z' => 'z',\n        'Z' => 'Z',\n        '0' => '0',\n        '9' => '9',\n        /* Basic control characters and null */\n        \"\\r\" => '\\r',\n        \"\\n\" => '\\n',\n        \"\\x08\" => '\\b',\n        \"\\t\" => '\\t',\n        \"\\x0C\" => '\\f',\n        \"\\0\" => '\\\\u0000',\n        /* Encode spaces for quoteless attribute protection */\n        ' ' => '\\\\u0020',\n    ];\n\n    protected $urlSpecialChars = [\n        /* HTML special chars - escape without exception to percent encoding */\n        '<' => '%3C',\n        '>' => '%3E',\n        '\\'' => '%27',\n        '\"' => '%22',\n        '&' => '%26',\n        /* Characters beyond ASCII value 255 to hex sequence */\n        'Ā' => '%C4%80',\n        /* Punctuation and unreserved check */\n        ',' => '%2C',\n        '.' => '.',\n        '_' => '_',\n        '-' => '-',\n        ':' => '%3A',\n        ';' => '%3B',\n        '!' => '%21',\n        /* Basic alnums excluded */\n        'a' => 'a',\n        'A' => 'A',\n        'z' => 'z',\n        'Z' => 'Z',\n        '0' => '0',\n        '9' => '9',\n        /* Basic control characters and null */\n        \"\\r\" => '%0D',\n        \"\\n\" => '%0A',\n        \"\\t\" => '%09',\n        \"\\0\" => '%00',\n        /* PHP quirks from the past */\n        ' ' => '%20',\n        '~' => '~',\n        '+' => '%2B',\n    ];\n\n    protected $cssSpecialChars = [\n        /* HTML special chars - escape without exception to hex */\n        '<' => '\\\\3C ',\n        '>' => '\\\\3E ',\n        '\\'' => '\\\\27 ',\n        '\"' => '\\\\22 ',\n        '&' => '\\\\26 ',\n        /* Characters beyond ASCII value 255 to unicode escape */\n        'Ā' => '\\\\100 ',\n        /* Immune chars excluded */\n        ',' => '\\\\2C ',\n        '.' => '\\\\2E ',\n        '_' => '\\\\5F ',\n        /* Basic alnums excluded */\n        'a' => 'a',\n        'A' => 'A',\n        'z' => 'z',\n        'Z' => 'Z',\n        '0' => '0',\n        '9' => '9',\n        /* Basic control characters and null */\n        \"\\r\" => '\\\\D ',\n        \"\\n\" => '\\\\A ',\n        \"\\t\" => '\\\\9 ',\n        \"\\0\" => '\\\\0 ',\n        /* Encode spaces for quoteless attribute protection */\n        ' ' => '\\\\20 ',\n    ];\n\n    public function testHtmlEscapingConvertsSpecialChars()\n    {\n        foreach ($this->htmlSpecialChars as $key => $value) {\n            $this->assertEquals($value, (new EscaperRuntime())->escape($key, 'html'), 'Failed to escape: '.$key);\n        }\n    }\n\n    public function testHtmlAttributeEscapingConvertsSpecialChars()\n    {\n        foreach ($this->htmlAttrSpecialChars as $key => $value) {\n            $this->assertEquals($value, (new EscaperRuntime())->escape($key, 'html_attr'), 'Failed to escape: '.$key);\n        }\n    }\n\n    public function testHtmlAttributeRelaxedEscapingConvertsSpecialChars()\n    {\n        foreach ($this->htmlAttrSpecialChars as $key => $value) {\n            $this->assertEquals($value, (new EscaperRuntime())->escape($key, 'html_attr_relaxed'), 'Failed to escape: '.$key);\n        }\n    }\n\n    public function testJavascriptEscapingConvertsSpecialChars()\n    {\n        foreach ($this->jsSpecialChars as $key => $value) {\n            $this->assertEquals($value, (new EscaperRuntime())->escape($key, 'js'), 'Failed to escape: '.$key);\n        }\n    }\n\n    public function testJavascriptEscapingConvertsSpecialCharsWithInternalEncoding()\n    {\n        $previousInternalEncoding = mb_internal_encoding();\n        try {\n            mb_internal_encoding('ISO-8859-1');\n            foreach ($this->jsSpecialChars as $key => $value) {\n                $this->assertEquals($value, (new EscaperRuntime())->escape($key, 'js'), 'Failed to escape: '.$key);\n            }\n        } finally {\n            if (false !== $previousInternalEncoding) {\n                mb_internal_encoding($previousInternalEncoding);\n            }\n        }\n    }\n\n    public function testJavascriptEscapingReturnsStringIfZeroLength()\n    {\n        $this->assertEquals('', (new EscaperRuntime())->escape('', 'js'));\n    }\n\n    public function testJavascriptEscapingReturnsStringIfContainsOnlyDigits()\n    {\n        $this->assertEquals('123', (new EscaperRuntime())->escape('123', 'js'));\n    }\n\n    public function testCssEscapingConvertsSpecialChars()\n    {\n        foreach ($this->cssSpecialChars as $key => $value) {\n            $this->assertEquals($value, (new EscaperRuntime())->escape($key, 'css'), 'Failed to escape: '.$key);\n        }\n    }\n\n    public function testCssEscapingReturnsStringIfZeroLength()\n    {\n        $this->assertEquals('', (new EscaperRuntime())->escape('', 'css'));\n    }\n\n    public function testCssEscapingReturnsStringIfContainsOnlyDigits()\n    {\n        $this->assertEquals('123', (new EscaperRuntime())->escape('123', 'css'));\n    }\n\n    public function testUrlEscapingConvertsSpecialChars()\n    {\n        foreach ($this->urlSpecialChars as $key => $value) {\n            $this->assertEquals($value, (new EscaperRuntime())->escape($key, 'url'), 'Failed to escape: '.$key);\n        }\n    }\n\n    /**\n     * Range tests to confirm escaped range of characters is within OWASP recommendation.\n     */\n\n    /**\n     * Only testing the first few 2 ranges on this prot. function as that's all these\n     * other range tests require.\n     */\n    public function testUnicodeCodepointConversionToUtf8()\n    {\n        $expected = ' ~ޙ';\n        $codepoints = [0x20, 0x7E, 0x799];\n        $result = '';\n        foreach ($codepoints as $value) {\n            $result .= $this->codepointToUtf8($value);\n        }\n        $this->assertEquals($expected, $result);\n    }\n\n    /**\n     * Convert a Unicode Codepoint to a literal UTF-8 character.\n     *\n     * @param int $codepoint Unicode codepoint in hex notation\n     *\n     * @return string UTF-8 literal string\n     */\n    protected function codepointToUtf8($codepoint)\n    {\n        if ($codepoint < 0x80) {\n            return \\chr($codepoint);\n        }\n        if ($codepoint < 0x800) {\n            return \\chr($codepoint >> 6 & 0x3F | 0xC0)\n                .\\chr($codepoint & 0x3F | 0x80);\n        }\n        if ($codepoint < 0x10000) {\n            return \\chr($codepoint >> 12 & 0x0F | 0xE0)\n                .\\chr($codepoint >> 6 & 0x3F | 0x80)\n                .\\chr($codepoint & 0x3F | 0x80);\n        }\n        if ($codepoint < 0x110000) {\n            return \\chr($codepoint >> 18 & 0x07 | 0xF0)\n                .\\chr($codepoint >> 12 & 0x3F | 0x80)\n                .\\chr($codepoint >> 6 & 0x3F | 0x80)\n                .\\chr($codepoint & 0x3F | 0x80);\n        }\n        throw new \\Exception('Codepoint requested outside of Unicode range.');\n    }\n\n    public function testJavascriptEscapingEscapesOwaspRecommendedRanges()\n    {\n        $immune = [',', '.', '_']; // Exceptions to escaping ranges\n        for ($chr = 0; $chr < 0xFF; ++$chr) {\n            if ($chr >= 0x30 && $chr <= 0x39\n            || $chr >= 0x41 && $chr <= 0x5A\n            || $chr >= 0x61 && $chr <= 0x7A) {\n                $literal = $this->codepointToUtf8($chr);\n                $this->assertEquals($literal, (new EscaperRuntime())->escape($literal, 'js'));\n            } else {\n                $literal = $this->codepointToUtf8($chr);\n                if (\\in_array($literal, $immune)) {\n                    $this->assertEquals($literal, (new EscaperRuntime())->escape($literal, 'js'));\n                } else {\n                    $this->assertNotEquals(\n                        $literal,\n                        (new EscaperRuntime())->escape($literal, 'js'),\n                        \"$literal should be escaped!\");\n                }\n            }\n        }\n    }\n\n    public function testHtmlAttributeEscapingEscapesOwaspRecommendedRanges()\n    {\n        $immune = [',', '.', '-', '_']; // Exceptions to escaping ranges\n        for ($chr = 0; $chr < 0xFF; ++$chr) {\n            if ($chr >= 0x30 && $chr <= 0x39\n            || $chr >= 0x41 && $chr <= 0x5A\n            || $chr >= 0x61 && $chr <= 0x7A) {\n                $literal = $this->codepointToUtf8($chr);\n                $this->assertEquals($literal, (new EscaperRuntime())->escape($literal, 'html_attr'));\n            } else {\n                $literal = $this->codepointToUtf8($chr);\n                if (\\in_array($literal, $immune)) {\n                    $this->assertEquals($literal, (new EscaperRuntime())->escape($literal, 'html_attr'));\n                } else {\n                    $this->assertNotEquals(\n                        $literal,\n                        (new EscaperRuntime())->escape($literal, 'html_attr'),\n                        \"$literal should be escaped!\");\n                }\n            }\n        }\n    }\n\n    public function testHtmlAttributeRelaxedEscapingEscapesOwaspRecommendedRanges()\n    {\n        $immune = [',', '.', '-', '_', ':', '@', '[', ']']; // Exceptions to escaping ranges\n        for ($chr = 0; $chr < 0xFF; ++$chr) {\n            if ($chr >= 0x30 && $chr <= 0x39\n            || $chr >= 0x41 && $chr <= 0x5A\n            || $chr >= 0x61 && $chr <= 0x7A) {\n                $literal = $this->codepointToUtf8($chr);\n                $this->assertEquals($literal, (new EscaperRuntime())->escape($literal, 'html_attr_relaxed'));\n            } else {\n                $literal = $this->codepointToUtf8($chr);\n                if (\\in_array($literal, $immune)) {\n                    $this->assertEquals($literal, (new EscaperRuntime())->escape($literal, 'html_attr_relaxed'));\n                } else {\n                    $this->assertNotEquals($literal, (new EscaperRuntime())->escape($literal, 'html_attr_relaxed'), \"$literal should be escaped!\");\n                }\n            }\n        }\n    }\n\n    public function testCssEscapingEscapesOwaspRecommendedRanges()\n    {\n        // CSS has no exceptions to escaping ranges\n        for ($chr = 0; $chr < 0xFF; ++$chr) {\n            if ($chr >= 0x30 && $chr <= 0x39\n            || $chr >= 0x41 && $chr <= 0x5A\n            || $chr >= 0x61 && $chr <= 0x7A) {\n                $literal = $this->codepointToUtf8($chr);\n                $this->assertEquals($literal, (new EscaperRuntime())->escape($literal, 'css'));\n            } else {\n                $literal = $this->codepointToUtf8($chr);\n                $this->assertNotEquals(\n                    $literal,\n                    (new EscaperRuntime())->escape($literal, 'css'),\n                    \"$literal should be escaped!\");\n            }\n        }\n    }\n\n    public function testUnknownCustomEscaper()\n    {\n        $this->expectException(RuntimeError::class);\n\n        (new EscaperRuntime())->escape('foo', 'bar');\n    }\n\n    /**\n     * @dataProvider provideCustomEscaperCases\n     */\n    public function testCustomEscaper($expected, $string, $strategy, $charset)\n    {\n        $escaper = new EscaperRuntime();\n        $escaper->setEscaper('foo', 'Twig\\Tests\\Runtime\\escaper');\n        $this->assertSame($expected, $escaper->escape($string, $strategy, $charset));\n    }\n\n    public static function provideCustomEscaperCases()\n    {\n        return [\n            ['foo**ISO-8859-1', 'foo', 'foo', 'ISO-8859-1'],\n            ['**ISO-8859-1', null, 'foo', 'ISO-8859-1'],\n            ['42**UTF-8', 42, 'foo', null],\n        ];\n    }\n\n    /**\n     * @dataProvider provideObjectsForEscaping\n     */\n    public function testObjectEscaping(string $escapedHtml, string $escapedJs, array $safeClasses)\n    {\n        $obj = new Extension_TestClass();\n        $escaper = new EscaperRuntime();\n        $escaper->setSafeClasses($safeClasses);\n        $this->assertSame($escapedHtml, $escaper->escape($obj, 'html', null, true));\n        $this->assertSame($escapedJs, $escaper->escape($obj, 'js', null, true));\n    }\n\n    public static function provideObjectsForEscaping()\n    {\n        return [\n            ['&lt;br /&gt;', '<br />', ['\\Twig\\Tests\\Runtime\\Extension_TestClass' => ['js']]],\n            ['<br />', '\\u003Cbr\\u0020\\/\\u003E', ['\\Twig\\Tests\\Runtime\\Extension_TestClass' => ['html']]],\n            ['&lt;br /&gt;', '<br />', ['\\Twig\\Tests\\Runtime\\Extension_SafeHtmlInterface' => ['js']]],\n            ['<br />', '<br />', ['\\Twig\\Tests\\Runtime\\Extension_SafeHtmlInterface' => ['all']]],\n        ];\n    }\n}\n\nfunction escaper($string, $charset)\n{\n    return $string.'**'.$charset;\n}\n\ninterface Extension_SafeHtmlInterface\n{\n}\nclass Extension_TestClass implements Extension_SafeHtmlInterface\n{\n    public function __toString()\n    {\n        return '<br />';\n    }\n}\n"
  },
  {
    "path": "tests/TemplateTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Error\\RuntimeError;\nuse Twig\\Extension\\CoreExtension;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Sandbox\\SecurityError;\nuse Twig\\Sandbox\\SecurityPolicy;\nuse Twig\\Source;\nuse Twig\\Template;\nuse Twig\\TemplateWrapper;\n\nclass TemplateTest extends TestCase\n{\n    public function testDisplayBlocksAcceptTemplateOnlyAsBlocks()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $template = new TemplateForTest($twig);\n\n        $this->expectException(\\LogicException::class);\n        $template->displayBlock('foo', [], ['foo' => [new \\stdClass(), 'foo']]);\n    }\n\n    /**\n     * @dataProvider getAttributeExceptions\n     */\n    public function testGetAttributeExceptions($template, $message)\n    {\n        $templates = ['index' => $template];\n        $env = new Environment(new ArrayLoader($templates), ['strict_variables' => true]);\n        $template = $env->load('index');\n\n        $context = [\n            'string' => 'foo',\n            'null' => null,\n            'empty_array' => [],\n            'array' => ['foo' => 'foo'],\n            'array_access' => new TemplateArrayAccessObject(),\n            'magic_exception' => new TemplateMagicPropertyObjectWithException(),\n            'object' => new \\stdClass(),\n        ];\n\n        try {\n            $template->render($context);\n            $this->fail('Accessing an invalid attribute should throw an exception.');\n        } catch (RuntimeError $e) {\n            $this->assertSame(\\sprintf($message, 'index'), $e->getMessage());\n        }\n    }\n\n    public static function getAttributeExceptions()\n    {\n        return [\n            ['{{ string[\"a\"] }}', 'Impossible to access a key (\"a\") on a string variable (\"foo\") in \"%s\" at line 1.'],\n            ['{{ null[\"a\"] }}', 'Impossible to access a key (\"a\") on a null variable in \"%s\" at line 1.'],\n            ['{{ empty_array[\"a\"] }}', 'Key \"a\" does not exist as the sequence/mapping is empty in \"%s\" at line 1.'],\n            ['{{ array[\"a\"] }}', 'Key \"a\" for sequence/mapping with keys \"foo\" does not exist in \"%s\" at line 1.'],\n            ['{{ array_access[\"a\"] }}', 'Key \"a\" does not exist in ArrayAccess-able object of class \"Twig\\Tests\\TemplateArrayAccessObject\" in \"%s\" at line 1.'],\n            ['{{ string.a }}', 'Impossible to access an attribute (\"a\") on a string variable (\"foo\") in \"%s\" at line 1.'],\n            ['{{ string.a() }}', 'Impossible to invoke a method (\"a\") on a string variable (\"foo\") in \"%s\" at line 1.'],\n            ['{{ null.a }}', 'Impossible to access an attribute (\"a\") on a null variable in \"%s\" at line 1.'],\n            ['{{ null.a() }}', 'Impossible to invoke a method (\"a\") on a null variable in \"%s\" at line 1.'],\n            ['{{ array.a() }}', 'Impossible to invoke a method (\"a\") on a sequence/mapping in \"%s\" at line 1.'],\n            ['{{ empty_array.a }}', 'Key \"a\" does not exist as the sequence/mapping is empty in \"%s\" at line 1.'],\n            ['{{ array.a }}', 'Key \"a\" for sequence/mapping with keys \"foo\" does not exist in \"%s\" at line 1.'],\n            ['{{ array.(-10) }}', 'Key \"-10\" for sequence/mapping with keys \"foo\" does not exist in \"%s\" at line 1.'],\n            ['{{ array_access.a }}', 'Neither the property \"a\" nor one of the methods \"a()\", \"geta()\", \"isa()\", \"hasa()\" or \"__call()\" exist and have public access in class \"Twig\\Tests\\TemplateArrayAccessObject\" in \"%s\" at line 1.'],\n            ['{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Neither the property \"missing_method\" nor one of the methods \"missing_method()\", \"getmissing_method()\", \"ismissing_method()\", \"hasmissing_method()\" or \"__call()\" exist and have public access in class \"Twig\\Tests\\TemplateArrayAccessObject\" in \"%s\" at line 1.'],\n            ['{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template (\"Hey! Don\\'t try to isset me!\") in \"%s\" at line 1.'],\n            ['{{ object[\"a\"] }}', 'Impossible to access a key \"a\" on an object of class \"stdClass\" that does not implement ArrayAccess interface in \"%s\" at line 1.'],\n        ];\n    }\n\n    /**\n     * @dataProvider getGetAttributeWithSandbox\n     */\n    public function testGetAttributeWithSandbox($object, $item, $allowed)\n    {\n        $twig = new Environment(new ArrayLoader());\n        $policy = new SecurityPolicy([], [], [/* method */], [/* prop */], []);\n        $twig->addExtension(new SandboxExtension($policy, !$allowed));\n        $template = new TemplateForTest($twig);\n\n        try {\n            CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, [], 'any', false, false, true);\n\n            if (!$allowed) {\n                $this->fail();\n            } else {\n                $this->addToAssertionCount(1);\n            }\n        } catch (SecurityError $e) {\n            if ($allowed) {\n                $this->fail();\n            } else {\n                $this->addToAssertionCount(1);\n            }\n\n            $this->assertStringContainsString('is not allowed', $e->getMessage());\n        }\n    }\n\n    public static function getGetAttributeWithSandbox()\n    {\n        return [\n            [new TemplatePropertyObject(), 'defined', false],\n            [new TemplatePropertyObject(), 'defined', true],\n            [new TemplateMethodObject(), 'defined', false],\n            [new TemplateMethodObject(), 'defined', true],\n        ];\n    }\n\n    /**\n     * @dataProvider getRenderTemplateWithoutOutputData\n     */\n    public function testRenderTemplateWithoutOutput(string $template)\n    {\n        $twig = new Environment(new ArrayLoader(['index' => $template]));\n        $this->assertSame('', $twig->render('index'));\n    }\n\n    public static function getRenderTemplateWithoutOutputData()\n    {\n        return [\n            [''],\n            ['{% for var in [] %}{% endfor %}'],\n            ['{% if false %}{% endif %}'],\n        ];\n    }\n\n    /**\n     * @dataProvider getNullCoalesceWithImportedMacroData\n     */\n    public function testNullCoalesceWithImportedMacro(array $templates, string $expected)\n    {\n        $twig = new Environment(new ArrayLoader($templates));\n\n        $this->assertSame($expected, trim($twig->render('index.twig')));\n    }\n\n    public static function getNullCoalesceWithImportedMacroData(): array\n    {\n        return [\n            'from import' => [\n                [\n                    'index.twig' => '{% from \"helper.twig\" import foo %}{{ foo(\"bar\") ?? \"\" }}',\n                    'helper.twig' => '{% macro foo(param) %}{{ param }}{% endmacro %}',\n                ],\n                'bar',\n            ],\n            'from import with undefined macro falls back' => [\n                [\n                    'index.twig' => '{% from \"helper.twig\" import foo, nonexistent %}{{ nonexistent(\"bar\") ?? \"fallback\" }}',\n                    'helper.twig' => '{% macro foo(param) %}{{ param }}{% endmacro %}',\n                ],\n                'fallback',\n            ],\n            'from import used multiple times' => [\n                [\n                    'index.twig' => '{% from \"helper.twig\" import foo %}{{ foo(\"a\") ?? \"\" }}-{{ foo(\"b\") ?? \"\" }}',\n                    'helper.twig' => '{% macro foo(param) %}{{ param }}{% endmacro %}',\n                ],\n                'a-b',\n            ],\n        ];\n    }\n\n    public function testRenderBlockWithUndefinedBlock()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $template = new TemplateForTest($twig, 'index.twig');\n\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('Block \"unknown\" on template \"index.twig\" does not exist in \"index.twig\".');\n\n        $template->renderBlock('unknown', []);\n    }\n\n    public function testDisplayBlockWithUndefinedBlock()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $template = new TemplateForTest($twig, 'index.twig');\n\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('Block \"unknown\" on template \"index.twig\" does not exist in \"index.twig\".');\n\n        $template->displayBlock('unknown', []);\n    }\n\n    public function testDisplayBlockWithUndefinedParentBlock()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $template = new TemplateForTest($twig, 'parent.twig');\n\n        $this->expectException(RuntimeError::class);\n        $this->expectExceptionMessage('Block \"foo\" should not call parent() in \"index.twig\" as the block does not exist in the parent template \"parent.twig\"');\n\n        $template->displayBlock('foo', [], ['foo' => [new TemplateForTest($twig, 'index.twig'), 'block_foo']], false);\n    }\n\n    public function testGetAttributeOnArrayWithConfusableKey()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $template = new TemplateForTest($twig);\n\n        $array = ['Zero', 'One', -1 => 'MinusOne', '' => 'EmptyString', '1.5' => 'FloatButString', '01' => 'IntegerButStringWithLeadingZeros'];\n\n        $this->assertSame('Zero', $array[false]);\n        $this->assertSame('One', $array[true]);\n        if (\\PHP_VERSION_ID < 80100) {\n            // This line will trigger a deprecation warning on PHP 8.1.\n            $this->assertSame('One', $array[1.5]);\n        }\n        $this->assertSame('One', $array['1']);\n        if (\\PHP_VERSION_ID < 80100) {\n            // This line will trigger a deprecation warning on PHP 8.1.\n            $this->assertSame('MinusOne', $array[-1.5]);\n        }\n        $this->assertSame('FloatButString', $array['1.5']);\n        $this->assertSame('IntegerButStringWithLeadingZeros', $array['01']);\n        $this->assertSame('EmptyString', $array['']);\n\n        $this->assertSame('Zero', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, false), 'false is treated as 0 when accessing a sequence/mapping (equals PHP behavior)');\n        $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, true), 'true is treated as 1 when accessing a sequence/mapping (equals PHP behavior)');\n        $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, 1.5), 'float is casted to int when accessing a sequence/mapping (equals PHP behavior)');\n        $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1'), '\"1\" is treated as integer 1 when accessing a sequence/mapping (equals PHP behavior)');\n        $this->assertSame('MinusOne', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, -1.5), 'negative float is casted to int when accessing a sequence/mapping (equals PHP behavior)');\n        $this->assertSame('FloatButString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1.5'), '\"1.5\" is treated as-is when accessing a sequence/mapping (equals PHP behavior)');\n        $this->assertSame('IntegerButStringWithLeadingZeros', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '01'), '\"01\" is treated as-is when accessing a sequence/mapping (equals PHP behavior)');\n        $this->assertSame('EmptyString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, null), 'null is treated as \"\" when accessing a sequence/mapping (equals PHP behavior)');\n    }\n\n    /**\n     * @dataProvider getGetAttributeTests\n     */\n    public function testGetAttribute($defined, $value, $object, $item, $arguments, $type)\n    {\n        $twig = new Environment(new ArrayLoader());\n        $template = new TemplateForTest($twig);\n\n        $this->assertEquals($value, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type));\n    }\n\n    /**\n     * @dataProvider getGetAttributeTests\n     */\n    public function testGetAttributeStrict($defined, $value, $object, $item, $arguments, $type, $exceptionMessage = null)\n    {\n        $twig = new Environment(new ArrayLoader(), ['strict_variables' => true]);\n        $template = new TemplateForTest($twig);\n\n        if ($defined) {\n            $this->assertEquals($value, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type));\n        } else {\n            $this->expectException(RuntimeError::class);\n            if (null !== $exceptionMessage) {\n                $this->expectExceptionMessage($exceptionMessage);\n            }\n            $this->assertEquals($value, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type));\n        }\n    }\n\n    /**\n     * @dataProvider getGetAttributeTests\n     */\n    public function testGetAttributeDefined($defined, $value, $object, $item, $arguments, $type)\n    {\n        $twig = new Environment(new ArrayLoader());\n        $template = new TemplateForTest($twig);\n\n        $this->assertEquals($defined, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type, true));\n    }\n\n    /**\n     * @dataProvider getGetAttributeTests\n     */\n    public function testGetAttributeDefinedStrict($defined, $value, $object, $item, $arguments, $type)\n    {\n        $twig = new Environment(new ArrayLoader(), ['strict_variables' => true]);\n        $template = new TemplateForTest($twig);\n\n        $this->assertEquals($defined, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type, true));\n    }\n\n    public function testGetAttributeCallExceptions()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $template = new TemplateForTest($twig);\n\n        $object = new TemplateMagicMethodExceptionObject();\n\n        $this->assertNull(CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, 'foo'));\n    }\n\n    public static function getGetAttributeTests()\n    {\n        $array = [\n            'defined' => 'defined',\n            'zero' => 0,\n            'null' => null,\n            '1' => 1,\n            'bar' => true,\n            'foo' => true,\n            'baz' => 'baz',\n            'baf' => 'baf',\n            '09' => '09',\n            '+4' => '+4',\n        ];\n\n        $objectArray = new TemplateArrayAccessObject();\n        $arrayObject = new \\ArrayObject($array);\n        $stdObject = (object) $array;\n        $magicPropertyObject = new TemplateMagicPropertyObject();\n        $propertyObject = new TemplatePropertyObject();\n        $propertyObject1 = new TemplatePropertyObjectAndIterator();\n        $propertyObject2 = new TemplatePropertyObjectAndArrayAccess();\n        $propertyObject3 = new TemplatePropertyObjectDefinedWithUndefinedValue();\n        $methodObject = new TemplateMethodObject();\n        $magicMethodObject = new TemplateMagicMethodObject();\n\n        $anyType = Template::ANY_CALL;\n        $methodType = Template::METHOD_CALL;\n        $arrayType = Template::ARRAY_CALL;\n\n        $basicTests = [\n            // array(defined, value, property to fetch)\n            [true,  'defined', 'defined'],\n            [false, null,      'undefined'],\n            [false, null,      'protected'],\n            [true,  0,         'zero'],\n            [true,  1,         1],\n            [true,  1,         1.0],\n            [true,  null,      'null'],\n            [true,  true,      'bar'],\n            [true,  true,      'foo'],\n            [true,  'baz',     'baz'],\n            [true,  'baf',     'baf'],\n            [true,  '09',      '09'],\n            [true,  '+4',      '+4'],\n        ];\n        $testObjects = [\n            // array(object, type of fetch)\n            [$array,               $arrayType],\n            [$objectArray,         $arrayType],\n            [$arrayObject,         $anyType],\n            [$stdObject,           $anyType],\n            [$magicPropertyObject, $anyType],\n            [$methodObject,        $methodType],\n            [$methodObject,        $anyType],\n            [$propertyObject,      $anyType],\n            [$propertyObject1,     $anyType],\n            [$propertyObject2,     $anyType],\n        ];\n\n        $tests = [];\n        foreach ($testObjects as $testObject) {\n            foreach ($basicTests as $test) {\n                // properties cannot be numbers\n                if (($testObject[0] instanceof \\stdClass || $testObject[0] instanceof TemplatePropertyObject) && is_numeric($test[2])) {\n                    continue;\n                }\n\n                if ('+4' === $test[2] && $methodObject === $testObject[0]) {\n                    continue;\n                }\n\n                $tests[] = [$test[0], $test[1], $testObject[0], $test[2], [], $testObject[1]];\n            }\n        }\n\n        // additional properties tests\n        $tests = array_merge($tests, [\n            [true, null, $propertyObject3, 'foo', [], $anyType],\n        ]);\n\n        // additional method tests\n        $tests = array_merge($tests, [\n            [true, 'defined', $methodObject, 'defined',    [], $methodType],\n            [true, 'defined', $methodObject, 'DEFINED',    [], $methodType],\n            [true, 'defined', $methodObject, 'getDefined', [], $methodType],\n            [true, 'defined', $methodObject, 'GETDEFINED', [], $methodType],\n            [true, 'static',  $methodObject, 'static',     [], $methodType],\n            [true, 'static',  $methodObject, 'getStatic',  [], $methodType],\n\n            [true, '__call_undefined', $magicMethodObject, 'undefined', [], $methodType],\n            [true, '__call_UNDEFINED', $magicMethodObject, 'UNDEFINED', [], $methodType],\n        ]);\n\n        // add the same tests for the any type\n        foreach ($tests as $test) {\n            if ($anyType !== $test[5]) {\n                $test[5] = $anyType;\n                $tests[] = $test;\n            }\n        }\n\n        $methodAndPropObject = new TemplateMethodAndPropObject();\n\n        // additional method tests\n        $tests = array_merge($tests, [\n            [true, 'a', $methodAndPropObject, 'a', [], $anyType],\n            [true, 'a', $methodAndPropObject, 'a', [], $methodType],\n            [false, null, $methodAndPropObject, 'a', [], $arrayType],\n\n            [true, 'b_prop', $methodAndPropObject, 'b', [], $anyType],\n            [true, 'b', $methodAndPropObject, 'B', [], $anyType],\n            [true, 'b', $methodAndPropObject, 'b', [], $methodType],\n            [true, 'b', $methodAndPropObject, 'B', [], $methodType],\n            [false, null, $methodAndPropObject, 'b', [], $arrayType],\n\n            [false, null, $methodAndPropObject, 'c', [], $anyType],\n            [false, null, $methodAndPropObject, 'c', [], $methodType],\n            [false, null, $methodAndPropObject, 'c', [], $arrayType],\n        ]);\n\n        $arrayAccess = new TemplateArrayAccess();\n        $tests = array_merge($tests, [\n            [true, ['foo' => 'bar'], $arrayAccess, 'vars', [], $anyType],\n        ]);\n\n        // test for Closure::__invoke()\n        $tests[] = [true, 'closure called', static fn (): string => 'closure called', '__invoke', [], $anyType];\n        $tests[] = [true, 'closure called', static fn (): string => 'closure called', '__invoke', [], $methodType];\n\n        // tests when input is not an array or object\n        $tests = array_merge($tests, [\n            [false, null, 42, 'a', [], $anyType, 'Impossible to access an attribute (\"a\") on a int variable (\"42\") in \"index.twig\".'],\n            [false, null, 'string', 'a', [], $anyType, 'Impossible to access an attribute (\"a\") on a string variable (\"string\") in \"index.twig\".'],\n            [false, null, [], 'a', [], $anyType, 'Key \"a\" does not exist as the sequence/mapping is empty in \"index.twig\".'],\n        ]);\n\n        return $tests;\n    }\n\n    public function testGetIsMethods()\n    {\n        $twig = new Environment(new ArrayLoader());\n\n        $getIsObject = new TemplateGetIsMethods();\n        $template = new TemplateForTest($twig, 'index.twig');\n        // first time should not create a cache for \"get\"\n        $this->assertNull(CoreExtension::getAttribute($twig, $template->getSourceContext(), $getIsObject, 'get'));\n        // 0 should be in the method cache now, so this should fail\n        $this->assertNull(CoreExtension::getAttribute($twig, $template->getSourceContext(), $getIsObject, 0));\n    }\n}\n\nclass TemplateForTest extends Template\n{\n    private $name;\n\n    public function __construct(Environment $env, $name = 'index.twig')\n    {\n        parent::__construct($env);\n        $this->name = $name;\n    }\n\n    public function getZero()\n    {\n        return 0;\n    }\n\n    public function getEmpty()\n    {\n        return '';\n    }\n\n    public function getString()\n    {\n        return 'some_string';\n    }\n\n    public function getTrue()\n    {\n        return true;\n    }\n\n    public function getTemplateName(): string\n    {\n        return $this->name;\n    }\n\n    public function getDebugInfo(): array\n    {\n        return [];\n    }\n\n    public function getSourceContext(): Source\n    {\n        return new Source('', $this->getTemplateName());\n    }\n\n    protected function doGetParent(array $context): bool|string|Template|TemplateWrapper\n    {\n        return false;\n    }\n\n    protected function doDisplay(array $context, array $blocks = []): iterable\n    {\n    }\n\n    public function block_name($context, array $blocks = [])\n    {\n    }\n}\n\nclass TemplateArrayAccessObject implements \\ArrayAccess\n{\n    protected $protected = 'protected';\n\n    public $attributes = [\n        'defined' => 'defined',\n        'zero' => 0,\n        'null' => null,\n        '1' => 1,\n        'bar' => true,\n        'foo' => true,\n        'baz' => 'baz',\n        'baf' => 'baf',\n        '09' => '09',\n        '+4' => '+4',\n    ];\n\n    public function offsetExists($name): bool\n    {\n        return \\array_key_exists($name, $this->attributes);\n    }\n\n    #[\\ReturnTypeWillChange]\n    public function offsetGet($name)\n    {\n        return \\array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;\n    }\n\n    public function offsetSet($name, $value): void\n    {\n    }\n\n    public function offsetUnset($name): void\n    {\n    }\n}\n\nclass TemplateMagicPropertyObject\n{\n    public $defined = 'defined';\n\n    public $attributes = [\n        'zero' => 0,\n        'null' => null,\n        '1' => 1,\n        'bar' => true,\n        'foo' => true,\n        'baz' => 'baz',\n        'baf' => 'baf',\n        '09' => '09',\n        '+4' => '+4',\n    ];\n\n    protected $protected = 'protected';\n\n    public function __isset($name): bool\n    {\n        return \\array_key_exists($name, $this->attributes);\n    }\n\n    public function __get($name)\n    {\n        return \\array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;\n    }\n}\n\nclass TemplateMagicPropertyObjectWithException\n{\n    public function __isset($key): bool\n    {\n        throw new \\Exception('Hey! Don\\'t try to isset me!');\n    }\n}\n\nclass TemplatePropertyObject\n{\n    public $defined = 'defined';\n    public $zero = 0;\n    public $null;\n    public $bar = true;\n    public $foo = true;\n    public $baz = 'baz';\n    public $baf = 'baf';\n\n    protected $protected = 'protected';\n}\n\nclass TemplatePropertyObjectAndIterator extends TemplatePropertyObject implements \\IteratorAggregate\n{\n    public function getIterator(): \\Traversable\n    {\n        return new \\ArrayIterator(['foo', 'bar']);\n    }\n}\n\nclass TemplatePropertyObjectAndArrayAccess extends TemplatePropertyObject implements \\ArrayAccess\n{\n    private $data = [\n        'defined' => 'defined',\n        'zero' => 0,\n        'null' => null,\n        'bar' => true,\n        'foo' => true,\n        'baz' => 'baz',\n        'baf' => 'baf',\n    ];\n\n    public function offsetExists($offset): bool\n    {\n        return \\array_key_exists($offset, $this->data);\n    }\n\n    #[\\ReturnTypeWillChange]\n    public function offsetGet($offset)\n    {\n        return $this->offsetExists($offset) ? $this->data[$offset] : 'n/a';\n    }\n\n    public function offsetSet($offset, $value): void\n    {\n    }\n\n    public function offsetUnset($offset): void\n    {\n    }\n}\n\nclass TemplatePropertyObjectDefinedWithUndefinedValue\n{\n    public $foo;\n\n    public function __construct()\n    {\n        $this->foo = @$notExist;\n    }\n}\n\nclass TemplateMethodObject\n{\n    public function getDefined()\n    {\n        return 'defined';\n    }\n\n    public function get1()\n    {\n        return 1;\n    }\n\n    public function get09()\n    {\n        return '09';\n    }\n\n    public function getZero()\n    {\n        return 0;\n    }\n\n    public function getNull()\n    {\n    }\n\n    public function isBar()\n    {\n        return true;\n    }\n\n    public function hasFoo()\n    {\n        return true;\n    }\n\n    public function hasBaz()\n    {\n        return 'should never be returned (has)';\n    }\n\n    public function isBaz()\n    {\n        return 'should never be returned (is)';\n    }\n\n    public function getBaz()\n    {\n        return 'Baz';\n    }\n\n    public function baz()\n    {\n        return 'baz';\n    }\n\n    public function hasBaf()\n    {\n        return 'should never be returned (has)';\n    }\n\n    public function isBaf()\n    {\n        return 'baf';\n    }\n\n    protected function getProtected()\n    {\n        return 'protected';\n    }\n\n    public static function getStatic()\n    {\n        return 'static';\n    }\n}\n\nclass TemplateGetIsMethods\n{\n    public function get()\n    {\n    }\n\n    public function is()\n    {\n    }\n}\n\nclass TemplateMethodAndPropObject\n{\n    private $a = 'a_prop';\n\n    public function getA()\n    {\n        return 'a';\n    }\n\n    public $b = 'b_prop';\n\n    public function getB()\n    {\n        return 'b';\n    }\n\n    private $c = 'c_prop';\n\n    private function getC()\n    {\n        return 'c';\n    }\n}\n\nclass TemplateArrayAccess implements \\ArrayAccess\n{\n    public $vars = [\n        'foo' => 'bar',\n    ];\n    private $children = [];\n\n    public function offsetExists($offset): bool\n    {\n        return \\array_key_exists($offset, $this->children);\n    }\n\n    #[\\ReturnTypeWillChange]\n    public function offsetGet($offset)\n    {\n        return $this->children[$offset];\n    }\n\n    public function offsetSet($offset, $value): void\n    {\n        $this->children[$offset] = $value;\n    }\n\n    public function offsetUnset($offset): void\n    {\n        unset($this->children[$offset]);\n    }\n}\n\nclass TemplateMagicMethodObject\n{\n    public function __call($method, $arguments)\n    {\n        return '__call_'.$method;\n    }\n}\n\nclass TemplateMagicMethodExceptionObject\n{\n    public function __call($method, $arguments)\n    {\n        throw new \\BadMethodCallException(\\sprintf('Unknown method \"%s\".', $method));\n    }\n}\n"
  },
  {
    "path": "tests/TemplateWrapperTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\n\nclass TemplateWrapperTest extends TestCase\n{\n    public function testHasGetBlocks()\n    {\n        $twig = new Environment(new ArrayLoader([\n            'index' => '{% block foo %}{% endblock %}',\n            'index_with_use' => '{% use \"imported\" %}{% block foo %}{% endblock %}',\n            'index_with_extends' => '{% extends \"extended\" %}{% block foo %}{% endblock %}',\n            'imported' => '{% block imported %}{% endblock %}',\n            'extended' => '{% block extended %}{% endblock %}',\n        ]));\n\n        $wrapper = $twig->load('index');\n        $this->assertTrue($wrapper->hasBlock('foo'));\n        $this->assertFalse($wrapper->hasBlock('bar'));\n        $this->assertEquals(['foo'], $wrapper->getBlockNames());\n\n        $wrapper = $twig->load('index_with_use');\n        $this->assertTrue($wrapper->hasBlock('foo'));\n        $this->assertTrue($wrapper->hasBlock('imported'));\n        $this->assertEquals(['imported', 'foo'], $wrapper->getBlockNames());\n\n        $wrapper = $twig->load('index_with_extends');\n        $this->assertTrue($wrapper->hasBlock('foo'));\n        $this->assertTrue($wrapper->hasBlock('extended'));\n        $this->assertEquals(['foo', 'extended'], $wrapper->getBlockNames());\n    }\n\n    public function testRenderBlock()\n    {\n        $twig = new Environment(new ArrayLoader([\n            'index' => '{% block foo %}{{ foo }}{{ bar }}{% endblock %}',\n        ]));\n        $twig->addGlobal('bar', 'BAR');\n\n        $wrapper = $twig->load('index');\n        $this->assertEquals('FOOBAR', $wrapper->renderBlock('foo', ['foo' => 'FOO']));\n    }\n\n    public function testDisplayBlock()\n    {\n        $twig = new Environment(new ArrayLoader([\n            'index' => '{% block foo %}{{ foo }}{{ bar }}{% endblock %}',\n        ]));\n\n        $twig->addGlobal('bar', 'BAR');\n\n        $wrapper = $twig->load('index');\n\n        ob_start();\n        $wrapper->displayBlock('foo', ['foo' => 'FOO']);\n\n        $this->assertEquals('FOOBAR', ob_get_clean());\n    }\n}\n"
  },
  {
    "path": "tests/TokenParser/GuardTokenParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\TokenParser;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Parser;\nuse Twig\\Source;\n\nclass GuardTokenParserTest extends TestCase\n{\n    public function testUndefinedHandlers()\n    {\n        $this->expectNotToPerformAssertions();\n\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $env->registerUndefinedFunctionCallback(static fn ($name) => throw new SyntaxError('boom.'));\n        (new Parser($env))->parse($env->tokenize(new Source('{% guard function boom %}{% endguard %}', '')));\n    }\n}\n"
  },
  {
    "path": "tests/TokenParser/TypesTokenParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\TokenParser;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\Parser;\nuse Twig\\Source;\n\nclass TypesTokenParserTest extends TestCase\n{\n    /** @dataProvider getMappingTests */\n    public function testMappingParsing(string $template, array $expected): void\n    {\n        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);\n        $stream = $env->tokenize(new Source($template, ''));\n        $parser = new Parser($env);\n\n        $typesNode = $parser->parse($stream)->getNode('body')->getNode('0');\n\n        self::assertEquals($expected, $typesNode->getAttribute('mapping'));\n    }\n\n    public static function getMappingTests(): array\n    {\n        return [\n            // empty mapping\n            [\n                '{% types {} %}',\n                [],\n            ],\n\n            // simple\n            [\n                '{% types {foo: \"bar\"} %}',\n                [\n                    'foo' => ['type' => 'bar', 'optional' => false],\n                ],\n            ],\n\n            // trailing comma\n            [\n                '{% types {foo: \"bar\",} %}',\n                [\n                    'foo' => ['type' => 'bar', 'optional' => false],\n                ],\n            ],\n\n            // optional name\n            [\n                '{% types {foo?: \"bar\"} %}',\n                [\n                    'foo' => ['type' => 'bar', 'optional' => true],\n                ],\n            ],\n\n            // multiple pairs, duplicate values\n            [\n                '{% types {foo: \"foo\", bar?: \"foo\", baz: \"baz\"} %}',\n                [\n                    'foo' => ['type' => 'foo', 'optional' => false],\n                    'bar' => ['type' => 'foo', 'optional' => true],\n                    'baz' => ['type' => 'baz', 'optional' => false],\n                ],\n            ],\n\n            // without {} enclosing\n            [\n                '{% types foo: \"foo\", bar: \"bar\" %}',\n                [\n                    'foo' => ['type' => 'foo', 'optional' => false],\n                    'bar' => ['type' => 'bar', 'optional' => false],\n                ],\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/TokenStreamTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Source;\nuse Twig\\Token;\nuse Twig\\TokenStream;\n\nclass TokenStreamTest extends TestCase\n{\n    protected static $tokens;\n\n    protected function setUp(): void\n    {\n        self::$tokens = [\n            new Token(Token::TEXT_TYPE, 1, 1),\n            new Token(Token::TEXT_TYPE, 2, 1),\n            new Token(Token::TEXT_TYPE, 3, 1),\n            new Token(Token::TEXT_TYPE, 4, 1),\n            new Token(Token::TEXT_TYPE, 5, 1),\n            new Token(Token::TEXT_TYPE, 6, 1),\n            new Token(Token::TEXT_TYPE, 7, 1),\n            new Token(Token::EOF_TYPE, 0, 1),\n        ];\n    }\n\n    public function testNext()\n    {\n        $stream = new TokenStream(self::$tokens, new Source('', ''));\n        $repr = [];\n        while (!$stream->isEOF()) {\n            $token = $stream->next();\n\n            $repr[] = $token->getValue();\n        }\n        $this->assertEquals('1, 2, 3, 4, 5, 6, 7', implode(', ', $repr), '->next() advances the pointer and returns the current token');\n    }\n\n    public function testEndOfTemplateNext()\n    {\n        $stream = new TokenStream([\n            new Token(Token::BLOCK_START_TYPE, 1, 1),\n        ], new Source('', ''));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unexpected end of template');\n\n        while (!$stream->isEOF()) {\n            $stream->next();\n        }\n    }\n\n    public function testEndOfTemplateLook()\n    {\n        $stream = new TokenStream([\n            new Token(Token::BLOCK_START_TYPE, 1, 1),\n        ], new Source('', ''));\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unexpected end of template');\n\n        while (!$stream->isEOF()) {\n            $stream->look();\n            $stream->next();\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Util/CallableArgumentsExtractorTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Util;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Bridge\\PhpUnit\\ExpectDeprecationTrait;\nuse Twig\\Error\\SyntaxError;\nuse Twig\\Node\\EmptyNode;\nuse Twig\\Node\\Expression\\ConstantExpression;\nuse Twig\\Node\\Expression\\FunctionExpression;\nuse Twig\\Node\\Expression\\VariadicExpression;\nuse Twig\\Node\\Nodes;\nuse Twig\\Source;\nuse Twig\\TwigFunction;\nuse Twig\\Util\\CallableArgumentsExtractor;\n\nclass CallableArgumentsExtractorTest extends TestCase\n{\n    use ExpectDeprecationTrait;\n\n    public function testGetArguments()\n    {\n        $this->assertEquals(['U', null], $this->getArguments('date', 'date', ['format' => 'U', 'timestamp' => null]));\n    }\n\n    public function testGetArgumentsWhenPositionalArgumentsAfterNamedArguments()\n    {\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Positional arguments cannot be used after named arguments for function \"date\" in \"test.twig\" at line 2.');\n\n        $this->getArguments('date', 'date', ['timestamp' => 123456, 'Y-m-d']);\n    }\n\n    public function testGetArgumentsWhenArgumentIsDefinedTwice()\n    {\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Argument \"format\" is defined twice for function \"date\" in \"test.twig\" at line 2.');\n\n        $this->getArguments('date', 'date', ['Y-m-d', 'format' => 'U']);\n    }\n\n    public function testGetArgumentsWithWrongNamedArgumentName()\n    {\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown argument \"unknown\" for function \"date(format, timestamp)\".');\n\n        $this->getArguments('date', 'date', ['Y-m-d', 'timestamp' => null, 'unknown' => '']);\n    }\n\n    public function testGetArgumentsWithWrongNamedArgumentNames()\n    {\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Unknown arguments \"unknown1\", \"unknown2\" for function \"date(format, timestamp)\".');\n\n        $this->getArguments('date', 'date', ['Y-m-d', 'timestamp' => null, 'unknown1' => '', 'unknown2' => '']);\n    }\n\n    public function testResolveArgumentsWithMissingValueForOptionalArgument()\n    {\n        if (\\PHP_VERSION_ID >= 80000) {\n            $this->markTestSkipped('substr_compare() has a default value in 8.0, so the test does not work anymore, one should find another PHP built-in function for this test to work in PHP 8.');\n        }\n\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Argument \"case_sensitivity\" could not be assigned for function \"substr_compare(main_str, str, offset, length, case_sensitivity)\" because it is mapped to an internal PHP function which cannot determine default value for optional argument \"length\".');\n\n        $this->getArguments('substr_compare', 'substr_compare', ['abcd', 'bc', 'offset' => 1, 'case_sensitivity' => true]);\n    }\n\n    public function testResolveArgumentsOnlyNecessaryArgumentsForCustomFunction()\n    {\n        $this->assertEquals(['arg1'], $this->getArguments('custom_function', [$this, 'customFunction'], ['arg1' => 'arg1']));\n    }\n\n    public function testGetArgumentsForStaticMethod()\n    {\n        $this->assertEquals(['arg1'], $this->getArguments('custom_static_function', __CLASS__.'::customStaticFunction', ['arg1' => 'arg1']));\n    }\n\n    /**\n     * @dataProvider getGetArgumentsConversionData\n     */\n    public function testGetArgumentsConversion($arg1, $arg2)\n    {\n        $this->assertEquals([null], $this->getArguments('custom', eval(\"return fn (\\$$arg1) => '';\"), [$arg1 => null]));\n        $this->assertEquals([null], $this->getArguments('custom', eval(\"return fn (\\$$arg2) => '';\"), [$arg2 => null]));\n        $this->assertEquals([null], $this->getArguments('custom', eval(\"return fn (\\$$arg1) => '';\"), [$arg2 => null]));\n        $this->assertEquals([null], $this->getArguments('custom', eval(\"return fn (\\$$arg2) => '';\"), [$arg1 => null]));\n    }\n\n    public static function getGetArgumentsConversionData()\n    {\n        yield ['some_name', 'some_name'];\n        yield ['someName', 'some_name'];\n        yield ['no_svg', 'noSVG'];\n        yield ['error_404', 'error404'];\n        yield ['errCode_404', 'err_code_404'];\n        yield ['errCode404', 'err_code_404'];\n        yield ['aBc', 'a_b_c'];\n        yield ['aBC', 'a_b_c'];\n    }\n\n    /**\n     * @group legacy\n     */\n    public function testGetArgumentsConversionForVariadics()\n    {\n        $this->expectDeprecation('Since twig/twig 3.15: Using \"snake_case\" for variadic arguments is required for a smooth upgrade with Twig 4.0; rename \"someNumberVariadic\" to \"some_number_variadic\" in \"test.twig\" at line 2.');\n\n        $this->assertEquals([\n            new ConstantExpression('a', 0),\n            new ConstantExpression(12, 0),\n            new VariadicExpression([\n                new ConstantExpression('some_text_variadic', 2), new ConstantExpression('a', 0),\n                new ConstantExpression('some_number_variadic', 2), new ConstantExpression(12, 0),\n            ], 2),\n        ], $this->getArguments('custom', eval(\"return fn (string \\$someText, int \\$some_number, ...\\$args) => '';\"), ['some_text' => 'a', 'someNumber' => 12, 'some_text_variadic' => 'a', 'someNumberVariadic' => 12], true));\n    }\n\n    public function testGetArgumentsError()\n    {\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('Value for argument \"some_name\" is required for function \"custom_static_function\" in \"test.twig\" at line 2.');\n\n        $this->getArguments('custom_static_function', [$this, 'customFunctionSnakeCamel'], ['someCity' => 'Paris']);\n    }\n\n    public function testResolveArgumentsWithMissingParameterForArbitraryArguments()\n    {\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessage('The last parameter of \"Twig\\\\Tests\\\\Util\\\\CallableArgumentsExtractorTest::customFunctionWithArbitraryArguments\" for function \"foo\" must be an array with default value, eg. \"array $arg = []\".');\n\n        $this->getArguments('foo', [$this, 'customFunctionWithArbitraryArguments'], [], true);\n    }\n\n    public function testGetArgumentsWithInvalidCallable()\n    {\n        $this->expectException(\\LogicException::class);\n        $this->expectExceptionMessage('Callback for function \"foo\" is not callable in the current scope.');\n        $this->getArguments('foo', '<not-a-callable>', [], true);\n    }\n\n    public function testResolveArgumentsWithMissingParameterForArbitraryArgumentsOnFunction()\n    {\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessageMatches('#^The last parameter of \"Twig\\\\\\\\Tests\\\\\\\\Util\\\\\\\\custom_call_test_function\" for function \"foo\" must be an array with default value, eg\\\\. \"array \\\\$arg \\\\= \\\\[\\\\]\"\\\\.$#');\n\n        $this->getArguments('foo', 'Twig\\Tests\\Util\\custom_call_test_function', [], true);\n    }\n\n    public function testResolveArgumentsWithMissingParameterForArbitraryArgumentsOnObject()\n    {\n        $this->expectException(SyntaxError::class);\n        $this->expectExceptionMessageMatches('#^The last parameter of \"Twig\\\\\\\\Tests\\\\\\\\Util\\\\\\\\CallableTestClass\\\\:\\\\:__invoke\" for function \"foo\" must be an array with default value, eg\\\\. \"array \\\\$arg \\\\= \\\\[\\\\]\"\\\\.$#');\n\n        $this->getArguments('foo', new CallableTestClass(), [], true);\n    }\n\n    public static function customStaticFunction($arg1, $arg2 = 'default', $arg3 = [])\n    {\n    }\n\n    public function customFunction($arg1, $arg2 = 'default', $arg3 = [])\n    {\n    }\n\n    public function customFunctionSnakeCamel($someName, $some_city)\n    {\n    }\n\n    public function customFunctionWithArbitraryArguments()\n    {\n    }\n\n    private function getArguments(string $name, $callable, array $args, bool $isVariadic = false): array\n    {\n        $function = new TwigFunction($name, $callable, ['is_variadic' => $isVariadic]);\n        $node = new ExpressionCall($function, new EmptyNode(), 2);\n        $node->setSourceContext(new Source('', 'test.twig'));\n        foreach ($args as $name => $arg) {\n            $args[$name] = new ConstantExpression($arg, 0);\n        }\n\n        $arguments = (new CallableArgumentsExtractor($node, $function))->extractArguments(new Nodes($args));\n        foreach ($arguments as $name => $argument) {\n            $arguments[$name] = $isVariadic ? $argument : $argument->getAttribute('value');\n        }\n\n        return $arguments;\n    }\n}\n\nclass ExpressionCall extends FunctionExpression\n{\n}\n\nclass CallableTestClass\n{\n    public function __invoke($required)\n    {\n    }\n}\n\nfunction custom_call_test_function($required)\n{\n}\n"
  },
  {
    "path": "tests/Util/DeprecationCollectorTest.php",
    "content": "<?php\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Twig\\Tests\\Util;\n\n/*\n * This file is part of Twig.\n *\n * (c) Fabien Potencier\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nuse PHPUnit\\Framework\\TestCase;\nuse Twig\\DeprecatedCallableInfo;\nuse Twig\\Environment;\nuse Twig\\Loader\\ArrayLoader;\nuse Twig\\TwigFunction;\nuse Twig\\Util\\DeprecationCollector;\n\nclass DeprecationCollectorTest extends TestCase\n{\n    /**\n     * @requires PHP 5.3\n     */\n    public function testCollect()\n    {\n        $twig = new Environment(new ArrayLoader());\n        $twig->addFunction(new TwigFunction('deprec', [$this, 'deprec'], ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.1')]));\n\n        $collector = new DeprecationCollector($twig);\n        $deprecations = $collector->collect(new Iterator());\n\n        $this->assertEquals(['Since foo/bar 1.1: Twig Function \"deprec\" is deprecated in deprec.twig at line 1.'], $deprecations);\n    }\n\n    public function deprec()\n    {\n    }\n}\n\nclass Iterator implements \\IteratorAggregate\n{\n    public function getIterator(): \\Traversable\n    {\n        return new \\ArrayIterator([\n            'ok.twig' => '{{ foo }}',\n            'deprec.twig' => '{{ deprec(\"foo\") }}',\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/drupal_test.sh",
    "content": "#!/bin/bash\n\nset -x\nset -e\n\nREPO=`pwd`\ncd /tmp\nrm -rf drupal-twig-test\ncomposer create-project --no-interaction drupal/recommended-project:10.1.x-dev drupal-twig-test\ncd drupal-twig-test\n(cd vendor/twig && rm -rf twig && ln -sf $REPO twig)\ncomposer dump-autoload\nphp ./web/core/scripts/drupal install --no-interaction demo_umami > output\nperl -p -i -e 's/^([A-Za-z]+)\\: (.+)$/export DRUPAL_\\1=\\2/' output\nsource output\n#echo '$config[\"system.logging\"][\"error_level\"] = \"verbose\";' >> web/sites/default/settings.php\n\nwget https://get.symfony.com/cli/installer -O - | bash\nexport PATH=\"$HOME/.symfony5/bin:$PATH\"\nsymfony server:start -d --no-tls\n\ncurl -LsS -o blackfire-player.phar https://get.blackfire.io/blackfire-player-v1.31.0.phar\nchmod +x blackfire-player.phar\ncat > drupal-tests.bkf <<EOF\nname \"Drupal tests\"\n\nscenario\n    name \"homepage\"\n    set name \"admin\"\n    set pass \"pass\"\n\n    visit url('/')\n        expect status_code() == 200\n    click link('Articles')\n        expect status_code() == 200\n    click link('Dairy-free and delicious milk chocolate')\n        expect body() matches \"/Dairy\\-free milk chocolate is made in largely the same way as regular chocolate/\"\n        expect status_code() == 200\n    click link('Log in')\n        expect status_code() == 200\n    submit button(\"Log in\")\n        param name name\n        param pass pass\n        expect status_code() == 303\n    follow\n        expect status_code() == 200\n    click link('Structure')\n        expect status_code() == 200\nEOF\n./blackfire-player.phar run drupal-tests.bkf --endpoint=`symfony var:export SYMFONY_DEFAULT_ROUTE_URL` --variable name=$DRUPAL_Username --variable pass=$DRUPAL_Password\nsymfony server:stop\n"
  }
]