Skip to content

Commit e0d4692

Browse files
committed
more thorough type hinting
1 parent f1c8b1b commit e0d4692

File tree

4 files changed

+180
-49
lines changed

4 files changed

+180
-49
lines changed

phpstan.neon

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ parameters:
22
paths:
33
- ./src
44
- ./tests
5-
level: 5
5+
level: 7
6+
ignoreErrors:
7+
- '#^Function .* has parameter \$args with no type specified.$#'

src/microhtml.php

Lines changed: 88 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,36 @@
44

55
namespace MicroHTML;
66

7+
/**
8+
* @phpstan-type Attrs array<string,string|null|bool|int|float>
9+
* @phpstan-type Child \MicroHTML\HTMLElement|string|null|bool|int|float
10+
* @phpstan-type Arg Attrs|Child
11+
*/
712
class HTMLElement
813
{
914
protected string $tag;
15+
/** @var Attrs $attrs */
1016
protected array $attrs;
17+
/** @var array<Child> $children */
1118
protected array $children;
1219

20+
/** @param array<Arg> $args */
1321
public function __construct(string $tag, array $args)
1422
{
1523
$this->tag = $tag;
1624

1725
if (count($args) > 0 && is_array($args[0])) {
1826
$this->attrs = $args[0];
27+
// @phpstan-ignore-next-line
1928
$this->children = array_slice($args, 1);
2029
} else {
2130
$this->attrs = [];
31+
// @phpstan-ignore-next-line
2232
$this->children = $args;
2333
}
2434
}
2535

36+
/** @param Child $args */
2637
public function appendChild(...$args): void
2738
{
2839
foreach ($args as $arg) {
@@ -58,10 +69,10 @@ protected function renderChildren(): string
5869
if ($child instanceof HTMLElement) {
5970
$sub .= $child;
6071
} else {
61-
if (is_null($child)) {
72+
if (is_null($child) || is_bool($child)) {
6273
$child = "";
6374
}
64-
if (is_numeric($child) || is_bool($child)) {
75+
if (is_numeric($child)) {
6576
$child = (string)$child;
6677
}
6778
$sub .= htmlentities($child, ENT_QUOTES, "UTF-8");
@@ -79,8 +90,21 @@ public function __toString(): string
7990
}
8091
}
8192

93+
/**
94+
* https://developer.mozilla.org/en-US/docs/Glossary/Void_element
95+
*
96+
* @phpstan-import-type Attrs from HTMLElement
97+
*/
8298
class SelfClosingHTMLElement extends HTMLElement
8399
{
100+
/**
101+
* @param Attrs $attrs
102+
*/
103+
public function __construct(string $tag, array $attrs)
104+
{
105+
parent::__construct($tag, [$attrs]);
106+
}
107+
84108
public function __toString(): string
85109
{
86110
$tag = $this->tag;
@@ -89,8 +113,14 @@ public function __toString(): string
89113
}
90114
}
91115

116+
/**
117+
* @phpstan-import-type Child from HTMLElement
118+
*/
92119
class EmptyHTMLElement extends HTMLElement
93120
{
121+
/**
122+
* @param array<Child> $args
123+
*/
94124
public function __construct(array $args)
95125
{
96126
parent::__construct("", $args);
@@ -103,9 +133,14 @@ public function __toString(): string
103133
}
104134
}
105135

136+
function emptyHTML(...$args): HTMLElement
137+
{
138+
return new EmptyHTMLElement($args);
139+
}
140+
106141
class RawHTMLElement extends HTMLElement
107142
{
108-
private $html;
143+
private string $html;
109144

110145
public function __construct(string $html)
111146
{
@@ -123,15 +158,18 @@ function rawHTML(string $html): HTMLElement
123158
{
124159
return new RawHTMLElement($html);
125160
}
126-
function emptyHTML(...$args): HTMLElement
127-
{
128-
return new EmptyHTMLElement($args);
129-
}
130-
function joinHTML(HTMLElement|string $glue, array $pieces): HTMLElement
161+
162+
/**
163+
* @param array<\MicroHTML\HTMLElement|string|null|bool|int|float> $pieces
164+
*/
165+
function joinHTML(HTMLElement|string $glue, array $pieces, bool $filterNulls = false): HTMLElement
131166
{
132167
$out = emptyHTML();
133168
$n = 0;
134169
foreach ($pieces as $piece) {
170+
if ($filterNulls && $piece === null) {
171+
continue;
172+
}
135173
if ($n++ > 0) {
136174
$out->appendChild($glue);
137175
}
@@ -147,21 +185,24 @@ function HTML(...$args): HTMLElement
147185
}
148186

149187
# Document metadata
150-
function BASE(...$args): HTMLElement
188+
/** @param array<string,string|null|bool|int|float> $attrs */
189+
function BASE(array $attrs = []): SelfClosingHTMLElement
151190
{
152-
return new SelfClosingHTMLElement("base", $args);
191+
return new SelfClosingHTMLElement("base", $attrs);
153192
}
154193
function HEAD(...$args): HTMLElement
155194
{
156195
return new HTMLElement("head", $args);
157196
}
158-
function LINK(...$args): HTMLElement
197+
/** @param array<string,string|null|bool|int|float> $attrs */
198+
function LINK(array $attrs = []): SelfClosingHTMLElement
159199
{
160-
return new SelfClosingHTMLElement("link", $args);
200+
return new SelfClosingHTMLElement("link", $attrs);
161201
}
162-
function META(...$args): HTMLElement
202+
/** @param array<string,string|null|bool|int|float> $attrs */
203+
function META(array $attrs = []): SelfClosingHTMLElement
163204
{
164-
return new SelfClosingHTMLElement("meta", $args);
205+
return new SelfClosingHTMLElement("meta", $attrs);
165206
}
166207
function STYLE(...$args): HTMLElement
167208
{
@@ -273,9 +314,10 @@ function FIGURE(...$args): HTMLElement
273314
{
274315
return new HTMLElement("figure", $args);
275316
}
276-
function HR(...$args): HTMLElement
317+
/** @param array<string,string|null|bool|int|float> $attrs */
318+
function HR(array $attrs = []): SelfClosingHTMLElement
277319
{
278-
return new SelfClosingHTMLElement("hr", $args);
320+
return new SelfClosingHTMLElement("hr", $attrs);
279321
}
280322
function LI(...$args): HTMLElement
281323
{
@@ -319,9 +361,10 @@ function BDO(...$args): HTMLElement
319361
{
320362
return new HTMLElement("bdo", $args);
321363
}
322-
function BR(...$args): HTMLElement
364+
/** @param array<string,string|null|bool|int|float> $attrs */
365+
function BR(array $attrs = []): SelfClosingHTMLElement
323366
{
324-
return new SelfClosingHTMLElement("br", $args);
367+
return new SelfClosingHTMLElement("br", $attrs);
325368
}
326369
function CITE(...$args): HTMLElement
327370
{
@@ -423,31 +466,35 @@ function VAR_(...$args): HTMLElement
423466
{
424467
return new HTMLElement("var", $args);
425468
}
426-
function WBR(...$args): HTMLElement
469+
/** @param array<string,string|null|bool|int|float> $attrs */
470+
function WBR(array $attrs = []): SelfClosingHTMLElement
427471
{
428-
return new HTMLElement("wbr", $args);
472+
return new SelfClosingHTMLElement("wbr", $attrs);
429473
}
430474

431475
# Image and multimedia
432-
function AREA(...$args): HTMLElement
476+
/** @param array<string,string|null|bool|int|float> $attrs */
477+
function AREA(array $attrs = []): SelfClosingHTMLElement
433478
{
434-
return new SelfClosingHTMLElement("area", $args);
479+
return new SelfClosingHTMLElement("area", $attrs);
435480
}
436481
function AUDIO(...$args): HTMLElement
437482
{
438483
return new HTMLElement("audio", $args);
439484
}
440-
function IMG(...$args): HTMLElement
485+
/** @param array<string,string|null|bool|int|float> $attrs */
486+
function IMG(array $attrs = []): SelfClosingHTMLElement
441487
{
442-
return new SelfClosingHTMLElement("img", $args);
488+
return new SelfClosingHTMLElement("img", $attrs);
443489
}
444490
function MAP(...$args): HTMLElement
445491
{
446492
return new HTMLElement("map", $args);
447493
}
448-
function TRACK(...$args): HTMLElement
494+
/** @param array<string,string|null|bool|int|float> $attrs */
495+
function TRACK(array $attrs = []): SelfClosingHTMLElement
449496
{
450-
return new SelfClosingHTMLElement("track", $args);
497+
return new SelfClosingHTMLElement("track", $attrs);
451498
}
452499
function VIDEO(...$args): HTMLElement
453500
{
@@ -459,9 +506,10 @@ function APPLET(...$args): HTMLElement
459506
{
460507
return new HTMLElement("applet", $args);
461508
}
462-
function EMBED(...$args): HTMLElement
509+
/** @param array<string,string|null|bool|int|float> $attrs */
510+
function EMBED(array $attrs = []): SelfClosingHTMLElement
463511
{
464-
return new HTMLElement("embed", $args);
512+
return new SelfClosingHTMLElement("embed", $attrs);
465513
}
466514
function IFRAME(...$args): HTMLElement
467515
{
@@ -475,17 +523,19 @@ function OBJECT(...$args): HTMLElement
475523
{
476524
return new HTMLElement("object", $args);
477525
}
478-
function PARAM(...$args): HTMLElement
526+
/** @param array<string,string|null|bool|int|float> $attrs */
527+
function PARAM(array $attrs = []): SelfClosingHTMLElement
479528
{
480-
return new HTMLElement("param", $args);
529+
return new SelfClosingHTMLElement("param", $attrs);
481530
}
482531
function PICTURE(...$args): HTMLElement
483532
{
484533
return new HTMLElement("picture", $args);
485534
}
486-
function SOURCE(...$args): HTMLElement
535+
/** @param array<string,string|null|bool|int|float> $attrs */
536+
function SOURCE(array $attrs = []): SelfClosingHTMLElement
487537
{
488-
return new HTMLElement("source", $args);
538+
return new SelfClosingHTMLElement("source", $attrs);
489539
}
490540

491541
# Scripting
@@ -517,9 +567,10 @@ function CAPTION(...$args): HTMLElement
517567
{
518568
return new HTMLElement("caption", $args);
519569
}
520-
function COL(...$args): HTMLElement
570+
/** @param array<string,string|null|bool|int|float> $attrs */
571+
function COL(array $attrs = []): SelfClosingHTMLElement
521572
{
522-
return new SelfClosingHTMLElement("col", $args);
573+
return new SelfClosingHTMLElement("col", $attrs);
523574
}
524575
function COLGROUP(...$args): HTMLElement
525576
{
@@ -571,9 +622,10 @@ function FORM(...$args): HTMLElement
571622
{
572623
return new HTMLElement("form", $args);
573624
}
574-
function INPUT(...$args): HTMLElement
625+
/** @param array<string,string|null|bool|int|float> $attrs */
626+
function INPUT(array $attrs = []): SelfClosingHTMLElement
575627
{
576-
return new SelfClosingHTMLElement("input", $args);
628+
return new SelfClosingHTMLElement("input", $attrs);
577629
}
578630
function LABEL(...$args): HTMLElement
579631
{

tests/CodeTest.php

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,74 @@
22

33
declare(strict_types=1);
44

5+
use PHPUnit\Framework\Attributes\DataProvider;
6+
57
class CodeTest extends \PHPUnit\Framework\TestCase
68
{
9+
public const EXCEPTIONS = [
10+
"VAR_" => "var"
11+
];
12+
public const VOID_TAGS = [
13+
"area",
14+
"base",
15+
"br",
16+
"col",
17+
"embed",
18+
"hr",
19+
"img",
20+
"input",
21+
"link",
22+
"meta",
23+
"param",
24+
"source",
25+
"track",
26+
"wbr"
27+
];
28+
729
/**
8-
* Check for typos - function name should match tag name
30+
* Get a list of all tag-generation functions in the MicroHTML library.
31+
*
32+
* @return array<array<string>> An array of tag names.
933
*/
10-
public function testSync(): void
34+
public static function getFunctionList(): array
1135
{
12-
$exceptions = [
13-
"VAR_" => "var"
14-
];
1536
$lines = file("src/microhtml.php");
16-
$this->assertNotFalse($lines);
37+
if (!$lines) {
38+
throw new \RuntimeException("Failed to read src/microhtml.php");
39+
}
40+
$tags = [];
1741
foreach ($lines as $line) {
1842
if (preg_match("/function ([A-Z][^(]*)/", $line, $matches)) {
19-
$fun = $matches[1];
20-
$tag = $exceptions[$fun] ?? strtolower($fun);
21-
$name = "\MicroHTML\\$fun";
22-
$this->assertIsCallable($name);
23-
// Call eg \MicroHTML\SECTION() and check that it contains "section"
24-
$this->assertStringContainsString($tag, (string)$name());
43+
$tags[] = [$matches[1]];
2544
}
2645
}
46+
return $tags;
47+
}
48+
49+
public function testDataProvider(): void
50+
{
51+
$this->assertNotEmpty($this->getFunctionList());
52+
}
53+
54+
/**
55+
* Check for typos - function name should match tag name
56+
*
57+
* @param string $function The function name to validate.
58+
*/
59+
#[DataProvider('getFunctionList')]
60+
public function testTag(string $function): void
61+
{
62+
$tag = self::EXCEPTIONS[$function] ?? strtolower($function);
63+
$name = "\MicroHTML\\$function";
64+
$this->assertIsCallable($name);
65+
$element = $name();
66+
// Call eg \MicroHTML\SECTION() and check that it contains "section"
67+
$this->assertStringContainsString($tag, (string)$element);
68+
// Check that void tags inherit from SelfClosingHTMLElement
69+
if (in_array($tag, self::VOID_TAGS)) {
70+
$this->assertInstanceOf(\MicroHTML\SelfClosingHTMLElement::class, $element);
71+
} else {
72+
$this->assertNotInstanceOf(\MicroHTML\SelfClosingHTMLElement::class, $element);
73+
}
2774
}
2875
}

0 commit comments

Comments
 (0)