Skip to content

Commit c59e3e6

Browse files
committed
Fix built-in helper context passed to inverse()
1 parent b5fe5d9 commit c59e3e6

3 files changed

Lines changed: 32 additions & 10 deletions

File tree

src/HelperOptions.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ private function invokeBlock(Closure $closure, mixed $context, mixed $data): str
9494
// block-scoped. PHP copy-on-write makes this assignment cheap when no inline partials are registered.
9595
$savedInlinePartials = $cx->inlinePartials;
9696
$scope = $this->scope;
97-
// Skip depths push when the caller explicitly passes the current scope (equivalent to
98-
// HBS.js options.fn(this) / options.inverse(this)), since the scope level isn't changing.
97+
// Skip depths push when the caller explicitly passes the current scope (e.g. fn($options->scope)),
98+
// equivalent to HBS.js options.fn(this) where the scope level isn't changing.
9999
$pushDepths = $context !== $scope;
100-
$resolvedContext = $pushDepths ? ($context === Scope::Use ? $scope : $context) : $scope;
100+
$resolvedContext = ($context === Scope::Use) ? $scope : $context;
101101
$outerFrame = null;
102102
$bpStack = null;
103103

src/Runtime.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static function defaultHelpers(): array
3535
$condition = $args[0] instanceof Closure ? $args[0]($options->scope) : $args[0];
3636
return static::ifvar($condition, (bool) ($options->hash['includeZero'] ?? false))
3737
? $options->fn($options->scope)
38-
: $options->inverse();
38+
: $options->inverse($options->scope);
3939
},
4040
'unless' => static function (mixed ...$args): string {
4141
if (count($args) !== 2) {
@@ -45,7 +45,7 @@ public static function defaultHelpers(): array
4545
$options = $args[1];
4646
$condition = $args[0] instanceof Closure ? $args[0]($options->scope) : $args[0];
4747
return static::ifvar($condition, (bool) ($options->hash['includeZero'] ?? false))
48-
? $options->inverse()
48+
? $options->inverse($options->scope)
4949
: $options->fn($options->scope);
5050
},
5151
'each' => static function (mixed $context, ?HelperOptions $options = null): string {
@@ -72,7 +72,7 @@ public static function defaultHelpers(): array
7272
if (static::ifvar($context)) {
7373
return $options->fn($context, ['blockParams' => [$context]]);
7474
}
75-
return $options->inverse();
75+
return $options->inverse($options->scope);
7676
},
7777
'lookup' => static function (mixed $obj, string|int $key): mixed {
7878
if (is_array($obj)) {
@@ -89,12 +89,12 @@ public static function defaultHelpers(): array
8989
return '';
9090
},
9191
'helperMissing' => static function (mixed ...$args): mixed {
92-
/** @var HelperOptions $options */
93-
$options = end($args);
94-
if (count($args) === 1 && !isset($options->fn)) {
92+
if (count($args) === 1) {
9593
// Bare variable lookup with no match — return null (mirrors HBS.js undefined).
9694
return null;
9795
}
96+
/** @var HelperOptions $options */
97+
$options = end($args);
9898
throw new \Exception('Missing helper: "' . $options->name . '"');
9999
},
100100
'blockHelperMissing' => static function (mixed $context, HelperOptions $options): string {
@@ -105,7 +105,7 @@ public static function defaultHelpers(): array
105105
return array_is_list($context) ? $options->iterate($context) : $options->fn($context);
106106
}
107107
if ($context === false || $context === null) {
108-
return $options->inverse();
108+
return $options->inverse($options->scope);
109109
}
110110
// true renders with the outer scope unchanged; any other truthy value becomes the new scope.
111111
return $options->fn($context === true ? $options->scope : $context);

tests/RegressionTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,16 @@ public static function builtInProvider(): array
13801380
'data' => ['name' => 'NoOne'],
13811381
'expected' => 'No, NoOne',
13821382
],
1383+
'with else branch: ../ at root has no parent' => [
1384+
'template' => '{{#with missing}}FOUND{{else}}{{../name}}{{/with}}',
1385+
'data' => ['name' => 'Root', 'missing' => null],
1386+
'expected' => '',
1387+
],
1388+
'with else branch: ../ inside each resolves to actual parent' => [
1389+
'template' => '{{#each items}}{{#with missing}}FOUND{{else}}{{../name}}{{/with}}{{/each}}',
1390+
'data' => ['items' => [['name' => 'Item']], 'name' => 'Root', 'missing' => null],
1391+
'expected' => 'Root',
1392+
],
13831393

13841394
'bare log renders empty' => [
13851395
'template' => '{{log}}',
@@ -1851,6 +1861,12 @@ public static function ifElseProvider(): array
18511861
'data' => new SafeString('0'),
18521862
'expected' => 'T',
18531863
],
1864+
1865+
'if+includeZero else branch: ../ inside each resolves to actual parent' => [
1866+
'template' => '{{#each items}}{{#if falsy includeZero=true}}Y{{else}}{{../name}}{{/if}}{{/each}}',
1867+
'data' => ['items' => [['name' => 'Item', 'falsy' => false]], 'name' => 'Root'],
1868+
'expected' => 'Root',
1869+
],
18541870
];
18551871
}
18561872

@@ -1991,6 +2007,12 @@ public static function sectionProvider(): array
19912007
'template' => '{{#* inline "p"}}BEFORE{{/inline}}{{#foo}}{{else}}{{#* inline "p"}}INSIDE{{/inline}}{{> p}}{{/foo}}{{> p}}',
19922008
'expected' => 'INSIDEBEFORE',
19932009
],
2010+
2011+
'blockHelperMissing false context: ../ inside each resolves to actual parent' => [
2012+
'template' => '{{#each items}}{{#falsy}}Y{{else}}{{../name}}{{/falsy}}{{/each}}',
2013+
'data' => ['items' => [['name' => 'Item', 'falsy' => false]], 'name' => 'Root'],
2014+
'expected' => 'Root',
2015+
],
19942016
];
19952017
}
19962018

0 commit comments

Comments
 (0)