diff --git a/src/View/Antlers/Language/Errors/AntlersErrorCodes.php b/src/View/Antlers/Language/Errors/AntlersErrorCodes.php index 77b41a6b8c0..ea92b3e33e0 100644 --- a/src/View/Antlers/Language/Errors/AntlersErrorCodes.php +++ b/src/View/Antlers/Language/Errors/AntlersErrorCodes.php @@ -133,4 +133,5 @@ class AntlersErrorCodes const TYPE_UNEXPECTED_EOI_PARSING_SHORTHAND_PARAMETER = 'ANTLR_133'; const TYPE_DIRECTIVE_MISSING_ARGUMENTS = 'ANTLR_134'; const RUNTIME_METHOD_CALL_USER_CONTENT = 'ANTLR_135'; + const TYPE_MIXED_MODIFIER_STYLES = 'ANTLR_136'; } diff --git a/src/View/Antlers/Language/Parser/AntlersNodeParser.php b/src/View/Antlers/Language/Parser/AntlersNodeParser.php index 3addbb5cdf4..f3cbe529130 100644 --- a/src/View/Antlers/Language/Parser/AntlersNodeParser.php +++ b/src/View/Antlers/Language/Parser/AntlersNodeParser.php @@ -15,6 +15,7 @@ use Statamic\View\Antlers\Language\Nodes\DirectiveNode; use Statamic\View\Antlers\Language\Nodes\Parameters\ParameterNode; use Statamic\View\Antlers\Language\Nodes\RecursiveNode; +use Statamic\View\Antlers\Language\Nodes\Structures\ModifierSeparator; use Statamic\View\Antlers\Language\Nodes\TagIdentifier; use Statamic\View\Antlers\Language\Runtime\Sandbox\TypeCoercion; use Statamic\View\Antlers\Language\Utilities\StringUtilities; @@ -272,6 +273,18 @@ public function parseNode(AntlersNode $node) $node->runtimeNodes = $runtimeNodes; + if ($node->hasParameters) { + foreach ($node->runtimeNodes as $runtimeNode) { + if ($runtimeNode instanceof ModifierSeparator) { + throw ErrorFactory::makeSyntaxError( + AntlersErrorCodes::TYPE_MIXED_MODIFIER_STYLES, + $node, + 'Cannot mix pipe and shorthand parameter modifier styles on the same expression. Use either "| modifier(\'value\')" or "modifier=\'value\'", but not both.' + ); + } + } + } + $trimmedInner = trim($node->content); if (ConditionPairAnalyzer::isConditionalStructure($node) && $node->name->name != 'else' && count($runtimeNodes) == 0 diff --git a/tests/Antlers/Parser/ParserErrorsTest.php b/tests/Antlers/Parser/ParserErrorsTest.php index ff79c841007..4040ff57d09 100644 --- a/tests/Antlers/Parser/ParserErrorsTest.php +++ b/tests/Antlers/Parser/ParserErrorsTest.php @@ -214,4 +214,19 @@ public function test_incomplete_shorthand_parameters_throws_error_two() { $this->assertThrowsParserError('{{ tag_name :$}}'); } + + public function test_mixed_pipe_and_shorthand_modifier_throws_exception() + { + $this->assertThrowsParserError('{{ active_platforms | sort="value" }}'); + } + + public function test_mixed_pipe_and_shorthand_modifier_paired_throws_exception() + { + $this->assertThrowsParserError('{{ active_platforms | sort="value" }}items{{ /active_platforms }}'); + } + + public function test_mixed_pipe_and_shorthand_with_registered_modifier_throws_exception() + { + $this->assertThrowsParserError('{{ thing | upper="value" }}'); + } } diff --git a/tests/Antlers/Runtime/CoreModifiersTest.php b/tests/Antlers/Runtime/CoreModifiersTest.php index 64293910821..098ec5c67e2 100644 --- a/tests/Antlers/Runtime/CoreModifiersTest.php +++ b/tests/Antlers/Runtime/CoreModifiersTest.php @@ -586,6 +586,20 @@ public function test_rawurlencode_except_slashes() { $this->assertSame('please%20and%20thank%20you/Mommy', $this->resultOf('{{ test_url_encode | rawurlencode_except_slashes }}')); } + + public function test_pipe_function_modifier_still_renders() + { + $data = ['items' => [['value' => 'Zebra'], ['value' => 'Apple'], ['value' => 'Mango']]]; + $result = $this->renderString('{{ items | sort("value") }}{{ value }}{{ /items }}', $data, true); + $this->assertSame('applemangozebra', strtolower($result)); + } + + public function test_shorthand_parameter_modifier_still_renders() + { + $data = ['items' => [['value' => 'Zebra'], ['value' => 'Apple'], ['value' => 'Mango']]]; + $result = $this->renderString('{{ items sort="value" }}{{ value }}{{ /items }}', $data, true); + $this->assertSame('applemangozebra', strtolower($result)); + } } class SimpleEntryObject implements Arrayable