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 +1 -1
- package/dist/{wrec-C03zEU8-.js → wrec-fWpfp5nc.js} +52 -49
- package/dist/wrec-ssr.es.js +1 -1
- package/dist/wrec.es.js +1 -1
- package/package.json +1 -1
- package/scripts/lint.js +78 -27
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
|
|
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
|
|
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.
|
|
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
|
|
205
|
-
function
|
|
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
|
|
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
|
|
214
|
-
let [r
|
|
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 =
|
|
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" &&
|
|
229
|
+
i !== a && (e.setAttribute(r, a), r === "value" && R(e) && (e.value = a));
|
|
222
230
|
}
|
|
223
231
|
else {
|
|
224
|
-
let r =
|
|
232
|
+
let r = K.getPropName(t);
|
|
225
233
|
e[r] = n;
|
|
226
234
|
}
|
|
227
235
|
}
|
|
228
|
-
function
|
|
229
|
-
let [r
|
|
230
|
-
e instanceof CSSStyleRule ? e.style.setProperty(r, 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
|
|
233
|
-
async function
|
|
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
|
|
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
|
|
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 =
|
|
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"
|
|
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) :
|
|
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 (
|
|
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 &&
|
|
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 =
|
|
552
|
-
if (!(
|
|
553
|
-
|
|
554
|
-
let
|
|
555
|
-
e.addEventListener(
|
|
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[
|
|
559
|
-
|
|
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))
|
|
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 ?
|
|
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 ?
|
|
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
|
|
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)) &&
|
|
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 &&
|
|
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 =
|
|
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
|
|
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 =
|
|
856
|
+
n = B(n, e.index, e[0].length, i);
|
|
854
857
|
}
|
|
855
858
|
}
|
|
856
859
|
}
|
|
857
860
|
return n;
|
|
858
861
|
}
|
|
859
|
-
function
|
|
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 =
|
|
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 =
|
|
870
|
+
n = B(n, i, t.length, r);
|
|
868
871
|
}
|
|
869
872
|
}
|
|
870
873
|
return n;
|
|
871
874
|
}
|
|
872
875
|
//#endregion
|
|
873
|
-
export { a,
|
|
876
|
+
export { a, J as i, O as n, q as r, K as t };
|
package/dist/wrec-ssr.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, i as t, n, r, t as i } from "./wrec-
|
|
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-
|
|
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
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
|
|
401
|
-
|
|
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
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
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') {
|