Skip to content

Commit b2ad21c

Browse files
authored
Ensure all subclasses of AbstractTag always call the constructor (#230)
1 parent 1b74ad6 commit b2ad21c

File tree

8 files changed

+118
-2
lines changed

8 files changed

+118
-2
lines changed

.github/workflows/mt.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ jobs:
4242
- name: Install dependencies
4343
run: |
4444
composer update --prefer-dist --no-interaction --no-progress ${{ matrix.dependencies }}
45+
composer dump-autoload --optimize
4546
4647
- name: Run mutation testing
4748
run: |

.github/workflows/tests.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jobs:
5656
- name: Install dependencies
5757
run: |
5858
composer update --prefer-dist --no-interaction --no-progress ${{ matrix.dependencies }}
59+
composer dump-autoload --optimize
5960
6061
- name: Run tests
6162
run: |

src/Liquid/Document.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ class Document extends AbstractBlock
2828
*/
2929
public function __construct(array &$tokens, ?FileSystem $fileSystem = null)
3030
{
31-
$this->fileSystem = $fileSystem;
32-
$this->parse($tokens);
31+
parent::__construct('', $tokens, $fileSystem);
3332
}
3433

3534
/**

src/Liquid/Tag/TagAssign.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class TagAssign extends AbstractTag
5050
*/
5151
public function __construct($markup, array &$tokens, ?FileSystem $fileSystem = null)
5252
{
53+
parent::__construct($markup, $tokens, $fileSystem);
54+
5355
$syntaxRegexp = new Regexp('/(\w+)\s*=\s*(.*)\s*/');
5456

5557
if ($syntaxRegexp->match($markup)) {

src/Liquid/Tag/TagCycle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class TagCycle extends AbstractTag
5757
*/
5858
public function __construct($markup, array &$tokens, ?FileSystem $fileSystem = null)
5959
{
60+
parent::__construct($markup, $tokens, $fileSystem);
61+
6062
$simpleSyntax = new Regexp("/" . Liquid::get('QUOTED_FRAGMENT') . "/");
6163
$namedSyntax = new Regexp("/(" . Liquid::get('QUOTED_FRAGMENT') . ")\s*\:\s*(.*)/");
6264

src/Liquid/Tag/TagDecrement.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class TagDecrement extends AbstractTag
4747
*/
4848
public function __construct($markup, array &$tokens, ?FileSystem $fileSystem = null)
4949
{
50+
parent::__construct($markup, $tokens, $fileSystem);
51+
5052
$syntax = new Regexp('/(' . Liquid::get('VARIABLE_NAME') . ')/');
5153

5254
if ($syntax->match($markup)) {

src/Liquid/Tag/TagIncrement.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class TagIncrement extends AbstractTag
4747
*/
4848
public function __construct($markup, array &$tokens, ?FileSystem $fileSystem = null)
4949
{
50+
parent::__construct($markup, $tokens, $fileSystem);
51+
5052
$syntax = new Regexp('/(' . Liquid::get('VARIABLE_NAME') . ')/');
5153

5254
if ($syntax->match($markup)) {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Liquid package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*
9+
* @package Liquid
10+
*/
11+
12+
namespace Liquid;
13+
14+
use ReflectionClass;
15+
use function array_filter;
16+
use function class_exists;
17+
use function file;
18+
use function interface_exists;
19+
use function join;
20+
use function trait_exists;
21+
use function var_dump;
22+
23+
/**
24+
* @coversNothing
25+
*/
26+
class StaticAnalysisTest extends TestCase
27+
{
28+
/**
29+
* @return iterable
30+
*/
31+
public static function provideClasses()
32+
{
33+
$files = require 'vendor/composer/autoload_classmap.php';
34+
35+
$seenNonVendor = false;
36+
foreach ($files as $class => $filename) {
37+
$path = str_replace(getcwd(), '.', $filename);
38+
39+
if (strpos($path, './vendor/') === 0) {
40+
continue;
41+
}
42+
43+
$seenNonVendor = true;
44+
yield $class => [str_replace(getcwd(), '.', $filename), $class];
45+
}
46+
47+
if ($seenNonVendor === false) {
48+
throw new \RuntimeException('Please generate the classmap.');
49+
}
50+
}
51+
52+
/**
53+
* @dataProvider provideClasses
54+
* @param mixed $filename
55+
* @param mixed $class
56+
*/
57+
public function testClassExists($filename, $class)
58+
{
59+
$this->assertTrue(class_exists($class) || trait_exists($class) || interface_exists($class));
60+
}
61+
62+
public static function provideAbstractTagSubclasses()
63+
{
64+
foreach (self::provideClasses() as $class => $data) {
65+
if (!is_subclass_of($class, AbstractTag::class)) {
66+
continue;
67+
}
68+
69+
$refClass = new ReflectionClass($class);
70+
if (!$refClass->hasMethod('__construct')) {
71+
continue;
72+
}
73+
74+
if ($refClass->getMethod('__construct')->getDeclaringClass()->getName() === AbstractTag::class) {
75+
continue; // Skip if the constructor is not overridden
76+
}
77+
78+
yield $class => [...$data, $refClass];
79+
}
80+
}
81+
82+
/**
83+
* @dataProvider provideAbstractTagSubclasses
84+
* @param string $filename
85+
* @param class-string<AbstractTag> $class
86+
* @param ReflectionClass<AbstractTag> $refClass
87+
*/
88+
public function testAbstractTagChildCallsConstruct($filename, $class, ReflectionClass $refClass)
89+
{
90+
$refMethod = $refClass->getMethod('__construct');
91+
$startLine = $refMethod->getStartLine();
92+
$endLine = $refMethod->getEndLine();
93+
94+
$code = array_slice(file($refMethod->getFileName()), $startLine - 1, $endLine - $startLine + 1);
95+
96+
$this->assertNotEmpty($code);
97+
98+
$linesWithConstruct = array_filter($code, function ($line) {
99+
return strpos($line, 'parent::__construct') !== false;
100+
});
101+
102+
$this->assertNotEmpty(
103+
$linesWithConstruct,
104+
"The constructor of {$refClass->getName()} should call parent::__construct()"
105+
);
106+
}
107+
}

0 commit comments

Comments
 (0)