wrec 0.31.1 → 0.31.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <img alt="shipwreck" src="shipwreck.png" style="width: 256px">
4
4
 
5
- Wrec is a small library that greatly simplifies building web components.
5
+ Wrec is a library that greatly simplifies building web components.
6
6
  It is described in detail, with several working examples,
7
7
  in [my blog](https://mvolkmann.github.io/blog/wrec/).
8
8
  Also, see my series of
@@ -167,7 +167,7 @@ var f = () => /* @__PURE__ */ new Map(), p = new Set([
167
167
  whenDefined: () => Promise.reject(/* @__PURE__ */ Error("customElements is not available in this environment"))
168
168
  }, g = class extends Error {}, _ = /([a-zA-Z-]+)\s*:\s*([^;}]+)/g, v = "a-zA-Z_$", y = `[${v}][${v + "0-9"}]*`, b = RegExp(`this\\.(${y})\\s*\\(`, "g"), x = /<!--\s*(.*?)\s*-->/, S = /<(\w+)(?:\s[^>]*)?>((?:[^<]|<(?!\w))*?)<\/\1>/g, C = RegExp(`^this\\.${y}$`), w = RegExp(`this\\.${y}(\\.${y})*`, "g"), T = RegExp(`this\\.${y}(\\.${y})*`), E = 5;
169
169
  function D(e) {
170
- return e instanceof HTMLButtonElement || e instanceof HTMLFieldSetElement || e instanceof HTMLInputElement || e instanceof HTMLSelectElement || e instanceof HTMLTextAreaElement || e instanceof G;
170
+ return e instanceof HTMLButtonElement || e instanceof HTMLFieldSetElement || e instanceof HTMLInputElement || e instanceof HTMLSelectElement || e instanceof HTMLTextAreaElement || e instanceof K;
171
171
  }
172
172
  function O(e, t, n) {
173
173
  let r = document.createElement(e);
@@ -195,42 +195,50 @@ function F(e) {
195
195
  return t === "string" || t === "number" || t === "boolean";
196
196
  }
197
197
  function I(e) {
198
- return e.localName === "textarea";
198
+ return e instanceof HTMLInputElement && e.type === "radio";
199
199
  }
200
200
  function L(e) {
201
+ return e.localName === "textarea";
202
+ }
203
+ function R(e) {
201
204
  let { localName: t } = e;
202
205
  return t === "input" || t === "select";
203
206
  }
204
- var R = (e) => e.replace(/<!--[\s\S]*?-->/g, "");
205
- function z(e, t, n, r) {
207
+ var z = (e) => e.replace(/<!--[\s\S]*?-->/g, "");
208
+ function B(e, t, n, r) {
206
209
  return e.slice(0, t) + r + e.slice(t + n);
207
210
  }
208
- function B(e) {
211
+ function V(e) {
209
212
  let t = Number(e);
210
213
  if (isNaN(t)) throw new g(`can't convert "${e}" to a number`);
211
214
  return t;
212
215
  }
213
- function V(e, t, n) {
214
- let [r, i] = t.split(":");
216
+ function H(e, t, n) {
217
+ let [r] = t.split(":");
218
+ if (r === "checked" && I(e) && typeof n == "string") {
219
+ let t = e.value === n;
220
+ t ? e.setAttribute(r, r) : e.removeAttribute(r), e.checked = t;
221
+ return;
222
+ }
215
223
  if (F(n)) if (typeof n == "boolean") {
216
224
  n ? e.setAttribute(r, r) : e.removeAttribute(r);
217
- let t = G.getPropName(r);
225
+ let t = K.getPropName(r);
218
226
  e[t] = n;
219
227
  } else {
220
228
  let i = e.getAttribute(t), a = String(n);
221
- i !== a && (e.setAttribute(r, a), r === "value" && L(e) && (e.value = a));
229
+ i !== a && (e.setAttribute(r, a), r === "value" && R(e) && (e.value = a));
222
230
  }
223
231
  else {
224
- let r = G.getPropName(t);
232
+ let r = K.getPropName(t);
225
233
  e[r] = n;
226
234
  }
227
235
  }
228
- function H(e, t, n) {
229
- let [r, i] = t.split(":");
230
- e instanceof CSSStyleRule ? e.style.setProperty(r, n) : (V(e, r, n), r === "value" && L(e) && (e.value = n));
236
+ function U(e, t, n) {
237
+ let [r] = t.split(":");
238
+ e instanceof CSSStyleRule ? e.style.setProperty(r, n) : (H(e, r, n), r === "value" && R(e) && (e.value = n));
231
239
  }
232
- var U = (e) => typeof e == "string" ? [e] : e;
233
- async function W(e) {
240
+ var W = (e) => typeof e == "string" ? [e] : e;
241
+ async function G(e) {
234
242
  let t = /* @__PURE__ */ new Set();
235
243
  for (let n of j(e.content)) {
236
244
  let { localName: e } = n;
@@ -246,7 +254,7 @@ async function W(e) {
246
254
  }
247
255
  return Promise.all([...t].map(async (e) => Promise.race([h.whenDefined(e), n(e)])));
248
256
  }
249
- var G = class e extends m {
257
+ var K = class e extends m {
250
258
  static {
251
259
  this.attrToPropMap = /* @__PURE__ */ new Map();
252
260
  }
@@ -319,7 +327,7 @@ var G = class e extends m {
319
327
  }
320
328
  async #d() {
321
329
  let e = this.#n, { template: t } = e;
322
- t || (t = e.template = document.createElement("template"), t.innerHTML = e.buildHTML()), await W(t), this.shadowRoot.replaceChildren(t.content.cloneNode(!0));
330
+ t || (t = e.template = document.createElement("template"), t.innerHTML = e.buildHTML()), await G(t), this.shadowRoot.replaceChildren(t.content.cloneNode(!0));
323
331
  }
324
332
  static buildHTML() {
325
333
  let e = "<style>\n :host([hidden]) { display: none; }";
@@ -357,7 +365,7 @@ var G = class e extends m {
357
365
  return this[u];
358
366
  },
359
367
  set(e) {
360
- n.computed && !this.#t.has(t) && this.#F(null, t, "is a computed property and cannot be set directly"), c === Number && typeof e == "string" && (e = B(e));
368
+ n.computed && !this.#t.has(t) && this.#F(null, t, "is a computed property and cannot be set directly"), c === Number && typeof e == "string" && (e = V(e));
361
369
  let r = this[u];
362
370
  if (e === r) return;
363
371
  this.#q(t, c, e), this[u] = e;
@@ -404,12 +412,12 @@ var G = class e extends m {
404
412
  let i = this[a];
405
413
  i === void 0 && this.#I(t, r, a);
406
414
  let [o, s] = r.split(":"), c = e.getPropName(o);
407
- if (o === "checked" && P(t)) {
415
+ if (o === "checked") {
408
416
  let { type: e } = this.#n.properties[a];
409
- e !== Boolean && this.#F(t, r, `refers to property "${a}" whose type is not Boolean`);
417
+ P(t) && e !== Boolean && this.#F(t, r, `refers to property "${a}" whose type is not Boolean`), I(t) && e !== String && this.#F(t, r, `refers to property "${a}" whose type is not String`);
410
418
  }
411
419
  let l = this.#T(a);
412
- n && t.#T(c) || (t[c] = i), o === "value" && (s ? (t["on" + s] === void 0 && this.#F(t, r, "refers to an unsupported event name"), t.setAttribute(o, this[a])) : s = "change"), n && !l && t.#c.set(e.getPropName(o), a);
420
+ n && t.#T(c) || (o === "checked" && I(t) ? t.checked = t.value === i : t[c] = i), o === "value" && (s ? (t["on" + s] === void 0 && this.#F(t, r, "refers to an unsupported event name"), t.setAttribute(o, this[a])) : s = "change"), n && !l && t.#c.set(e.getPropName(o), a);
413
421
  }
414
422
  this.#N(i, t, r);
415
423
  }
@@ -426,7 +434,7 @@ var G = class e extends m {
426
434
  if (t instanceof HTMLElement) this.#V(t, e);
427
435
  else if (!(t instanceof CSSStyleRule)) {
428
436
  let { element: n, attrName: r } = t;
429
- n instanceof CSSStyleRule ? n.style.setProperty(r, e) : H(n, r, e);
437
+ n instanceof CSSStyleRule ? n.style.setProperty(r, e) : U(n, r, e);
430
438
  }
431
439
  }
432
440
  if (r.size > 0) {
@@ -452,7 +460,7 @@ var G = class e extends m {
452
460
  }
453
461
  } else {
454
462
  let t = "";
455
- if (I(e)) {
463
+ if (L(e)) {
456
464
  this.#N(e.textContent, e);
457
465
  let n = e.textContent?.match(x);
458
466
  n && (t = n[1]);
@@ -462,7 +470,7 @@ var G = class e extends m {
462
470
  }
463
471
  if (t) {
464
472
  let n = this.#O(e, t);
465
- n && I(e) ? e.textContent = this[n] : this.#N(t, e);
473
+ n && L(e) ? e.textContent = this[n] : this.#N(t, e);
466
474
  }
467
475
  }
468
476
  }
@@ -548,20 +556,15 @@ var G = class e extends m {
548
556
  if (n.length !== 1) return;
549
557
  let [r] = n;
550
558
  if (!C.test(r)) return;
551
- let i = P(e), a = L(e) || I(e), [o, s] = (t ?? "").split(":");
552
- if (!(a && o === "value" || i && o === "checked" || I(e))) return;
553
- s ? e["on" + s] === void 0 && this.#F(e, t, "refers to an unsupported event name") : s = "change";
554
- let c = M(r);
555
- e.addEventListener(s, (e) => {
559
+ let i = P(e), a = I(e), o = R(e) || L(e), [s, c] = (t ?? "").split(":");
560
+ if (!(o && s === "value" || i && s === "checked" || a && s === "checked" || L(e))) return;
561
+ c ? e["on" + c] === void 0 && this.#F(e, t, "refers to an unsupported event name") : c = "change";
562
+ let l = M(r);
563
+ e.addEventListener(c, (e) => {
556
564
  let { target: t } = e;
557
565
  if (!t) return;
558
- let { type: n } = this.#n.properties[c];
559
- if (o === "checked") this[c] = t.checked;
560
- else {
561
- let e = t.value;
562
- this[c] = n === Number ? B(e) : e;
563
- }
564
- this.#k(c);
566
+ let { type: n } = this.#n.properties[l], r = t, { value: o } = r;
567
+ s === "checked" ? i ? this[l] = r.checked : a && r.checked && (this[l] = o) : this[l] = n === Number ? V(o) : o, this.#k(l);
565
568
  });
566
569
  }
567
570
  #C(e) {
@@ -620,7 +623,7 @@ var G = class e extends m {
620
623
  for (let t of a.matchAll(b)) {
621
624
  let r = t[1];
622
625
  if (typeof this[r] != "function") throw new g(`property ${e} computed calls non-method ${r}`);
623
- for (let [e, t] of Object.entries(n.properties)) U(t.usedBy)?.includes(r) && i(e, a);
626
+ for (let [e, t] of Object.entries(n.properties)) W(t.usedBy)?.includes(r) && i(e, a);
624
627
  }
625
628
  }
626
629
  #M(e, t) {
@@ -636,7 +639,7 @@ var G = class e extends m {
636
639
  let r = this.#G(t, n, e);
637
640
  if (!r) {
638
641
  let r = e.replaceAll("this..", "this.");
639
- n ? H(t, n, r) : "textContent" in t && (t.textContent = r);
642
+ n ? U(t, n, r) : "textContent" in t && (t.textContent = r);
640
643
  return;
641
644
  }
642
645
  let i = this.#n;
@@ -656,7 +659,7 @@ var G = class e extends m {
656
659
  attrName: n
657
660
  } : t), t instanceof HTMLElement && this.#S(t, n, r);
658
661
  let o = this.#v(e);
659
- n ? H(t, n, o) : this.#V(t, o);
662
+ n ? U(t, n, o) : this.#V(t, o);
660
663
  }
661
664
  #P(e, t) {
662
665
  let n = t?.trim() ?? "", r = this.#n.properties[n];
@@ -692,7 +695,7 @@ var G = class e extends m {
692
695
  }
693
696
  return n;
694
697
  }
695
- if (i === Number) return B(n);
698
+ if (i === Number) return V(n);
696
699
  if (i === Boolean) {
697
700
  if (n === "true") return !0;
698
701
  if (n === "false" || n === "null") return !1;
@@ -701,7 +704,7 @@ var G = class e extends m {
701
704
  }
702
705
  }
703
706
  #z(e, t, n, r) {
704
- F(n) && !this.#T(e) && n !== (t === Boolean ? this.hasAttribute(r) : this.#L(e, r)) && V(this, r || e, n);
707
+ F(n) && !this.#T(e) && n !== (t === Boolean ? this.hasAttribute(r) : this.#L(e, r)) && H(this, r || e, n);
705
708
  }
706
709
  #B(e) {
707
710
  for (let [t, n] of this.#x([e])) this.#M(t, this.#v(n));
@@ -713,7 +716,7 @@ var G = class e extends m {
713
716
  let r = typeof t;
714
717
  r !== "string" && r !== "number" && this.#F(e, void 0, " computed content is not a string or number");
715
718
  let i = String(t);
716
- e instanceof HTMLElement && I(e) ? e.value = i : n && r === "string" && i.trim().startsWith("<") ? (e.innerHTML = d(i), this.#J(e), this.#E(e)) : n && (e.textContent = i);
719
+ e instanceof HTMLElement && L(e) ? e.value = i : n && r === "string" && i.trim().startsWith("<") ? (e.innerHTML = d(i), this.#J(e), this.#E(e)) : n && (e.textContent = i);
717
720
  }
718
721
  #H(e, t) {
719
722
  let n = this.#c.get(e);
@@ -738,7 +741,7 @@ var G = class e extends m {
738
741
  }
739
742
  let { properties: n, propToExprsMap: r } = e;
740
743
  for (let [i, a] of Object.entries(n)) {
741
- let n = U(a.usedBy);
744
+ let n = W(a.usedBy);
742
745
  if (!n) continue;
743
746
  e.methodToExprsMap || t.call(this);
744
747
  let { methodToExprsMap: o } = e, s = r.get(i);
@@ -839,7 +842,7 @@ var G = class e extends m {
839
842
  }
840
843
  }
841
844
  };
842
- function K(e, ...t) {
845
+ function q(e, ...t) {
843
846
  let n = N(e, t);
844
847
  for (;;) {
845
848
  let e = _.exec(n);
@@ -850,24 +853,24 @@ function K(e, ...t) {
850
853
  if (!r.startsWith("--")) {
851
854
  let i = `--${r}: ${t};
852
855
  ${r}: var(--${r})`;
853
- n = z(n, e.index, e[0].length, i);
856
+ n = B(n, e.index, e[0].length, i);
854
857
  }
855
858
  }
856
859
  }
857
860
  return n;
858
861
  }
859
- function q(e, ...t) {
862
+ function J(e, ...t) {
860
863
  let n = N(e, t);
861
864
  for (;;) {
862
865
  let e = S.exec(n);
863
866
  if (!e || e[1] === "style") break;
864
- let t = R(e[2]);
867
+ let t = z(e[2]);
865
868
  if (T.test(t)) {
866
869
  let r = `<!-- ${t.trim()} -->`, i = e.index + e[0].indexOf(">") + 1;
867
- n = z(n, i, t.length, r);
870
+ n = B(n, i, t.length, r);
868
871
  }
869
872
  }
870
873
  return n;
871
874
  }
872
875
  //#endregion
873
- export { a, q as i, O as n, K as r, G as t };
876
+ export { a, J as i, O as n, q as r, K as t };
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, n, r, t as i } from "./wrec-C03zEU8-.js";
1
+ import { a as e, i as t, n, r, t as i } from "./wrec-fWpfp5nc.js";
2
2
  //#region \0rolldown/runtime.js
3
3
  var a = Object.defineProperty, o = Object.getOwnPropertyDescriptor, s = Object.getOwnPropertyNames, c = Object.prototype.hasOwnProperty, l = (e, t) => () => (e && (t = e(e = 0)), t), u = (e, t) => () => (t || e((t = { exports: {} }).exports, t), t.exports), d = (e, t) => {
4
4
  let n = {};
package/dist/wrec.es.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./wrec-C03zEU8-.js";
1
+ import { a as e, i as t, n, r, t as i } from "./wrec-fWpfp5nc.js";
2
2
  export { i as Wrec, e as WrecState, n as createElement, r as css, t as html };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "wrec",
3
3
  "description": "a small library that greatly simplifies building web components",
4
4
  "author": "R. Mark Volkmann",
5
- "version": "0.31.1",
5
+ "version": "0.31.3",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/scripts/lint.js CHANGED
@@ -30,6 +30,8 @@
30
30
  // - invalid HTML element nesting in templates
31
31
  // - invalid ref attribute targets
32
32
  // - duplicate ref attribute values
33
+ // - checkbox checked bindings that do not reference Boolean properties
34
+ // - radio checked bindings that do not reference String properties
33
35
 
34
36
  import fs from 'node:fs';
35
37
  import path from 'node:path';
@@ -43,8 +45,9 @@ import {
43
45
  } from './ast-utils.js';
44
46
 
45
47
  const CSS_PROPERTY_RE = /([a-zA-Z-]+)\s*:\s*([^;}]+)/g;
46
- const REFS_TEST_RE = /this\.[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*/;
47
48
  const IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/;
49
+ const PROPERTY_REF_RE = /^this\.([A-Za-z_$][\w$]*)$/;
50
+ const REFS_TEST_RE = /this\.[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*/;
48
51
  const HTML_GLOBAL_ATTRIBUTES = new Set([
49
52
  'aria-label',
50
53
  'class',
@@ -142,13 +145,7 @@ const componentPropertyCache = new Map();
142
145
 
143
146
  // Analyzes code for invalid property access,
144
147
  // method calls, and arithmetic usage.
145
- function analyzeCodeNode(
146
- codeNode,
147
- checker,
148
- classNode,
149
- findings,
150
- metadata
151
- ) {
148
+ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
152
149
  if (metadata.eventHandler && ts.isIdentifier(codeNode)) {
153
150
  if (!metadata.classMethods.has(codeNode.text)) {
154
151
  uniquePush(
@@ -315,9 +312,7 @@ function buildAugmentedSource(
315
312
  : `${classNode.name.text} & __WrecSupportedProps`;
316
313
  const rewrittenText = item.text.replace(/\bthis\b/g, WREC_REF_NAME);
317
314
  const helperBody =
318
- item.shape === 'block'
319
- ? rewrittenText
320
- : `return (${rewrittenText});`;
315
+ item.shape === 'block' ? rewrittenText : `return (${rewrittenText});`;
321
316
  return `
322
317
  function __wrec_expr_${index}() {
323
318
  const ${WREC_REF_NAME} = null as unknown as ${targetType};
@@ -397,9 +392,11 @@ function collectMethodCodeItems(classNode) {
397
392
  context: 'instance',
398
393
  eventHandler: false,
399
394
  kind: 'method',
400
- location: member.getSourceFile().getLineAndCharacterOfPosition(
401
- member.name.getStart(member.getSourceFile())
402
- ),
395
+ location: member
396
+ .getSourceFile()
397
+ .getLineAndCharacterOfPosition(
398
+ member.name.getStart(member.getSourceFile())
399
+ ),
403
400
  shape: 'block',
404
401
  text: member.body.getText()
405
402
  });
@@ -799,6 +796,7 @@ function formatReport(
799
796
  findings.duplicateProperties.length > 0 ||
800
797
  findings.reservedProperties.length > 0 ||
801
798
  findings.invalidUsedByReferences.length > 0 ||
799
+ findings.invalidCheckedBindings.length > 0 ||
802
800
  findings.invalidComputedProperties.length > 0 ||
803
801
  findings.invalidRefAttributes.length > 0 ||
804
802
  findings.invalidValuesConfigurations.length > 0 ||
@@ -868,6 +866,13 @@ function formatReport(
868
866
  );
869
867
  }
870
868
 
869
+ if (findings.invalidCheckedBindings.length > 0) {
870
+ lines.push('invalid checked bindings:');
871
+ findings.invalidCheckedBindings.forEach(message =>
872
+ lines.push(` ${message}`)
873
+ );
874
+ }
875
+
871
876
  if (findings.invalidRefAttributes.length > 0) {
872
877
  lines.push('invalid ref attributes:');
873
878
  findings.invalidRefAttributes.forEach(message =>
@@ -1046,6 +1051,12 @@ function getComponentPropertyMaps(filePath, sourceText, seen = new Set()) {
1046
1051
  return propertyMaps;
1047
1052
  }
1048
1053
 
1054
+ // Returns the referenced property name for a single `this.prop` binding.
1055
+ function getPropertyNameInAttribute(attrValue) {
1056
+ const match = attrValue.trim().match(PROPERTY_REF_RE);
1057
+ return match ? match[1] : undefined;
1058
+ }
1059
+
1049
1060
  // Returns trimmed source text for a TypeScript expression node.
1050
1061
  function getExpressionText(sourceFile, expression) {
1051
1062
  return expression.getText(sourceFile).trim();
@@ -1057,6 +1068,12 @@ function getHtmlTagName(node) {
1057
1068
  return typeof tagName === 'string' ? tagName.toLowerCase() : '';
1058
1069
  }
1059
1070
 
1071
+ // Returns the literal input type attribute value when one is present.
1072
+ function getInputType(node) {
1073
+ const type = node.getAttribute('type');
1074
+ return typeof type === 'string' ? type.toLowerCase() : undefined;
1075
+ }
1076
+
1060
1077
  // Gets an object-literal property with the given key.
1061
1078
  function getObjectProperty(objectLiteral, key) {
1062
1079
  for (const property of objectLiteral.properties) {
@@ -1295,6 +1312,7 @@ export function lintSource(filePath, sourceText, options = {}) {
1295
1312
  duplicateProperties,
1296
1313
  extraArguments: [],
1297
1314
  incompatibleArguments: [],
1315
+ invalidCheckedBindings: [],
1298
1316
  invalidComputedProperties: [],
1299
1317
  invalidDefaultValues: [],
1300
1318
  invalidEventHandlers: [],
@@ -1377,19 +1395,13 @@ export function lintSource(filePath, sourceText, options = {}) {
1377
1395
 
1378
1396
  helperCodeNodes.forEach((codeNode, index) => {
1379
1397
  if (!codeNode) return;
1380
- analyzeCodeNode(
1381
- codeNode,
1382
- augmentedChecker,
1383
- augmentedClassNode,
1384
- findings,
1385
- {
1386
- classMethods: allMethods,
1387
- contextKeys: new Set(contextKeys),
1388
- checkContextCalls: allCodeItems[index]?.checkContextCalls ?? true,
1389
- eventHandler: allCodeItems[index]?.eventHandler ?? false,
1390
- sourceFile: augmentedSourceFile
1391
- }
1392
- );
1398
+ analyzeCodeNode(codeNode, augmentedChecker, augmentedClassNode, findings, {
1399
+ classMethods: allMethods,
1400
+ contextKeys: new Set(contextKeys),
1401
+ checkContextCalls: allCodeItems[index]?.checkContextCalls ?? true,
1402
+ eventHandler: allCodeItems[index]?.eventHandler ?? false,
1403
+ sourceFile: augmentedSourceFile
1404
+ });
1393
1405
  });
1394
1406
 
1395
1407
  findings.duplicateProperties.sort();
@@ -1403,6 +1415,7 @@ export function lintSource(filePath, sourceText, options = {}) {
1403
1415
  a.methodName.localeCompare(b.methodName) ||
1404
1416
  a.parameterName.localeCompare(b.parameterName)
1405
1417
  );
1418
+ findings.invalidCheckedBindings.sort();
1406
1419
  findings.invalidComputedProperties.sort();
1407
1420
  findings.invalidDefaultValues.sort();
1408
1421
  findings.invalidEventHandlers.sort();
@@ -1552,6 +1565,37 @@ function uniquePush(array, value) {
1552
1565
  if (!array.includes(value)) array.push(value);
1553
1566
  }
1554
1567
 
1568
+ // Validates checked bindings for checkbox and radio input elements.
1569
+ function validateCheckedBinding(
1570
+ node,
1571
+ attrName,
1572
+ attrValue,
1573
+ findings,
1574
+ supportedProps
1575
+ ) {
1576
+ if (getHtmlTagName(node) !== 'input') return;
1577
+
1578
+ const [baseAttrName] = attrName.split(':');
1579
+ if (baseAttrName !== 'checked') return;
1580
+
1581
+ const inputType = getInputType(node);
1582
+ if (inputType !== 'checkbox' && inputType !== 'radio') return;
1583
+
1584
+ const propName = getPropertyNameInAttribute(attrValue);
1585
+ if (!propName) return;
1586
+
1587
+ const propInfo = supportedProps.get(propName);
1588
+ if (!propInfo) return;
1589
+
1590
+ const expectedType = inputType === 'checkbox' ? 'boolean' : 'string';
1591
+ if (propInfo.typeText === expectedType) return;
1592
+
1593
+ findings.invalidCheckedBindings.push(
1594
+ `input type="${inputType}" attribute "${attrName}" refers to ` +
1595
+ `property "${propName}" whose type is not ${expectedType}`
1596
+ );
1597
+ }
1598
+
1555
1599
  // Validates computed property references and method calls.
1556
1600
  function validateComputedProperty(
1557
1601
  propName,
@@ -1926,6 +1970,13 @@ function walkHtmlNode(
1926
1970
  findings,
1927
1971
  componentPropertyMaps
1928
1972
  );
1973
+ validateCheckedBinding(
1974
+ node,
1975
+ attrName,
1976
+ attrValue,
1977
+ findings,
1978
+ supportedProps
1979
+ );
1929
1980
  validateHtmlAttribute(node, attrName, findings);
1930
1981
  validateValueBindingEvent(node, attrName, findings);
1931
1982
  if (attrName === 'ref') {