mirror of
https://github.com/marko-js/marko.git
synced 2026-02-01 16:07:13 +00:00
fix: improve exclusive change handler errors
This commit is contained in:
parent
7d72ba8c2f
commit
0247892da8
5
.changeset/late-lions-read.md
Normal file
5
.changeset/late-lions-read.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@marko/runtime-tags": patch
|
||||
---
|
||||
|
||||
Improve error messaging when exclusive change handlers are specified on native elements.
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"vars": {
|
||||
"props": {
|
||||
"$_": "r",
|
||||
"$init": "m"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1,4 @@
|
||||
// size: 73 (min) 77 (brotli)
|
||||
(_._script("a1", ($scope) => _._attrs_script($scope, 0)),
|
||||
_._resume("a0", function () {}),
|
||||
init());
|
||||
@ -0,0 +1,17 @@
|
||||
export const $template = "<input>";
|
||||
export const $walks = /* get, over(1) */" b";
|
||||
import * as _ from "@marko/runtime-tags/debug/dom";
|
||||
const $setup__script = _._script("__tests__/template.marko_0", $scope => _._attrs_script($scope, "#input/0"));
|
||||
export function $setup($scope) {
|
||||
_._attrs($scope, "#input/0", {
|
||||
type: "checkbox",
|
||||
checkedValue: 1,
|
||||
...{
|
||||
checkedChange: $checkedChange
|
||||
}
|
||||
});
|
||||
$setup__script($scope);
|
||||
}
|
||||
function $checkedChange() {}
|
||||
_._resume("__tests__/template.marko_0/checkedChange", $checkedChange);
|
||||
export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);
|
||||
@ -0,0 +1,13 @@
|
||||
import * as _ from "@marko/runtime-tags/debug/html";
|
||||
export default _._template("__tests__/template.marko", input => {
|
||||
const $scope0_id = _._scope_id();
|
||||
_._html(`<input${_._attrs({
|
||||
type: "checkbox",
|
||||
checkedValue: 1,
|
||||
...{
|
||||
checkedChange: _._resume(function () {}, "__tests__/template.marko_0/checkedChange")
|
||||
}
|
||||
}, "#input/0", $scope0_id, "input")}>${_._el_resume($scope0_id, "#input/0")}`);
|
||||
_._script($scope0_id, "__tests__/template.marko_0");
|
||||
_._scope($scope0_id, {}, "__tests__/template.marko", 0);
|
||||
});
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
<input type="checkbox" checkedValue=1 ...{ checkedChange() {} }>
|
||||
@ -0,0 +1 @@
|
||||
export const error_runtime = true;
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"vars": {
|
||||
"props": {
|
||||
"$_": "r",
|
||||
"$init": "m"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1,4 @@
|
||||
// size: 73 (min) 77 (brotli)
|
||||
(_._script("a1", ($scope) => _._attrs_script($scope, 0)),
|
||||
_._resume("a0", function () {}),
|
||||
init());
|
||||
@ -0,0 +1,17 @@
|
||||
export const $template = "<input>";
|
||||
export const $walks = /* get, over(1) */" b";
|
||||
import * as _ from "@marko/runtime-tags/debug/dom";
|
||||
const $setup__script = _._script("__tests__/template.marko_0", $scope => _._attrs_script($scope, "#input/0"));
|
||||
export function $setup($scope) {
|
||||
_._attrs($scope, "#input/0", {
|
||||
type: "checkbox",
|
||||
...{
|
||||
checkedValue: 1,
|
||||
checkedChange: $checkedChange
|
||||
}
|
||||
});
|
||||
$setup__script($scope);
|
||||
}
|
||||
function $checkedChange() {}
|
||||
_._resume("__tests__/template.marko_0/checkedChange", $checkedChange);
|
||||
export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);
|
||||
@ -0,0 +1,13 @@
|
||||
import * as _ from "@marko/runtime-tags/debug/html";
|
||||
export default _._template("__tests__/template.marko", input => {
|
||||
const $scope0_id = _._scope_id();
|
||||
_._html(`<input${_._attrs({
|
||||
type: "checkbox",
|
||||
...{
|
||||
checkedValue: 1,
|
||||
checkedChange: _._resume(function () {}, "__tests__/template.marko_0/checkedChange")
|
||||
}
|
||||
}, "#input/0", $scope0_id, "input")}>${_._el_resume($scope0_id, "#input/0")}`);
|
||||
_._script($scope0_id, "__tests__/template.marko_0");
|
||||
_._scope($scope0_id, {}, "__tests__/template.marko", 0);
|
||||
});
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
@ -0,0 +1 @@
|
||||
<input type="checkbox" ...{ checkedValue: 1, checkedChange() {} }>
|
||||
@ -0,0 +1 @@
|
||||
export const error_runtime = true;
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
at packages/runtime-tags/src/__tests__/fixtures/error-mututally-exclusive-static-native-attrs/template.marko:1:2
|
||||
> 1 | <input type="checkbox" checkedValue=1 checkedChange() {}>
|
||||
| ^^^^^ The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
2 |
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
at packages/runtime-tags/src/__tests__/fixtures/error-mututally-exclusive-static-native-attrs/template.marko:1:2
|
||||
> 1 | <input type="checkbox" checkedValue=1 checkedChange() {}>
|
||||
| ^^^^^ The attributes checkedChange and checkedValue are mutually exclusive.
|
||||
2 |
|
||||
@ -0,0 +1 @@
|
||||
<input type="checkbox" checkedValue=1 checkedChange() {}>
|
||||
@ -0,0 +1 @@
|
||||
export const error_compiler = true;
|
||||
@ -21,3 +21,55 @@ export function _assert_hoist(value: unknown) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertExclusiveAttrs(
|
||||
attrs: Record<string, unknown> | undefined,
|
||||
onError = throwErr,
|
||||
) {
|
||||
if (attrs) {
|
||||
let exclusiveAttrs: undefined | string[];
|
||||
if (attrs.checkedChange) {
|
||||
(exclusiveAttrs ||= []).push("checkedChange");
|
||||
}
|
||||
|
||||
if (attrs.checkedValue) {
|
||||
(exclusiveAttrs ||= []).push("checkedValue");
|
||||
|
||||
if (attrs.checked) {
|
||||
exclusiveAttrs.push("checked");
|
||||
}
|
||||
} else if (attrs.checkedValueChange) {
|
||||
(exclusiveAttrs ||= []).push("checkedValueChange");
|
||||
if (attrs.checked) {
|
||||
exclusiveAttrs.push("checked");
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.valueChange) {
|
||||
(exclusiveAttrs ||= []).push("valueChange");
|
||||
}
|
||||
|
||||
if (exclusiveAttrs && exclusiveAttrs.length > 1) {
|
||||
onError(
|
||||
`The attributes ${joinWithAnd(exclusiveAttrs)} are mutually exclusive.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function throwErr(msg: string) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
function joinWithAnd(a: string[]) {
|
||||
switch (a.length) {
|
||||
case 0:
|
||||
return "";
|
||||
case 1:
|
||||
return a[0];
|
||||
case 2:
|
||||
return `${a[0]} and ${a[1]}`;
|
||||
default:
|
||||
return `${a.slice(0, -1).join(", ")}, and ${a[a.length - 1]}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { assertExclusiveAttrs } from "../common/errors";
|
||||
import {
|
||||
classValue,
|
||||
getEventHandlerName,
|
||||
@ -123,6 +124,10 @@ export function _attrs(
|
||||
}
|
||||
}
|
||||
|
||||
if (MARKO_DEBUG) {
|
||||
assertExclusiveAttrs(nextAttrs);
|
||||
}
|
||||
|
||||
attrsInternal(scope, nodeAccessor, nextAttrs);
|
||||
}
|
||||
|
||||
@ -167,6 +172,10 @@ export function _attrs_partial(
|
||||
if (!skip[key]) partial[key] = nextAttrs[key];
|
||||
}
|
||||
|
||||
if (MARKO_DEBUG) {
|
||||
assertExclusiveAttrs({ ...nextAttrs, ...skip });
|
||||
}
|
||||
|
||||
attrsInternal(scope, nodeAccessor, partial);
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { assertExclusiveAttrs } from "../common/errors";
|
||||
import {
|
||||
classValue,
|
||||
getEventHandlerName,
|
||||
@ -172,6 +173,10 @@ export function _attrs(
|
||||
let events: Record<string, unknown> | undefined;
|
||||
switch (tagName) {
|
||||
case "input":
|
||||
if (MARKO_DEBUG) {
|
||||
assertExclusiveAttrs(data);
|
||||
}
|
||||
|
||||
if (data.checkedChange) {
|
||||
result += _attr_input_checked(
|
||||
scopeId,
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
getTagDef,
|
||||
} from "@marko/compiler/babel-utils";
|
||||
|
||||
import { assertExclusiveAttrs } from "../../../common/errors";
|
||||
import { getEventHandlerName, isEventHandler } from "../../../common/helpers";
|
||||
import { WalkCode } from "../../../common/types";
|
||||
import evaluate from "../../util/evaluate";
|
||||
@ -125,7 +126,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
assertExclusiveControllableGroups(tag, seen);
|
||||
assertExclusiveAttrs(seen, (msg) => {
|
||||
throw tag.get("name").buildCodeFrameError(msg);
|
||||
});
|
||||
|
||||
if (
|
||||
node.var ||
|
||||
@ -747,28 +750,6 @@ export default {
|
||||
}),
|
||||
} satisfies TemplateVisitor<t.MarkoTag>;
|
||||
|
||||
function assertExclusiveControllableGroups(
|
||||
tag: t.NodePath<t.MarkoTag>,
|
||||
attrs: Record<string, t.MarkoAttribute>,
|
||||
) {
|
||||
const exclusiveGroups = [
|
||||
attrs.open || attrs.openChange,
|
||||
attrs.checked || attrs.checkedChange,
|
||||
attrs.checkedValue || attrs.checkedValueChange,
|
||||
attrs.valueChange,
|
||||
].filter(Boolean);
|
||||
|
||||
if (exclusiveGroups.length > 1) {
|
||||
throw tag
|
||||
.get("name")
|
||||
.buildCodeFrameError(
|
||||
`The attributes ${exclusiveGroups
|
||||
.map((attr) => `"${attr.name}"`)
|
||||
.join(", ")} are mutually exclusive.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type RelatedControllable = ReturnType<typeof getRelatedControllable>;
|
||||
function getRelatedControllable(
|
||||
tagName: string,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user