@fluixi/ts-plugin 0.1.0-alpha.17 → 0.1.0-alpha.19

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.
Files changed (2) hide show
  1. package/dist/index.js +748 -306
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1,37 +1,41 @@
1
1
  "use strict";
2
2
 
3
3
  // ../template-parser/dist/index.mjs
4
- function x(e) {
5
- let t = (i2, n) => {
6
- i2.parent = n;
7
- let r = R(i2);
8
- for (let s2 of r) t(s2, i2);
4
+ function setParents(root) {
5
+ const visit = (node, parent) => {
6
+ node.parent = parent;
7
+ const kids = childrenOf(node);
8
+ for (const child of kids) visit(child, node);
9
9
  };
10
- return t(e, null), e;
10
+ visit(root, null);
11
+ return root;
11
12
  }
12
- function R(e) {
13
- switch (e.kind) {
13
+ function childrenOf(node) {
14
+ switch (node.kind) {
14
15
  case "Root":
15
16
  case "Element":
16
17
  case "Component":
17
18
  case "Fragment":
18
- return e.children;
19
+ return node.children;
19
20
  default:
20
21
  return [];
21
22
  }
22
23
  }
23
- var m = class {
24
- constructor(t) {
25
- this.pieces = t;
24
+ var Reader = class {
25
+ // offset within the current static piece
26
+ constructor(pieces) {
27
+ this.pieces = pieces;
26
28
  this.pi = 0;
27
29
  this.oi = 0;
28
30
  this.normalize();
29
31
  }
32
+ /** Skip fully-consumed static pieces so `current()` is live text or a hole. */
30
33
  normalize() {
31
- for (; this.pi < this.pieces.length; ) {
32
- let t = this.pieces[this.pi];
33
- if (t.kind === "static" && this.oi >= t.text.length) {
34
- this.pi++, this.oi = 0;
34
+ while (this.pi < this.pieces.length) {
35
+ const p = this.pieces[this.pi];
36
+ if (p.kind === "static" && this.oi >= p.text.length) {
37
+ this.pi++;
38
+ this.oi = 0;
35
39
  continue;
36
40
  }
37
41
  break;
@@ -40,174 +44,449 @@ var m = class {
40
44
  eof() {
41
45
  return this.pi >= this.pieces.length;
42
46
  }
47
+ /** Absolute source offset of the cursor. */
43
48
  pos() {
44
49
  if (this.eof()) {
45
- let i2 = this.pieces[this.pieces.length - 1];
46
- return i2 ? i2.kind === "static" ? i2.start + i2.text.length : i2.end : 0;
50
+ const last = this.pieces[this.pieces.length - 1];
51
+ return last ? last.kind === "static" ? last.start + last.text.length : last.end : 0;
47
52
  }
48
- let t = this.pieces[this.pi];
49
- return t.kind === "static" ? t.start + this.oi : t.start;
53
+ const p = this.pieces[this.pi];
54
+ return p.kind === "static" ? p.start + this.oi : p.start;
50
55
  }
56
+ /** True when the cursor sits exactly on a hole marker. */
51
57
  atHole() {
52
58
  return !this.eof() && this.pieces[this.pi].kind === "hole";
53
59
  }
60
+ /** True when advancing one char would land on a hole (e.g. the `<` in `<${Tag}`). */
54
61
  holeFollows() {
55
62
  if (this.eof()) return false;
56
- let t = this.pieces[this.pi];
57
- if (t.kind !== "static" || this.oi + 1 < t.text.length) return false;
58
- let i2 = this.pieces[this.pi + 1];
59
- return i2 !== void 0 && i2.kind === "hole";
63
+ const p = this.pieces[this.pi];
64
+ if (p.kind !== "static") return false;
65
+ if (this.oi + 1 < p.text.length) return false;
66
+ const next = this.pieces[this.pi + 1];
67
+ return next !== void 0 && next.kind === "hole";
60
68
  }
69
+ /** Consume the hole at the cursor. Caller must have checked {@link atHole}. */
61
70
  takeHole() {
62
- let t = this.pieces[this.pi];
63
- if (t.kind !== "hole") throw new Error("takeHole called off a hole");
64
- return this.pi++, this.oi = 0, this.normalize(), { index: t.index, loc: { start: t.start, end: t.end } };
71
+ const p = this.pieces[this.pi];
72
+ if (p.kind !== "hole") throw new Error("takeHole called off a hole");
73
+ this.pi++;
74
+ this.oi = 0;
75
+ this.normalize();
76
+ return { index: p.index, loc: { start: p.start, end: p.end } };
65
77
  }
66
- peek(t = 0) {
78
+ /** Char `k` ahead within the current static segment, or '' at a boundary/hole. */
79
+ peek(k = 0) {
67
80
  if (this.eof()) return "";
68
- let i2 = this.pieces[this.pi];
69
- return i2.kind !== "static" ? "" : i2.text[this.oi + t] ?? "";
81
+ const p = this.pieces[this.pi];
82
+ if (p.kind !== "static") return "";
83
+ return p.text[this.oi + k] ?? "";
70
84
  }
71
- startsWith(t) {
85
+ /** Whether the current static segment starts with `s` at the cursor. */
86
+ startsWith(s) {
72
87
  if (this.eof()) return false;
73
- let i2 = this.pieces[this.pi];
74
- return i2.kind !== "static" ? false : i2.text.startsWith(t, this.oi);
88
+ const p = this.pieces[this.pi];
89
+ if (p.kind !== "static") return false;
90
+ return p.text.startsWith(s, this.oi);
75
91
  }
92
+ /** Advance one character inside the current static segment. */
76
93
  advance() {
77
94
  if (this.eof()) return "";
78
- let t = this.pieces[this.pi];
79
- if (t.kind !== "static") return "";
80
- let i2 = t.text[this.oi++] ?? "";
81
- return this.normalize(), i2;
82
- }
83
- readWhile(t) {
84
- let i2 = "";
85
- for (; !this.eof() && !this.atHole(); ) {
86
- let n = this.peek();
87
- if (n === "" || !t(n)) break;
88
- i2 += n, this.oi++;
95
+ const p = this.pieces[this.pi];
96
+ if (p.kind !== "static") return "";
97
+ const ch = p.text[this.oi++] ?? "";
98
+ this.normalize();
99
+ return ch;
100
+ }
101
+ /** Advance while `pred` holds and we stay inside one static segment. */
102
+ readWhile(pred) {
103
+ let out = "";
104
+ while (!this.eof() && !this.atHole()) {
105
+ const ch = this.peek();
106
+ if (ch === "" || !pred(ch)) break;
107
+ out += ch;
108
+ this.oi++;
89
109
  }
90
- return this.normalize(), i2;
110
+ this.normalize();
111
+ return out;
91
112
  }
92
113
  skipWhitespace() {
93
- this.readWhile((t) => /\s/.test(t));
114
+ this.readWhile((ch) => /\s/.test(ch));
94
115
  }
95
- span(t) {
96
- return { start: t, end: this.pos() };
116
+ span(start) {
117
+ return { start, end: this.pos() };
97
118
  }
98
119
  };
99
- var y = /* @__PURE__ */ new Set(["capture", "once", "passive", "prevent", "stop", "self"]);
100
- function p(e, t) {
101
- return e.value && e.value.kind === "hole" ? e.value.hole : (e.report("invalid-binding", `${t} requires an expression value: ${e.name}=\${\u2026}`), -1);
102
- }
103
- var D = (e, t) => ({ kind: "Attribute", name: t ? e.name.slice(1) : e.name, value: e.value, boolean: t, loc: e.loc });
104
- var f = [{ id: "event", match: (e) => e[0] === "@" || e.startsWith("on:") || /^on[A-Z]/.test(e), build: (e) => {
105
- let t, i2;
106
- e.name[0] === "@" ? (t = "at", i2 = e.name.slice(1)) : e.name.startsWith("on:") ? (t = "colon", i2 = e.name.slice(3)) : (t = "on", i2 = e.name.slice(2));
107
- let [n, ...r] = i2.split("."), s2 = n.toLowerCase();
108
- for (let o of r) y.has(o) || e.report("invalid-directive", `Unknown event modifier ".${o}" \u2014 expected one of ${[...y].join(", ")}`);
109
- return { kind: "EventBinding", name: s2, syntax: t, modifiers: r, hole: p(e, "event binding"), loc: e.loc };
110
- } }, { id: "bind", elementOnly: true, match: (e) => e.startsWith("bind:"), build: (e) => ({ kind: "BindDirective", name: e.name.slice(5), hole: p(e, "two-way binding"), loc: e.loc }) }, { id: "class-directive", elementOnly: true, match: (e) => e.startsWith("class:"), build: (e) => ({ kind: "ClassDirective", name: e.name.slice(6), hole: p(e, "class directive"), loc: e.loc }) }, { id: "style-directive", elementOnly: true, match: (e) => e.startsWith("style:"), build: (e) => ({ kind: "StyleDirective", name: e.name.slice(6), hole: p(e, "style directive"), loc: e.loc }) }, { id: "load", match: (e) => e.startsWith("load:"), build: (e) => ({ kind: "LoadDirective", strategy: e.name.slice(5), modifier: e.value && e.value.kind === "static" ? e.value.value : null, loc: e.loc }) }, { id: "use", match: (e) => e === "use" || e.startsWith("use:"), build: (e) => ({ kind: "UseDirective", name: e.name.startsWith("use:") ? e.name.slice(4) : null, hole: e.value && e.value.kind === "hole" ? e.value.hole : null, loc: e.loc }) }, { id: "if", elementOnly: true, match: (e) => e === "if", build: (e) => ({ kind: "IfDirective", hole: p(e, "if directive"), loc: e.loc }) }, { id: "each", elementOnly: true, match: (e) => e === "each", build: (e) => ({ kind: "EachDirective", hole: p(e, "each directive"), key: null, loc: e.loc }) }, { id: "else", elementOnly: true, match: (e) => e === "else", build: (e) => (e.value !== null && e.report("invalid-directive", "`else` takes no value"), { kind: "ElseDirective", loc: e.loc }) }, { id: "ref", match: (e) => e === "ref", build: (e) => ({ kind: "RefBinding", hole: p(e, "ref binding"), loc: e.loc }) }, { id: "property", match: (e) => e[0] === ".", build: (e) => ({ kind: "PropertyBinding", name: e.name.slice(1), hole: p(e, "property binding"), loc: e.loc }) }, { id: "boolean", match: (e) => e[0] === "?", build: (e) => D(e, true) }];
111
- function k(e, t = f, i2 = { component: false }) {
112
- for (let n of t) if (!(n.elementOnly && i2.component) && n.match(e.name)) return n.build(e);
113
- return D(e, false);
114
- }
115
- var E = /* @__PURE__ */ new Set(["svg", "path", "circle", "rect", "line", "polygon", "polyline", "ellipse", "g", "defs", "clipPath", "text"]);
116
- var S = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
117
- var T = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
118
- function C(e) {
119
- return e.length > 0 && (e[0] !== e[0].toLowerCase() || e.includes("."));
120
- }
121
- var P = /* @__PURE__ */ new Set(["a", "button", "input", "select", "textarea", "summary", "details", "option", "label"]);
122
- var W = /* @__PURE__ */ new Set(["keydown", "keyup", "keypress"]);
123
- function u(e, t) {
124
- return e.attributes.find((i2) => "name" in i2 && i2.name === t);
125
- }
126
- function w(e) {
127
- return e.attributes.some((t) => t.kind === "Spread");
128
- }
129
- function v(e, t) {
130
- let i2 = u(e, t);
131
- if (!i2 || i2.kind !== "Attribute" || !i2.value) return;
132
- let n = i2.value;
133
- return n.kind === "static" ? n.value : void 0;
134
- }
135
- function H(e) {
136
- let t = v(e, "role");
137
- return t === "presentation" || t === "none" ? true : v(e, "aria-hidden") === "true";
138
- }
139
- function A(e, t) {
140
- return e.attributes.some((i2) => i2.kind === "EventBinding" && t(i2.name));
141
- }
142
- function B(e) {
143
- let t = [], i2 = (r, s2, o) => {
144
- t.push({ code: s2, message: o, severity: "warning", loc: r.loc });
145
- }, n = (r) => {
146
- for (let s2 of r) {
147
- s2.kind === "Element" && L(s2, i2);
148
- let o = "children" in s2 ? s2.children : void 0;
149
- o && n(o);
120
+ var EVENT_MODIFIERS = /* @__PURE__ */ new Set(["capture", "once", "passive", "prevent", "stop", "self"]);
121
+ function requireHole(raw, what) {
122
+ if (raw.value && raw.value.kind === "hole") return raw.value.hole;
123
+ raw.report("invalid-binding", `${what} requires an expression value: ${raw.name}=\${\u2026}`);
124
+ return -1;
125
+ }
126
+ var attribute = (raw, boolean) => ({
127
+ kind: "Attribute",
128
+ name: boolean ? raw.name.slice(1) : raw.name,
129
+ value: raw.value,
130
+ boolean,
131
+ loc: raw.loc
132
+ });
133
+ var CORE_DIRECTIVES = [
134
+ // Events: `@click=${h}`, `on:click=${h}` (native), `onClick=${h}`, each with
135
+ // optional dotted modifiers (`@click.capture.once`, `on:input.passive`).
136
+ {
137
+ id: "event",
138
+ match: (n) => n[0] === "@" || n.startsWith("on:") || /^on[A-Z]/.test(n),
139
+ build: (raw) => {
140
+ let syntax;
141
+ let rest;
142
+ if (raw.name[0] === "@") {
143
+ syntax = "at";
144
+ rest = raw.name.slice(1);
145
+ } else if (raw.name.startsWith("on:")) {
146
+ syntax = "colon";
147
+ rest = raw.name.slice(3);
148
+ } else {
149
+ syntax = "on";
150
+ rest = raw.name.slice(2);
151
+ }
152
+ const [raw_name, ...modifiers] = rest.split(".");
153
+ const name = raw_name.toLowerCase();
154
+ for (const m of modifiers) {
155
+ if (!EVENT_MODIFIERS.has(m)) {
156
+ raw.report("invalid-directive", `Unknown event modifier ".${m}" \u2014 expected one of ${[...EVENT_MODIFIERS].join(", ")}`);
157
+ }
158
+ }
159
+ return {
160
+ kind: "EventBinding",
161
+ name,
162
+ syntax,
163
+ modifiers,
164
+ hole: requireHole(raw, "event binding"),
165
+ loc: raw.loc
166
+ };
167
+ }
168
+ },
169
+ // Two-way binding: `bind:value=${signal}`.
170
+ {
171
+ id: "bind",
172
+ elementOnly: true,
173
+ match: (n) => n.startsWith("bind:"),
174
+ build: (raw) => ({
175
+ kind: "BindDirective",
176
+ name: raw.name.slice("bind:".length),
177
+ hole: requireHole(raw, "two-way binding"),
178
+ loc: raw.loc
179
+ })
180
+ },
181
+ // Dynamic class toggle: `class:active=${flag}`.
182
+ {
183
+ id: "class-directive",
184
+ elementOnly: true,
185
+ match: (n) => n.startsWith("class:"),
186
+ build: (raw) => ({
187
+ kind: "ClassDirective",
188
+ name: raw.name.slice("class:".length),
189
+ hole: requireHole(raw, "class directive"),
190
+ loc: raw.loc
191
+ })
192
+ },
193
+ // Dynamic style: `style:color=${c}`.
194
+ {
195
+ id: "style-directive",
196
+ elementOnly: true,
197
+ match: (n) => n.startsWith("style:"),
198
+ build: (raw) => ({
199
+ kind: "StyleDirective",
200
+ name: raw.name.slice("style:".length),
201
+ hole: requireHole(raw, "style directive"),
202
+ loc: raw.loc
203
+ })
204
+ },
205
+ // Load strategy: `load:visible[="200px"]`. Components only, an intrinsic element
206
+ // has no module to defer.
207
+ {
208
+ id: "load",
209
+ match: (n) => n.startsWith("load:"),
210
+ build: (raw) => ({
211
+ kind: "LoadDirective",
212
+ strategy: raw.name.slice("load:".length),
213
+ modifier: raw.value && raw.value.kind === "static" ? raw.value.value : null,
214
+ loc: raw.loc
215
+ })
216
+ },
217
+ // Custom directive: `use=${d}` or `use:tooltip[=${opts}]`.
218
+ {
219
+ id: "use",
220
+ match: (n) => n === "use" || n.startsWith("use:"),
221
+ build: (raw) => {
222
+ const named = raw.name.startsWith("use:");
223
+ return {
224
+ kind: "UseDirective",
225
+ name: named ? raw.name.slice("use:".length) : null,
226
+ hole: raw.value && raw.value.kind === "hole" ? raw.value.hole : null,
227
+ loc: raw.loc
228
+ };
229
+ }
230
+ },
231
+ // Conditional rendering: `if=${cond}`.
232
+ {
233
+ id: "if",
234
+ elementOnly: true,
235
+ match: (n) => n === "if",
236
+ build: (raw) => ({
237
+ kind: "IfDirective",
238
+ hole: requireHole(raw, "if directive"),
239
+ loc: raw.loc
240
+ })
241
+ },
242
+ // Loop rendering: `each=${items}` (a sibling `key` folds in at parse time).
243
+ {
244
+ id: "each",
245
+ elementOnly: true,
246
+ match: (n) => n === "each",
247
+ build: (raw) => ({
248
+ kind: "EachDirective",
249
+ hole: requireHole(raw, "each directive"),
250
+ key: null,
251
+ loc: raw.loc
252
+ })
253
+ },
254
+ // `else` (valueless).
255
+ {
256
+ id: "else",
257
+ elementOnly: true,
258
+ match: (n) => n === "else",
259
+ build: (raw) => {
260
+ if (raw.value !== null) raw.report("invalid-directive", "`else` takes no value");
261
+ return { kind: "ElseDirective", loc: raw.loc };
150
262
  }
263
+ },
264
+ // Ref binding: `ref=${el}`.
265
+ {
266
+ id: "ref",
267
+ match: (n) => n === "ref",
268
+ build: (raw) => ({
269
+ kind: "RefBinding",
270
+ hole: requireHole(raw, "ref binding"),
271
+ loc: raw.loc
272
+ })
273
+ },
274
+ // Property binding: `.currentTime=${t}`.
275
+ {
276
+ id: "property",
277
+ match: (n) => n[0] === ".",
278
+ build: (raw) => ({
279
+ kind: "PropertyBinding",
280
+ name: raw.name.slice(1),
281
+ hole: requireHole(raw, "property binding"),
282
+ loc: raw.loc
283
+ })
284
+ },
285
+ // Boolean attribute (lit `?disabled`): a plain attribute flagged boolean.
286
+ {
287
+ id: "boolean",
288
+ match: (n) => n[0] === "?",
289
+ build: (raw) => attribute(raw, true)
290
+ }
291
+ ];
292
+ function resolveAttribute(raw, directives = CORE_DIRECTIVES, ctx = { component: false }) {
293
+ for (const d of directives) {
294
+ if (d.elementOnly && ctx.component) continue;
295
+ if (d.match(raw.name)) return d.build(raw);
296
+ }
297
+ return attribute(raw, false);
298
+ }
299
+ var SVG_TAGS = /* @__PURE__ */ new Set([
300
+ "svg",
301
+ "path",
302
+ "circle",
303
+ "rect",
304
+ "line",
305
+ "polygon",
306
+ "polyline",
307
+ "ellipse",
308
+ "g",
309
+ "defs",
310
+ "clipPath",
311
+ "text"
312
+ ]);
313
+ var RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
314
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
315
+ "area",
316
+ "base",
317
+ "br",
318
+ "col",
319
+ "embed",
320
+ "hr",
321
+ "img",
322
+ "input",
323
+ "link",
324
+ "meta",
325
+ "param",
326
+ "source",
327
+ "track",
328
+ "wbr"
329
+ ]);
330
+ function isComponentTag(tag) {
331
+ return tag.length > 0 && (tag[0] !== tag[0].toLowerCase() || tag.includes("."));
332
+ }
333
+ var INTERACTIVE = /* @__PURE__ */ new Set([
334
+ "a",
335
+ "button",
336
+ "input",
337
+ "select",
338
+ "textarea",
339
+ "summary",
340
+ "details",
341
+ "option",
342
+ "label"
343
+ ]);
344
+ var KEY_EVENTS = /* @__PURE__ */ new Set(["keydown", "keyup", "keypress"]);
345
+ function attributeNamed(node, name) {
346
+ return node.attributes.find((a) => "name" in a && a.name === name);
347
+ }
348
+ function hasSpread(node) {
349
+ return node.attributes.some((a) => a.kind === "Spread");
350
+ }
351
+ function staticValue(node, name) {
352
+ const attribute2 = attributeNamed(node, name);
353
+ if (!attribute2 || attribute2.kind !== "Attribute" || !attribute2.value) return void 0;
354
+ const value = attribute2.value;
355
+ return value.kind === "static" ? value.value : void 0;
356
+ }
357
+ function isPresentational(node) {
358
+ const role = staticValue(node, "role");
359
+ if (role === "presentation" || role === "none") return true;
360
+ return staticValue(node, "aria-hidden") === "true";
361
+ }
362
+ function hasEvent(node, predicate) {
363
+ return node.attributes.some((a) => a.kind === "EventBinding" && predicate(a.name));
364
+ }
365
+ function checkAccessibility(root) {
366
+ const out = [];
367
+ const report = (node, code, message) => {
368
+ out.push({ code, message, severity: "warning", loc: node.loc });
151
369
  };
152
- return n(e.children), t;
153
- }
154
- function L(e, t) {
155
- if (w(e)) return;
156
- let i2 = e.tag.toLowerCase();
157
- i2 === "img" && !u(e, "alt") && !H(e) && t(e, "a11y-img-alt", 'An `img` needs an `alt`. Use `alt=""` when the image is decorative.'), i2 === "a" && !u(e, "href") && !u(e, "role") && t(e, "a11y-anchor-href", "An `a` without `href` is not a link. Use a `button` for an action."), i2 === "iframe" && !u(e, "title") && t(e, "a11y-iframe-title", "An `iframe` needs a `title` describing its content.");
158
- let n = v(e, "tabindex") ?? v(e, "tabIndex");
159
- n !== void 0 && Number(n) > 0 && t(e, "a11y-positive-tabindex", "A positive `tabindex` reorders tab navigation for the entire page. Use `0`, or restructure the markup."), !P.has(i2) && A(e, (r) => r === "click") && !u(e, "role") && !A(e, (r) => W.has(r)) && t(e, "a11y-click-without-keyboard", `A click handler on \`${i2}\` is unreachable by keyboard. Use a \`button\`, or add a \`role\` and a key handler.`);
160
- }
161
- var V = /[A-Za-z0-9\-_:@.?]/;
162
- var N = class {
163
- constructor(t, i2) {
370
+ const visit = (nodes) => {
371
+ for (const node of nodes) {
372
+ if (node.kind === "Element") check(node, report);
373
+ const children = "children" in node ? node.children : void 0;
374
+ if (children) visit(children);
375
+ }
376
+ };
377
+ visit(root.children);
378
+ return out;
379
+ }
380
+ function check(node, report) {
381
+ if (hasSpread(node)) return;
382
+ const tag = node.tag.toLowerCase();
383
+ if (tag === "img" && !attributeNamed(node, "alt") && !isPresentational(node)) {
384
+ report(node, "a11y-img-alt", 'An `img` needs an `alt`. Use `alt=""` when the image is decorative.');
385
+ }
386
+ if (tag === "a" && !attributeNamed(node, "href") && !attributeNamed(node, "role")) {
387
+ report(node, "a11y-anchor-href", "An `a` without `href` is not a link. Use a `button` for an action.");
388
+ }
389
+ if (tag === "iframe" && !attributeNamed(node, "title")) {
390
+ report(node, "a11y-iframe-title", "An `iframe` needs a `title` describing its content.");
391
+ }
392
+ const tabindex = staticValue(node, "tabindex") ?? staticValue(node, "tabIndex");
393
+ if (tabindex !== void 0 && Number(tabindex) > 0) {
394
+ report(
395
+ node,
396
+ "a11y-positive-tabindex",
397
+ "A positive `tabindex` reorders tab navigation for the entire page. Use `0`, or restructure the markup."
398
+ );
399
+ }
400
+ if (!INTERACTIVE.has(tag) && hasEvent(node, (name) => name === "click") && !attributeNamed(node, "role") && !hasEvent(node, (name) => KEY_EVENTS.has(name))) {
401
+ report(
402
+ node,
403
+ "a11y-click-without-keyboard",
404
+ `A click handler on \`${tag}\` is unreachable by keyboard. Use a \`button\`, or add a \`role\` and a key handler.`
405
+ );
406
+ }
407
+ }
408
+ var NAME_CHAR = /[A-Za-z0-9\-_:@.?]/;
409
+ var Parser = class {
410
+ constructor(pieces, opts) {
164
411
  this.diagnostics = [];
165
- this.r = new m(t), this.svg = i2.svg ?? false, this.directives = i2.directives ?? f, this.accessibility = i2.accessibility ?? true;
412
+ this.r = new Reader(pieces);
413
+ this.svg = opts.svg ?? false;
414
+ this.directives = opts.directives ?? CORE_DIRECTIVES;
415
+ this.accessibility = opts.accessibility ?? true;
166
416
  }
167
417
  parse() {
168
- let t = this.r.pos(), i2 = [];
169
- for (; !this.r.eof() && (i2.push(...this.parseChildren()), !this.r.eof()); ) {
170
- let r = this.r.pos();
171
- this.consumeCloseTag(), this.report("stray-close-tag", "error", "Close tag without a matching open tag", this.r.span(r));
418
+ const start = this.r.pos();
419
+ const children = [];
420
+ while (!this.r.eof()) {
421
+ children.push(...this.parseChildren());
422
+ if (this.r.eof()) break;
423
+ const s = this.r.pos();
424
+ this.consumeCloseTag();
425
+ this.report("stray-close-tag", "error", "Close tag without a matching open tag", this.r.span(s));
172
426
  }
173
- let n = { kind: "Root", children: i2, loc: this.r.span(t) };
174
- return x(n), this.validateTree(n), this.accessibility && this.diagnostics.push(...B(n)), { root: n, diagnostics: this.diagnostics };
427
+ const root = { kind: "Root", children, loc: this.r.span(start) };
428
+ setParents(root);
429
+ this.validateTree(root);
430
+ if (this.accessibility) {
431
+ this.diagnostics.push(...checkAccessibility(root));
432
+ }
433
+ return { root, diagnostics: this.diagnostics };
175
434
  }
176
- validateTree(t) {
177
- let i2 = (n) => {
178
- let r = null;
179
- for (let s2 of n) s2.kind === "Text" && /^\s*$/.test(s2.value) || (s2.kind === "Element" || s2.kind === "Component" ? (s2.attributes.some((l2) => l2.kind === "ElseDirective") && (r != null && (r.kind === "Element" || r.kind === "Component") && r.attributes.some((a2) => a2.kind === "IfDirective") || this.report("else-without-if", "error", "`else` must immediately follow an element with `if`", s2.loc)), r = s2) : r = null, "children" in s2 && s2.children && i2(s2.children));
435
+ /** Cross-node validation that needs the built tree (sibling relationships). */
436
+ validateTree(root) {
437
+ const visit = (nodes) => {
438
+ let prevBranch = null;
439
+ for (const node of nodes) {
440
+ if (node.kind === "Text" && /^\s*$/.test(node.value)) continue;
441
+ if (node.kind === "Element" || node.kind === "Component") {
442
+ const hasElse = node.attributes.some((a) => a.kind === "ElseDirective");
443
+ if (hasElse) {
444
+ const prevHasIf = prevBranch != null && (prevBranch.kind === "Element" || prevBranch.kind === "Component") && prevBranch.attributes.some((a) => a.kind === "IfDirective");
445
+ if (!prevHasIf) {
446
+ this.report("else-without-if", "error", "`else` must immediately follow an element with `if`", node.loc);
447
+ }
448
+ }
449
+ prevBranch = node;
450
+ } else {
451
+ prevBranch = null;
452
+ }
453
+ if ("children" in node && node.children) visit(node.children);
454
+ }
180
455
  };
181
- i2(t.children);
456
+ visit(root.children);
182
457
  }
458
+ // --- content ------------------------------------------------------------
459
+ /** Parse sibling nodes until EOF or a `</` close the caller must consume. */
183
460
  parseChildren() {
184
- let t = [];
185
- for (; !this.r.eof() && !this.r.startsWith("</"); ) {
461
+ const out = [];
462
+ while (!this.r.eof()) {
463
+ if (this.r.startsWith("</")) break;
186
464
  if (this.r.startsWith("<!--")) {
187
- t.push(this.parseComment());
465
+ out.push(this.parseComment());
188
466
  continue;
189
467
  }
190
468
  if (this.isFragmentStart()) {
191
- t.push(this.parseFragment());
469
+ out.push(this.parseFragment());
192
470
  continue;
193
471
  }
194
472
  if (this.isDynamicTagStart()) {
195
- t.push(this.parseDynamicComponent());
473
+ out.push(this.parseDynamicComponent());
196
474
  continue;
197
475
  }
198
476
  if (this.isTagStart()) {
199
- t.push(this.parseElement());
477
+ out.push(this.parseElement());
200
478
  continue;
201
479
  }
202
480
  if (this.r.atHole()) {
203
- let n = this.r.takeHole(), r = { kind: "Expression", hole: n.index, loc: n.loc };
204
- t.push(r);
481
+ const h = this.r.takeHole();
482
+ const node = { kind: "Expression", hole: h.index, loc: h.loc };
483
+ out.push(node);
205
484
  continue;
206
485
  }
207
- let i2 = this.readText();
208
- i2 && t.push(i2);
486
+ const text = this.readText();
487
+ if (text) out.push(text);
209
488
  }
210
- return t;
489
+ return out;
211
490
  }
212
491
  isTagStart() {
213
492
  return this.r.peek() === "<" && /[A-Za-z]/.test(this.r.peek(1));
@@ -215,166 +494,306 @@ var N = class {
215
494
  isFragmentStart() {
216
495
  return this.r.peek() === "<" && this.r.peek(1) === ">";
217
496
  }
497
+ /** `<${Comp} …>`: a component whose tag is an interpolated expression. */
218
498
  isDynamicTagStart() {
219
499
  return this.r.peek() === "<" && this.r.holeFollows();
220
500
  }
501
+ /** Parse `<${Comp} …/>` or `<${Comp} …>…</${Comp}>` into a dynamic ComponentNode. */
221
502
  parseDynamicComponent() {
222
- let t = this.r.pos();
503
+ const start = this.r.pos();
223
504
  this.r.advance();
224
- let i2 = this.r.takeHole().index, n = this.parseAttributes(true);
505
+ const tagHole = this.r.takeHole().index;
506
+ const attributes = this.parseAttributes(true);
225
507
  this.r.skipWhitespace();
226
- let r = false, s2 = [];
227
- return this.r.startsWith("/>") ? (this.r.advance(), this.r.advance(), r = true) : (this.r.peek() === ">" && this.r.advance(), s2 = this.parseChildren(), this.r.startsWith("</") ? (this.r.advance(), this.r.advance(), this.r.atHole() ? this.r.takeHole() : this.readName(), this.r.skipWhitespace(), this.r.peek() === ">" && this.r.advance()) : this.report("unclosed-tag", "error", "Unclosed dynamic component", this.r.span(t))), { kind: "Component", tag: "", tagHole: i2, attributes: n, children: s2, selfClosing: r, loc: this.r.span(t) };
508
+ let selfClosing = false;
509
+ let children = [];
510
+ if (this.r.startsWith("/>")) {
511
+ this.r.advance();
512
+ this.r.advance();
513
+ selfClosing = true;
514
+ } else {
515
+ if (this.r.peek() === ">") this.r.advance();
516
+ children = this.parseChildren();
517
+ if (this.r.startsWith("</")) {
518
+ this.r.advance();
519
+ this.r.advance();
520
+ if (this.r.atHole()) this.r.takeHole();
521
+ else this.readName();
522
+ this.r.skipWhitespace();
523
+ if (this.r.peek() === ">") this.r.advance();
524
+ } else {
525
+ this.report("unclosed-tag", "error", "Unclosed dynamic component", this.r.span(start));
526
+ }
527
+ }
528
+ return { kind: "Component", tag: "", tagHole, attributes, children, selfClosing, loc: this.r.span(start) };
228
529
  }
530
+ /** A raw text run (preserved verbatim; whitespace is normalized at lowering). */
229
531
  readText() {
230
- let t = this.r.pos(), i2 = "";
231
- for (; !this.r.eof() && !this.r.atHole() && !(this.r.startsWith("</") || this.r.startsWith("<!--") || this.isTagStart() || this.isFragmentStart() || this.isDynamicTagStart()); ) i2 += this.r.advance();
232
- return i2 ? { kind: "Text", value: i2, raw: false, loc: this.r.span(t) } : null;
532
+ const start = this.r.pos();
533
+ let value = "";
534
+ while (!this.r.eof() && !this.r.atHole()) {
535
+ if (this.r.startsWith("</") || this.r.startsWith("<!--") || this.isTagStart() || this.isFragmentStart() || this.isDynamicTagStart()) break;
536
+ value += this.r.advance();
537
+ }
538
+ if (!value) return null;
539
+ return { kind: "Text", value, raw: false, loc: this.r.span(start) };
233
540
  }
234
541
  parseComment() {
235
- let t = this.r.pos();
236
- this.r.advance(), this.r.advance(), this.r.advance(), this.r.advance();
237
- let i2 = "";
238
- for (; !this.r.eof() && !this.r.startsWith("-->"); ) {
542
+ const start = this.r.pos();
543
+ this.r.advance();
544
+ this.r.advance();
545
+ this.r.advance();
546
+ this.r.advance();
547
+ let value = "";
548
+ while (!this.r.eof() && !this.r.startsWith("-->")) {
239
549
  if (this.r.atHole()) {
240
- this.r.takeHole(), i2 += "\0";
550
+ this.r.takeHole();
551
+ value += "\0";
241
552
  continue;
242
553
  }
243
- i2 += this.r.advance();
554
+ value += this.r.advance();
244
555
  }
245
- return this.r.startsWith("-->") ? (this.r.advance(), this.r.advance(), this.r.advance()) : this.report("unclosed-comment", "warning", "Unterminated comment", this.r.span(t)), { kind: "Comment", value: i2, loc: this.r.span(t) };
556
+ if (this.r.startsWith("-->")) {
557
+ this.r.advance();
558
+ this.r.advance();
559
+ this.r.advance();
560
+ } else {
561
+ this.report("unclosed-comment", "warning", "Unterminated comment", this.r.span(start));
562
+ }
563
+ return { kind: "Comment", value, loc: this.r.span(start) };
246
564
  }
247
565
  parseFragment() {
248
- let t = this.r.pos();
249
- this.r.advance(), this.r.advance();
250
- let i2 = this.parseChildren();
566
+ const start = this.r.pos();
567
+ this.r.advance();
568
+ this.r.advance();
569
+ const children = this.parseChildren();
251
570
  if (this.r.startsWith("</")) {
252
- let n = this.consumeCloseTag();
253
- n && this.report("mismatched-close-tag", "error", `Expected </> to close fragment, got </${n}>`, this.r.span(t));
254
- } else this.report("unclosed-tag", "error", "Unterminated fragment", this.r.span(t));
255
- return { kind: "Fragment", children: i2, loc: this.r.span(t) };
571
+ const name = this.consumeCloseTag();
572
+ if (name) this.report("mismatched-close-tag", "error", `Expected </> to close fragment, got </${name}>`, this.r.span(start));
573
+ } else {
574
+ this.report("unclosed-tag", "error", "Unterminated fragment", this.r.span(start));
575
+ }
576
+ return { kind: "Fragment", children, loc: this.r.span(start) };
256
577
  }
257
578
  parseElement() {
258
- let t = this.r.pos();
579
+ const start = this.r.pos();
259
580
  this.r.advance();
260
- let i2 = this.readName(), n = C(i2), r = this.parseAttributes(n);
261
- this.foldEachKey(r, t), this.r.skipWhitespace();
262
- let s2 = i2.toLowerCase(), o = !n && S.has(s2), l2 = !n && T.has(s2), a2 = false, c = [];
263
- if (this.r.startsWith("/>")) this.r.advance(), this.r.advance(), a2 = true;
264
- else if (this.r.peek() === ">" && this.r.advance(), !l2) if (c = o ? this.parseRawText(i2) : this.parseChildren(), this.r.startsWith("</")) {
265
- let g = this.consumeCloseTag();
266
- g && g !== i2 && this.report("mismatched-close-tag", "error", `Expected </${i2}>, got </${g}>`, this.r.span(t));
267
- } else this.report("unclosed-tag", "error", `Unclosed <${i2}>`, this.r.span(t));
268
- let h = this.r.span(t);
269
- if (n) return { kind: "Component", tag: i2, tagHole: null, attributes: r, children: c, selfClosing: a2, loc: h };
270
- let d = this.svg || E.has(i2) ? "svg" : "html";
271
- return { kind: "Element", tag: i2, namespace: d, attributes: r, children: c, selfClosing: a2, rawText: o, loc: h };
272
- }
273
- parseRawText(t) {
274
- let i2 = [], n = "</" + t.toLowerCase(), r = "", s2 = this.r.pos(), o = () => {
275
- r && i2.push({ kind: "Text", value: r, raw: true, loc: this.r.span(s2) }), r = "";
581
+ const tag = this.readName();
582
+ const component = isComponentTag(tag);
583
+ const attributes = this.parseAttributes(component);
584
+ this.foldEachKey(attributes, start);
585
+ this.r.skipWhitespace();
586
+ const lower = tag.toLowerCase();
587
+ const rawText = !component && RAW_TEXT_ELEMENTS.has(lower);
588
+ const isVoid = !component && VOID_ELEMENTS.has(lower);
589
+ let selfClosing = false;
590
+ let children = [];
591
+ if (this.r.startsWith("/>")) {
592
+ this.r.advance();
593
+ this.r.advance();
594
+ selfClosing = true;
595
+ } else {
596
+ if (this.r.peek() === ">") this.r.advance();
597
+ if (!isVoid) {
598
+ children = rawText ? this.parseRawText(tag) : this.parseChildren();
599
+ if (this.r.startsWith("</")) {
600
+ const name = this.consumeCloseTag();
601
+ if (name && name !== tag) {
602
+ this.report("mismatched-close-tag", "error", `Expected </${tag}>, got </${name}>`, this.r.span(start));
603
+ }
604
+ } else {
605
+ this.report("unclosed-tag", "error", `Unclosed <${tag}>`, this.r.span(start));
606
+ }
607
+ }
608
+ }
609
+ const loc = this.r.span(start);
610
+ if (component) {
611
+ return { kind: "Component", tag, tagHole: null, attributes, children, selfClosing, loc };
612
+ }
613
+ const namespace = this.svg || SVG_TAGS.has(tag) ? "svg" : "html";
614
+ return { kind: "Element", tag, namespace, attributes, children, selfClosing, rawText, loc };
615
+ }
616
+ /** Read a raw-text element body: only holes break the text run. */
617
+ parseRawText(tag) {
618
+ const out = [];
619
+ const close = "</" + tag.toLowerCase();
620
+ let text = "";
621
+ let start = this.r.pos();
622
+ const flush = () => {
623
+ if (text) out.push({ kind: "Text", value: text, raw: true, loc: this.r.span(start) });
624
+ text = "";
276
625
  };
277
- for (; !this.r.eof() && !this.startsWithCloseFor(n); ) {
626
+ while (!this.r.eof()) {
627
+ if (this.startsWithCloseFor(close)) break;
278
628
  if (this.r.atHole()) {
279
- o();
280
- let l2 = this.r.takeHole();
281
- i2.push({ kind: "Expression", hole: l2.index, loc: l2.loc }), s2 = this.r.pos();
629
+ flush();
630
+ const h = this.r.takeHole();
631
+ out.push({ kind: "Expression", hole: h.index, loc: h.loc });
632
+ start = this.r.pos();
282
633
  continue;
283
634
  }
284
- r || (s2 = this.r.pos()), r += this.r.advance();
635
+ if (!text) start = this.r.pos();
636
+ text += this.r.advance();
285
637
  }
286
- return o(), i2;
287
- }
288
- startsWithCloseFor(t) {
289
- let i2 = 0;
290
- for (; i2 < t.length; ) {
291
- let n = this.r.peek(i2);
292
- if (n === "" || n.toLowerCase() !== t[i2]) return false;
293
- i2++;
638
+ flush();
639
+ return out;
640
+ }
641
+ startsWithCloseFor(close) {
642
+ let i = 0;
643
+ while (i < close.length) {
644
+ const ch = this.r.peek(i);
645
+ if (ch === "" || ch.toLowerCase() !== close[i]) return false;
646
+ i++;
294
647
  }
295
648
  return true;
296
649
  }
297
- parseAttributes(t) {
298
- let i2 = [];
299
- for (; !this.r.eof() && (this.r.skipWhitespace(), !(this.r.peek() === ">" || this.r.startsWith("/>"))); ) {
650
+ // --- attributes ---------------------------------------------------------
651
+ parseAttributes(component) {
652
+ const props = [];
653
+ while (!this.r.eof()) {
654
+ this.r.skipWhitespace();
655
+ if (this.r.peek() === ">" || this.r.startsWith("/>")) break;
300
656
  if (this.r.atHole()) {
301
- let a2 = this.r.takeHole();
302
- i2.push({ kind: "Spread", hole: a2.index, loc: a2.loc });
657
+ const h = this.r.takeHole();
658
+ props.push({ kind: "Spread", hole: h.index, loc: h.loc });
303
659
  continue;
304
660
  }
305
661
  if (this.r.startsWith("...")) {
306
- let a2 = this.r.pos();
307
- if (this.r.advance(), this.r.advance(), this.r.advance(), this.r.atHole()) {
308
- let c = this.r.takeHole();
309
- i2.push({ kind: "Spread", hole: c.index, loc: this.r.span(a2) });
310
- } else this.report("invalid-binding", "error", "Spread `...` must be followed by ${\u2026}", this.r.span(a2));
662
+ const s = this.r.pos();
663
+ this.r.advance();
664
+ this.r.advance();
665
+ this.r.advance();
666
+ if (this.r.atHole()) {
667
+ const h = this.r.takeHole();
668
+ props.push({ kind: "Spread", hole: h.index, loc: this.r.span(s) });
669
+ } else {
670
+ this.report("invalid-binding", "error", "Spread `...` must be followed by ${\u2026}", this.r.span(s));
671
+ }
311
672
  continue;
312
673
  }
313
- let n = this.r.pos(), r = this.readName();
314
- if (!r) {
674
+ const nameStart = this.r.pos();
675
+ const name = this.readName();
676
+ if (!name) {
315
677
  this.r.advance();
316
678
  continue;
317
679
  }
318
- let s2 = this.r.span(n);
680
+ const nameLoc = this.r.span(nameStart);
319
681
  this.r.skipWhitespace();
320
- let o = null;
321
- this.r.peek() === "=" && (this.r.advance(), this.r.skipWhitespace(), o = this.parseAttrValue());
322
- let l2 = { name: r, nameLoc: s2, value: o, loc: this.r.span(n), report: (a2, c) => this.report(a2, "error", c, this.r.span(n)) };
323
- i2.push(k(l2, this.directives, { component: t }));
682
+ let value = null;
683
+ if (this.r.peek() === "=") {
684
+ this.r.advance();
685
+ this.r.skipWhitespace();
686
+ value = this.parseAttrValue();
687
+ }
688
+ const raw = {
689
+ name,
690
+ nameLoc,
691
+ value,
692
+ loc: this.r.span(nameStart),
693
+ report: (code, message) => this.report(code, "error", message, this.r.span(nameStart))
694
+ };
695
+ props.push(resolveAttribute(raw, this.directives, { component }));
324
696
  }
325
- return i2;
697
+ return props;
326
698
  }
327
699
  parseAttrValue() {
328
- if (this.r.atHole()) return { kind: "hole", hole: this.r.takeHole().index };
329
- let t = this.r.peek();
330
- return t === '"' || t === "'" ? this.classifyParts(this.readQuotedParts(t)) : { kind: "static", value: this.r.readWhile((n) => !/[\s>]/.test(n)) };
700
+ if (this.r.atHole()) {
701
+ const h = this.r.takeHole();
702
+ return { kind: "hole", hole: h.index };
703
+ }
704
+ const quote = this.r.peek();
705
+ if (quote === '"' || quote === "'") {
706
+ return this.classifyParts(this.readQuotedParts(quote));
707
+ }
708
+ const value = this.r.readWhile((ch) => !/[\s>]/.test(ch));
709
+ return { kind: "static", value };
331
710
  }
332
- readQuotedParts(t) {
711
+ readQuotedParts(quote) {
333
712
  this.r.advance();
334
- let i2 = [], n = "";
335
- for (; !this.r.eof() && this.r.peek() !== t; ) {
713
+ const parts = [];
714
+ let text = "";
715
+ while (!this.r.eof() && this.r.peek() !== quote) {
336
716
  if (this.r.atHole()) {
337
- n && (i2.push({ text: n }), n = ""), i2.push({ hole: this.r.takeHole().index });
717
+ if (text) {
718
+ parts.push({ text });
719
+ text = "";
720
+ }
721
+ parts.push({ hole: this.r.takeHole().index });
338
722
  continue;
339
723
  }
340
- let r = this.r.advance();
341
- if (r === "") break;
342
- n += r;
724
+ const ch = this.r.advance();
725
+ if (ch === "") break;
726
+ text += ch;
343
727
  }
344
- return n && i2.push({ text: n }), this.r.peek() === t && this.r.advance(), i2;
345
- }
346
- classifyParts(t) {
347
- let i2 = t.filter((n) => "hole" in n);
348
- return i2.length === 0 ? { kind: "static", value: t.map((n) => "text" in n ? n.text : "").join("") } : t.length === 1 ? { kind: "hole", hole: i2[0].hole } : { kind: "mixed", parts: t };
349
- }
350
- foldEachKey(t, i2) {
351
- let n = null, r = 0, s2 = 0, o = 0, l2 = 0;
352
- for (let d of t) d.kind === "EachDirective" ? (n = d, s2++) : d.kind === "IfDirective" ? r++ : d.kind === "ElseDirective" ? o++ : d.kind === "RefBinding" && l2++;
353
- let a2 = this.r.span(i2);
354
- s2 > 1 && this.report("duplicate-directive", "error", "Multiple `each` directives on one element", a2), r > 1 && this.report("duplicate-directive", "error", "Multiple `if` directives on one element", a2), l2 > 1 && this.report("duplicate-directive", "error", "Multiple `ref` bindings on one element", a2), r > 0 && s2 > 0 && this.report("unsupported-combination", "error", "`if` and `each` cannot both apply to one element \u2014 wrap one in another element", a2), r > 0 && o > 0 && this.report("invalid-directive", "error", "`if` and `else` cannot be on the same element", a2);
355
- let c = t.findIndex((d) => d.kind === "Attribute" && d.name === "key");
356
- if (!n) {
357
- c !== -1 && this.report("invalid-directive", "error", "`key` requires an `each` directive on the same element", a2);
728
+ if (text) parts.push({ text });
729
+ if (this.r.peek() === quote) this.r.advance();
730
+ return parts;
731
+ }
732
+ classifyParts(parts) {
733
+ const holes = parts.filter((p) => "hole" in p);
734
+ if (holes.length === 0) {
735
+ return { kind: "static", value: parts.map((p) => "text" in p ? p.text : "").join("") };
736
+ }
737
+ if (parts.length === 1) return { kind: "hole", hole: holes[0].hole };
738
+ return { kind: "mixed", parts };
739
+ }
740
+ /** Fold `key` into `each`, and validate an element's directive combination. */
741
+ foldEachKey(attrs, elLoc) {
742
+ let each = null;
743
+ let ifCount = 0;
744
+ let eachCount = 0;
745
+ let elseCount = 0;
746
+ let refCount = 0;
747
+ for (const a of attrs) {
748
+ if (a.kind === "EachDirective") {
749
+ each = a;
750
+ eachCount++;
751
+ } else if (a.kind === "IfDirective") ifCount++;
752
+ else if (a.kind === "ElseDirective") elseCount++;
753
+ else if (a.kind === "RefBinding") refCount++;
754
+ }
755
+ const span = this.r.span(elLoc);
756
+ if (eachCount > 1) this.report("duplicate-directive", "error", "Multiple `each` directives on one element", span);
757
+ if (ifCount > 1) this.report("duplicate-directive", "error", "Multiple `if` directives on one element", span);
758
+ if (refCount > 1) this.report("duplicate-directive", "error", "Multiple `ref` bindings on one element", span);
759
+ if (ifCount > 0 && eachCount > 0) {
760
+ this.report("unsupported-combination", "error", "`if` and `each` cannot both apply to one element \u2014 wrap one in another element", span);
761
+ }
762
+ if (ifCount > 0 && elseCount > 0) {
763
+ this.report("invalid-directive", "error", "`if` and `else` cannot be on the same element", span);
764
+ }
765
+ const keyIdx = attrs.findIndex((a) => a.kind === "Attribute" && a.name === "key");
766
+ if (!each) {
767
+ if (keyIdx !== -1) this.report("invalid-directive", "error", "`key` requires an `each` directive on the same element", span);
358
768
  return;
359
769
  }
360
- if (c === -1) return;
361
- let h = t[c];
362
- h.kind === "Attribute" && h.value && (h.value.kind === "static" ? n.key = { static: h.value.value } : h.value.kind === "hole" && (n.key = { hole: h.value.hole })), t.splice(c, 1);
770
+ if (keyIdx === -1) return;
771
+ const key = attrs[keyIdx];
772
+ if (key.kind === "Attribute" && key.value) {
773
+ if (key.value.kind === "static") each.key = { static: key.value.value };
774
+ else if (key.value.kind === "hole") each.key = { hole: key.value.hole };
775
+ }
776
+ attrs.splice(keyIdx, 1);
363
777
  }
778
+ // --- low level ----------------------------------------------------------
364
779
  readName() {
365
- return this.r.readWhile((t) => V.test(t));
780
+ return this.r.readWhile((ch) => NAME_CHAR.test(ch));
366
781
  }
782
+ /** Consume `</name>` (or `</>`), returning the close-tag name (may be ''). */
367
783
  consumeCloseTag() {
368
- this.r.advance(), this.r.advance();
369
- let t = this.readName();
370
- return this.r.skipWhitespace(), this.r.peek() === ">" && this.r.advance(), t;
784
+ this.r.advance();
785
+ this.r.advance();
786
+ const name = this.readName();
787
+ this.r.skipWhitespace();
788
+ if (this.r.peek() === ">") this.r.advance();
789
+ return name;
371
790
  }
372
- report(t, i2, n, r) {
373
- this.diagnostics.push({ code: t, severity: i2, message: n, loc: r });
791
+ report(code, severity, message, loc) {
792
+ this.diagnostics.push({ code, severity, message, loc });
374
793
  }
375
794
  };
376
- function b(e, t = {}) {
377
- return new N(e, t).parse();
795
+ function parseTemplate(pieces, opts = {}) {
796
+ return new Parser(pieces, opts).parse();
378
797
  }
379
798
 
380
799
  // src/pieces.ts
@@ -390,9 +809,9 @@ function templatePieces(ts, tpl, sf) {
390
809
  const pieces = [
391
810
  { kind: "static", text: rawContent(ts, tpl.head, sf), start: tpl.head.getStart(sf) + 1 }
392
811
  ];
393
- tpl.templateSpans.forEach((span, i2) => {
812
+ tpl.templateSpans.forEach((span, i) => {
394
813
  const e = span.expression;
395
- pieces.push({ kind: "hole", index: i2, start: e.getStart(sf), end: e.getEnd() });
814
+ pieces.push({ kind: "hole", index: i, start: e.getStart(sf), end: e.getEnd() });
396
815
  pieces.push({
397
816
  kind: "static",
398
817
  text: rawContent(ts, span.literal, sf),
@@ -413,7 +832,7 @@ function parseCached(ts, sf, tpl) {
413
832
  const key = tpl.getStart(sf);
414
833
  const hit = byTemplate.get(key);
415
834
  if (hit) return hit;
416
- const result = b(templatePieces(ts, tpl, sf));
835
+ const result = parseTemplate(templatePieces(ts, tpl, sf));
417
836
  byTemplate.set(key, result);
418
837
  return result;
419
838
  }
@@ -437,7 +856,7 @@ function templatesIn(ts, sf) {
437
856
  ts.forEachChild(node, visit);
438
857
  };
439
858
  visit(sf);
440
- found.sort((a2, b2) => a2.getStart(sf) - b2.getStart(sf));
859
+ found.sort((a, b) => a.getStart(sf) - b.getStart(sf));
441
860
  templates.set(sf, found);
442
861
  return found;
443
862
  }
@@ -510,21 +929,42 @@ function importedNames(ts, decl) {
510
929
  }
511
930
 
512
931
  // ../compiler/dist/resolve/builtins.mjs
513
- var s = ["Show", "For", "Index", "Switch", "Match", "Portal", "Dynamic", "ErrorBoundary"];
514
- var i = ["Suspense", "SuspenseList", "Await"];
515
- var u2 = ["Router", "Outlet", "Redirect", "Link"];
516
- var p2 = [...s, ...i, ...u2];
517
- var a = /* @__PURE__ */ new Set([...s, ...i]);
518
- function l(o = {}) {
519
- let { controlFlowModule: e = "@fluixi/dom", coreModule: r = "@fluixi/core", routerModule: c = "@fluixi/core/router" } = o, n = {};
520
- for (let t of s) n[t] = e;
521
- for (let t of i) n[t] = r;
522
- for (let t of u2) n[t] = c;
523
- return n;
932
+ var DOM_CONTROL_FLOW = [
933
+ "Show",
934
+ "For",
935
+ "Index",
936
+ "Switch",
937
+ "Match",
938
+ "Portal",
939
+ "Dynamic",
940
+ "ErrorBoundary"
941
+ ];
942
+ var CORE_CONTROL_FLOW = ["Suspense", "SuspenseList", "Await"];
943
+ var ROUTER_COMPONENTS = ["Router", "Outlet", "Redirect", "Link"];
944
+ var BUILTIN_COMPONENTS = [
945
+ ...DOM_CONTROL_FLOW,
946
+ ...CORE_CONTROL_FLOW,
947
+ ...ROUTER_COMPONENTS
948
+ ];
949
+ var RESERVED_COMPONENTS = /* @__PURE__ */ new Set([
950
+ ...DOM_CONTROL_FLOW,
951
+ ...CORE_CONTROL_FLOW
952
+ ]);
953
+ function builtinComponentMap(modules = {}) {
954
+ const {
955
+ controlFlowModule = "@fluixi/dom",
956
+ coreModule = "@fluixi/core",
957
+ routerModule = "@fluixi/core/router"
958
+ } = modules;
959
+ const map = {};
960
+ for (const name of DOM_CONTROL_FLOW) map[name] = controlFlowModule;
961
+ for (const name of CORE_CONTROL_FLOW) map[name] = coreModule;
962
+ for (const name of ROUTER_COMPONENTS) map[name] = routerModule;
963
+ return map;
524
964
  }
525
965
 
526
966
  // src/auto-import.ts
527
- var AUTO_IMPORTED = l();
967
+ var AUTO_IMPORTED = builtinComponentMap();
528
968
  var exportsByProgram = /* @__PURE__ */ new WeakMap();
529
969
  function moduleExports(ts, program, host, from, moduleName) {
530
970
  let cache2 = exportsByProgram.get(program);
@@ -597,7 +1037,7 @@ function findExport(ts, host, options, fileName, name, depth) {
597
1037
  return void 0;
598
1038
  }
599
1039
  function declaredName(ts, statement, name) {
600
- const exported = ts.canHaveModifiers(statement) ? ts.getModifiers(statement)?.some((m2) => m2.kind === ts.SyntaxKind.ExportKeyword) : false;
1040
+ const exported = ts.canHaveModifiers(statement) ? ts.getModifiers(statement)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) : false;
601
1041
  if (!exported) return void 0;
602
1042
  if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name?.text === name) {
603
1043
  return statement.name;
@@ -620,7 +1060,7 @@ function resolveRelative(ts, host, options, from, specifier) {
620
1060
  function autoImportedSymbol(ts, program, host, from, name) {
621
1061
  const moduleName = AUTO_IMPORTED[name];
622
1062
  if (!moduleName) return void 0;
623
- const symbol = moduleExports(ts, program, host, from, moduleName)?.find((s2) => s2.name === name);
1063
+ const symbol = moduleExports(ts, program, host, from, moduleName)?.find((s) => s.name === name);
624
1064
  if (!symbol) return void 0;
625
1065
  if (!(symbol.flags & ts.SymbolFlags.Alias)) return symbol;
626
1066
  try {
@@ -641,7 +1081,7 @@ function componentQuickInfo(ts, ls, host, fileName, position) {
641
1081
  if (!hit) return void 0;
642
1082
  const checker = program.getTypeChecker();
643
1083
  const flags = ts.SymbolFlags.Value | ts.SymbolFlags.Function | ts.SymbolFlags.Class | ts.SymbolFlags.Alias;
644
- const symbol = checker.getSymbolsInScope(template, flags).find((s2) => s2.name === hit.name) ?? autoImportedSymbol(ts, program, host, sf, hit.name);
1084
+ const symbol = checker.getSymbolsInScope(template, flags).find((s) => s.name === hit.name) ?? autoImportedSymbol(ts, program, host, sf, hit.name);
645
1085
  const decl = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
646
1086
  if (!decl) return void 0;
647
1087
  const declFile = decl.getSourceFile();
@@ -778,21 +1218,21 @@ function holeRoles(nodes, out) {
778
1218
  };
779
1219
  for (const node of nodes) {
780
1220
  if (node.kind === "Element" || node.kind === "Component") {
781
- for (const a2 of node.attributes) {
782
- if (a2.kind === "EventBinding") out.set(a2.hole, { kind: "event", event: a2.name.toLowerCase() });
783
- if (node.kind === "Element" && a2.kind === "RefBinding") out.set(a2.hole, { kind: "refEl", tag: node.tag });
784
- if (node.kind === "Element" && a2.kind === "UseDirective" && a2.hole != null) out.set(a2.hole, { kind: "refEl", tag: node.tag });
1221
+ for (const a of node.attributes) {
1222
+ if (a.kind === "EventBinding") out.set(a.hole, { kind: "event", event: a.name.toLowerCase() });
1223
+ if (node.kind === "Element" && a.kind === "RefBinding") out.set(a.hole, { kind: "refEl", tag: node.tag });
1224
+ if (node.kind === "Element" && a.kind === "UseDirective" && a.hole != null) out.set(a.hole, { kind: "refEl", tag: node.tag });
785
1225
  }
786
- const eachDir = node.attributes.find((a2) => a2.kind === "EachDirective");
1226
+ const eachDir = node.attributes.find((a) => a.kind === "EachDirective");
787
1227
  if (eachDir && eachDir.kind === "EachDirective") {
788
1228
  renderChildren(node, { kind: "each", eachHole: eachDir.hole });
789
1229
  }
790
1230
  if (node.kind === "Component") {
791
1231
  const propHole = (name) => {
792
- const a2 = node.attributes.find(
793
- (x2) => x2.kind === "Attribute" && x2.name === name && x2.value != null && x2.value.kind === "hole"
1232
+ const a = node.attributes.find(
1233
+ (x) => x.kind === "Attribute" && x.name === name && x.value != null && x.value.kind === "hole"
794
1234
  );
795
- return a2 && a2.kind === "Attribute" && a2.value && a2.value.kind === "hole" ? a2.value.hole : void 0;
1235
+ return a && a.kind === "Attribute" && a.value && a.value.kind === "hole" ? a.value.hole : void 0;
796
1236
  };
797
1237
  const eachHole = propHole("each");
798
1238
  if (node.tag === "For" && eachHole !== void 0) renderChildren(node, { kind: "each", eachHole });
@@ -805,7 +1245,7 @@ function holeRoles(nodes, out) {
805
1245
  }
806
1246
  }
807
1247
  function resolveGlobalType(ts, checker, location, name) {
808
- const sym = checker.getSymbolsInScope(location, ts.SymbolFlags.Type).find((s2) => s2.getName() === name);
1248
+ const sym = checker.getSymbolsInScope(location, ts.SymbolFlags.Type).find((s) => s.getName() === name);
809
1249
  return sym ? checker.getDeclaredTypeOfSymbol(sym) : void 0;
810
1250
  }
811
1251
  function resolveElementType(ts, checker, location, tag) {
@@ -818,7 +1258,7 @@ function inferParamAt(ts, program, sf, position) {
818
1258
  const template = templateAtCached(ts, sf, position);
819
1259
  if (!template || ts.isNoSubstitutionTemplateLiteral(template.template)) return void 0;
820
1260
  const spans = template.template.templateSpans;
821
- const holeExprs = spans.map((s2) => s2.expression);
1261
+ const holeExprs = spans.map((s) => s.expression);
822
1262
  const holeIndex = holeExprs.findIndex((e) => position >= e.getStart(sf) && position <= e.getEnd());
823
1263
  if (holeIndex === -1) return void 0;
824
1264
  const roles = /* @__PURE__ */ new Map();
@@ -867,7 +1307,7 @@ function inferParamsIn(ts, program, sf) {
867
1307
  for (const template of templatesIn(ts, sf)) {
868
1308
  const tpl = template.template;
869
1309
  if (ts.isNoSubstitutionTemplateLiteral(tpl)) continue;
870
- const holeExprs = tpl.templateSpans.map((s2) => s2.expression);
1310
+ const holeExprs = tpl.templateSpans.map((s) => s.expression);
871
1311
  const roles = /* @__PURE__ */ new Map();
872
1312
  holeRoles(parseCached(ts, sf, tpl).root.children, roles);
873
1313
  for (const [holeIndex, role] of roles) {
@@ -942,8 +1382,8 @@ function componentPropCompletions(ts, ls, host, fileName, position) {
942
1382
  if (!propsType) return void 0;
943
1383
  const already = new Set(comp.attributeNames);
944
1384
  const entries = [];
945
- for (const p3 of propsType.getProperties()) {
946
- const name = p3.getName();
1385
+ for (const p of propsType.getProperties()) {
1386
+ const name = p.getName();
947
1387
  if (name === "children" || already.has(name)) continue;
948
1388
  entries.push(propEntry(ts, name, span));
949
1389
  }
@@ -997,10 +1437,10 @@ function propNameAt(ts, sf, tpl, position) {
997
1437
  for (const node of nodes) {
998
1438
  if (node.kind === "Component") {
999
1439
  const c = node;
1000
- for (const a2 of c.attributes) {
1001
- if ((a2.kind === "Attribute" || a2.kind === "PropertyBinding" || a2.kind === "BindDirective") && "loc" in a2) {
1002
- const start = a2.loc.start;
1003
- if (position >= start && position <= start + a2.name.length) hit = { tag: c.tag, prop: a2.name, start };
1440
+ for (const a of c.attributes) {
1441
+ if ((a.kind === "Attribute" || a.kind === "PropertyBinding" || a.kind === "BindDirective") && "loc" in a) {
1442
+ const start = a.loc.start;
1443
+ if (position >= start && position <= start + a.name.length) hit = { tag: c.tag, prop: a.name, start };
1004
1444
  }
1005
1445
  }
1006
1446
  }
@@ -1045,7 +1485,7 @@ function propEntry(ts, name, replacementSpan) {
1045
1485
  sortText: "0_" + name,
1046
1486
  insertText: name,
1047
1487
  // Without a span the editor filters these against whatever word *it* computes at the
1048
- // cursor, and inside a template literal that is not the attribute name so the list
1488
+ // cursor, and inside a template literal that is not the attribute name, so the list
1049
1489
  // showed in full until the first keystroke and then emptied. Saying exactly which
1050
1490
  // characters are being replaced makes filtering match what is typed.
1051
1491
  replacementSpan
@@ -1061,7 +1501,7 @@ function attrNameSpan(text, position) {
1061
1501
  }
1062
1502
  function componentTypeOf(ts, program, checker, host, sf, template, name) {
1063
1503
  const flags = ts.SymbolFlags.Value | ts.SymbolFlags.Function | ts.SymbolFlags.Class | ts.SymbolFlags.Alias;
1064
- const symbol = checker.getSymbolsInScope(template, flags).find((s2) => s2.name === name) ?? autoImportedSymbol(ts, program, host, sf, name);
1504
+ const symbol = checker.getSymbolsInScope(template, flags).find((s) => s.name === name) ?? autoImportedSymbol(ts, program, host, sf, name);
1065
1505
  if (!symbol) return void 0;
1066
1506
  const resolved = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
1067
1507
  const decl = resolved.valueDeclaration ?? resolved.declarations?.[0];
@@ -1069,8 +1509,8 @@ function componentTypeOf(ts, program, checker, host, sf, template, name) {
1069
1509
  }
1070
1510
  function firstParamType(ts, checker, type) {
1071
1511
  for (const sig of checker.getSignaturesOfType(type, ts.SignatureKind.Call)) {
1072
- const p3 = sig.getParameters()[0];
1073
- if (p3) return checker.getTypeOfSymbolAtLocation(p3, p3.valueDeclaration ?? sig.getDeclaration());
1512
+ const p = sig.getParameters()[0];
1513
+ if (p) return checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? sig.getDeclaration());
1074
1514
  }
1075
1515
  return void 0;
1076
1516
  }
@@ -1101,8 +1541,8 @@ function firstChildOrCloseStart(c) {
1101
1541
  }
1102
1542
  function attributeNames(c) {
1103
1543
  const out = [];
1104
- for (const a2 of c.attributes) {
1105
- if (a2.kind === "Attribute" || a2.kind === "PropertyBinding" || a2.kind === "BindDirective") out.push(a2.name);
1544
+ for (const a of c.attributes) {
1545
+ if (a.kind === "Attribute" || a.kind === "PropertyBinding" || a.kind === "BindDirective") out.push(a.name);
1106
1546
  }
1107
1547
  return out;
1108
1548
  }
@@ -1119,13 +1559,13 @@ function paramMemberCompletions(ts, ls, fileName, position) {
1119
1559
  const checker = program.getTypeChecker();
1120
1560
  const already = /* @__PURE__ */ new Set();
1121
1561
  const entries = [];
1122
- for (const p3 of checker.getApparentType(info.type).getProperties()) {
1123
- const name = p3.getName();
1562
+ for (const p of checker.getApparentType(info.type).getProperties()) {
1563
+ const name = p.getName();
1124
1564
  if (already.has(name) || name.startsWith("__")) continue;
1125
1565
  already.add(name);
1126
1566
  entries.push({
1127
1567
  name,
1128
- kind: memberKind(ts, p3),
1568
+ kind: memberKind(ts, p),
1129
1569
  kindModifiers: "",
1130
1570
  sortText: "0_" + name,
1131
1571
  insertText: name
@@ -1213,8 +1653,8 @@ function elementAtAttributeArea(ts, sf, tpl, position) {
1213
1653
  const openEnd = firstAttributeEnd(el);
1214
1654
  if (position > openStart && position <= openEnd) {
1215
1655
  const present = /* @__PURE__ */ new Set();
1216
- for (const a2 of el.attributes) {
1217
- if ("loc" in a2 && a2.loc) present.add(writtenName(sf.text, a2.loc.start));
1656
+ for (const a of el.attributes) {
1657
+ if ("loc" in a && a.loc) present.add(writtenName(sf.text, a.loc.start));
1218
1658
  }
1219
1659
  hit = { tag: el.tag, present };
1220
1660
  }
@@ -1233,8 +1673,8 @@ function writtenName(text, start) {
1233
1673
  }
1234
1674
  function firstAttributeEnd(el) {
1235
1675
  let end = el.loc.start + 1 + el.tag.length;
1236
- for (const a2 of el.attributes) {
1237
- if ("loc" in a2 && a2.loc) end = Math.max(end, a2.loc.end);
1676
+ for (const a of el.attributes) {
1677
+ if ("loc" in a && a.loc) end = Math.max(end, a.loc.end);
1238
1678
  }
1239
1679
  return end + 1;
1240
1680
  }
@@ -1249,8 +1689,8 @@ function elementAttributeCompletions(ts, ls, host, fileName, position) {
1249
1689
  const props = attributesFor(ts, program, host, sf, hit.tag);
1250
1690
  if (!props) return void 0;
1251
1691
  const entries = [];
1252
- for (const p3 of props) {
1253
- const name = p3.getName();
1692
+ for (const p of props) {
1693
+ const name = p.getName();
1254
1694
  if (name === "children" || hit.present.has(name)) continue;
1255
1695
  entries.push({
1256
1696
  name,
@@ -1268,9 +1708,9 @@ function attributeNameAt(ts, sf, tpl, position) {
1268
1708
  for (const node of nodes) {
1269
1709
  if (node.kind === "Element") {
1270
1710
  const el = node;
1271
- for (const a2 of el.attributes) {
1272
- if (!("loc" in a2) || !a2.loc) continue;
1273
- const start = a2.loc.start;
1711
+ for (const a of el.attributes) {
1712
+ if (!("loc" in a) || !a.loc) continue;
1713
+ const start = a.loc.start;
1274
1714
  const name = writtenName(sf.text, start);
1275
1715
  if (name && position >= start && position <= start + name.length) {
1276
1716
  hit = { tag: el.tag, name, start };
@@ -1293,7 +1733,7 @@ function elementAttributeHover(ts, ls, host, fileName, position) {
1293
1733
  const hit = attributeNameAt(ts, sf, tpl.template, position);
1294
1734
  if (!hit) return void 0;
1295
1735
  const props = attributesFor(ts, program, host, sf, hit.tag);
1296
- const symbol = props?.find((p3) => p3.getName() === hit.name);
1736
+ const symbol = props?.find((p) => p.getName() === hit.name);
1297
1737
  if (!symbol) return void 0;
1298
1738
  const decl = symbol.valueDeclaration ?? symbol.declarations?.[0];
1299
1739
  if (!decl) return void 0;
@@ -1333,7 +1773,7 @@ function accessorDiagnostics(ts, program, sf) {
1333
1773
  for (const template of templatesIn(ts, sf)) {
1334
1774
  const tpl = template.template;
1335
1775
  if (ts.isNoSubstitutionTemplateLiteral(tpl)) continue;
1336
- const holes = tpl.templateSpans.map((s2) => s2.expression);
1776
+ const holes = tpl.templateSpans.map((s) => s.expression);
1337
1777
  const visit = (nodes, parent) => {
1338
1778
  for (const node of nodes) {
1339
1779
  if (node.kind === "Expression" && parent?.kind === "Element") {
@@ -1366,7 +1806,7 @@ function isAccessor(ts, checker, node) {
1366
1806
  const signatures = checker.getTypeAtLocation(node).getCallSignatures();
1367
1807
  if (signatures.length !== 1) return false;
1368
1808
  const signature = signatures[0];
1369
- if (signature.getParameters().some((p3) => !isOptional(ts, p3))) return false;
1809
+ if (signature.getParameters().some((p) => !isOptional(ts, p))) return false;
1370
1810
  const returned = signature.getReturnType();
1371
1811
  return !(returned.flags & (ts.TypeFlags.Void | ts.TypeFlags.Never));
1372
1812
  }
@@ -1443,7 +1883,7 @@ function templateFixAll(ts, ls, fileName, errorCodes) {
1443
1883
  textChanges.push({ span: { start: at, length: 0 }, newText: "()" });
1444
1884
  }
1445
1885
  if (textChanges.length === 0) return void 0;
1446
- textChanges.sort((a2, b2) => b2.span.start - a2.span.start);
1886
+ textChanges.sort((a, b) => b.span.start - a.span.start);
1447
1887
  return { changes: [{ fileName, textChanges }], commands: void 0 };
1448
1888
  }
1449
1889
 
@@ -1482,12 +1922,12 @@ function closingTagName(text, end, tag) {
1482
1922
  }
1483
1923
  function tagAt(ts, sf, position) {
1484
1924
  if (!templateAtCached(ts, sf, position)) return void 0;
1485
- return tagSpans(ts, sf).find((s2) => position >= s2.start && position <= s2.start + s2.length);
1925
+ return tagSpans(ts, sf).find((s) => position >= s.start && position <= s.start + s.length);
1486
1926
  }
1487
1927
  function symbolFor(ts, program, host, location, name) {
1488
1928
  const checker = program.getTypeChecker();
1489
1929
  const flags = ts.SymbolFlags.Value | ts.SymbolFlags.Function | ts.SymbolFlags.Class | ts.SymbolFlags.Alias;
1490
- const symbol = checker.getSymbolsInScope(location, flags).find((s2) => s2.name === name);
1930
+ const symbol = checker.getSymbolsInScope(location, flags).find((s) => s.name === name);
1491
1931
  if (!symbol) return autoImportedSymbol(ts, program, host, location.getSourceFile(), name);
1492
1932
  return symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
1493
1933
  }
@@ -1577,8 +2017,8 @@ function templateReferences(ts, ls, host, fileName, position) {
1577
2017
  if (!program || !sf) return [];
1578
2018
  const hit = symbolAt(ts, program, host, sf, position);
1579
2019
  if (!hit) return [];
1580
- return tagOccurrences(ts, program, host, hit.symbol, hit.name).map(({ fileName: f2, span }) => ({
1581
- fileName: f2,
2020
+ return tagOccurrences(ts, program, host, hit.symbol, hit.name).map(({ fileName: f, span }) => ({
2021
+ fileName: f,
1582
2022
  textSpan: span,
1583
2023
  isWriteAccess: false,
1584
2024
  isDefinition: false
@@ -1590,8 +2030,8 @@ function templateRenameLocations(ts, ls, host, fileName, position) {
1590
2030
  if (!program || !sf) return [];
1591
2031
  const hit = symbolAt(ts, program, host, sf, position);
1592
2032
  if (!hit) return [];
1593
- return tagOccurrences(ts, program, host, hit.symbol, hit.name).map(({ fileName: f2, span }) => ({
1594
- fileName: f2,
2033
+ return tagOccurrences(ts, program, host, hit.symbol, hit.name).map(({ fileName: f, span }) => ({
2034
+ fileName: f,
1595
2035
  textSpan: span
1596
2036
  }));
1597
2037
  }
@@ -1613,8 +2053,8 @@ function init(modules) {
1613
2053
  isMemberCompletion: false,
1614
2054
  isNewIdentifierLocation: true,
1615
2055
  // Re-ask on every keystroke instead of letting the editor filter what it already
1616
- // has. Inside a template the valid set changes with the cursor a tag name, then
1617
- // that tag's props and a list cached from one position is wrong at the next.
2056
+ // has. Inside a template the valid set changes with the cursor, a tag name, then
2057
+ // that tag's props: and a list cached from one position is wrong at the next.
1618
2058
  // Without this the props appear only after enough typing to force a fresh
1619
2059
  // request, which reads as completion working intermittently.
1620
2060
  isIncomplete: true,
@@ -1623,8 +2063,8 @@ function init(modules) {
1623
2063
  const IMPLICIT_ANY = /* @__PURE__ */ new Set([7006, 7044]);
1624
2064
  const dedupe = (items) => {
1625
2065
  const seen = /* @__PURE__ */ new Set();
1626
- return items.filter((i2) => {
1627
- const key = `${i2.fileName}:${i2.textSpan.start}:${i2.textSpan.length}`;
2066
+ return items.filter((i) => {
2067
+ const key = `${i.fileName}:${i.textSpan.start}:${i.textSpan.length}`;
1628
2068
  if (seen.has(key)) return false;
1629
2069
  seen.add(key);
1630
2070
  return true;
@@ -1761,3 +2201,5 @@ function init(modules) {
1761
2201
  };
1762
2202
  }
1763
2203
  module.exports = init;
2204
+ /*! @fluixi/template-parser v0.1.0-alpha.4 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2205
+ /*! @fluixi/compiler v1.0.0-alpha.85 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */