@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.
- package/dist/index.js +748 -306
- 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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
for (
|
|
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
|
-
|
|
10
|
+
visit(root, null);
|
|
11
|
+
return root;
|
|
11
12
|
}
|
|
12
|
-
function
|
|
13
|
-
switch (
|
|
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
|
|
19
|
+
return node.children;
|
|
19
20
|
default:
|
|
20
21
|
return [];
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
this.pi
|
|
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
|
-
|
|
46
|
-
return
|
|
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
|
-
|
|
49
|
-
return
|
|
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
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
81
|
+
const p = this.pieces[this.pi];
|
|
82
|
+
if (p.kind !== "static") return "";
|
|
83
|
+
return p.text[this.oi + k] ?? "";
|
|
70
84
|
}
|
|
71
|
-
|
|
85
|
+
/** Whether the current static segment starts with `s` at the cursor. */
|
|
86
|
+
startsWith(s) {
|
|
72
87
|
if (this.eof()) return false;
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
110
|
+
this.normalize();
|
|
111
|
+
return out;
|
|
91
112
|
}
|
|
92
113
|
skipWhitespace() {
|
|
93
|
-
this.readWhile((
|
|
114
|
+
this.readWhile((ch) => /\s/.test(ch));
|
|
94
115
|
}
|
|
95
|
-
span(
|
|
96
|
-
return { start
|
|
116
|
+
span(start) {
|
|
117
|
+
return { start, end: this.pos() };
|
|
97
118
|
}
|
|
98
119
|
};
|
|
99
|
-
var
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
461
|
+
const out = [];
|
|
462
|
+
while (!this.r.eof()) {
|
|
463
|
+
if (this.r.startsWith("</")) break;
|
|
186
464
|
if (this.r.startsWith("<!--")) {
|
|
187
|
-
|
|
465
|
+
out.push(this.parseComment());
|
|
188
466
|
continue;
|
|
189
467
|
}
|
|
190
468
|
if (this.isFragmentStart()) {
|
|
191
|
-
|
|
469
|
+
out.push(this.parseFragment());
|
|
192
470
|
continue;
|
|
193
471
|
}
|
|
194
472
|
if (this.isDynamicTagStart()) {
|
|
195
|
-
|
|
473
|
+
out.push(this.parseDynamicComponent());
|
|
196
474
|
continue;
|
|
197
475
|
}
|
|
198
476
|
if (this.isTagStart()) {
|
|
199
|
-
|
|
477
|
+
out.push(this.parseElement());
|
|
200
478
|
continue;
|
|
201
479
|
}
|
|
202
480
|
if (this.r.atHole()) {
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
208
|
-
|
|
486
|
+
const text = this.readText();
|
|
487
|
+
if (text) out.push(text);
|
|
209
488
|
}
|
|
210
|
-
return
|
|
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
|
-
|
|
503
|
+
const start = this.r.pos();
|
|
223
504
|
this.r.advance();
|
|
224
|
-
|
|
505
|
+
const tagHole = this.r.takeHole().index;
|
|
506
|
+
const attributes = this.parseAttributes(true);
|
|
225
507
|
this.r.skipWhitespace();
|
|
226
|
-
let
|
|
227
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
236
|
-
this.r.advance()
|
|
237
|
-
|
|
238
|
-
|
|
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()
|
|
550
|
+
this.r.takeHole();
|
|
551
|
+
value += "\0";
|
|
241
552
|
continue;
|
|
242
553
|
}
|
|
243
|
-
|
|
554
|
+
value += this.r.advance();
|
|
244
555
|
}
|
|
245
|
-
|
|
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
|
-
|
|
249
|
-
this.r.advance()
|
|
250
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
} else
|
|
255
|
-
|
|
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
|
-
|
|
579
|
+
const start = this.r.pos();
|
|
259
580
|
this.r.advance();
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
let
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
626
|
+
while (!this.r.eof()) {
|
|
627
|
+
if (this.startsWithCloseFor(close)) break;
|
|
278
628
|
if (this.r.atHole()) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
635
|
+
if (!text) start = this.r.pos();
|
|
636
|
+
text += this.r.advance();
|
|
285
637
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
302
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
-
|
|
680
|
+
const nameLoc = this.r.span(nameStart);
|
|
319
681
|
this.r.skipWhitespace();
|
|
320
|
-
let
|
|
321
|
-
this.r.peek() === "="
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
697
|
+
return props;
|
|
326
698
|
}
|
|
327
699
|
parseAttrValue() {
|
|
328
|
-
if (this.r.atHole())
|
|
329
|
-
|
|
330
|
-
|
|
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(
|
|
711
|
+
readQuotedParts(quote) {
|
|
333
712
|
this.r.advance();
|
|
334
|
-
|
|
335
|
-
|
|
713
|
+
const parts = [];
|
|
714
|
+
let text = "";
|
|
715
|
+
while (!this.r.eof() && this.r.peek() !== quote) {
|
|
336
716
|
if (this.r.atHole()) {
|
|
337
|
-
|
|
717
|
+
if (text) {
|
|
718
|
+
parts.push({ text });
|
|
719
|
+
text = "";
|
|
720
|
+
}
|
|
721
|
+
parts.push({ hole: this.r.takeHole().index });
|
|
338
722
|
continue;
|
|
339
723
|
}
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
724
|
+
const ch = this.r.advance();
|
|
725
|
+
if (ch === "") break;
|
|
726
|
+
text += ch;
|
|
343
727
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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 (
|
|
361
|
-
|
|
362
|
-
|
|
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((
|
|
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()
|
|
369
|
-
|
|
370
|
-
|
|
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(
|
|
373
|
-
this.diagnostics.push({ code
|
|
791
|
+
report(code, severity, message, loc) {
|
|
792
|
+
this.diagnostics.push({ code, severity, message, loc });
|
|
374
793
|
}
|
|
375
794
|
};
|
|
376
|
-
function
|
|
377
|
-
return new
|
|
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,
|
|
812
|
+
tpl.templateSpans.forEach((span, i) => {
|
|
394
813
|
const e = span.expression;
|
|
395
|
-
pieces.push({ kind: "hole", index:
|
|
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 =
|
|
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((
|
|
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
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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 =
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
|
782
|
-
if (
|
|
783
|
-
if (node.kind === "Element" &&
|
|
784
|
-
if (node.kind === "Element" &&
|
|
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((
|
|
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
|
|
793
|
-
(
|
|
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
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
|
946
|
-
const name =
|
|
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
|
|
1001
|
-
if ((
|
|
1002
|
-
const start =
|
|
1003
|
-
if (position >= start && position <= 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
|
|
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((
|
|
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
|
|
1073
|
-
if (
|
|
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
|
|
1105
|
-
if (
|
|
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
|
|
1123
|
-
const name =
|
|
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,
|
|
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
|
|
1217
|
-
if ("loc" in
|
|
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
|
|
1237
|
-
if ("loc" in
|
|
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
|
|
1253
|
-
const name =
|
|
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
|
|
1272
|
-
if (!("loc" in
|
|
1273
|
-
const 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((
|
|
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((
|
|
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((
|
|
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((
|
|
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((
|
|
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((
|
|
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:
|
|
1581
|
-
fileName:
|
|
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:
|
|
1594
|
-
fileName:
|
|
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
|
|
1617
|
-
// that tag's props
|
|
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((
|
|
1627
|
-
const key = `${
|
|
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 */
|