Skip to content

Commit 6950e55

Browse files
WIP
1 parent 5df5732 commit 6950e55

File tree

5 files changed

+107
-24
lines changed

5 files changed

+107
-24
lines changed

TwigCS/Tests/AbstractSniffTest.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ protected function checkGenericSniff(SniffInterface $sniff, array $expects): voi
4949
return;
5050
}
5151

52+
$fixedFile = __DIR__.'/Fixtures/'.$className.'.fixed.twig';
53+
if (file_exists($fixedFile)) {
54+
$fixer = new Fixer($ruleset, $tokenizer);
55+
$sniff->enableFixer($fixer);
56+
$fixer->fixFile($file);
57+
58+
$diff = $fixer->generateDiff($fixedFile);
59+
if ('' !== $diff) {
60+
self::fail($diff);
61+
}
62+
}
63+
5264
$messages = $report->getMessages();
5365
$messagePositions = [];
5466

@@ -60,23 +72,11 @@ protected function checkGenericSniff(SniffInterface $sniff, array $expects): voi
6072
if (null !== $line) {
6173
$errorMessage = sprintf('Line %s: %s', $line, $errorMessage);
6274
}
63-
$this->fail($errorMessage);
75+
self::fail($errorMessage);
6476
}
6577

6678
$messagePositions[] = [$message->getLine() => $message->getLinePosition()];
6779
}
68-
$this->assertEquals($expects, $messagePositions);
69-
70-
$fixedFile = __DIR__.'/Fixtures/'.$className.'.fixed.twig';
71-
if (file_exists($fixedFile)) {
72-
$fixer = new Fixer($ruleset, $tokenizer);
73-
$sniff->enableFixer($fixer);
74-
$fixer->fixFile($file);
75-
76-
$diff = $fixer->generateDiff($fixedFile);
77-
if ('' !== $diff) {
78-
$this->fail($diff);
79-
}
80-
}
80+
self::assertEquals($expects, $messagePositions);
8181
}
8282
}

TwigCS/Tests/Fixtures/OperatorSpacingTest.fixed.twig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ Untouch +-/*%==:
2929
{{ 1..10 }}
3030
{{ -5..-2 }}
3131
{{ [1, -2] }}
32+
{% if -2 == -3 or -2 == -3 %}{% endif %}
33+
{% if a - 2 == -4 %}{% endif %}
34+
{{ a in -2..-3 }}

TwigCS/Tests/Fixtures/OperatorSpacingTest.twig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ Untouch +-/*%==:
2929
{{ 1..10 }}
3030
{{ -5..-2 }}
3131
{{ [1, -2] }}
32+
{% if -2 == -3 or -2 == -3 %}{% endif %}
33+
{% if a - 2 == -4 %}{% endif %}
34+
{{ a in -2..-3 }}

TwigCS/src/Token/Tokenizer.php

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,13 @@ public function __construct(Environment $env, array $options = [])
103103

104104
$tokenizerHelper = new TokenizerHelper($env, $this->options);
105105
$this->regexes = [
106-
'lex_block' => $tokenizerHelper->getBlockRegex(),
107-
'lex_comment' => $tokenizerHelper->getCommentRegex(),
108-
'lex_variable' => $tokenizerHelper->getVariableRegex(),
109-
'operator' => $tokenizerHelper->getOperatorRegex(),
110-
'lex_tokens_start' => $tokenizerHelper->getTokensStartRegex(),
106+
'lex_block' => $tokenizerHelper->getBlockRegex(),
107+
'lex_comment' => $tokenizerHelper->getCommentRegex(),
108+
'lex_variable' => $tokenizerHelper->getVariableRegex(),
109+
'operator' => $tokenizerHelper->getOperatorRegex(),
110+
'lex_tokens_start' => $tokenizerHelper->getTokensStartRegex(),
111+
'interpolation_start' => $tokenizerHelper->getInterpolationStartRegex(),
112+
'interpolation_end' => $tokenizerHelper->getInterpolationEndRegex(),
111113
];
112114
}
113115

@@ -150,6 +152,12 @@ public function tokenize(Source $source): array
150152
$this->lexData();
151153
}
152154
break;
155+
case self::STATE_STRING:
156+
$this->lexString();
157+
break;
158+
case self::STATE_INTERPOLATION:
159+
$this->lexInterpolation();
160+
break;
153161
default:
154162
throw new Exception('Unhandled state in tokenize', 1);
155163
}
@@ -284,6 +292,10 @@ protected function lexExpression(): void
284292
$this->lexWhitespace();
285293
} elseif (PHP_EOL === $currentToken) {
286294
$this->lexEOL();
295+
} elseif ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) {
296+
// arrow functions
297+
$this->pushToken(Token::ARROW_TYPE, '=>');
298+
$this->moveCursor('=>');
287299
} elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
288300
$this->lexOperator($match[0]);
289301
} elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {
@@ -304,6 +316,10 @@ protected function lexExpression(): void
304316
// strings
305317
$this->pushToken(Token::STRING_TYPE, addcslashes(stripcslashes($match[0]), '\\'));
306318
$this->moveCursor($match[0]);
319+
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
320+
$this->bracketsAndTernary[] = ['"', $this->line];
321+
$this->pushState(self::STATE_STRING);
322+
$this->moveCursor($match[0]);
307323
} else {
308324
// unlexable
309325
throw new Exception(sprintf('Unexpected character "%s"', $currentToken));
@@ -368,6 +384,50 @@ protected function lexComment(): void
368384
}
369385
}
370386

387+
/**
388+
* @throws Exception
389+
*/
390+
protected function lexString(): void
391+
{
392+
if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {
393+
$this->bracketsAndTernary[] = [$this->options['interpolation'][0], $this->line];
394+
$this->pushToken(Token::INTERPOLATION_START_TYPE);
395+
$this->moveCursor($match[0]);
396+
$this->pushState(self::STATE_INTERPOLATION);
397+
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && strlen($match[0]) > 0) {
398+
$this->pushToken(Token::STRING_TYPE, stripcslashes($match[0]));
399+
$this->moveCursor($match[0]);
400+
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
401+
$bracket = array_pop($this->bracketsAndTernary);
402+
403+
if ('"' !== $this->code[$this->cursor]) {
404+
throw new Exception(sprintf('Unclosed "%s"', $bracket[0]));
405+
}
406+
407+
$this->popState();
408+
$this->moveCursor('"');
409+
} else {
410+
throw new Exception(sprintf('Unexpected character "%s"', $this->code[$this->cursor]));
411+
}
412+
}
413+
414+
/**
415+
* @throws Exception
416+
*/
417+
protected function lexInterpolation(): void
418+
{
419+
$bracket = end($this->bracketsAndTernary);
420+
if ($this->options['interpolation'][0] === $bracket[0]
421+
&& preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) {
422+
array_pop($this->bracketsAndTernary);
423+
$this->pushToken(Token::INTERPOLATION_END_TYPE);
424+
$this->moveCursor($match[0]);
425+
$this->popState();
426+
} else {
427+
$this->lexExpression();
428+
}
429+
}
430+
371431
/**
372432
* @param int $limit
373433
*/
@@ -515,12 +575,13 @@ protected function lexPunctuation(): void
515575
throw new Exception(sprintf('Unexpected "%s"', $currentToken));
516576
}
517577

518-
$expect = array_pop($this->bracketsAndTernary)[0];
519-
if ('?' === $expect) {
520-
throw new Exception('Unclosed ternary');
578+
$bracket = array_pop($this->bracketsAndTernary);
579+
if ('?' === $bracket[0]) {
580+
// Because {{ foo ? 'yes' }} is the same as {{ foo ? 'yes' : '' }}
581+
$bracket = array_pop($this->bracketsAndTernary);
521582
}
522-
if (strtr($expect, '([{', ')]}') !== $currentToken) {
523-
throw new Exception(sprintf('Unclosed "%s"', $expect));
583+
if (strtr($bracket[0], '([{', ')]}') !== $currentToken) {
584+
throw new Exception(sprintf('Unclosed "%s"', $bracket[0]));
524585
}
525586
}
526587

TwigCS/src/Token/TokenizerHelper.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ public function getTokensStartRegex(): string
7979
.'/s';
8080
}
8181

82+
/**
83+
* @return string
84+
*/
85+
public function getInterpolationStartRegex(): string
86+
{
87+
return '{'.preg_quote($this->options['interpolation'][0], '#').'\s*}A';
88+
}
89+
90+
/**
91+
* @return string
92+
*/
93+
public function getInterpolationEndRegex(): string
94+
{
95+
return '{\s*'.preg_quote($this->options['interpolation'][1], '#').'}A';
96+
}
97+
8298
/**
8399
* @return string
84100
*/

0 commit comments

Comments
 (0)