@@ -180,6 +180,11 @@ private function consumeAttributes(string $componentName): string
180
180
181
181
// <twig:component someProp> -> someProp: true
182
182
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
+
183
188
$ attributes [] = sprintf ('%s: true ' , $ key );
184
189
$ this ->consumeWhitespace ();
185
190
continue ;
@@ -188,24 +193,15 @@ private function consumeAttributes(string $componentName): string
188
193
$ this ->expectAndConsumeChar ('= ' );
189
194
$ quote = $ this ->consumeChar (["' " , '" ' ]);
190
195
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 );
200
199
} else {
201
200
$ 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
- }
208
201
}
202
+
203
+ $ attributes [] = sprintf ('%s: %s ' , $ key , $ attributeValue );
204
+
209
205
$ this ->expectAndConsumeChar ($ quote );
210
206
$ this ->consumeWhitespace ();
211
207
}
@@ -246,6 +242,12 @@ private function consumeChar($validChars = null): string
246
242
return $ char ;
247
243
}
248
244
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
+ */
249
251
private function consumeUntil (string $ endString ): string
250
252
{
251
253
$ start = $ this ->position ;
@@ -284,9 +286,14 @@ private function expectAndConsumeChar(string $char): void
284
286
throw new \InvalidArgumentException ('Expected a single character ' );
285
287
}
286
288
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 ) {
288
294
throw new SyntaxError ("Expected ' {$ char }' but found ' {$ this ->input [$ this ->position ]}'. " , $ this ->line );
289
295
}
296
+
290
297
++$ this ->position ;
291
298
}
292
299
@@ -370,39 +377,43 @@ private function consumeUntilEndBlock(): string
370
377
371
378
private function consumeAttributeValue (string $ quote ): string
372
379
{
373
- $ attributeValue = '' ;
380
+ $ parts = [];
381
+ $ currentPart = '' ;
374
382
while ($ this ->position < $ this ->length ) {
375
- if (substr ( $ this ->input , $ this -> position , 1 ) === $ quote ) {
383
+ if ($ this ->check ( $ quote) ) {
376
384
break ;
377
385
}
378
386
379
387
if ("\n" === $ this ->input [$ this ->position ]) {
380
388
++$ this ->line ;
381
389
}
382
390
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
+ }
389
397
390
- if ( ' {{ ' === substr ( $ this -> input , $ this -> position , 2 )) {
398
+ // consume the entire {{ }} block
391
399
$ this ->consume ('{{ ' );
392
- $ attributeValue .= "'~( " ;
393
400
$ this ->consumeWhitespace ();
394
- $ value = rtrim ($ this ->consumeUntil ('}} ' ));
401
+ $ parts [] = sprintf ( ' (%s) ' , rtrim ($ this ->consumeUntil ('}} ' ) ));
395
402
$ this ->expectAndConsumeChar ('} ' );
396
403
$ this ->expectAndConsumeChar ('} ' );
397
- $ attributeValue .= $ value ;
398
- $ attributeValue .= " )~' " ;
404
+
405
+ continue ;
399
406
}
400
407
401
- $ attributeValue .= $ this ->input [$ this ->position ];
408
+ $ currentPart .= $ this ->input [$ this ->position ];
402
409
++$ this ->position ;
403
410
}
404
411
405
- return $ attributeValue ;
412
+ if ('' !== $ currentPart ) {
413
+ $ parts [] = sprintf ("'%s' " , str_replace ("' " , "\' " , $ currentPart ));
414
+ }
415
+
416
+ return implode ('~ ' , $ parts );
406
417
}
407
418
408
419
private function doesStringEventuallyExist (string $ needle ): bool
0 commit comments