Skip to content

Commit 4570340

Browse files
🐛 Bugfix
1 parent 0740604 commit 4570340

10 files changed

+225
-202
lines changed

TwigCS/Environment/StubbedEnvironment.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
namespace TwigCS\Environment;
44

5+
use Symfony\Bridge\Twig\TokenParser\DumpTokenParser;
6+
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
7+
use Symfony\Bridge\Twig\TokenParser\StopwatchTokenParser;
8+
use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser;
9+
use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser;
10+
use Symfony\Bridge\Twig\TokenParser\TransTokenParser;
511
use Twig\Environment;
612
use Twig\Loader\LoaderInterface;
713
use Twig\TwigFilter;
@@ -43,6 +49,13 @@ public function __construct(LoaderInterface $loader = null, $options = [])
4349
{
4450
parent::__construct($loader, $options);
4551

52+
$this->addTokenParser(new DumpTokenParser());
53+
$this->addTokenParser(new FormThemeTokenParser());
54+
$this->addTokenParser(new StopwatchTokenParser(false));
55+
$this->addTokenParser(new TransChoiceTokenParser());
56+
$this->addTokenParser(new TransDefaultDomainTokenParser());
57+
$this->addTokenParser(new TransTokenParser());
58+
4659
$this->stubCallable = function () {
4760
/* This will be used as stub filter, function or test */
4861
};

TwigCS/Linter.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use TwigCS\Sniff\PostParserSniffInterface;
1313
use TwigCS\Sniff\PreParserSniffInterface;
1414
use TwigCS\Sniff\SniffInterface;
15-
use TwigCS\Token\TokenizerInterface;
15+
use TwigCS\Token\Tokenizer;
1616

1717
/**
1818
* Linter is the main class and will process twig files against a set of rules.
@@ -30,25 +30,26 @@ class Linter
3030
protected $sniffsExtension;
3131

3232
/**
33-
* @var TokenizerInterface
33+
* @var Tokenizer
3434
*/
3535
protected $tokenizer;
3636

3737
/**
38-
* @param Environment $env
39-
* @param TokenizerInterface $tokenizer
38+
* @param Environment $env
39+
* @param Tokenizer $tokenizer
4040
*/
41-
public function __construct(Environment $env, TokenizerInterface $tokenizer)
41+
public function __construct(Environment $env, Tokenizer $tokenizer)
4242
{
4343
$this->env = $env;
44+
4445
$this->sniffsExtension = $this->env->getExtension('TwigCS\Extension\SniffsExtension');
4546
$this->tokenizer = $tokenizer;
4647
}
4748

4849
/**
4950
* Run the linter on the given $files against the given $ruleset.
5051
*
51-
* @param array|string $files List of files to process.
52+
* @param array|string $files List of files to process.theme
5253
* @param Ruleset $ruleset Set of rules to check.
5354
*
5455
* @return Report an object with all violations and stats.
@@ -70,8 +71,8 @@ public function run($files, Ruleset $ruleset)
7071
$sniffViolation = new SniffViolation(
7172
SniffInterface::MESSAGE_TYPE_NOTICE,
7273
$msg,
73-
'',
74-
''
74+
null,
75+
null
7576
);
7677

7778
$report->addMessage($sniffViolation);
@@ -142,8 +143,8 @@ public function processTemplate($file, $ruleset, $report)
142143
} catch (\Exception $e) {
143144
$sniffViolation = new SniffViolation(
144145
SniffInterface::MESSAGE_TYPE_ERROR,
145-
sprintf('Unable to tokenize file "%s"', (string) $file),
146-
'',
146+
sprintf('Unable to tokenize file'),
147+
null,
147148
(string) $file
148149
);
149150

TwigCS/Report/TextFormatter.php

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,33 +46,40 @@ public function display(Report $report, $level = null)
4646
'level' => $level,
4747
]);
4848

49-
$this->io->text((count($fileMessages) > 0 ? '<fg=red>KO</fg=red>' : '<info>OK</info>').' '.$file);
49+
if (count($fileMessages) > 0) {
50+
$this->io->text('<fg=red>KO</fg=red> '.$file);
51+
}
5052

5153
$rows = [];
5254
foreach ($fileMessages as $message) {
5355
$lines = $this->getContext(file_get_contents($file), $message->getLine(), $this::ERROR_CONTEXT_LIMIT);
5456

5557
$formattedText = [];
58+
if (!$message->getLine()) {
59+
$formattedText[] = $this->formatErrorMessage($message);
60+
}
61+
5662
foreach ($lines as $no => $code) {
5763
$formattedText[] = sprintf($this::ERROR_LINE_FORMAT, $no, wordwrap($code, $this::ERROR_LINE_WIDTH));
5864

5965
if ($no === $message->getLine()) {
60-
$formattedText[] = sprintf(
61-
'<fg=red>'.$this::ERROR_LINE_FORMAT.'</fg=red>',
62-
$this::ERROR_CURSOR_CHAR,
63-
wordwrap($message->getMessage(), $this::ERROR_LINE_WIDTH)
64-
);
66+
$formattedText[] = $this->formatErrorMessage($message);
6567
}
6668
}
6769

70+
if (count($rows) > 0) {
71+
$rows[] = new TableSeparator();
72+
}
73+
6874
$rows[] = [
69-
new TableCell('<comment>'.$message->getLevelAsString().'</comment>', ['rowspan' => 2]),
75+
new TableCell('<comment>'.$message->getLevelAsString().'</comment>'),
7076
implode("\n", $formattedText),
7177
];
72-
$rows[] = new TableSeparator();
7378
}
7479

75-
$this->io->table([], $rows);
80+
if (count($rows) > 0) {
81+
$this->io->table([], $rows);
82+
}
7683
}
7784

7885
$summaryString = sprintf(
@@ -131,4 +138,18 @@ protected function getContext($template, $line, $context)
131138

132139
return $result;
133140
}
141+
142+
/**
143+
* @param SniffViolation $message
144+
*
145+
* @return string
146+
*/
147+
protected function formatErrorMessage(SniffViolation $message)
148+
{
149+
return sprintf(
150+
'<fg=red>'.$this::ERROR_LINE_FORMAT.'</fg=red>',
151+
$this::ERROR_CURSOR_CHAR,
152+
wordwrap($message->getMessage(), $this::ERROR_LINE_WIDTH)
153+
);
154+
}
134155
}

TwigCS/Sniff/AbstractPostParserSniff.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function addMessage($messageType, $message, Node $node)
5656
/**
5757
* @param Node $node
5858
*
59-
* @return string
59+
* @return int|null
6060
*/
6161
public function getTemplateLine(Node $node)
6262
{
@@ -68,7 +68,7 @@ public function getTemplateLine(Node $node)
6868
return $node->getLine();
6969
}
7070

71-
return '';
71+
return null;
7272
}
7373

7474
/**

TwigCS/Tests/AbstractSniffTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function setUp()
3434
$this->env = new StubbedEnvironment(
3535
$twigLoaderInterface,
3636
[
37-
'stub_tags' => ['dump', 'render', 'some_other_block', 'stylesheets', 'trans'],
37+
'stub_tags' => ['render', 'some_other_block', 'stylesheets'],
3838
'stub_tests' => ['some_test'],
3939
]
4040
);

TwigCS/Token/Tokenizer.php

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* An override of Twig's Lexer to add whitespace and new line detection.
1111
*/
12-
class Tokenizer implements TokenizerInterface
12+
class Tokenizer
1313
{
1414
const STATE_DATA = Lexer::STATE_DATA;
1515
const STATE_BLOCK = Lexer::STATE_BLOCK;
@@ -92,27 +92,20 @@ class Tokenizer implements TokenizerInterface
9292
public function __construct(Environment $env, array $options = [])
9393
{
9494
$this->options = array_merge([
95-
'tag_comment' => ['{#', '#}'],
96-
'tag_block' => ['{%', '%}'],
97-
'tag_variable' => ['{{', '}}'],
98-
'whitespace_trim' => '-',
99-
'whitespace_line_trim' => '~',
100-
'whitespace_line_chars' => ' \t\0\x0B',
101-
'interpolation' => ['#{', '}'],
95+
'tag_comment' => ['{#', '#}'],
96+
'tag_block' => ['{%', '%}'],
97+
'tag_variable' => ['{{', '}}'],
98+
'whitespace_trim' => '-',
99+
'interpolation' => ['#{', '}'],
102100
], $options);
103101

104102
$tokenizerHelper = new TokenizerHelper($env, $this->options);
105103
$this->regexes = [
106-
'lex_var' => $tokenizerHelper->getVarRegex(),
107-
'lex_block' => $tokenizerHelper->getBlockRegex(),
108-
'lex_raw_data' => $tokenizerHelper->getRawDataRegex(),
109-
'operator' => $tokenizerHelper->getOperatorRegex(),
110-
'lex_comment' => $tokenizerHelper->getCommentRegex(),
111-
'lex_block_raw' => $tokenizerHelper->getBlockRawRegex(),
112-
'lex_block_line' => $tokenizerHelper->getBlockLineRegex(),
113-
'lex_tokens_start' => $tokenizerHelper->getTokensStartRegex(),
114-
'interpolation_start' => $tokenizerHelper->getInterpolationStartRegex(),
115-
'interpolation_end' => $tokenizerHelper->getInterpolationEndRegex(),
104+
'lex_block' => $tokenizerHelper->getBlockRegex(),
105+
'lex_comment' => $tokenizerHelper->getCommentRegex(),
106+
'lex_variable' => $tokenizerHelper->getVariableRegex(),
107+
'operator' => $tokenizerHelper->getOperatorRegex(),
108+
'lex_tokens_start' => $tokenizerHelper->getTokensStartRegex(),
116109
];
117110
}
118111

@@ -127,7 +120,14 @@ public function tokenize(Source $source)
127120
$this->preflightSource($this->code);
128121

129122
while ($this->cursor < $this->end) {
130-
$nextToken = $this->getTokenPosition();
123+
$lastToken = $this->getTokenPosition();
124+
$nextToken = $this->getTokenPosition(1);
125+
126+
while (null !== $nextToken && $nextToken['position'] < $this->cursor) {
127+
$this->moveCurrentPosition();
128+
$lastToken = $nextToken;
129+
$nextToken = $this->getTokenPosition(1);
130+
}
131131

132132
switch ($this->getState()) {
133133
case self::STATE_BLOCK:
@@ -139,14 +139,8 @@ public function tokenize(Source $source)
139139
case self::STATE_COMMENT:
140140
$this->lexComment();
141141
break;
142-
// case self::STATE_STRING:
143-
// $this->lexString();
144-
// break;
145-
// case self::STATE_INTERPOLATION:
146-
// $this->lexInterpolation();
147-
// break;
148142
case self::STATE_DATA:
149-
if ($this->cursor === $nextToken['position']) {
143+
if (null !== $lastToken && $this->cursor === $lastToken['position']) {
150144
$this->lexStart();
151145
} else {
152146
$this->lexData();
@@ -230,15 +224,19 @@ protected function preflightSource($code)
230224
}
231225

232226
/**
227+
* @param int $offset
228+
*
233229
* @return array|null
234230
*/
235-
protected function getTokenPosition()
231+
protected function getTokenPosition($offset = 0)
236232
{
237-
if (empty($this->tokenPositions) || !isset($this->tokenPositions[$this->currentPosition])) {
233+
if (empty($this->tokenPositions)
234+
|| !isset($this->tokenPositions[$this->currentPosition + $offset])
235+
) {
238236
return null;
239237
}
240238

241-
return $this->tokenPositions[$this->currentPosition];
239+
return $this->tokenPositions[$this->currentPosition + $offset];
242240
}
243241

244242
/**
@@ -277,23 +275,20 @@ protected function pushToken($type, $value = null)
277275
protected function lex($endType, $endRegex)
278276
{
279277
preg_match($endRegex, $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor);
278+
280279
if (!isset($match[0])) {
281-
// Should not happen, but in case it is;
282-
throw new \Exception(sprintf('Unclosed "%s" in "%s" at line %d', $endType, $this->filename, $this->line));
283-
}
284-
if ($match[0][1] === $this->cursor) {
280+
$this->lexExpression();
281+
} elseif ($match[0][1] === $this->cursor) {
285282
$this->pushToken($endType, $match[0][0]);
286283
$this->moveCursor($match[0][0]);
287284
$this->moveCurrentPosition();
288285
$this->popState();
286+
} elseif ($this->getState() === self::STATE_COMMENT) {
287+
// Parse as text until the end position.
288+
$this->lexData($match[0][1]);
289289
} else {
290-
if ($this->getState() === self::STATE_COMMENT) {
291-
// Parse as text until the end position.
292-
$this->lexData($match[0][1]);
293-
} else {
294-
while ($this->cursor < $match[0][1]) {
295-
$this->lexExpression();
296-
}
290+
while ($this->cursor < $match[0][1]) {
291+
$this->lexExpression();
297292
}
298293
}
299294
}
@@ -304,7 +299,9 @@ protected function lex($endType, $endRegex)
304299
protected function lexExpression()
305300
{
306301
$currentToken = $this->code[$this->cursor];
307-
if (' ' === $currentToken) {
302+
if (preg_match('/\t/', $currentToken)) {
303+
$this->lexTab();
304+
} elseif (' ' === $currentToken) {
308305
$this->lexWhitespace();
309306
} elseif (PHP_EOL === $currentToken) {
310307
$this->lexEOL();
@@ -332,11 +329,11 @@ protected function lexExpression()
332329
} elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
333330
// closing bracket
334331
if (empty($this->brackets)) {
335-
throw new \Exception(sprintf('Unexpected "%s".', $this->code[$this->cursor]));
332+
throw new \Exception(sprintf('Unexpected "%s"', $this->code[$this->cursor]));
336333
}
337334
$expect = array_pop($this->brackets)[0];
338335
if (strtr($expect, '([{', ')]}') !== $this->code[$this->cursor]) {
339-
throw new \Exception(sprintf('Unclosed "%s".', $expect));
336+
throw new \Exception(sprintf('Unclosed "%s"', $expect));
340337
}
341338
}
342339
$this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
@@ -347,7 +344,7 @@ protected function lexExpression()
347344
$this->moveCursor($match[0]);
348345
} else {
349346
// unlexable
350-
throw new \Exception(sprintf('Unexpected character "%s".', $this->code[$this->cursor]));
347+
throw new \Exception(sprintf('Unexpected character "%s"', $currentToken));
351348
}
352349
}
353350

@@ -384,6 +381,7 @@ protected function lexData($limit = 0)
384381
if (0 === $limit && null !== $nextToken) {
385382
$limit = $nextToken['position'];
386383
}
384+
387385
$currentToken = $this->code[$this->cursor];
388386
if (preg_match('/\t/', $currentToken)) {
389387
$this->lexTab();

0 commit comments

Comments
 (0)