fix: support rest tag variables

This commit is contained in:
dpiercey 2025-07-03 10:37:18 -07:00 committed by Dylan Piercey
parent 0d1064c0ae
commit 7dd3fda134
25 changed files with 745 additions and 54 deletions

View File

@ -0,0 +1,5 @@
---
"@marko/runtime-tags": patch
---
Fix support for rest patterns in tag variables not creating a new object.

View File

@ -12,7 +12,8 @@
"$$clickCount_closure": "s",
"$$clickCount": "c",
"$$onClick": "u",
"$$setup$FancyButton$content": "d"
"$$setup$FancyButton$content": "d",
"$$attrs": "f"
}
}
}

View File

@ -3,14 +3,17 @@ export const $walks = /* get, next(1), replace, out(1) */" D%l";
export const $setup = () => {};
import * as _$ from "@marko/runtime-tags/debug/dom";
const $attrs_effect = _$.effect("__tests__/tags/FancyButton.marko_0_attrs", $scope => _$.attrsEvents($scope, "#button/0"));
const $attrs = ($scope, attrs) => {
const $attrs = /* @__PURE__ */_$.value("attrs", ($scope, attrs) => {
_$.attrs($scope, "#button/0", attrs);
$attrs_effect($scope);
};
});
const $dynamicTag = /* @__PURE__ */_$.dynamicTag("#text/1");
const $content = /* @__PURE__ */_$.value("content", $dynamicTag);
export const $input = /* @__PURE__ */_$.value("input", ($scope, input) => {
$attrs($scope, input);
(({
content,
...attrs
}) => $attrs($scope, attrs))(input);
$content($scope, input.content);
});
export default /* @__PURE__ */_$.createTemplate("__tests__/tags/FancyButton.marko", $template, $walks, $setup, $input);

View File

@ -1,11 +1,14 @@
// size: 405 (min) 237 (brotli)
// size: 447 (min) 251 (brotli)
const $attrs_effect = _$.effect("a0", ($scope) => _$.attrsEvents($scope, 0)),
$attrs = _$.value(5, ($scope, attrs) => {
_$.attrs($scope, 0, attrs), $attrs_effect($scope);
}),
$dynamicTag = _$.dynamicTag(1),
$content = _$.value(4, $dynamicTag),
$input = _$.value(3, ($scope, input) => {
(($scope, attrs) => {
_$.attrs($scope, 0, attrs), $attrs_effect($scope);
})($scope, input),
(({ content: content, ...attrs }) => {
$attrs($scope, attrs);
})(input),
$content($scope, input.content);
}),
$clickCount$FancyButton$content = _$.dynamicClosureRead(

View File

@ -10,8 +10,8 @@ export default _$.createTemplate("__tests__/tags/FancyButton.marko", (input, $se
_$.write(`</button>${_$.markResumeNode($scope0_id, "#button/0")}`);
_$.writeEffect($scope0_id, "__tests__/tags/FancyButton.marko_0_attrs");
_$.writeScope($scope0_id, {
input
attrs
}, "__tests__/tags/FancyButton.marko", 0, {
input: "1:22"
attrs: "1:22"
});
});

View File

@ -11,7 +11,7 @@
</button>
<!--M_*2 #button/0-->
<script>
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",input:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,_.d.content=_._["__tests__/template.marko_1_renderer"](_.a),(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",attrs:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
</script>
</body>
</html>
@ -34,7 +34,7 @@ container.querySelector("button").click();
</button>
<!--M_*2 #button/0-->
<script>
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",input:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,_.d.content=_._["__tests__/template.marko_1_renderer"](_.a),(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",attrs:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
</script>
</body>
</html>
@ -61,7 +61,7 @@ container.querySelector("button").click();
</button>
<!--M_*2 #button/0-->
<script>
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",input:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,_.d.content=_._["__tests__/template.marko_1_renderer"](_.a),(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",attrs:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
</script>
</body>
</html>
@ -88,7 +88,7 @@ container.querySelector("button").click();
</button>
<!--M_*2 #button/0-->
<script>
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",input:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,_.d.content=_._["__tests__/template.marko_1_renderer"](_.a),(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",attrs:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
</script>
</body>
</html>

View File

@ -1,6 +1,6 @@
# Write
```html
<button><!--M_[3-->0<!--M_*3 #text/0--><!--M_]2 #text/1--></button><!--M_*2 #button/0--><script>WALKER_RUNTIME("M")("_");M._.r=[_=>(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",input:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,_.d.content=_._["__tests__/template.marko_1_renderer"](_.a),(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()</script>
<button><!--M_[3-->0<!--M_*3 #text/0--><!--M_]2 #text/1--></button><!--M_*2 #button/0--><script>WALKER_RUNTIME("M")("_");M._.r=[_=>(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",attrs:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()</script>
```
# Render End
@ -16,7 +16,7 @@
</button>
<!--M_*2 #button/0-->
<script>
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",input:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,_.d.content=_._["__tests__/template.marko_1_renderer"](_.a),(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.f=[0,_.a={clickCount:0,"ClosureScopes:clickCount":_.g=new Set,"#childScope/0":_.e={"EventAttributes:#button/0":_.b={},"ConditionalScope:#text/1":_.c={"ClosureSignalIndex:clickCount":0},"ConditionalRenderer:#text/1":"__tests__/template.marko_1_renderer",attrs:_.d={}}},_.e,_.c],_.b.click=_.d.onClick=_._["__tests__/template.marko_0/onClick"](_.a),_.c._=_.a,(_.g).add(_.c),_.f),"__tests__/tags/FancyButton.marko_0_attrs",2];M._.w()
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
{
"vars": {
"props": {
"$_$": "t",
"$init": "o",
"$$bar_effect": "r",
"$$bar": "n",
"$$expr_bar_$fooChange_effect": "i",
"$$expr_bar_$fooChange": "a",
"$$obj": "e",
"$$partialObj": "d",
"$$partialObj_a": "m",
"$$a": "f",
"$$partialObj_b": "s"
}
}
}

View File

@ -0,0 +1,67 @@
# Render
```html
<div
class="obj"
>
{"a":1,"b":2,"c":3}
</div>
<div
class="partialObj"
>
{"b":2,"c":3}
</div>
<div
class="a"
>
1
</div>
<div
class="b"
>
2
</div>
<div
class="a"
>
removed a
</div>
<button>
Update
</button>
```
# Render
```js
container.querySelector("button").click();
```
```html
<div
class="obj"
>
{"a":4,"b":5,"d":6}
</div>
<div
class="partialObj"
>
{"b":5,"d":6}
</div>
<div
class="a"
>
4
</div>
<div
class="b"
>
5
</div>
<div
class="a"
>
removed a
</div>
<button>
Update
</button>
```

View File

@ -0,0 +1,79 @@
# Render
```html
<div
class="obj"
>
{"a":1,"b":2,"c":3}
</div>
<div
class="partialObj"
>
{"b":2,"c":3}
</div>
<div
class="a"
>
1
</div>
<div
class="b"
>
2
</div>
<div
class="a"
>
removed a
</div>
<button>
Update
</button>
```
# Mutations
```
INSERT div0, div1, div2, div3, div4, button
```
# Render
```js
container.querySelector("button").click();
```
```html
<div
class="obj"
>
{"a":4,"b":5,"d":6}
</div>
<div
class="partialObj"
>
{"b":5,"d":6}
</div>
<div
class="a"
>
4
</div>
<div
class="b"
>
5
</div>
<div
class="a"
>
removed a
</div>
<button>
Update
</button>
```
# Mutations
```
UPDATE div0/#text "{\"a\":1,\"b\":2,\"c\":3}" => "{\"a\":4,\"b\":5,\"d\":6}"
UPDATE div1/#text "{\"b\":2,\"c\":3}" => "{\"b\":5,\"d\":6}"
UPDATE div2/#text "1" => "4"
UPDATE div3/#text "2" => "5"
```

View File

@ -0,0 +1,29 @@
// size: 398 (min) 206 (brotli)
const $obj = _$.state(6, ($scope, obj) => {
_$.data($scope[0], JSON.stringify(obj)),
(({ a: a, ...partialObj }) => {
$partialObj($scope, partialObj);
})(obj),
$a($scope, obj.a),
$partialObj_b($scope, obj.b);
}),
$partialObj = _$.value(8, ($scope, partialObj) => {
_$.data($scope[1], JSON.stringify(partialObj)),
$partialObj_a($scope, partialObj.a);
}),
$partialObj_a = _$.value(10, ($scope, partialObj_a) =>
_$.data(
$scope[4],
void 0 === partialObj_a ? "removed a" : "didn't remove a",
),
),
$a = _$.value(7, ($scope, a) => _$.data($scope[2], a)),
$partialObj_b = _$.value(9, ($scope, partialObj_b) =>
_$.data($scope[3], partialObj_b),
);
_$.effect("a0", ($scope) =>
_$.on($scope[5], "click", function () {
$obj($scope, { a: 4, b: 5, d: 6 });
}),
),
init();

View File

@ -0,0 +1,35 @@
export const $template = "<div class=obj> </div><div class=partialObj> </div><div class=a> </div><div class=b> </div><div class=a> </div><button>Update</button>";
export const $walks = /* next(1), get, out(1), next(1), get, out(1), next(1), get, out(1), next(1), get, out(1), next(1), get, out(1), get, over(1) */"D lD lD lD lD l b";
import * as _$ from "@marko/runtime-tags/debug/dom";
const $obj = /* @__PURE__ */_$.state("obj/6", ($scope, obj) => {
_$.data($scope["#text/0"], JSON.stringify(obj));
(({
a,
...partialObj
}) => $partialObj($scope, partialObj))(obj);
$a($scope, obj.a);
$partialObj_b($scope, obj.b);
});
const $partialObj = /* @__PURE__ */_$.value("partialObj", ($scope, partialObj) => {
_$.data($scope["#text/1"], JSON.stringify(partialObj));
$partialObj_a($scope, partialObj.a);
});
const $partialObj_a = /* @__PURE__ */_$.value("partialObj_a", ($scope, partialObj_a) => _$.data($scope["#text/4"], partialObj_a === undefined ? "removed a" : "didn't remove a"));
const $a = /* @__PURE__ */_$.value("a", ($scope, a) => _$.data($scope["#text/2"], a));
const $partialObj_b = /* @__PURE__ */_$.value("partialObj_b", ($scope, partialObj_b) => _$.data($scope["#text/3"], partialObj_b));
const $setup_effect = _$.effect("__tests__/template.marko_0", $scope => _$.on($scope["#button/5"], "click", function () {
$obj($scope, {
a: 4,
b: 5,
d: 6
});
}));
export function $setup($scope) {
$obj($scope, {
a: 1,
b: 2,
c: 3
});
$setup_effect($scope);
}
export default /* @__PURE__ */_$.createTemplate("__tests__/template.marko", $template, $walks, $setup);

View File

@ -0,0 +1,17 @@
import * as _$ from "@marko/runtime-tags/debug/html";
export default _$.createTemplate("__tests__/template.marko", input => {
const $scope0_id = _$.nextScopeId();
let obj = {
a: 1,
b: 2,
c: 3
};
const {
a,
...partialObj
} = obj;
_$.write(`<div class=obj>${_$.escapeXML(JSON.stringify(obj))}${_$.markResumeNode($scope0_id, "#text/0")}</div><div class=partialObj>${_$.escapeXML(JSON.stringify(partialObj))}${_$.markResumeNode($scope0_id, "#text/1")}</div><div class=a>${_$.escapeXML(a)}${_$.markResumeNode($scope0_id, "#text/2")}</div><div class=b>${_$.escapeXML(partialObj.b)}${_$.markResumeNode($scope0_id, "#text/3")}</div><div class=a>${_$.escapeXML(partialObj.a === undefined ? "removed a" : "didn't remove a")}${_$.markResumeNode($scope0_id, "#text/4")}</div><button>Update</button>${_$.markResumeNode($scope0_id, "#button/5")}`);
_$.writeEffect($scope0_id, "__tests__/template.marko_0");
_$.writeScope($scope0_id, {}, "__tests__/template.marko", 0);
_$.resumeClosestBranch($scope0_id);
});

View File

@ -0,0 +1,67 @@
# Render
```html
<div
class="obj"
>
{"a":1,"b":2,"c":3}
</div>
<div
class="partialObj"
>
{"b":2,"c":3}
</div>
<div
class="a"
>
1
</div>
<div
class="b"
>
2
</div>
<div
class="a"
>
removed a
</div>
<button>
Update
</button>
```
# Render
```js
container.querySelector("button").click();
```
```html
<div
class="obj"
>
{"a":4,"b":5,"d":6}
</div>
<div
class="partialObj"
>
{"b":5,"d":6}
</div>
<div
class="a"
>
4
</div>
<div
class="b"
>
5
</div>
<div
class="a"
>
removed a
</div>
<button>
Update
</button>
```

View File

@ -0,0 +1,103 @@
# Render
```html
<html>
<head />
<body>
<div
class="obj"
>
{"a":1,"b":2,"c":3}
<!--M_*1 #text/0-->
</div>
<div
class="partialObj"
>
{"b":2,"c":3}
<!--M_*1 #text/1-->
</div>
<div
class="a"
>
1
<!--M_*1 #text/2-->
</div>
<div
class="b"
>
2
<!--M_*1 #text/3-->
</div>
<div
class="a"
>
removed a
<!--M_*1 #text/4-->
</div>
<button>
Update
</button>
<!--M_*1 #button/5-->
<script>
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.a=[0,{}]),"__tests__/template.marko_0",1];M._.w()
</script>
</body>
</html>
```
# Render
```js
container.querySelector("button").click();
```
```html
<html>
<head />
<body>
<div
class="obj"
>
{"a":4,"b":5,"d":6}
<!--M_*1 #text/0-->
</div>
<div
class="partialObj"
>
{"b":5,"d":6}
<!--M_*1 #text/1-->
</div>
<div
class="a"
>
4
<!--M_*1 #text/2-->
</div>
<div
class="b"
>
5
<!--M_*1 #text/3-->
</div>
<div
class="a"
>
removed a
<!--M_*1 #text/4-->
</div>
<button>
Update
</button>
<!--M_*1 #button/5-->
<script>
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.a=[0,{}]),"__tests__/template.marko_0",1];M._.w()
</script>
</body>
</html>
```
# Mutations
```
UPDATE html/body/div0/#text "{\"a\":1,\"b\":2,\"c\":3}" => "{\"a\":4,\"b\":5,\"d\":6}"
UPDATE html/body/div1/#text "{\"b\":2,\"c\":3}" => "{\"b\":5,\"d\":6}"
UPDATE html/body/div2/#text "1" => "4"
UPDATE html/body/div3/#text "2" => "5"
```

View File

@ -0,0 +1,31 @@
# Render End
```html
<div
class="obj"
>
{"a":1,"b":2,"c":3}
</div>
<div
class="partialObj"
>
{"b":2,"c":3}
</div>
<div
class="a"
>
1
</div>
<div
class="b"
>
2
</div>
<div
class="a"
>
removed a
</div>
<button>
Update
</button>
```

View File

@ -0,0 +1,77 @@
# Write
```html
<div class=obj>{"a":1,"b":2,"c":3}<!--M_*1 #text/0--></div><div class=partialObj>{"b":2,"c":3}<!--M_*1 #text/1--></div><div class=a>1<!--M_*1 #text/2--></div><div class=b>2<!--M_*1 #text/3--></div><div class=a>removed a<!--M_*1 #text/4--></div><button>Update</button><!--M_*1 #button/5--><script>WALKER_RUNTIME("M")("_");M._.r=[_=>(_.a=[0,{}]),"__tests__/template.marko_0",1];M._.w()</script>
```
# Render End
```html
<html>
<head />
<body>
<div
class="obj"
>
{"a":1,"b":2,"c":3}
<!--M_*1 #text/0-->
</div>
<div
class="partialObj"
>
{"b":2,"c":3}
<!--M_*1 #text/1-->
</div>
<div
class="a"
>
1
<!--M_*1 #text/2-->
</div>
<div
class="b"
>
2
<!--M_*1 #text/3-->
</div>
<div
class="a"
>
removed a
<!--M_*1 #text/4-->
</div>
<button>
Update
</button>
<!--M_*1 #button/5-->
<script>
WALKER_RUNTIME("M")("_");M._.r=[_=&gt;(_.a=[0,{}]),"__tests__/template.marko_0",1];M._.w()
</script>
</body>
</html>
```
# Mutations
```
INSERT html
INSERT html/head
INSERT html/body
INSERT html/body/div0
INSERT html/body/div0/#text
INSERT html/body/div0/#comment
INSERT html/body/div1
INSERT html/body/div1/#text
INSERT html/body/div1/#comment
INSERT html/body/div2
INSERT html/body/div2/#text
INSERT html/body/div2/#comment
INSERT html/body/div3
INSERT html/body/div3/#text
INSERT html/body/div3/#comment
INSERT html/body/div4
INSERT html/body/div4/#text
INSERT html/body/div4/#comment
INSERT html/body/button
INSERT html/body/button/#text
INSERT html/body/#comment
INSERT html/body/script
INSERT html/body/script/#text
```

View File

@ -0,0 +1,24 @@
let/obj={
a: 1,
b: 2,
c: 3,
}
const/{
a,
...partialObj
}=obj
.obj -- ${JSON.stringify(obj)}
.partialObj -- ${JSON.stringify(partialObj)}
.a -- ${a}
.b -- ${partialObj.b}
.a -- ${partialObj.a === undefined ? "removed a" : "didn't remove a"}
button onClick() {
obj = {
a: 4,
b: 5,
d: 6
}
} -- Update

View File

@ -0,0 +1,5 @@
export const steps = [{}, click];
function click(container: Element) {
container.querySelector("button")!.click();
}

View File

@ -7,7 +7,11 @@ export function getDeclaredBindingExpression(
binding: Binding,
): t.Identifier | t.OptionalMemberExpression | t.MemberExpression {
const canonicalBinding = getCanonicalBinding(binding)!;
if (canonicalBinding.declared || !canonicalBinding.upstreamAlias) {
if (
canonicalBinding.declared ||
!canonicalBinding.upstreamAlias ||
canonicalBinding.excludeProperties !== undefined
) {
return t.identifier(canonicalBinding.name);
} else if (canonicalBinding.property !== undefined) {
return toMemberExpression(

View File

@ -48,6 +48,9 @@ export class Sorted<T> {
}
}
}
has<U extends NonNullable<T>>(data: Opt<U>, item: U): boolean {
return this.findIndex(data, item) !== -1;
}
findIndex<U extends NonNullable<T>>(data: Opt<U>, item: U) {
if (data) {
if (Array.isArray(data)) {

View File

@ -88,7 +88,7 @@ export interface Binding {
hoists: Map<Section, Binding>;
property: string | undefined;
propertyAliases: Map<string, Binding>;
excludeProperties: undefined | string[];
excludeProperties: Opt<string>;
upstreamAlias: Binding | undefined;
downstreamExpressions: Set<ReferencedExtra>;
scopeOffset: Binding | undefined;
@ -145,6 +145,7 @@ export function createBinding(
section: Section,
upstreamAlias?: Binding["upstreamAlias"],
property?: string,
excludeProperties?: Opt<string>,
loc: t.SourceLocation | null = null,
declared = false,
): Binding {
@ -159,7 +160,7 @@ export function createBinding(
declared,
closureSections: undefined,
assignmentSections: undefined,
excludeProperties: undefined,
excludeProperties,
sources: undefined,
aliases: new Set(),
hoists: new Map(),
@ -168,7 +169,7 @@ export function createBinding(
downstreamExpressions: new Set(),
scopeOffset: undefined,
export: undefined,
nullable: true,
nullable: excludeProperties === undefined,
};
if (property) {
@ -208,6 +209,7 @@ export function trackVarReferences(
canonicalUpstreamAlias.section,
canonicalUpstreamAlias,
undefined,
undefined,
);
return canonicalUpstreamAlias;
}
@ -219,6 +221,7 @@ export function trackVarReferences(
getOrCreateSection(tag),
undefined,
undefined,
undefined,
);
return tagVar.extra?.binding;
}
@ -252,15 +255,28 @@ export function trackParamsReferences(
section.params = paramsBinding;
for (let i = 0; i < params.length; i++) {
// TODO: need to support spread here.
createBindingsAndTrackReferences(
params[i],
type,
body.scope,
section,
paramsBinding,
i + "",
);
const param = params[i];
if (param.type === "RestElement") {
createBindingsAndTrackReferences(
param.argument,
type,
body.scope,
section,
paramsBinding,
undefined,
addNumericPropertiesUntil(undefined, i - 1),
);
} else {
createBindingsAndTrackReferences(
param,
type,
body.scope,
section,
paramsBinding,
i + "",
undefined,
);
}
}
return paramsBinding;
@ -286,6 +302,7 @@ export function trackHoistedReference(
hoistSection,
undefined,
undefined,
undefined,
binding.loc,
true,
)),
@ -374,6 +391,7 @@ function trackAssignment(
binding.section,
binding.upstreamAlias,
changePropName,
undefined,
id.node.loc,
true,
);
@ -405,6 +423,7 @@ function createBindingsAndTrackReferences(
section: Section,
upstreamAlias: Binding["upstreamAlias"] | undefined,
property: string | undefined,
excludeProperties: Opt<string>,
) {
switch (lVal.type) {
case "Identifier":
@ -416,6 +435,7 @@ function createBindingsAndTrackReferences(
section,
upstreamAlias,
property,
excludeProperties,
lVal.loc,
true,
)),
@ -432,13 +452,14 @@ function createBindingsAndTrackReferences(
section,
upstreamAlias,
property,
excludeProperties,
lVal.loc,
));
const hasRest =
lVal.properties[lVal.properties.length - 1]?.type === "RestElement";
for (const prop of lVal.properties) {
if (prop.type === "RestElement") {
// TODO: this makes rest an alias, but it really should be
// a partial alias with some keys removed
createBindingsAndTrackReferences(
prop.argument,
type,
@ -446,6 +467,7 @@ function createBindingsAndTrackReferences(
section,
patternBinding,
property,
excludeProperties,
);
} else {
let key: string;
@ -459,6 +481,10 @@ function createBindingsAndTrackReferences(
throw new Error("computed keys not supported in object pattern");
}
if (hasRest) {
excludeProperties = propsUtil.add(excludeProperties, key);
}
createBindingsAndTrackReferences(
prop.value as t.LVal,
type,
@ -466,6 +492,7 @@ function createBindingsAndTrackReferences(
section,
patternBinding,
key,
undefined,
);
}
}
@ -482,6 +509,7 @@ function createBindingsAndTrackReferences(
section,
upstreamAlias,
property,
excludeProperties,
lVal.loc,
));
@ -490,8 +518,10 @@ function createBindingsAndTrackReferences(
i++;
if (element) {
if (element.type === "RestElement") {
// TODO: this makes rest an alias, but it really should be
// a partial alias with some keys removed
excludeProperties = addNumericPropertiesUntil(
excludeProperties,
i - 1,
);
createBindingsAndTrackReferences(
element.argument,
type,
@ -499,6 +529,7 @@ function createBindingsAndTrackReferences(
section,
patternBinding,
property,
excludeProperties,
);
} else {
createBindingsAndTrackReferences(
@ -508,6 +539,7 @@ function createBindingsAndTrackReferences(
section,
patternBinding,
`${i}`,
undefined,
);
}
}
@ -524,6 +556,7 @@ function createBindingsAndTrackReferences(
section,
upstreamAlias,
property,
undefined,
);
break;
}
@ -545,6 +578,14 @@ function trackReference(
const prop = getMemberExpressionPropString(parent);
if (prop === undefined) break;
if (
reference.upstreamAlias &&
reference.excludeProperties !== undefined &&
!propsUtil.has(reference.excludeProperties, prop)
) {
reference = reference.upstreamAlias;
}
if (reference.propertyAliases.has(prop)) {
root = root.parentPath as t.NodePath<t.MemberExpression>;
reference = reference.propertyAliases.get(prop)!;
@ -1159,6 +1200,10 @@ export const bindingUtil = new Sorted(function compareBindings(
: a.id - b.id;
});
const propsUtil = new Sorted(function compareProps(a: string, b: string) {
return a < b ? -1 : a > b ? 1 : 0;
});
const [getReadsByExpression] = createProgramState(
() => new Map<ReferencedExtra, Opt<Read>>(),
);
@ -1201,9 +1246,16 @@ export function dropReferences(node: t.Node | t.Node[]) {
}
export function getCanonicalBinding(binding?: Binding) {
return (
binding && (binding.property ? binding : binding.upstreamAlias || binding)
);
const alias = binding?.upstreamAlias;
if (
alias &&
binding.property === undefined &&
binding.excludeProperties === undefined
) {
return alias;
}
return binding;
}
export function getAllTagReferenceNodes(
@ -1269,7 +1321,11 @@ export function getScopeAccessor(binding: Binding, includeId?: boolean) {
export function getDebugScopeAccess(binding: Binding) {
let root = binding;
let access = "";
while (!(root.loc || root.declared) && root.upstreamAlias) {
while (
!(root.loc || root.declared) &&
root.upstreamAlias &&
root.excludeProperties === undefined
) {
if (root.property !== undefined) {
access = toAccess(root.property) + access;
}
@ -1481,7 +1537,13 @@ function isSupersetSources(a: Binding, b: Binding) {
}
function getCanonicalProperty(binding: Binding) {
return binding.property ?? binding.upstreamAlias?.property;
if (binding.property !== undefined) {
return binding.property;
}
if (binding.upstreamAlias && binding.excludeProperties === undefined) {
return binding.upstreamAlias.property;
}
}
function createRead(binding: Binding, props: Opt<string>) {
@ -1533,3 +1595,11 @@ export function isRegisteredFnExtra(
): extra is RegisteredFnExtra {
return isReferencedExtra(extra) && extra.registerId !== undefined;
}
function addNumericPropertiesUntil(props: Opt<string>, len: number) {
let result = props;
for (let i = len; i--; ) {
result = propsUtil.add(result, i + "");
}
return result;
}

View File

@ -57,7 +57,11 @@ import {
toFirstExpressionOrBlock,
toParenthesizedExpressionIfNeeded,
} from "./to-first-expression-or-block";
import { toMemberExpression, toObjectProperty } from "./to-property-name";
import {
toMemberExpression,
toObjectProperty,
toPropertyName,
} from "./to-property-name";
import { traverseContains, traverseReplace } from "./traverse";
export type Signal = {
@ -275,7 +279,10 @@ export function initValue(
(binding.type === BindingType.param ||
binding.type === BindingType.local ||
binding.type === BindingType.input);
const isNakedAlias = binding.upstreamAlias && !binding.property;
const isNakedAlias =
binding.upstreamAlias &&
binding.property === undefined &&
binding.excludeProperties === undefined;
const needsGuard =
!isNakedAlias &&
(binding.closureSections ||
@ -311,7 +318,7 @@ export function initValue(
export function getSignalFn(signal: Signal): t.Expression {
const section = signal.section;
const binding = signal.referencedBindings;
const params: t.Identifier[] = [scopeIdentifier];
const params: (t.Identifier | t.ObjectPattern)[] = [scopeIdentifier];
const isIntersection = Array.isArray(binding);
const isBinding = binding && !isIntersection;
const isValue = isBinding && binding.section === section;
@ -328,6 +335,7 @@ export function getSignalFn(signal: Signal): t.Expression {
valueParam.start = (binding.loc.start as any).index;
valueParam.end = (binding.loc.end as any).index;
}
params.push(valueParam);
}
@ -339,15 +347,49 @@ export function getSignalFn(signal: Signal): t.Expression {
aliasSignal.values.length ||
aliasSignal.effect.length
) {
signal.render.push(
t.expressionStatement(
t.callExpression(aliasSignal.identifier, [
scopeIdentifier,
t.identifier(binding.name),
...getTranslatedExtraArgs(aliasSignal),
]),
),
);
if (alias.excludeProperties !== undefined) {
const props: t.ObjectPattern["properties"] = [];
const aliasId = t.identifier(alias.name);
forEach(alias.excludeProperties, (name) => {
const propId = toPropertyName(name);
const shorthand = propId.type === "Identifier";
props.push(
t.objectProperty(
propId,
propId.type === "Identifier" ? propId : t.objectPattern([]),
false,
shorthand,
),
);
});
props.push(t.restElement(aliasId));
signal.render.push(
t.expressionStatement(
t.callExpression(
t.arrowFunctionExpression(
[t.objectPattern(props)],
t.callExpression(aliasSignal.identifier, [
scopeIdentifier,
aliasId,
...getTranslatedExtraArgs(aliasSignal),
]),
),
[t.identifier(binding.name)],
),
),
);
} else {
signal.render.push(
t.expressionStatement(
t.callExpression(aliasSignal.identifier, [
scopeIdentifier,
t.identifier(binding.name),
...getTranslatedExtraArgs(aliasSignal),
]),
),
);
}
}
}
@ -375,7 +417,8 @@ export function getSignalFn(signal: Signal): t.Expression {
!valSignal.referencedBindings ||
Array.isArray(valSignal.referencedBindings) ||
!valSignal.referencedBindings.upstreamAlias ||
valSignal.referencedBindings.property ||
valSignal.referencedBindings.property !== undefined ||
valSignal.referencedBindings.excludeProperties !== undefined ||
valSignal.effect.length ||
valSignal.render.length ||
valSignal.values.length
@ -481,7 +524,11 @@ export function getSignalFn(signal: Signal): t.Expression {
for (; i--; ) {
const param = params[i];
const arg = args[i];
if (arg.type !== "Identifier" || param.name !== arg.name) {
if (
arg.type !== "Identifier" ||
param.type !== "Identifier" ||
param.name !== arg.name
) {
break;
}
}
@ -525,7 +572,10 @@ export function subscribe(references: ReferencedBindings, subscriber: Signal) {
if (references) {
forEach(references, (binding) => {
const source =
(binding.property === undefined && binding.upstreamAlias) || binding;
(binding.property === undefined &&
binding.excludeProperties === undefined &&
binding.upstreamAlias) ||
binding;
const providerSignal = getSignal(subscriber.section, source);
providerSignal.intersection = push(
providerSignal.intersection,
@ -768,7 +818,8 @@ export function writeSignals(section: Section) {
signal.referencedBindings &&
!Array.isArray(signal.referencedBindings) &&
signal.referencedBindings.upstreamAlias &&
!signal.referencedBindings.property &&
signal.referencedBindings.property === undefined &&
signal.referencedBindings.excludeProperties === undefined &&
t.isFunction(value) &&
t.isBlockStatement(value.body) &&
!value.body.body.length