@@ -180,6 +180,11 @@ private function consumeAttributes(string $componentName): string
180180
181181 // <twig:component someProp> -> someProp: true
182182 if (!$ this ->check ('= ' )) {
183+ // don't allow "<twig:component :someProp>"
184+ if ($ isAttributeDynamic ) {
185+ throw new SyntaxError (sprintf ('Expected "=" after ":%s" when parsing the "<twig:%s" syntax. ' , $ key , $ componentName ), $ this ->line );
186+ }
187+
183188 $ attributes [] = sprintf ('%s: true ' , $ key );
184189 $ this ->consumeWhitespace ();
185190 continue ;
@@ -188,24 +193,15 @@ private function consumeAttributes(string $componentName): string
188193 $ this ->expectAndConsumeChar ('= ' );
189194 $ quote = $ this ->consumeChar (["' " , '" ' ]);
190195
191- // someProp="{{ dynamicVar }}"
192- if ($ this ->consume ('{{ ' )) {
193- $ this ->consumeWhitespace ();
194- $ attributeValue = rtrim ($ this ->consumeUntil ('} ' ));
195- $ this ->expectAndConsumeChar ('} ' );
196- $ this ->expectAndConsumeChar ('} ' );
197- $ this ->consumeUntil ($ quote );
198-
199- $ attributes [] = sprintf ('%s: %s ' , $ key , $ attributeValue );
196+ if ($ isAttributeDynamic ) {
197+ // :someProp="dynamicVar"
198+ $ attributeValue = $ this ->consumeUntil ($ quote );
200199 } else {
201200 $ attributeValue = $ this ->consumeAttributeValue ($ quote );
202-
203- if ($ isAttributeDynamic ) {
204- $ attributes [] = sprintf ('%s: %s ' , $ key , $ attributeValue );
205- } else {
206- $ attributes [] = sprintf ("%s: '%s' " , $ key , $ attributeValue );
207- }
208201 }
202+
203+ $ attributes [] = sprintf ('%s: %s ' , $ key , $ attributeValue );
204+
209205 $ this ->expectAndConsumeChar ($ quote );
210206 $ this ->consumeWhitespace ();
211207 }
@@ -246,6 +242,12 @@ private function consumeChar($validChars = null): string
246242 return $ char ;
247243 }
248244
245+ /**
246+ * Moves the position forward until it finds $endString.
247+ *
248+ * Any string consumed *before* finding that string is returned.
249+ * The position is moved forward to just *before* $endString.
250+ */
249251 private function consumeUntil (string $ endString ): string
250252 {
251253 $ start = $ this ->position ;
@@ -284,9 +286,14 @@ private function expectAndConsumeChar(string $char): void
284286 throw new \InvalidArgumentException ('Expected a single character ' );
285287 }
286288
287- if ($ this ->position >= $ this ->length || $ this ->input [$ this ->position ] !== $ char ) {
289+ if ($ this ->position >= $ this ->length ) {
290+ throw new SyntaxError ("Expected ' {$ char }' but reached the end of the file. " , $ this ->line );
291+ }
292+
293+ if ($ this ->input [$ this ->position ] !== $ char ) {
288294 throw new SyntaxError ("Expected ' {$ char }' but found ' {$ this ->input [$ this ->position ]}'. " , $ this ->line );
289295 }
296+
290297 ++$ this ->position ;
291298 }
292299
@@ -370,39 +377,43 @@ private function consumeUntilEndBlock(): string
370377
371378 private function consumeAttributeValue (string $ quote ): string
372379 {
373- $ attributeValue = '' ;
380+ $ parts = [];
381+ $ currentPart = '' ;
374382 while ($ this ->position < $ this ->length ) {
375- if (substr ( $ this ->input , $ this -> position , 1 ) === $ quote ) {
383+ if ($ this ->check ( $ quote) ) {
376384 break ;
377385 }
378386
379387 if ("\n" === $ this ->input [$ this ->position ]) {
380388 ++$ this ->line ;
381389 }
382390
383- if ('\'' === $ this ->input [ $ this -> position ] ) {
384- $ attributeValue .= " \' " ;
385- ++ $ this -> position ;
386-
387- continue ;
388- }
391+ if ($ this ->check ( ' {{ ' ) ) {
392+ // mark any previous static text as complete: push into parts
393+ if ( '' !== $ currentPart ) {
394+ $ parts [] = sprintf ( " '%s' " , str_replace ( " ' " , " \' " , $ currentPart ));
395+ $ currentPart = '' ;
396+ }
389397
390- if ( ' {{ ' === substr ( $ this -> input , $ this -> position , 2 )) {
398+ // consume the entire {{ }} block
391399 $ this ->consume ('{{ ' );
392- $ attributeValue .= "'~( " ;
393400 $ this ->consumeWhitespace ();
394- $ value = rtrim ($ this ->consumeUntil ('}} ' ));
401+ $ parts [] = sprintf ( ' (%s) ' , rtrim ($ this ->consumeUntil ('}} ' ) ));
395402 $ this ->expectAndConsumeChar ('} ' );
396403 $ this ->expectAndConsumeChar ('} ' );
397- $ attributeValue .= $ value ;
398- $ attributeValue .= " )~' " ;
404+
405+ continue ;
399406 }
400407
401- $ attributeValue .= $ this ->input [$ this ->position ];
408+ $ currentPart .= $ this ->input [$ this ->position ];
402409 ++$ this ->position ;
403410 }
404411
405- return $ attributeValue ;
412+ if ('' !== $ currentPart ) {
413+ $ parts [] = sprintf ("'%s' " , str_replace ("' " , "\' " , $ currentPart ));
414+ }
415+
416+ return implode ('~ ' , $ parts );
406417 }
407418
408419 private function doesStringEventuallyExist (string $ needle ): bool
0 commit comments