|
12 | 12 | */
|
13 | 13 |
|
14 | 14 | use PHPUnit\Framework\TestCase;
|
| 15 | +use Twig\Attribute\YieldReady; |
| 16 | +use Twig\Compiler; |
15 | 17 | use Twig\Environment;
|
16 | 18 | use Twig\Error\Error;
|
17 | 19 | use Twig\Error\RuntimeError;
|
18 | 20 | use Twig\Error\SyntaxError;
|
19 | 21 | use Twig\Loader\ArrayLoader;
|
20 | 22 | use Twig\Loader\FilesystemLoader;
|
| 23 | +use Twig\Loader\LoaderInterface; |
| 24 | +use Twig\Node\Node; |
21 | 25 | use Twig\Source;
|
| 26 | +use Twig\Token; |
| 27 | +use Twig\TokenParser\AbstractTokenParser; |
22 | 28 |
|
23 | 29 | class ErrorTest extends TestCase
|
24 | 30 | {
|
@@ -253,6 +259,112 @@ public function testTwigExceptionUpdateFileAndLineTogether()
|
253 | 259 | }
|
254 | 260 | }
|
255 | 261 |
|
| 262 | + /** |
| 263 | + * @dataProvider getErrorWithoutLineAndContextData |
| 264 | + */ |
| 265 | + public function testErrorWithoutLineAndContext(LoaderInterface $loader, bool $debug, bool $addDebugInfo, bool $exceptionWithLineAndContext, int $errorLine) |
| 266 | + { |
| 267 | + $twig = new Environment($loader, ['debug' => $debug, 'cache' => false]); |
| 268 | + $twig->removeCache('no_line_and_context_exception.twig'); |
| 269 | + $twig->removeCache('no_line_and_context_exception_include_line_5.twig'); |
| 270 | + $twig->removeCache('no_line_and_context_exception_include_line_1.twig'); |
| 271 | + $twig->addTokenParser(new class($addDebugInfo, $exceptionWithLineAndContext) extends AbstractTokenParser { |
| 272 | + public function __construct(private bool $addDebugInfo, private bool $exceptionWithLineAndContext) |
| 273 | + { |
| 274 | + } |
| 275 | + |
| 276 | + public function parse(Token $token) |
| 277 | + { |
| 278 | + $stream = $this->parser->getStream(); |
| 279 | + $lineno = $stream->getCurrent()->getLine(); |
| 280 | + $stream->expect(Token::BLOCK_END_TYPE); |
| 281 | + |
| 282 | + return new #[YieldReady]class($lineno, $this->addDebugInfo, $this->exceptionWithLineAndContext) extends Node |
| 283 | + { |
| 284 | + public function __construct(int $lineno, private bool $addDebugInfo, private bool $exceptionWithLineAndContext) |
| 285 | + { |
| 286 | + parent::__construct([], [], $lineno); |
| 287 | + } |
| 288 | + |
| 289 | + public function compile(Compiler $compiler): void |
| 290 | + { |
| 291 | + if ($this->addDebugInfo) { |
| 292 | + $compiler->addDebugInfo($this); |
| 293 | + } |
| 294 | + if ($this->exceptionWithLineAndContext) { |
| 295 | + $compiler |
| 296 | + ->write('throw new \Twig\Error\RuntimeError("Runtime error.", ') |
| 297 | + ->repr($this->lineno)->raw(", \$this->getSourceContext()") |
| 298 | + ->raw(");\n") |
| 299 | + ; |
| 300 | + } else { |
| 301 | + $compiler->write('throw new \Twig\Error\RuntimeError("Runtime error.");'); |
| 302 | + } |
| 303 | + } |
| 304 | + }; |
| 305 | + } |
| 306 | + |
| 307 | + public function getTag() |
| 308 | + { |
| 309 | + return 'foo'; |
| 310 | + } |
| 311 | + }); |
| 312 | + |
| 313 | + try { |
| 314 | +//echo $twig->compile($twig->parse($twig->tokenize(new \Twig\Source($twig->getLoader()->getSourceContext('no_line_and_context_exception_include_line_'.$errorLine.'.twig')->getCode(), 'no_line_and_context_exception.twig'))))."\n"; |
| 315 | + $twig->render('no_line_and_context_exception.twig', ['line' => $errorLine]); |
| 316 | + $this->fail(); |
| 317 | + } catch (RuntimeError $e) { |
| 318 | + $line = $addDebugInfo || $exceptionWithLineAndContext ? $errorLine : 1; |
| 319 | + |
| 320 | + if (1 === $errorLine && !$addDebugInfo && !$exceptionWithLineAndContext) { |
| 321 | + $this->assertSame(\sprintf('Runtime error in "no_line_and_context_exception_include_line_%d.twig".', $errorLine), $e->getMessage()); |
| 322 | + $this->assertSame(0, $e->getTemplateLine()); |
| 323 | + } else { |
| 324 | + $this->assertSame(\sprintf('Runtime error in "no_line_and_context_exception_include_line_%d.twig" at line %d.', $errorLine, $line), $e->getMessage()); |
| 325 | + $this->assertSame($line, $e->getTemplateLine()); |
| 326 | + } |
| 327 | + |
| 328 | + if ($loader instanceof FilesystemLoader) { |
| 329 | + $this->assertStringContainsString(\sprintf('errors/no_line_and_context_exception_include_line_%d.twig', $errorLine), $e->getFile()); |
| 330 | + $this->assertSame($addDebugInfo || $exceptionWithLineAndContext ? $errorLine : (1 === $errorLine ? -1 : 1), $e->getLine()); |
| 331 | + } else { |
| 332 | + $this->assertStringContainsString('Environment.php', $e->getFile()); |
| 333 | + $this->assertNotSame($line, $e->getLine()); |
| 334 | + } |
| 335 | + } |
| 336 | + } |
| 337 | + |
| 338 | + public static function getErrorWithoutLineAndContextData(): iterable |
| 339 | + { |
| 340 | + $fileLoaders = [ |
| 341 | + new ArrayLoader([ |
| 342 | + 'no_line_and_context_exception.twig' => "\n\n{{ include('no_line_and_context_exception_include_line_' ~ line ~ '.twig') }}", |
| 343 | + 'no_line_and_context_exception_include_line_5.twig' => "\n\n\n\n{% foo %}", |
| 344 | + 'no_line_and_context_exception_include_line_1.twig' => '{% foo %}', |
| 345 | + ]), |
| 346 | + new FilesystemLoader(__DIR__.'/Fixtures/errors'), |
| 347 | + ]; |
| 348 | + |
| 349 | + foreach ($fileLoaders as $loader) { |
| 350 | + foreach ([false, true] as $exceptionWithLineAndContext) { |
| 351 | + foreach ([false, true] as $addDebugInfo) { |
| 352 | + foreach ([false, true] as $debug) { |
| 353 | + foreach ([5, 1] as $line) { |
| 354 | + $name = ($loader instanceof FilesystemLoader ? 'filesystem' : 'array') |
| 355 | + .($debug ? '_with_debug' : '_without_debug') |
| 356 | + .($addDebugInfo ? '_with_debug_info' : '_without_debug_info') |
| 357 | + .($exceptionWithLineAndContext ? '_with_context' : '_without_context') |
| 358 | + .('_line_'.$line) |
| 359 | + ; |
| 360 | + yield $name => [$loader, $debug, $addDebugInfo, $exceptionWithLineAndContext, $line]; |
| 361 | + } |
| 362 | + } |
| 363 | + } |
| 364 | + } |
| 365 | + } |
| 366 | + } |
| 367 | + |
256 | 368 | public static function getErroredTemplates()
|
257 | 369 | {
|
258 | 370 | return [
|
|
0 commit comments