wrec 0.31.6 → 0.32.1

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.
@@ -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 K;
170
+ return e instanceof HTMLButtonElement || e instanceof HTMLFieldSetElement || e instanceof HTMLInputElement || e instanceof HTMLSelectElement || e instanceof HTMLTextAreaElement || e instanceof X;
171
171
  }
172
172
  function O(e, t, n) {
173
173
  let r = document.createElement(e);
@@ -180,65 +180,79 @@ function j(e) {
180
180
  for (; n;) t.push(n), n.shadowRoot && t.push(...j(n.shadowRoot)), n.firstElementChild && t.push(...j(n)), n = n.nextElementSibling;
181
181
  return t;
182
182
  }
183
- var M = (e) => e.substring(E).split(".")[0];
184
- function N(e, t) {
183
+ function M(e, t) {
184
+ let n = e;
185
+ for (; n;) {
186
+ let e = Object.getOwnPropertyDescriptor(n, t);
187
+ if (e) return e;
188
+ n = Object.getPrototypeOf(n);
189
+ }
190
+ }
191
+ var N = (e) => e.substring(E).split(".")[0];
192
+ function P(e) {
193
+ return e.substring(4).trim();
194
+ }
195
+ function F(e, t) {
185
196
  let n = e[0];
186
197
  return t.forEach((t, r) => {
187
198
  n += t + e[r + 1];
188
199
  }), n;
189
200
  }
190
- function P(e) {
201
+ function I(e) {
191
202
  return e instanceof HTMLInputElement && e.type === "checkbox";
192
203
  }
193
- function F(e) {
204
+ function L(e) {
205
+ return e.startsWith("get ");
206
+ }
207
+ function R(e) {
194
208
  let t = typeof e;
195
209
  return t === "string" || t === "number" || t === "boolean";
196
210
  }
197
- function I(e) {
211
+ function z(e) {
198
212
  return e instanceof HTMLInputElement && e.type === "radio";
199
213
  }
200
- function L(e) {
214
+ function B(e) {
201
215
  return e.localName === "textarea";
202
216
  }
203
- function R(e) {
217
+ function V(e) {
204
218
  let { localName: t } = e;
205
219
  return t === "input" || t === "select";
206
220
  }
207
- var z = (e) => e.replace(/<!--[\s\S]*?-->/g, "");
208
- function B(e, t, n, r) {
221
+ var H = (e) => `get ${e}`, U = (e) => e.replace(/<!--[\s\S]*?-->/g, "");
222
+ function W(e, t, n, r) {
209
223
  return e.slice(0, t) + r + e.slice(t + n);
210
224
  }
211
- function V(e) {
225
+ function G(e) {
212
226
  let t = Number(e);
213
227
  if (isNaN(t)) throw new g(`can't convert "${e}" to a number`);
214
228
  return t;
215
229
  }
216
- function H(e, t, n) {
230
+ function K(e, t, n) {
217
231
  let [r] = t.split(":");
218
- if (r === "checked" && I(e) && typeof n == "string") {
232
+ if (r === "checked" && z(e) && typeof n == "string") {
219
233
  let t = e.value === n;
220
234
  t ? e.setAttribute(r, r) : e.removeAttribute(r), e.checked = t;
221
235
  return;
222
236
  }
223
- if (F(n)) if (typeof n == "boolean") {
237
+ if (R(n)) if (typeof n == "boolean") {
224
238
  n ? e.setAttribute(r, r) : e.removeAttribute(r);
225
- let t = K.getPropName(r);
239
+ let t = X.getPropName(r);
226
240
  e[t] = n;
227
241
  } else {
228
242
  let i = e.getAttribute(t), a = String(n);
229
- i !== a && (e.setAttribute(r, a), r === "value" && R(e) && (e.value = a));
243
+ i !== a && (e.setAttribute(r, a), r === "value" && V(e) && (e.value = a));
230
244
  }
231
245
  else {
232
- let r = K.getPropName(t);
246
+ let r = X.getPropName(t);
233
247
  e[r] = n;
234
248
  }
235
249
  }
236
- function U(e, t, n) {
250
+ function q(e, t, n) {
237
251
  let [r] = t.split(":");
238
- e instanceof CSSStyleRule ? e.style.setProperty(r, n) : (H(e, r, n), r === "value" && R(e) && (e.value = n));
252
+ e instanceof CSSStyleRule ? e.style.setProperty(r, n) : (K(e, r, n), r === "value" && V(e) && (e.value = n));
239
253
  }
240
- var W = (e) => typeof e == "string" ? [e] : e;
241
- async function G(e) {
254
+ var J = (e) => typeof e == "string" ? [e] : e;
255
+ async function Y(e) {
242
256
  let t = /* @__PURE__ */ new Set();
243
257
  for (let n of j(e.content)) {
244
258
  let { localName: e } = n;
@@ -254,7 +268,7 @@ async function G(e) {
254
268
  }
255
269
  return Promise.all([...t].map(async (e) => Promise.race([h.whenDefined(e), n(e)])));
256
270
  }
257
- var K = class e extends m {
271
+ var X = class e extends m {
258
272
  static {
259
273
  this.attrToPropMap = /* @__PURE__ */ new Map();
260
274
  }
@@ -327,7 +341,7 @@ var K = class e extends m {
327
341
  }
328
342
  async #d() {
329
343
  let e = this.#n, { template: t } = e;
330
- t || (t = e.template = document.createElement("template"), t.innerHTML = e.buildHTML()), await G(t), this.shadowRoot.replaceChildren(t.content.cloneNode(!0));
344
+ t || (t = e.template = document.createElement("template"), t.innerHTML = e.buildHTML()), await Y(t), this.shadowRoot.replaceChildren(t.content.cloneNode(!0));
331
345
  }
332
346
  static buildHTML() {
333
347
  let e = "<style>\n :host([hidden]) { display: none; }";
@@ -365,7 +379,7 @@ var K = class e extends m {
365
379
  return this[u];
366
380
  },
367
381
  set(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));
382
+ 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 = G(e));
369
383
  let r = this[u];
370
384
  if (e === r) return;
371
385
  this.#q(t, c, e), this[u] = e;
@@ -414,10 +428,10 @@ var K = class e extends m {
414
428
  let [o, s] = r.split(":"), c = e.getPropName(o);
415
429
  if (o === "checked") {
416
430
  let { type: e } = this.#n.properties[a];
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`);
431
+ I(t) && e !== Boolean && this.#F(t, r, `refers to property "${a}" whose type is not Boolean`), z(t) && e !== String && this.#F(t, r, `refers to property "${a}" whose type is not String`);
418
432
  }
419
433
  let l = this.#T(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);
434
+ n && t.#T(c) || (o === "checked" && z(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);
421
435
  }
422
436
  this.#N(i, t, r);
423
437
  }
@@ -434,7 +448,7 @@ var K = class e extends m {
434
448
  if (t instanceof HTMLElement) this.#V(t, e);
435
449
  else if (!(t instanceof CSSStyleRule)) {
436
450
  let { element: n, attrName: r } = t;
437
- n instanceof CSSStyleRule ? n.style.setProperty(r, e) : U(n, r, e);
451
+ n instanceof CSSStyleRule ? n.style.setProperty(r, e) : q(n, r, e);
438
452
  }
439
453
  }
440
454
  if (r.size > 0) {
@@ -460,7 +474,7 @@ var K = class e extends m {
460
474
  }
461
475
  } else {
462
476
  let t = "";
463
- if (L(e)) {
477
+ if (B(e)) {
464
478
  this.#N(e.textContent, e);
465
479
  let n = e.textContent?.match(x);
466
480
  n && (t = n[1]);
@@ -470,7 +484,7 @@ var K = class e extends m {
470
484
  }
471
485
  if (t) {
472
486
  let n = this.#O(e, t);
473
- n && L(e) ? e.textContent = this[n] : this.#N(t, e);
487
+ n && B(e) ? e.textContent = this[n] : this.#N(t, e);
474
488
  }
475
489
  }
476
490
  }
@@ -490,7 +504,7 @@ var K = class e extends m {
490
504
  this.#i = t, this.#a = new FormData(), this.#s = this.attachInternals(), this.#s.setFormValue(this.#a);
491
505
  for (let [e, n] of Object.entries(t)) {
492
506
  let t = this[e];
493
- F(t) && this.setFormValue(n, String(t));
507
+ R(t) && this.setFormValue(n, String(t));
494
508
  }
495
509
  let r = Object.keys(this.#n.properties), i = this.#o;
496
510
  for (let e of r) i[e] = this[e];
@@ -556,15 +570,15 @@ var K = class e extends m {
556
570
  if (n.length !== 1) return;
557
571
  let [r] = n;
558
572
  if (!C.test(r)) return;
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;
573
+ let i = I(e), a = z(e), o = V(e) || B(e), [s, c] = (t ?? "").split(":");
574
+ if (!(o && s === "value" || i && s === "checked" || a && s === "checked" || B(e))) return;
561
575
  c ? e["on" + c] === void 0 && this.#F(e, t, "refers to an unsupported event name") : c = "change";
562
- let l = M(r);
576
+ let l = N(r);
563
577
  e.addEventListener(c, (e) => {
564
578
  let { target: t } = e;
565
579
  if (!t) return;
566
580
  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);
581
+ s === "checked" ? i ? this[l] = r.checked : a && r.checked && (this[l] = o) : this[l] = n === Number ? G(o) : o, this.#k(l);
568
582
  });
569
583
  }
570
584
  #C(e) {
@@ -592,7 +606,7 @@ var K = class e extends m {
592
606
  propertyChangedCallback(e, t, n) {}
593
607
  #O(e, t) {
594
608
  if (!t || !C.test(t)) return;
595
- let n = M(t);
609
+ let n = N(t);
596
610
  return this[n] === void 0 && this.#I(e, "", n), n;
597
611
  }
598
612
  #k(e) {
@@ -617,13 +631,16 @@ var K = class e extends m {
617
631
  }
618
632
  let a = t.computed;
619
633
  for (let t of a.matchAll(w)) {
620
- let n = M(t[0]);
621
- this[n] === void 0 && this.#I(null, e, n), typeof this[n] != "function" && i(n, a);
634
+ let r = N(t[0]);
635
+ this[r] === void 0 && this.#I(null, e, r);
636
+ let o = H(r), s = !1;
637
+ for (let [e, t] of Object.entries(n.properties)) J(t.usedBy)?.includes(o) && (i(e, a), s = !0);
638
+ !s && typeof this[r] != "function" && i(r, a);
622
639
  }
623
640
  for (let t of a.matchAll(b)) {
624
641
  let r = t[1];
625
642
  if (typeof this[r] != "function") throw new g(`property ${e} computed calls non-method ${r}`);
626
- for (let [e, t] of Object.entries(n.properties)) W(t.usedBy)?.includes(r) && i(e, a);
643
+ for (let [e, t] of Object.entries(n.properties)) J(t.usedBy)?.includes(r) && i(e, a);
627
644
  }
628
645
  }
629
646
  #M(e, t) {
@@ -639,12 +656,12 @@ var K = class e extends m {
639
656
  let r = this.#G(t, n, e);
640
657
  if (!r) {
641
658
  let r = e.replaceAll("this..", "this.");
642
- n ? U(t, n, r) : "textContent" in t && (t.textContent = r);
659
+ n ? q(t, n, r) : "textContent" in t && (t.textContent = r);
643
660
  return;
644
661
  }
645
662
  let i = this.#n;
646
663
  r.forEach((t) => {
647
- let n = M(t);
664
+ let n = N(t);
648
665
  if (typeof this[n] == "function") return;
649
666
  let r = i.propToExprsMap, a = r.get(n);
650
667
  a || (a = [], r.set(n, a)), a.includes(e) || a.push(e);
@@ -659,7 +676,7 @@ var K = class e extends m {
659
676
  attrName: n
660
677
  } : t), t instanceof HTMLElement && this.#S(t, n, r);
661
678
  let o = this.#v(e);
662
- n ? U(t, n, o) : this.#V(t, o);
679
+ n ? q(t, n, o) : this.#V(t, o);
663
680
  }
664
681
  #P(e, t) {
665
682
  let n = t?.trim() ?? "", r = this.#n.properties[n];
@@ -669,7 +686,7 @@ var K = class e extends m {
669
686
  this.hasAttribute(e) || this.setAttribute(e, t);
670
687
  }
671
688
  setFormValue(e, t) {
672
- !this.#a || !F(t) || (this.#a.set(e, t), this.#s?.setFormValue(this.#a));
689
+ !this.#a || !R(t) || (this.#a.set(e, t), this.#s?.setFormValue(this.#a));
673
690
  }
674
691
  static ssr(e = {}) {
675
692
  throw new g("SSR is not available in the browser build.");
@@ -695,7 +712,7 @@ var K = class e extends m {
695
712
  }
696
713
  return n;
697
714
  }
698
- if (i === Number) return V(n);
715
+ if (i === Number) return G(n);
699
716
  if (i === Boolean) {
700
717
  if (n === "true") return !0;
701
718
  if (n === "false" || n === "null") return !1;
@@ -704,7 +721,7 @@ var K = class e extends m {
704
721
  }
705
722
  }
706
723
  #z(e, t, n, r) {
707
- F(n) && !this.#T(e) && n !== (t === Boolean ? this.hasAttribute(r) : this.#L(e, r)) && H(this, r || e, n);
724
+ R(n) && !this.#T(e) && n !== (t === Boolean ? this.hasAttribute(r) : this.#L(e, r)) && K(this, r || e, n);
708
725
  }
709
726
  #B(e) {
710
727
  for (let [t, n] of this.#x([e])) this.#M(t, this.#v(n));
@@ -716,7 +733,7 @@ var K = class e extends m {
716
733
  let r = typeof t;
717
734
  r !== "string" && r !== "number" && this.#F(e, void 0, " computed content is not a string or number");
718
735
  let i = String(t);
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);
736
+ e instanceof HTMLElement && B(e) ? e.value = i : n && r === "string" && i.trim().startsWith("<") ? (e.innerHTML = d(i), this.#J(e), this.#E(e)) : n && (e.textContent = i);
720
737
  }
721
738
  #H(e, t) {
722
739
  let n = this.#c.get(e);
@@ -729,27 +746,31 @@ var K = class e extends m {
729
746
  a[n] = t;
730
747
  }
731
748
  #U() {
732
- let e = this.#n;
733
- function t() {
734
- let t = f();
735
- e.methodToExprsMap = t;
736
- let n = Array.from(this.#r.keys());
737
- for (let e of n) for (let n of e.matchAll(b)) {
738
- let r = n[1], i = t.get(r);
739
- i || (i = [], t.set(r, i)), i.includes(e) || i.push(e);
749
+ let e = this.#n, t = (e, t, n) => {
750
+ let r = e.get(t);
751
+ r || (r = [], e.set(t, r)), r.includes(n) || r.push(n);
752
+ }, n = () => {
753
+ let n = f();
754
+ e.methodToExprsMap = n;
755
+ let r = Array.from(this.#r.keys());
756
+ for (let e of r) {
757
+ for (let r of e.matchAll(b)) t(n, r[1], e);
758
+ for (let r of e.matchAll(w)) t(n, H(N(r[0])), e);
740
759
  }
741
- }
742
- let { properties: n, propToExprsMap: r } = e;
743
- for (let [i, a] of Object.entries(n)) {
744
- let n = W(a.usedBy);
745
- if (!n) continue;
746
- e.methodToExprsMap || t.call(this);
747
- let { methodToExprsMap: o } = e, s = r.get(i);
748
- s || (s = [], r.set(i, s));
749
- for (let e of n) {
750
- if (typeof this[e] != "function") throw new g(`property ${i} usedBy contains non-method ${e}`);
751
- let t = o.get(e) || [];
752
- for (let e of t) s.includes(e) || s.push(e);
760
+ }, { properties: r, propToExprsMap: i } = e;
761
+ for (let [t, a] of Object.entries(r)) {
762
+ let r = J(a.usedBy);
763
+ if (!r) continue;
764
+ e.methodToExprsMap || n();
765
+ let { methodToExprsMap: o } = e, s = i.get(t);
766
+ s || (s = [], i.set(t, s));
767
+ for (let e of r) {
768
+ if (L(e)) {
769
+ let n = P(e);
770
+ if (typeof M(this, n)?.get != "function") throw new g(`property ${t} usedBy contains non-getter ${e}`);
771
+ } else if (typeof this[e] != "function") throw new g(`property ${t} usedBy contains non-method ${e}`);
772
+ let n = o.get(e) || [];
773
+ for (let e of n) s.includes(e) || s.push(e);
753
774
  }
754
775
  }
755
776
  }
@@ -799,7 +820,7 @@ var K = class e extends m {
799
820
  #G(e, t, n) {
800
821
  let r = n.match(w);
801
822
  if (r) return r.forEach((n) => {
802
- let r = M(n);
823
+ let r = N(n);
803
824
  this[r] === void 0 && this.#I(e, t, r);
804
825
  }), r;
805
826
  }
@@ -842,8 +863,8 @@ var K = class e extends m {
842
863
  }
843
864
  }
844
865
  };
845
- function q(e, ...t) {
846
- let n = N(e, t);
866
+ function Z(e, ...t) {
867
+ let n = F(e, t);
847
868
  for (;;) {
848
869
  let e = _.exec(n);
849
870
  if (!e) break;
@@ -853,24 +874,24 @@ function q(e, ...t) {
853
874
  if (!r.startsWith("--")) {
854
875
  let i = `--${r}: ${t};
855
876
  ${r}: var(--${r})`;
856
- n = B(n, e.index, e[0].length, i);
877
+ n = W(n, e.index, e[0].length, i);
857
878
  }
858
879
  }
859
880
  }
860
881
  return n;
861
882
  }
862
- function J(e, ...t) {
863
- let n = N(e, t);
883
+ function Q(e, ...t) {
884
+ let n = F(e, t);
864
885
  for (;;) {
865
886
  let e = S.exec(n);
866
887
  if (!e || e[1] === "style") break;
867
- let t = z(e[2]);
888
+ let t = U(e[2]);
868
889
  if (T.test(t)) {
869
890
  let r = `<!-- ${t.trim()} -->`, i = e.index + e[0].indexOf(">") + 1;
870
- n = B(n, i, t.length, r);
891
+ n = W(n, i, t.length, r);
871
892
  }
872
893
  }
873
894
  return n;
874
895
  }
875
896
  //#endregion
876
- export { a, J as i, O as n, q as r, K as t };
897
+ export { a, Q as i, O as n, Z as r, X as t };
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, n, r, t as i } from "./wrec-fWpfp5nc.js";
1
+ import { a as e, i as t, n, r, t as i } from "./wrec-Cpci7nM6.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-fWpfp5nc.js";
1
+ import { a as e, i as t, n, r, t as i } from "./wrec-Cpci7nM6.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.6",
5
+ "version": "0.32.1",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/scripts/lint.js CHANGED
@@ -119,6 +119,7 @@ const THIS_CALL_RE = /this\.([A-Za-z_$][\w$]*)\s*\(/g;
119
119
  const THIS_REF_RE = /this\.([A-Za-z_$][\w$]*)(\.[A-Za-z_$][\w$]*)*/g;
120
120
  const PLACEHOLDER_PREFIX = '__WREC_PLACEHOLDER__';
121
121
  const RESERVED_PROPERTY_NAMES = new Set(['class', 'style']);
122
+ const GETTER_PREFIX = 'get ';
122
123
  const SUPPORTED_EVENT_NAMES = new Set([
123
124
  'blur',
124
125
  'change',
@@ -348,6 +349,17 @@ function collectClassMethods(classNode) {
348
349
  return methods;
349
350
  }
350
351
 
352
+ // Collects all getter names defined in a component class.
353
+ function collectGetterNames(classNode) {
354
+ const getters = new Set();
355
+ for (const member of classNode.members) {
356
+ if (!ts.isGetAccessorDeclaration(member)) continue;
357
+ const name = getMemberName(member);
358
+ if (name) getters.add(name);
359
+ }
360
+ return getters;
361
+ }
362
+
351
363
  // Finds the synthetic `__wrec_expr_*` helper functions that were added by
352
364
  // `buildAugmentedSource` and returns their bodies in index order.
353
365
  // This gives the linter a stable list of typed code nodes
@@ -1062,6 +1074,11 @@ function getExpressionText(sourceFile, expression) {
1062
1074
  return expression.getText(sourceFile).trim();
1063
1075
  }
1064
1076
 
1077
+ // Returns the name from a getter reference.
1078
+ function getGetterName(reference) {
1079
+ return reference.slice(GETTER_PREFIX.length).trim();
1080
+ }
1081
+
1065
1082
  // Returns a lowercased HTML tag name for a parsed HTML node.
1066
1083
  function getHtmlTagName(node) {
1067
1084
  const tagName = node.rawTagName || node.tagName;
@@ -1243,6 +1260,11 @@ function isCallCallee(node) {
1243
1260
  return ts.isCallExpression(node.parent) && node.parent.expression === node;
1244
1261
  }
1245
1262
 
1263
+ // Returns whether a reference refers to a getter method.
1264
+ function isGetterReference(reference) {
1265
+ return reference.startsWith(GETTER_PREFIX);
1266
+ }
1267
+
1246
1268
  // Returns whether a declaration represents an imported binding.
1247
1269
  function isImportLikeDeclaration(node) {
1248
1270
  return (
@@ -1325,6 +1347,7 @@ export function lintSource(filePath, sourceText, options = {}) {
1325
1347
  propertyEntries,
1326
1348
  reservedProperties
1327
1349
  } = extractProperties(sourceFile, checker, classNode);
1350
+ const getterNames = collectGetterNames(classNode);
1328
1351
  const allMethods = collectClassMethods(classNode);
1329
1352
  const findings = {
1330
1353
  duplicateProperties,
@@ -1393,6 +1416,7 @@ export function lintSource(filePath, sourceText, options = {}) {
1393
1416
  checker,
1394
1417
  supportedProps,
1395
1418
  propertyEntries,
1419
+ getterNames,
1396
1420
  allMethods,
1397
1421
  findings
1398
1422
  );
@@ -1826,6 +1850,7 @@ function validatePropertyConfigs(
1826
1850
  checker,
1827
1851
  supportedProps,
1828
1852
  propertyEntries,
1853
+ getterNames,
1829
1854
  classMethods,
1830
1855
  findings
1831
1856
  ) {
@@ -1852,6 +1877,16 @@ function validatePropertyConfigs(
1852
1877
 
1853
1878
  if (methods) {
1854
1879
  for (const methodName of methods) {
1880
+ if (isGetterReference(methodName)) {
1881
+ const getterName = getGetterName(methodName);
1882
+ if (getterNames.has(getterName)) continue;
1883
+ findings.invalidUsedByReferences.push(
1884
+ `property "${propName}" usedBy references ` +
1885
+ `missing getter "${getterName}"`
1886
+ );
1887
+ continue;
1888
+ }
1889
+
1855
1890
  if (!classMethods.has(methodName)) {
1856
1891
  findings.invalidUsedByReferences.push(
1857
1892
  `property "${propName}" usedBy references ` +
@@ -28,6 +28,10 @@ import {
28
28
  } from './ast-utils.js';
29
29
 
30
30
  const cwd = process.cwd();
31
+ const CALL_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
32
+ const REFS_RE =
33
+ /this\.([A-Za-z_$][A-Za-z0-9_$]*)(\.[A-Za-z_$][A-Za-z0-9_$]*)*/g;
34
+ const GETTER_PREFIX = 'get ';
31
35
 
32
36
  // Records property names introduced by
33
37
  // an identifier or object binding pattern.
@@ -264,17 +268,17 @@ function buildConfigText(sourceFile, member, methodNames, quote) {
264
268
  return `{${openSpacing}${propertyStrings.join(', ')}${closeSpacing}}`;
265
269
  }
266
270
 
267
- // Walks a method body to collect component property reads
268
- // and method calls made through `this`.
271
+ // Walks a method or accessor body to collect component property reads
272
+ // and dependency targets reached through `this`.
269
273
  // This is only called by getMethodUsages.
270
- function collectMethodBodyUsage(node, props, calledMethods) {
274
+ function collectMethodBodyUsage(node, getterNames, props, calledMethods) {
271
275
  if (
272
276
  ts.isPropertyAccessExpression(node) &&
273
277
  node.expression.kind === ts.SyntaxKind.ThisKeyword
274
278
  ) {
275
279
  // Handles direct property access like `this.foo`
276
280
  // and records method calls like `this.foo()`.
277
- recordThisAccess(props, calledMethods, node, node.name.text);
281
+ recordThisAccess(props, calledMethods, getterNames, node, node.name.text);
278
282
  } else if (
279
283
  ts.isElementAccessExpression(node) &&
280
284
  node.expression.kind === ts.SyntaxKind.ThisKeyword &&
@@ -283,7 +287,13 @@ function collectMethodBodyUsage(node, props, calledMethods) {
283
287
  ) {
284
288
  // Handles string-based element access like `this['foo']`
285
289
  // and records method calls like `this['foo']()`.
286
- recordThisAccess(props, calledMethods, node, node.argumentExpression.text);
290
+ recordThisAccess(
291
+ props,
292
+ calledMethods,
293
+ getterNames,
294
+ node,
295
+ node.argumentExpression.text
296
+ );
287
297
  } else if (
288
298
  ts.isVariableDeclaration(node) &&
289
299
  node.initializer &&
@@ -315,7 +325,7 @@ function collectMethodBodyUsage(node, props, calledMethods) {
315
325
  }
316
326
 
317
327
  ts.forEachChild(node, child =>
318
- collectMethodBodyUsage(child, props, calledMethods)
328
+ collectMethodBodyUsage(child, getterNames, props, calledMethods)
319
329
  );
320
330
  }
321
331
 
@@ -385,7 +395,6 @@ export function evaluateSourceText(filePath, text) {
385
395
  // This is only called by getMethodUsages.
386
396
  function getCssCalledMethods(classNode) {
387
397
  const methodNames = new Set();
388
- const CALL_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
389
398
  let template;
390
399
 
391
400
  for (const member of classNode.members) {
@@ -410,11 +419,10 @@ function getCssCalledMethods(classNode) {
410
419
  // If no matching declaration was found, return an empty Set.
411
420
  if (!template) return methodNames;
412
421
 
413
- // Finds all method names called in CSS property value expressions
414
- // matching `this.method()`.
415
- const text = template.getText();
416
- for (const match of text.matchAll(CALL_RE)) {
417
- methodNames.add(match[1]);
422
+ // Finds all method calls and getter references
423
+ // in CSS property value expressions.
424
+ for (const target of getExpressionTargets(template.getText())) {
425
+ methodNames.add(target);
418
426
  }
419
427
 
420
428
  return methodNames;
@@ -425,7 +433,6 @@ function getCssCalledMethods(classNode) {
425
433
  // This is only called by getMethodUsages.
426
434
  function getComputedCalledMethods(classNode) {
427
435
  const methodNames = new Set();
428
- const CALL_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
429
436
  let propertiesNode;
430
437
 
431
438
  for (const member of classNode.members) {
@@ -473,10 +480,11 @@ function getComputedCalledMethods(classNode) {
473
480
  // If the property value isn't a string then skip it.
474
481
  if (!ts.isStringLiteralLike(configProperty.initializer)) continue;
475
482
 
476
- // Find all the method calls in the string JavaScript expression.
483
+ // Find all method calls and getter references
484
+ // in the string JavaScript expression.
477
485
  const computed = configProperty.initializer.text;
478
- for (const match of computed.matchAll(CALL_RE)) {
479
- methodNames.add(match[1]);
486
+ for (const target of getExpressionTargets(computed)) {
487
+ methodNames.add(target);
480
488
  }
481
489
  }
482
490
  }
@@ -484,6 +492,23 @@ function getComputedCalledMethods(classNode) {
484
492
  return methodNames;
485
493
  }
486
494
 
495
+ // Collects method-call and getter-reference targets from expression text.
496
+ function getExpressionTargets(text) {
497
+ const targets = new Set();
498
+ for (const match of text.matchAll(CALL_RE)) {
499
+ targets.add(match[1]);
500
+ }
501
+ for (const match of text.matchAll(REFS_RE)) {
502
+ targets.add(getGetterDependency(match[1]));
503
+ }
504
+ return targets;
505
+ }
506
+
507
+ // Returns the dependency target string for a getter reference.
508
+ function getGetterDependency(name) {
509
+ return `${GETTER_PREFIX}${name}`;
510
+ }
511
+
487
512
  // Returns the leading indentation in the line
488
513
  // that begins at a given position (`pos`) inside `text`.
489
514
  function getIndent(text, pos) {
@@ -495,19 +520,26 @@ function getIndent(text, pos) {
495
520
  // Returns a map where the keys are property names and
496
521
  // the values are Sets of public methods that use it transitively.
497
522
  function getMethodUsages(classNode, propertyNames) {
523
+ const getterNames = new Set();
498
524
  const methodInfo = new Map();
499
525
 
526
+ for (const member of classNode.members) {
527
+ if (!ts.isGetAccessorDeclaration(member)) continue;
528
+ const getterName = getNameText(member.name);
529
+ if (getterName) getterNames.add(getterName);
530
+ }
531
+
500
532
  for (const member of classNode.members) {
501
533
  // If the member doesn't represent an instance method, skip it.
502
534
  if (!isInstanceMethodMember(member)) continue;
503
535
 
504
536
  // If the member doesn't have a string name, skip it.
505
- const methodName = getNameText(member.name);
537
+ const methodName = getUsageTargetName(member);
506
538
  if (!methodName) continue;
507
539
 
508
540
  const props = new Set();
509
541
  const calledMethods = new Set();
510
- collectMethodBodyUsage(member.body, props, calledMethods);
542
+ collectMethodBodyUsage(member.body, getterNames, props, calledMethods);
511
543
  methodInfo.set(methodName, {
512
544
  calledMethods,
513
545
  isPrivate: ts.isPrivateIdentifier(member.name),
@@ -573,12 +605,9 @@ function getTemplateCalledMethods(classNode) {
573
605
 
574
606
  const methodNames = new Set();
575
607
  if (template) {
576
- // Finds all method names called in the HTML template
577
- // matching `this.method()`.
578
- const text = template.getText();
579
- const CALL_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
580
- for (const match of text.matchAll(CALL_RE)) {
581
- methodNames.add(match[1]);
608
+ // Finds all method calls and getter references in the HTML template.
609
+ for (const target of getExpressionTargets(template.getText())) {
610
+ methodNames.add(target);
582
611
  }
583
612
  }
584
613
  return methodNames;
@@ -614,6 +643,13 @@ function getTransitiveProps(methodInfo, memo, methodName, seen = new Set()) {
614
643
  return props;
615
644
  }
616
645
 
646
+ // Returns the usedBy dependency target name for a class member.
647
+ function getUsageTargetName(member) {
648
+ const name = getNameText(member.name);
649
+ if (!name) return undefined;
650
+ return ts.isGetAccessorDeclaration(member) ? getGetterDependency(name) : name;
651
+ }
652
+
617
653
  // Determines if a class member represents an instance method.
618
654
  // This is only called by getMethodUsages.
619
655
  function isInstanceMethodMember(member) {
@@ -671,10 +707,12 @@ function main() {
671
707
 
672
708
  // Records a `this` property access and
673
709
  // tracks it as a method call when applicable.
674
- function recordThisAccess(props, calledMethods, node, name) {
710
+ function recordThisAccess(props, calledMethods, getterNames, node, name) {
675
711
  props.add(name);
676
712
  if (ts.isCallExpression(node.parent) && node.parent.expression === node) {
677
713
  calledMethods.add(name);
714
+ } else if (getterNames.has(name)) {
715
+ calledMethods.add(getGetterDependency(name));
678
716
  }
679
717
  }
680
718