@fluixi/ts-plugin 0.1.0-alpha.2 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -14
- package/dist/index.js +1306 -710
- package/package.json +5 -3
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,134 +44,449 @@ var u = 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
|
-
|
|
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 };
|
|
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 });
|
|
369
|
+
};
|
|
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) {
|
|
124
411
|
this.diagnostics = [];
|
|
125
|
-
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;
|
|
126
416
|
}
|
|
127
417
|
parse() {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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));
|
|
132
426
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 };
|
|
434
|
+
}
|
|
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
|
+
}
|
|
140
455
|
};
|
|
141
|
-
|
|
456
|
+
visit(root.children);
|
|
142
457
|
}
|
|
458
|
+
// --- content ------------------------------------------------------------
|
|
459
|
+
/** Parse sibling nodes until EOF or a `</` close the caller must consume. */
|
|
143
460
|
parseChildren() {
|
|
144
|
-
|
|
145
|
-
|
|
461
|
+
const out = [];
|
|
462
|
+
while (!this.r.eof()) {
|
|
463
|
+
if (this.r.startsWith("</")) break;
|
|
146
464
|
if (this.r.startsWith("<!--")) {
|
|
147
|
-
|
|
465
|
+
out.push(this.parseComment());
|
|
148
466
|
continue;
|
|
149
467
|
}
|
|
150
468
|
if (this.isFragmentStart()) {
|
|
151
|
-
|
|
469
|
+
out.push(this.parseFragment());
|
|
152
470
|
continue;
|
|
153
471
|
}
|
|
154
472
|
if (this.isDynamicTagStart()) {
|
|
155
|
-
|
|
473
|
+
out.push(this.parseDynamicComponent());
|
|
156
474
|
continue;
|
|
157
475
|
}
|
|
158
476
|
if (this.isTagStart()) {
|
|
159
|
-
|
|
477
|
+
out.push(this.parseElement());
|
|
160
478
|
continue;
|
|
161
479
|
}
|
|
162
480
|
if (this.r.atHole()) {
|
|
163
|
-
|
|
164
|
-
|
|
481
|
+
const h = this.r.takeHole();
|
|
482
|
+
const node = { kind: "Expression", hole: h.index, loc: h.loc };
|
|
483
|
+
out.push(node);
|
|
165
484
|
continue;
|
|
166
485
|
}
|
|
167
|
-
|
|
168
|
-
|
|
486
|
+
const text = this.readText();
|
|
487
|
+
if (text) out.push(text);
|
|
169
488
|
}
|
|
170
|
-
return
|
|
489
|
+
return out;
|
|
171
490
|
}
|
|
172
491
|
isTagStart() {
|
|
173
492
|
return this.r.peek() === "<" && /[A-Za-z]/.test(this.r.peek(1));
|
|
@@ -175,175 +494,380 @@ var g = class {
|
|
|
175
494
|
isFragmentStart() {
|
|
176
495
|
return this.r.peek() === "<" && this.r.peek(1) === ">";
|
|
177
496
|
}
|
|
497
|
+
/** `<${Comp} …>`: a component whose tag is an interpolated expression. */
|
|
178
498
|
isDynamicTagStart() {
|
|
179
499
|
return this.r.peek() === "<" && this.r.holeFollows();
|
|
180
500
|
}
|
|
501
|
+
/** Parse `<${Comp} …/>` or `<${Comp} …>…</${Comp}>` into a dynamic ComponentNode. */
|
|
181
502
|
parseDynamicComponent() {
|
|
182
|
-
|
|
503
|
+
const start = this.r.pos();
|
|
183
504
|
this.r.advance();
|
|
184
|
-
|
|
505
|
+
const tagHole = this.r.takeHole().index;
|
|
506
|
+
const attributes = this.parseAttributes(true);
|
|
185
507
|
this.r.skipWhitespace();
|
|
186
|
-
let
|
|
187
|
-
|
|
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) };
|
|
188
529
|
}
|
|
530
|
+
/** A raw text run (preserved verbatim; whitespace is normalized at lowering). */
|
|
189
531
|
readText() {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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) };
|
|
193
540
|
}
|
|
194
541
|
parseComment() {
|
|
195
|
-
|
|
196
|
-
this.r.advance()
|
|
197
|
-
|
|
198
|
-
|
|
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("-->")) {
|
|
199
549
|
if (this.r.atHole()) {
|
|
200
|
-
this.r.takeHole()
|
|
550
|
+
this.r.takeHole();
|
|
551
|
+
value += "\0";
|
|
201
552
|
continue;
|
|
202
553
|
}
|
|
203
|
-
|
|
554
|
+
value += this.r.advance();
|
|
555
|
+
}
|
|
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));
|
|
204
562
|
}
|
|
205
|
-
return
|
|
563
|
+
return { kind: "Comment", value, loc: this.r.span(start) };
|
|
206
564
|
}
|
|
207
565
|
parseFragment() {
|
|
208
|
-
|
|
209
|
-
this.r.advance()
|
|
210
|
-
|
|
566
|
+
const start = this.r.pos();
|
|
567
|
+
this.r.advance();
|
|
568
|
+
this.r.advance();
|
|
569
|
+
const children = this.parseChildren();
|
|
211
570
|
if (this.r.startsWith("</")) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
} else
|
|
215
|
-
|
|
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) };
|
|
216
577
|
}
|
|
217
578
|
parseElement() {
|
|
218
|
-
|
|
579
|
+
const start = this.r.pos();
|
|
219
580
|
this.r.advance();
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
let
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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 = "";
|
|
236
625
|
};
|
|
237
|
-
|
|
626
|
+
while (!this.r.eof()) {
|
|
627
|
+
if (this.startsWithCloseFor(close)) break;
|
|
238
628
|
if (this.r.atHole()) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
629
|
+
flush();
|
|
630
|
+
const h = this.r.takeHole();
|
|
631
|
+
out.push({ kind: "Expression", hole: h.index, loc: h.loc });
|
|
632
|
+
start = this.r.pos();
|
|
242
633
|
continue;
|
|
243
634
|
}
|
|
244
|
-
|
|
635
|
+
if (!text) start = this.r.pos();
|
|
636
|
+
text += this.r.advance();
|
|
245
637
|
}
|
|
246
|
-
|
|
638
|
+
flush();
|
|
639
|
+
return out;
|
|
247
640
|
}
|
|
248
|
-
startsWithCloseFor(
|
|
641
|
+
startsWithCloseFor(close) {
|
|
249
642
|
let i = 0;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (
|
|
643
|
+
while (i < close.length) {
|
|
644
|
+
const ch = this.r.peek(i);
|
|
645
|
+
if (ch === "" || ch.toLowerCase() !== close[i]) return false;
|
|
253
646
|
i++;
|
|
254
647
|
}
|
|
255
648
|
return true;
|
|
256
649
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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;
|
|
260
656
|
if (this.r.atHole()) {
|
|
261
|
-
|
|
262
|
-
|
|
657
|
+
const h = this.r.takeHole();
|
|
658
|
+
props.push({ kind: "Spread", hole: h.index, loc: h.loc });
|
|
263
659
|
continue;
|
|
264
660
|
}
|
|
265
661
|
if (this.r.startsWith("...")) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
+
}
|
|
271
672
|
continue;
|
|
272
673
|
}
|
|
273
|
-
|
|
274
|
-
|
|
674
|
+
const nameStart = this.r.pos();
|
|
675
|
+
const name = this.readName();
|
|
676
|
+
if (!name) {
|
|
275
677
|
this.r.advance();
|
|
276
678
|
continue;
|
|
277
679
|
}
|
|
278
|
-
|
|
680
|
+
const nameLoc = this.r.span(nameStart);
|
|
279
681
|
this.r.skipWhitespace();
|
|
280
|
-
let
|
|
281
|
-
this.r.peek() === "="
|
|
282
|
-
|
|
283
|
-
|
|
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 }));
|
|
284
696
|
}
|
|
285
|
-
return
|
|
697
|
+
return props;
|
|
286
698
|
}
|
|
287
699
|
parseAttrValue() {
|
|
288
|
-
if (this.r.atHole())
|
|
289
|
-
|
|
290
|
-
|
|
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 };
|
|
291
710
|
}
|
|
292
|
-
readQuotedParts(
|
|
711
|
+
readQuotedParts(quote) {
|
|
293
712
|
this.r.advance();
|
|
294
|
-
|
|
295
|
-
|
|
713
|
+
const parts = [];
|
|
714
|
+
let text = "";
|
|
715
|
+
while (!this.r.eof() && this.r.peek() !== quote) {
|
|
296
716
|
if (this.r.atHole()) {
|
|
297
|
-
|
|
717
|
+
if (text) {
|
|
718
|
+
parts.push({ text });
|
|
719
|
+
text = "";
|
|
720
|
+
}
|
|
721
|
+
parts.push({ hole: this.r.takeHole().index });
|
|
298
722
|
continue;
|
|
299
723
|
}
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
|
|
724
|
+
const ch = this.r.advance();
|
|
725
|
+
if (ch === "") break;
|
|
726
|
+
text += ch;
|
|
727
|
+
}
|
|
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);
|
|
303
761
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
foldEachKey(e, i) {
|
|
311
|
-
let r = null, n = 0, s = 0, a = 0, l = 0;
|
|
312
|
-
for (let d of e) d.kind === "EachDirective" ? (r = d, s++) : d.kind === "IfDirective" ? n++ : d.kind === "ElseDirective" ? a++ : d.kind === "RefBinding" && l++;
|
|
313
|
-
let o = this.r.span(i);
|
|
314
|
-
s > 1 && this.report("duplicate-directive", "error", "Multiple `each` directives on one element", o), n > 1 && this.report("duplicate-directive", "error", "Multiple `if` directives on one element", o), l > 1 && this.report("duplicate-directive", "error", "Multiple `ref` bindings on one element", o), n > 0 && s > 0 && this.report("unsupported-combination", "error", "`if` and `each` cannot both apply to one element \u2014 wrap one in another element", o), n > 0 && a > 0 && this.report("invalid-directive", "error", "`if` and `else` cannot be on the same element", o);
|
|
315
|
-
let c = e.findIndex((d) => d.kind === "Attribute" && d.name === "key");
|
|
316
|
-
if (!r) {
|
|
317
|
-
c !== -1 && this.report("invalid-directive", "error", "`key` requires an `each` directive on the same element", o);
|
|
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);
|
|
318
768
|
return;
|
|
319
769
|
}
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
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);
|
|
323
777
|
}
|
|
778
|
+
// --- low level ----------------------------------------------------------
|
|
324
779
|
readName() {
|
|
325
|
-
return this.r.readWhile((
|
|
780
|
+
return this.r.readWhile((ch) => NAME_CHAR.test(ch));
|
|
326
781
|
}
|
|
782
|
+
/** Consume `</name>` (or `</>`), returning the close-tag name (may be ''). */
|
|
327
783
|
consumeCloseTag() {
|
|
328
|
-
this.r.advance()
|
|
329
|
-
|
|
330
|
-
|
|
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;
|
|
331
790
|
}
|
|
332
|
-
report(
|
|
333
|
-
this.diagnostics.push({ code
|
|
791
|
+
report(code, severity, message, loc) {
|
|
792
|
+
this.diagnostics.push({ code, severity, message, loc });
|
|
334
793
|
}
|
|
335
794
|
};
|
|
336
|
-
function
|
|
337
|
-
return new
|
|
795
|
+
function parseTemplate(pieces, opts = {}) {
|
|
796
|
+
return new Parser(pieces, opts).parse();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/pieces.ts
|
|
800
|
+
function rawContent(ts, node, sf) {
|
|
801
|
+
const text = node.getText(sf);
|
|
802
|
+
const closesWithSubstitution = ts.isTemplateHead(node) || ts.isTemplateMiddle(node);
|
|
803
|
+
return text.slice(1, closesWithSubstitution ? -2 : -1);
|
|
804
|
+
}
|
|
805
|
+
function templatePieces(ts, tpl, sf) {
|
|
806
|
+
if (ts.isNoSubstitutionTemplateLiteral(tpl)) {
|
|
807
|
+
return [{ kind: "static", text: rawContent(ts, tpl, sf), start: tpl.getStart(sf) + 1 }];
|
|
808
|
+
}
|
|
809
|
+
const pieces = [
|
|
810
|
+
{ kind: "static", text: rawContent(ts, tpl.head, sf), start: tpl.head.getStart(sf) + 1 }
|
|
811
|
+
];
|
|
812
|
+
tpl.templateSpans.forEach((span, i) => {
|
|
813
|
+
const e = span.expression;
|
|
814
|
+
pieces.push({ kind: "hole", index: i, start: e.getStart(sf), end: e.getEnd() });
|
|
815
|
+
pieces.push({
|
|
816
|
+
kind: "static",
|
|
817
|
+
text: rawContent(ts, span.literal, sf),
|
|
818
|
+
start: span.literal.getStart(sf) + 1
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
return pieces;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/cache.ts
|
|
825
|
+
var parsed = /* @__PURE__ */ new WeakMap();
|
|
826
|
+
function parseCached(ts, sf, tpl) {
|
|
827
|
+
let byTemplate = parsed.get(sf);
|
|
828
|
+
if (!byTemplate) {
|
|
829
|
+
byTemplate = /* @__PURE__ */ new Map();
|
|
830
|
+
parsed.set(sf, byTemplate);
|
|
831
|
+
}
|
|
832
|
+
const key = tpl.getStart(sf);
|
|
833
|
+
const hit = byTemplate.get(key);
|
|
834
|
+
if (hit) return hit;
|
|
835
|
+
const result = parseTemplate(templatePieces(ts, tpl, sf));
|
|
836
|
+
byTemplate.set(key, result);
|
|
837
|
+
return result;
|
|
338
838
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return
|
|
342
|
-
|
|
343
|
-
|
|
839
|
+
var templates = /* @__PURE__ */ new WeakMap();
|
|
840
|
+
function mayHaveTemplate(text) {
|
|
841
|
+
return text.includes("html`") || text.includes("svg`");
|
|
842
|
+
}
|
|
843
|
+
var NONE = [];
|
|
844
|
+
function templatesIn(ts, sf) {
|
|
845
|
+
const hit = templates.get(sf);
|
|
846
|
+
if (hit) return hit;
|
|
847
|
+
if (!mayHaveTemplate(sf.text)) {
|
|
848
|
+
templates.set(sf, NONE);
|
|
849
|
+
return NONE;
|
|
850
|
+
}
|
|
851
|
+
const found = [];
|
|
852
|
+
const visit = (node) => {
|
|
853
|
+
if (ts.isTaggedTemplateExpression(node) && ts.isIdentifier(node.tag) && (node.tag.text === "html" || node.tag.text === "svg")) {
|
|
854
|
+
found.push(node);
|
|
855
|
+
}
|
|
856
|
+
ts.forEachChild(node, visit);
|
|
857
|
+
};
|
|
858
|
+
visit(sf);
|
|
859
|
+
found.sort((a, b) => a.getStart(sf) - b.getStart(sf));
|
|
860
|
+
templates.set(sf, found);
|
|
861
|
+
return found;
|
|
344
862
|
}
|
|
345
|
-
function
|
|
346
|
-
|
|
863
|
+
function templateAtCached(ts, sf, pos) {
|
|
864
|
+
let match;
|
|
865
|
+
for (const tpl of templatesIn(ts, sf)) {
|
|
866
|
+
const start = tpl.template.getStart(sf);
|
|
867
|
+
if (start > pos) break;
|
|
868
|
+
if (pos <= tpl.template.getEnd()) match = tpl;
|
|
869
|
+
}
|
|
870
|
+
return match;
|
|
347
871
|
}
|
|
348
872
|
|
|
349
873
|
// src/collect.ts
|
|
@@ -351,23 +875,17 @@ var UNUSED_CODES = /* @__PURE__ */ new Set([6133, 6196, 6198, 6199, 6205, 6138,
|
|
|
351
875
|
function isUnusedCode(code) {
|
|
352
876
|
return UNUSED_CODES.has(code);
|
|
353
877
|
}
|
|
878
|
+
var tagCache = /* @__PURE__ */ new WeakMap();
|
|
354
879
|
function usedComponentTags(ts, sourceFile) {
|
|
880
|
+
const hit = tagCache.get(sourceFile);
|
|
881
|
+
if (hit) return hit;
|
|
355
882
|
const tags = /* @__PURE__ */ new Set();
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
ts.forEachChild(node, visit);
|
|
361
|
-
};
|
|
362
|
-
visit(sourceFile);
|
|
883
|
+
for (const template of templatesIn(ts, sourceFile)) {
|
|
884
|
+
collectTags(parseCached(ts, sourceFile, template.template).root.children, tags);
|
|
885
|
+
}
|
|
886
|
+
tagCache.set(sourceFile, tags);
|
|
363
887
|
return tags;
|
|
364
888
|
}
|
|
365
|
-
function templateStatics(ts, tpl) {
|
|
366
|
-
if (ts.isNoSubstitutionTemplateLiteral(tpl)) return [tpl.text];
|
|
367
|
-
const out = [tpl.head.text];
|
|
368
|
-
for (const span of tpl.templateSpans) out.push(span.literal.text);
|
|
369
|
-
return out;
|
|
370
|
-
}
|
|
371
889
|
function collectTags(nodes, out) {
|
|
372
890
|
for (const node of nodes) {
|
|
373
891
|
if (node.kind === "Component" && node.tag) out.add(node.tag.split(".")[0]);
|
|
@@ -410,63 +928,150 @@ function importedNames(ts, decl) {
|
|
|
410
928
|
return names;
|
|
411
929
|
}
|
|
412
930
|
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
931
|
+
// ../compiler/dist/resolve/builtins.mjs
|
|
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;
|
|
425
964
|
}
|
|
426
965
|
|
|
427
|
-
// src/
|
|
428
|
-
var
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
966
|
+
// src/auto-import.ts
|
|
967
|
+
var AUTO_IMPORTED = builtinComponentMap();
|
|
968
|
+
var exportsByProgram = /* @__PURE__ */ new WeakMap();
|
|
969
|
+
function moduleExports(ts, program, host, from, moduleName) {
|
|
970
|
+
let cache2 = exportsByProgram.get(program);
|
|
971
|
+
if (!cache2) {
|
|
972
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
973
|
+
exportsByProgram.set(program, cache2);
|
|
974
|
+
}
|
|
975
|
+
if (cache2.has(moduleName)) return cache2.get(moduleName);
|
|
976
|
+
let symbols;
|
|
977
|
+
try {
|
|
978
|
+
const resolved = ts.resolveModuleName(moduleName, from.fileName, program.getCompilerOptions(), host);
|
|
979
|
+
const file = resolved.resolvedModule && program.getSourceFile(resolved.resolvedModule.resolvedFileName);
|
|
980
|
+
const checker = program.getTypeChecker();
|
|
981
|
+
const moduleSymbol = file && checker.getSymbolAtLocation(file);
|
|
982
|
+
if (moduleSymbol) symbols = checker.getExportsOfModule(moduleSymbol);
|
|
983
|
+
} catch {
|
|
984
|
+
symbols = void 0;
|
|
434
985
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if (hit) return hit;
|
|
438
|
-
const result = k(templatePieces(ts, tpl, sf));
|
|
439
|
-
byTemplate.set(key, result);
|
|
440
|
-
return result;
|
|
986
|
+
cache2.set(moduleName, symbols);
|
|
987
|
+
return symbols;
|
|
441
988
|
}
|
|
442
|
-
var
|
|
443
|
-
function
|
|
444
|
-
const
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
989
|
+
var sitesByProgram = /* @__PURE__ */ new WeakMap();
|
|
990
|
+
function declarationSite(ts, program, host, from, name) {
|
|
991
|
+
const moduleName = AUTO_IMPORTED[name];
|
|
992
|
+
if (!moduleName) return void 0;
|
|
993
|
+
let cache2 = sitesByProgram.get(program);
|
|
994
|
+
if (!cache2) {
|
|
995
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
996
|
+
sitesByProgram.set(program, cache2);
|
|
997
|
+
}
|
|
998
|
+
const key = `${moduleName}:${name}`;
|
|
999
|
+
if (cache2.has(key)) return cache2.get(key);
|
|
1000
|
+
let site;
|
|
1001
|
+
try {
|
|
1002
|
+
const resolved = ts.resolveModuleName(moduleName, from.fileName, program.getCompilerOptions(), host);
|
|
1003
|
+
const entry = resolved.resolvedModule?.resolvedFileName;
|
|
1004
|
+
if (entry) site = findExport(ts, host, program.getCompilerOptions(), entry, name, 0);
|
|
1005
|
+
} catch {
|
|
1006
|
+
site = void 0;
|
|
1007
|
+
}
|
|
1008
|
+
cache2.set(key, site);
|
|
1009
|
+
return site;
|
|
1010
|
+
}
|
|
1011
|
+
function findExport(ts, host, options, fileName, name, depth) {
|
|
1012
|
+
if (depth > 4) return void 0;
|
|
1013
|
+
const text = host.readFile?.(fileName);
|
|
1014
|
+
if (text === void 0) return void 0;
|
|
1015
|
+
const sf = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true);
|
|
1016
|
+
const reExports = [];
|
|
1017
|
+
for (const statement of sf.statements) {
|
|
1018
|
+
const declared = declaredName(ts, statement, name);
|
|
1019
|
+
if (declared) {
|
|
1020
|
+
return { fileName, start: declared.getStart(sf), length: declared.getWidth(sf) };
|
|
450
1021
|
}
|
|
451
|
-
ts.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
1022
|
+
if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
1023
|
+
const specifier = statement.moduleSpecifier.text;
|
|
1024
|
+
if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
1025
|
+
const named = statement.exportClause.elements.find((e) => e.name.text === name);
|
|
1026
|
+
if (named) reExports.unshift(specifier);
|
|
1027
|
+
} else if (!statement.exportClause) {
|
|
1028
|
+
reExports.push(specifier);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
for (const specifier of reExports) {
|
|
1033
|
+
const target = resolveRelative(ts, host, options, fileName, specifier);
|
|
1034
|
+
const found = target && findExport(ts, host, options, target, name, depth + 1);
|
|
1035
|
+
if (found) return found;
|
|
1036
|
+
}
|
|
1037
|
+
return void 0;
|
|
457
1038
|
}
|
|
458
|
-
function
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
1039
|
+
function declaredName(ts, statement, name) {
|
|
1040
|
+
const exported = ts.canHaveModifiers(statement) ? ts.getModifiers(statement)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) : false;
|
|
1041
|
+
if (!exported) return void 0;
|
|
1042
|
+
if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name?.text === name) {
|
|
1043
|
+
return statement.name;
|
|
1044
|
+
}
|
|
1045
|
+
if (ts.isVariableStatement(statement)) {
|
|
1046
|
+
for (const d of statement.declarationList.declarations) {
|
|
1047
|
+
if (ts.isIdentifier(d.name) && d.name.text === name) return d.name;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return void 0;
|
|
1051
|
+
}
|
|
1052
|
+
function resolveRelative(ts, host, options, from, specifier) {
|
|
1053
|
+
if (!specifier.startsWith(".")) return void 0;
|
|
1054
|
+
try {
|
|
1055
|
+
return ts.resolveModuleName(specifier, from, options, host).resolvedModule?.resolvedFileName;
|
|
1056
|
+
} catch {
|
|
1057
|
+
return void 0;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function autoImportedSymbol(ts, program, host, from, name) {
|
|
1061
|
+
const moduleName = AUTO_IMPORTED[name];
|
|
1062
|
+
if (!moduleName) return void 0;
|
|
1063
|
+
const symbol = moduleExports(ts, program, host, from, moduleName)?.find((s) => s.name === name);
|
|
1064
|
+
if (!symbol) return void 0;
|
|
1065
|
+
if (!(symbol.flags & ts.SymbolFlags.Alias)) return symbol;
|
|
1066
|
+
try {
|
|
1067
|
+
return program.getTypeChecker().getAliasedSymbol(symbol);
|
|
1068
|
+
} catch {
|
|
1069
|
+
return symbol;
|
|
464
1070
|
}
|
|
465
|
-
return match;
|
|
466
1071
|
}
|
|
467
1072
|
|
|
468
1073
|
// src/hover.ts
|
|
469
|
-
function componentQuickInfo(ts, ls, fileName, position) {
|
|
1074
|
+
function componentQuickInfo(ts, ls, host, fileName, position) {
|
|
470
1075
|
const program = ls.getProgram();
|
|
471
1076
|
const sf = program?.getSourceFile(fileName);
|
|
472
1077
|
if (!program || !sf) return void 0;
|
|
@@ -476,11 +1081,12 @@ function componentQuickInfo(ts, ls, fileName, position) {
|
|
|
476
1081
|
if (!hit) return void 0;
|
|
477
1082
|
const checker = program.getTypeChecker();
|
|
478
1083
|
const flags = ts.SymbolFlags.Value | ts.SymbolFlags.Function | ts.SymbolFlags.Class | ts.SymbolFlags.Alias;
|
|
479
|
-
const symbol = checker.getSymbolsInScope(template, flags).find((s) => s.name === hit.name);
|
|
1084
|
+
const symbol = checker.getSymbolsInScope(template, flags).find((s) => s.name === hit.name) ?? autoImportedSymbol(ts, program, host, sf, hit.name);
|
|
480
1085
|
const decl = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
|
|
481
1086
|
if (!decl) return void 0;
|
|
482
|
-
const
|
|
483
|
-
const
|
|
1087
|
+
const declFile = decl.getSourceFile();
|
|
1088
|
+
const namePos = decl.name?.getStart(declFile) ?? decl.getStart(declFile);
|
|
1089
|
+
const info = ls.getQuickInfoAtPosition(declFile.fileName, namePos);
|
|
484
1090
|
if (!info) return void 0;
|
|
485
1091
|
return { ...info, textSpan: { start: hit.start, length: hit.name.length } };
|
|
486
1092
|
}
|
|
@@ -624,7 +1230,7 @@ function holeRoles(nodes, out) {
|
|
|
624
1230
|
if (node.kind === "Component") {
|
|
625
1231
|
const propHole = (name) => {
|
|
626
1232
|
const a = node.attributes.find(
|
|
627
|
-
(
|
|
1233
|
+
(x) => x.kind === "Attribute" && x.name === name && x.value != null && x.value.kind === "hole"
|
|
628
1234
|
);
|
|
629
1235
|
return a && a.kind === "Attribute" && a.value && a.value.kind === "hole" ? a.value.hole : void 0;
|
|
630
1236
|
};
|
|
@@ -667,6 +1273,12 @@ function inferParamAt(ts, program, sf, position) {
|
|
|
667
1273
|
const id = identifierAt(ts, sf, position);
|
|
668
1274
|
if (!id || id.text !== paramName || !contains(fn, id)) return void 0;
|
|
669
1275
|
const checker = program.getTypeChecker();
|
|
1276
|
+
const resolved = typeForRole(ts, checker, fn, role, holeExprs);
|
|
1277
|
+
const { typeText, type } = resolved;
|
|
1278
|
+
if (!typeText) return void 0;
|
|
1279
|
+
return { paramName, typeText, type, start: id.getStart(sf), length: id.getWidth(sf) };
|
|
1280
|
+
}
|
|
1281
|
+
function typeForRole(ts, checker, fn, role, holeExprs) {
|
|
670
1282
|
let typeText;
|
|
671
1283
|
let type;
|
|
672
1284
|
if (role.kind === "event") {
|
|
@@ -687,8 +1299,27 @@ function inferParamAt(ts, program, sf, position) {
|
|
|
687
1299
|
type = checker.getTypeAtLocation(holeExprs[role.eachHole]).getNumberIndexType();
|
|
688
1300
|
if (type) typeText = checker.typeToString(type);
|
|
689
1301
|
}
|
|
690
|
-
|
|
691
|
-
|
|
1302
|
+
return { typeText, type };
|
|
1303
|
+
}
|
|
1304
|
+
function inferParamsIn(ts, program, sf) {
|
|
1305
|
+
const checker = program.getTypeChecker();
|
|
1306
|
+
const out = [];
|
|
1307
|
+
for (const template of templatesIn(ts, sf)) {
|
|
1308
|
+
const tpl = template.template;
|
|
1309
|
+
if (ts.isNoSubstitutionTemplateLiteral(tpl)) continue;
|
|
1310
|
+
const holeExprs = tpl.templateSpans.map((s) => s.expression);
|
|
1311
|
+
const roles = /* @__PURE__ */ new Map();
|
|
1312
|
+
holeRoles(parseCached(ts, sf, tpl).root.children, roles);
|
|
1313
|
+
for (const [holeIndex, role] of roles) {
|
|
1314
|
+
const fn = holeExprs[holeIndex];
|
|
1315
|
+
if (!fn || !ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) continue;
|
|
1316
|
+
const param = fn.parameters[0];
|
|
1317
|
+
if (!param || !ts.isIdentifier(param.name) || param.type) continue;
|
|
1318
|
+
const { type } = typeForRole(ts, checker, fn, role, holeExprs);
|
|
1319
|
+
if (type) out.push({ fn, paramName: param.name.text, type });
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return out;
|
|
692
1323
|
}
|
|
693
1324
|
function paramQuickInfo(ts, ls, fileName, position) {
|
|
694
1325
|
const program = ls.getProgram();
|
|
@@ -730,7 +1361,7 @@ function contains(outer, inner) {
|
|
|
730
1361
|
}
|
|
731
1362
|
|
|
732
1363
|
// src/props.ts
|
|
733
|
-
function componentPropCompletions(ts, ls, fileName, position) {
|
|
1364
|
+
function componentPropCompletions(ts, ls, host, fileName, position) {
|
|
734
1365
|
const program = ls.getProgram();
|
|
735
1366
|
const sf = program?.getSourceFile(fileName);
|
|
736
1367
|
if (!program || !sf) return void 0;
|
|
@@ -738,28 +1369,27 @@ function componentPropCompletions(ts, ls, fileName, position) {
|
|
|
738
1369
|
if (!template) return void 0;
|
|
739
1370
|
const comp = componentTagAtAttrArea(ts, sf, template.template, position);
|
|
740
1371
|
if (!comp || !comp.tag) return void 0;
|
|
1372
|
+
const span = attrNameSpan(sf.text, position);
|
|
741
1373
|
const cf = CONTROL_FLOW_PROPS[comp.tag];
|
|
742
1374
|
if (cf) {
|
|
743
1375
|
const already2 = new Set(comp.attributeNames);
|
|
744
|
-
return cf.filter((n) => !already2.has(n)).map((name) => propEntry(ts, name));
|
|
1376
|
+
return cf.filter((n) => !already2.has(n)).map((name) => propEntry(ts, name, span));
|
|
745
1377
|
}
|
|
746
1378
|
const checker = program.getTypeChecker();
|
|
747
1379
|
const rootName = comp.tag.split(".")[0];
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
const compType = checker.getTypeAtLocation(decl);
|
|
751
|
-
const propsType = firstParamType(ts, checker, compType);
|
|
1380
|
+
const compType = componentTypeOf(ts, program, checker, host, sf, template.template, rootName);
|
|
1381
|
+
const propsType = compType && firstParamType(ts, checker, compType);
|
|
752
1382
|
if (!propsType) return void 0;
|
|
753
1383
|
const already = new Set(comp.attributeNames);
|
|
754
1384
|
const entries = [];
|
|
755
|
-
for (const
|
|
756
|
-
const name =
|
|
1385
|
+
for (const p of propsType.getProperties()) {
|
|
1386
|
+
const name = p.getName();
|
|
757
1387
|
if (name === "children" || already.has(name)) continue;
|
|
758
|
-
entries.push(propEntry(ts, name));
|
|
1388
|
+
entries.push(propEntry(ts, name, span));
|
|
759
1389
|
}
|
|
760
1390
|
return entries.length ? entries : void 0;
|
|
761
1391
|
}
|
|
762
|
-
function componentPropHover(ts, ls, fileName, position) {
|
|
1392
|
+
function componentPropHover(ts, ls, host, fileName, position) {
|
|
763
1393
|
const program = ls.getProgram();
|
|
764
1394
|
const sf = program?.getSourceFile(fileName);
|
|
765
1395
|
if (!program || !sf) return void 0;
|
|
@@ -774,11 +1404,13 @@ function componentPropHover(ts, ls, fileName, position) {
|
|
|
774
1404
|
if (!typeText) return void 0;
|
|
775
1405
|
} else if (hit.tag) {
|
|
776
1406
|
const checker = program.getTypeChecker();
|
|
777
|
-
const
|
|
778
|
-
const
|
|
1407
|
+
const root = hit.tag.split(".")[0];
|
|
1408
|
+
const compType = componentTypeOf(ts, program, checker, host, sf, template.template, root);
|
|
1409
|
+
const propsType = compType && firstParamType(ts, checker, compType);
|
|
779
1410
|
const sym = propsType && propsType.getProperty(hit.prop);
|
|
780
1411
|
if (!sym) return void 0;
|
|
781
|
-
|
|
1412
|
+
const at = sym.valueDeclaration ?? sym.declarations?.[0] ?? template.template;
|
|
1413
|
+
typeText = checker.typeToString(checker.getTypeOfSymbolAtLocation(sym, at));
|
|
782
1414
|
}
|
|
783
1415
|
if (!typeText) return void 0;
|
|
784
1416
|
return {
|
|
@@ -845,31 +1477,40 @@ var CONTROL_FLOW_PROPS = {
|
|
|
845
1477
|
Dynamic: ["component"],
|
|
846
1478
|
Await: ["value", "fallback"]
|
|
847
1479
|
};
|
|
848
|
-
function propEntry(ts, name) {
|
|
849
|
-
return {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1480
|
+
function propEntry(ts, name, replacementSpan) {
|
|
1481
|
+
return {
|
|
1482
|
+
name,
|
|
1483
|
+
kind: ts.ScriptElementKind.memberVariableElement,
|
|
1484
|
+
kindModifiers: "",
|
|
1485
|
+
sortText: "0_" + name,
|
|
1486
|
+
insertText: name,
|
|
1487
|
+
// Without a span the editor filters these against whatever word *it* computes at the
|
|
1488
|
+
// cursor, and inside a template literal that is not the attribute name, so the list
|
|
1489
|
+
// showed in full until the first keystroke and then emptied. Saying exactly which
|
|
1490
|
+
// characters are being replaced makes filtering match what is typed.
|
|
1491
|
+
replacementSpan
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
function attrNameSpan(text, position) {
|
|
1495
|
+
const isNameChar = (c) => !!c && /[A-Za-z0-9_$:@?.\-]/.test(c);
|
|
1496
|
+
let start = position;
|
|
1497
|
+
while (start > 0 && isNameChar(text[start - 1])) start--;
|
|
1498
|
+
let end = position;
|
|
1499
|
+
while (end < text.length && isNameChar(text[end])) end++;
|
|
1500
|
+
return { start, length: end - start };
|
|
1501
|
+
}
|
|
1502
|
+
function componentTypeOf(ts, program, checker, host, sf, template, name) {
|
|
1503
|
+
const flags = ts.SymbolFlags.Value | ts.SymbolFlags.Function | ts.SymbolFlags.Class | ts.SymbolFlags.Alias;
|
|
1504
|
+
const symbol = checker.getSymbolsInScope(template, flags).find((s) => s.name === name) ?? autoImportedSymbol(ts, program, host, sf, name);
|
|
1505
|
+
if (!symbol) return void 0;
|
|
1506
|
+
const resolved = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
1507
|
+
const decl = resolved.valueDeclaration ?? resolved.declarations?.[0];
|
|
1508
|
+
return decl ? checker.getTypeOfSymbolAtLocation(resolved, decl) : void 0;
|
|
868
1509
|
}
|
|
869
1510
|
function firstParamType(ts, checker, type) {
|
|
870
1511
|
for (const sig of checker.getSignaturesOfType(type, ts.SignatureKind.Call)) {
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
1512
|
+
const p = sig.getParameters()[0];
|
|
1513
|
+
if (p) return checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? sig.getDeclaration());
|
|
873
1514
|
}
|
|
874
1515
|
return void 0;
|
|
875
1516
|
}
|
|
@@ -882,7 +1523,7 @@ function componentTagAtAttrArea(ts, sf, tpl, position) {
|
|
|
882
1523
|
const c = node;
|
|
883
1524
|
const openStart = c.loc.start + 1 + (c.tag ? c.tag.length : 0);
|
|
884
1525
|
const openEnd = firstChildOrCloseStart(c);
|
|
885
|
-
if (position > openStart && position
|
|
1526
|
+
if (position > openStart && position < openEnd) {
|
|
886
1527
|
hit = { tag: c.tag, attributeNames: attributeNames(c) };
|
|
887
1528
|
}
|
|
888
1529
|
}
|
|
@@ -918,13 +1559,13 @@ function paramMemberCompletions(ts, ls, fileName, position) {
|
|
|
918
1559
|
const checker = program.getTypeChecker();
|
|
919
1560
|
const already = /* @__PURE__ */ new Set();
|
|
920
1561
|
const entries = [];
|
|
921
|
-
for (const
|
|
922
|
-
const name =
|
|
1562
|
+
for (const p of checker.getApparentType(info.type).getProperties()) {
|
|
1563
|
+
const name = p.getName();
|
|
923
1564
|
if (already.has(name) || name.startsWith("__")) continue;
|
|
924
1565
|
already.add(name);
|
|
925
1566
|
entries.push({
|
|
926
1567
|
name,
|
|
927
|
-
kind: memberKind(ts,
|
|
1568
|
+
kind: memberKind(ts, p),
|
|
928
1569
|
kindModifiers: "",
|
|
929
1570
|
sortText: "0_" + name,
|
|
930
1571
|
insertText: name
|
|
@@ -975,7 +1616,6 @@ function intrinsics(ts, program, host, sf) {
|
|
|
975
1616
|
);
|
|
976
1617
|
if (!type) {
|
|
977
1618
|
for (const file of program.getSourceFiles()) {
|
|
978
|
-
if (!file.isDeclarationFile && file.fileName.indexOf("@fluixi/dom") === -1) continue;
|
|
979
1619
|
if (file.fileName.indexOf("@fluixi/dom") === -1) continue;
|
|
980
1620
|
type = declared(file);
|
|
981
1621
|
if (type) break;
|
|
@@ -1049,8 +1689,8 @@ function elementAttributeCompletions(ts, ls, host, fileName, position) {
|
|
|
1049
1689
|
const props = attributesFor(ts, program, host, sf, hit.tag);
|
|
1050
1690
|
if (!props) return void 0;
|
|
1051
1691
|
const entries = [];
|
|
1052
|
-
for (const
|
|
1053
|
-
const name =
|
|
1692
|
+
for (const p of props) {
|
|
1693
|
+
const name = p.getName();
|
|
1054
1694
|
if (name === "children" || hit.present.has(name)) continue;
|
|
1055
1695
|
entries.push({
|
|
1056
1696
|
name,
|
|
@@ -1093,7 +1733,7 @@ function elementAttributeHover(ts, ls, host, fileName, position) {
|
|
|
1093
1733
|
const hit = attributeNameAt(ts, sf, tpl.template, position);
|
|
1094
1734
|
if (!hit) return void 0;
|
|
1095
1735
|
const props = attributesFor(ts, program, host, sf, hit.tag);
|
|
1096
|
-
const symbol = props?.find((
|
|
1736
|
+
const symbol = props?.find((p) => p.getName() === hit.name);
|
|
1097
1737
|
if (!symbol) return void 0;
|
|
1098
1738
|
const decl = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
1099
1739
|
if (!decl) return void 0;
|
|
@@ -1118,385 +1758,283 @@ function elementAttributeHover(ts, ls, host, fileName, position) {
|
|
|
1118
1758
|
};
|
|
1119
1759
|
}
|
|
1120
1760
|
|
|
1121
|
-
// src/
|
|
1122
|
-
var
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
mouseleave: 0,
|
|
1130
|
-
mouseover: 0,
|
|
1131
|
-
mouseout: 0,
|
|
1132
|
-
contextmenu: 0,
|
|
1133
|
-
keydown: 0,
|
|
1134
|
-
keyup: 0,
|
|
1135
|
-
keypress: 0,
|
|
1136
|
-
input: 0,
|
|
1137
|
-
beforeinput: 0,
|
|
1138
|
-
change: 0,
|
|
1139
|
-
submit: 0,
|
|
1140
|
-
focus: 0,
|
|
1141
|
-
blur: 0,
|
|
1142
|
-
focusin: 0,
|
|
1143
|
-
focusout: 0,
|
|
1144
|
-
pointerdown: 0,
|
|
1145
|
-
pointerup: 0,
|
|
1146
|
-
pointermove: 0,
|
|
1147
|
-
pointerenter: 0,
|
|
1148
|
-
pointerleave: 0,
|
|
1149
|
-
wheel: 0,
|
|
1150
|
-
scroll: 0,
|
|
1151
|
-
drag: 0,
|
|
1152
|
-
drop: 0,
|
|
1153
|
-
touchstart: 0,
|
|
1154
|
-
touchend: 0,
|
|
1155
|
-
touchmove: 0,
|
|
1156
|
-
animationend: 0,
|
|
1157
|
-
transitionend: 0
|
|
1158
|
-
}));
|
|
1159
|
-
var PRELUDE = (
|
|
1160
|
-
// Control-flow component prop types, declared inline so they ALWAYS resolve
|
|
1161
|
-
// (no dependency on @fluixi/core being importable from the virtual document).
|
|
1162
|
-
// Prop shapes drive hover + value checking on when/each/fallback/mount/…;
|
|
1163
|
-
// render-prop children are typed separately by __fxEach/__fxShow.
|
|
1164
|
-
'declare const __fxCF: {\n Show: <T>(props: { when: T; fallback?: unknown; children?: unknown }) => any;\n For: <T>(props: { each: readonly T[] | undefined | null; fallback?: unknown; by?: (item: T) => unknown; children?: unknown }) => any;\n Index: <T>(props: { each: readonly T[] | undefined | null; fallback?: unknown; children?: unknown }) => any;\n Switch: (props: { fallback?: unknown; children?: unknown }) => any;\n Match: <T>(props: { when: T; children?: unknown }) => any;\n Suspense: (props: { fallback?: unknown; children?: unknown }) => any;\n SuspenseList: (props: { revealOrder: "forwards" | "backwards" | "together"; children?: unknown }) => any;\n Portal: (props: { mount?: Node; useShadow?: boolean; children?: unknown }) => any;\n ErrorBoundary: (props: { fallback?: unknown; children?: unknown }) => any;\n Dynamic: (props: { component: unknown; [k: string]: unknown }) => any;\n Await: <T>(props: { value: Promise<T> | (() => Promise<T>); fallback?: unknown; children?: unknown }) => any;\n};\ndeclare const __fxEach: <T>(items: ArrayLike<T> | Iterable<T> | null | undefined, render: (item: T, index: () => number) => any) => any;\ndeclare const __fxIndex: <T>(items: ArrayLike<T> | Iterable<T> | null | undefined, render: (item: () => T, index: number) => any) => any;\ndeclare const __fxShow: <T>(when: T, render: (value: NonNullable<T>) => any) => any;\ndeclare const __fxOn: <K extends string>(name: K, handler: (event: K extends keyof HTMLElementEventMap ? HTMLElementEventMap[K] : Event) => any) => any;\ndeclare const __fxRef: <K extends string>(tag: K, ref: (el: K extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[K] : HTMLElement) => any) => any;\ndeclare const __fxExpr: (value: any) => any;\ndeclare function __fxProps<P>(comp: (props: P, ...rest: any[]) => any, props: NoInfer<P>): any;\n'
|
|
1165
|
-
);
|
|
1166
|
-
var CONTROL_FLOW = /* @__PURE__ */ new Set([
|
|
1167
|
-
"Show",
|
|
1168
|
-
"For",
|
|
1169
|
-
"Index",
|
|
1170
|
-
"Switch",
|
|
1171
|
-
"Match",
|
|
1172
|
-
"Dynamic",
|
|
1173
|
-
"ErrorBoundary",
|
|
1174
|
-
"Suspense",
|
|
1175
|
-
"SuspenseList",
|
|
1176
|
-
"Await",
|
|
1177
|
-
"Portal"
|
|
1178
|
-
]);
|
|
1179
|
-
function inHole(segments, pos) {
|
|
1180
|
-
return segments.some((s) => s.hole && pos >= s.srcStart && pos <= s.srcEnd);
|
|
1181
|
-
}
|
|
1182
|
-
function inKey(segments, pos) {
|
|
1183
|
-
return segments.some((s) => s.key && pos >= s.srcStart && pos <= s.srcEnd);
|
|
1761
|
+
// src/diagnostics.ts
|
|
1762
|
+
var ACCESSOR_NOT_CALLED = 9001;
|
|
1763
|
+
function acceptsAnyProperty(ts, checker, type) {
|
|
1764
|
+
const open = ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.TypeParameter | ts.TypeFlags.Never;
|
|
1765
|
+
if (type.flags & open) return true;
|
|
1766
|
+
if (checker.getIndexInfoOfType?.(type, ts.IndexKind.String)) return true;
|
|
1767
|
+
if (type.isUnion()) return type.types.some((t) => acceptsAnyProperty(ts, checker, t));
|
|
1768
|
+
return false;
|
|
1184
1769
|
}
|
|
1185
|
-
function
|
|
1186
|
-
|
|
1187
|
-
if (pos >= s.genStart && pos <= s.genEnd) return s.srcStart + (pos - s.genStart);
|
|
1188
|
-
}
|
|
1189
|
-
return -1;
|
|
1190
|
-
}
|
|
1191
|
-
function buildVirtual(ts, sf) {
|
|
1192
|
-
const templates2 = collectTemplates(ts, sf);
|
|
1193
|
-
if (templates2.length === 0) return void 0;
|
|
1194
|
-
const srcText = sf.text;
|
|
1195
|
-
const segments = [];
|
|
1196
|
-
let gen = "";
|
|
1197
|
-
let src = 0;
|
|
1198
|
-
const copy = (to) => {
|
|
1199
|
-
if (to <= src) return;
|
|
1200
|
-
const genStart = gen.length;
|
|
1201
|
-
gen += srcText.slice(src, to);
|
|
1202
|
-
segments.push({ srcStart: src, srcEnd: to, genStart, genEnd: gen.length });
|
|
1203
|
-
src = to;
|
|
1204
|
-
};
|
|
1205
|
-
gen += PRELUDE;
|
|
1206
|
-
for (const tpl of templates2) {
|
|
1207
|
-
copy(tpl.node.getStart(sf));
|
|
1208
|
-
gen = emitTemplate(ts, sf, tpl, gen, segments, srcText);
|
|
1209
|
-
src = tpl.node.getEnd();
|
|
1210
|
-
}
|
|
1211
|
-
copy(srcText.length);
|
|
1212
|
-
return { text: gen, segments };
|
|
1213
|
-
}
|
|
1214
|
-
function isFn(ts, node) {
|
|
1215
|
-
return ts.isArrowFunction(node) || ts.isFunctionExpression(node);
|
|
1216
|
-
}
|
|
1217
|
-
function collectTemplates(ts, sf) {
|
|
1770
|
+
function accessorDiagnostics(ts, program, sf) {
|
|
1771
|
+
const checker = program.getTypeChecker();
|
|
1218
1772
|
const out = [];
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
if (a.kind === "EventBinding") roles.set(a.hole, { kind: "event", event: a.name.toLowerCase() });
|
|
1240
|
-
if (a.kind === "RefBinding" && node.kind === "Element") roles.set(a.hole, { kind: "ref", tag: node.tag });
|
|
1241
|
-
if (a.kind === "UseDirective" && a.hole != null && node.kind === "Element") roles.set(a.hole, { kind: "ref", tag: node.tag });
|
|
1242
|
-
}
|
|
1243
|
-
const eachDir = node.attributes.find((a) => a.kind === "EachDirective");
|
|
1244
|
-
const propHole = (name) => {
|
|
1245
|
-
const a = node.attributes.find(
|
|
1246
|
-
(x2) => x2.kind === "Attribute" && x2.name === name && x2.value != null && x2.value.kind === "hole"
|
|
1247
|
-
);
|
|
1248
|
-
return a && a.kind === "Attribute" && a.value && a.value.kind === "hole" ? a.value.hole : void 0;
|
|
1249
|
-
};
|
|
1250
|
-
const wire = (arrayHole, kind) => {
|
|
1251
|
-
const r = renderChild(node);
|
|
1252
|
-
if (r !== void 0) {
|
|
1253
|
-
roles.set(r, { kind, arrayHole });
|
|
1254
|
-
consumed.add(arrayHole);
|
|
1255
|
-
}
|
|
1256
|
-
};
|
|
1257
|
-
if (eachDir && eachDir.kind === "EachDirective") wire(eachDir.hole, "eachRender");
|
|
1258
|
-
else if (node.kind === "Component" && node.tag === "For") {
|
|
1259
|
-
const h = propHole("each");
|
|
1260
|
-
if (h !== void 0) wire(h, "eachRender");
|
|
1261
|
-
} else if (node.kind === "Component" && node.tag === "Index") {
|
|
1262
|
-
const h = propHole("each");
|
|
1263
|
-
if (h !== void 0) wire(h, "indexRender");
|
|
1264
|
-
} else if (node.kind === "Component" && node.tag === "Show") {
|
|
1265
|
-
const w = propHole("when");
|
|
1266
|
-
const r = renderChild(node);
|
|
1267
|
-
if (w !== void 0 && r !== void 0) {
|
|
1268
|
-
roles.set(r, { kind: "showRender", whenHole: w });
|
|
1269
|
-
consumed.add(w);
|
|
1773
|
+
for (const template of templatesIn(ts, sf)) {
|
|
1774
|
+
const tpl = template.template;
|
|
1775
|
+
if (ts.isNoSubstitutionTemplateLiteral(tpl)) continue;
|
|
1776
|
+
const holes = tpl.templateSpans.map((s) => s.expression);
|
|
1777
|
+
const visit = (nodes, parent) => {
|
|
1778
|
+
for (const node of nodes) {
|
|
1779
|
+
if (node.kind === "Expression" && parent?.kind === "Element") {
|
|
1780
|
+
const expression = holes[node.hole];
|
|
1781
|
+
if (expression && isAccessorRead(ts, expression) && isAccessor(ts, checker, expression)) {
|
|
1782
|
+
const text = expression.getText(sf);
|
|
1783
|
+
out.push({
|
|
1784
|
+
file: sf,
|
|
1785
|
+
start: expression.getStart(sf),
|
|
1786
|
+
length: expression.getWidth(sf),
|
|
1787
|
+
category: ts.DiagnosticCategory.Warning,
|
|
1788
|
+
code: ACCESSOR_NOT_CALLED,
|
|
1789
|
+
messageText: `'${text}' is an accessor \u2014 call it (\`${text}()\`). Left bare it renders the function and never updates.`,
|
|
1790
|
+
source: "fluixi"
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1270
1793
|
}
|
|
1794
|
+
const children = "children" in node ? node.children : void 0;
|
|
1795
|
+
if (children) visit(children, node);
|
|
1271
1796
|
}
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1797
|
+
};
|
|
1798
|
+
visit(parseCached(ts, sf, tpl).root.children, null);
|
|
1274
1799
|
}
|
|
1800
|
+
return out;
|
|
1275
1801
|
}
|
|
1276
|
-
function
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
const
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
putKey(keyStart, keyText);
|
|
1317
|
-
gen += ": ";
|
|
1318
|
-
emitValue();
|
|
1319
|
-
};
|
|
1320
|
-
for (const a of node.attributes) {
|
|
1321
|
-
if (a.kind === "Attribute") {
|
|
1322
|
-
const keyStart = a.boolean ? a.loc.start + 1 : a.loc.start;
|
|
1323
|
-
const v2 = a.value;
|
|
1324
|
-
entry(keyStart, a.name, () => {
|
|
1325
|
-
if (!v2) {
|
|
1326
|
-
gen += "true";
|
|
1327
|
-
} else if (v2.kind === "static") {
|
|
1328
|
-
gen += JSON.stringify(v2.value);
|
|
1329
|
-
} else if (v2.kind === "hole") {
|
|
1330
|
-
putHole(v2.hole);
|
|
1331
|
-
} else {
|
|
1332
|
-
gen += "undefined as any";
|
|
1333
|
-
}
|
|
1802
|
+
function isAccessorRead(ts, node) {
|
|
1803
|
+
return ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node);
|
|
1804
|
+
}
|
|
1805
|
+
function isAccessor(ts, checker, node) {
|
|
1806
|
+
const signatures = checker.getTypeAtLocation(node).getCallSignatures();
|
|
1807
|
+
if (signatures.length !== 1) return false;
|
|
1808
|
+
const signature = signatures[0];
|
|
1809
|
+
if (signature.getParameters().some((p) => !isOptional(ts, p))) return false;
|
|
1810
|
+
const returned = signature.getReturnType();
|
|
1811
|
+
return !(returned.flags & (ts.TypeFlags.Void | ts.TypeFlags.Never));
|
|
1812
|
+
}
|
|
1813
|
+
function isOptional(ts, symbol) {
|
|
1814
|
+
const declaration = symbol.valueDeclaration;
|
|
1815
|
+
return !!declaration && ts.isParameter(declaration) && (!!declaration.questionToken || !!declaration.initializer || !!declaration.dotDotDotToken);
|
|
1816
|
+
}
|
|
1817
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
1818
|
+
function templateDiagnostics(ts, ls, fileName) {
|
|
1819
|
+
const program = ls.getProgram();
|
|
1820
|
+
const sf = program?.getSourceFile(fileName);
|
|
1821
|
+
if (!program || !sf) return [];
|
|
1822
|
+
const hit = cache.get(sf);
|
|
1823
|
+
if (hit) return hit;
|
|
1824
|
+
const checker = program.getTypeChecker();
|
|
1825
|
+
const out = [];
|
|
1826
|
+
out.push(...accessorDiagnostics(ts, program, sf));
|
|
1827
|
+
for (const { fn, paramName, type } of inferParamsIn(ts, program, sf)) {
|
|
1828
|
+
if (acceptsAnyProperty(ts, checker, type)) continue;
|
|
1829
|
+
const visit = (node) => {
|
|
1830
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === paramName && ts.isIdentifier(node.name)) {
|
|
1831
|
+
const symbol = checker.getSymbolAtLocation(node.expression);
|
|
1832
|
+
const declaredHere = symbol?.declarations?.some((d) => d.parent === fn);
|
|
1833
|
+
if (declaredHere && !checker.getPropertyOfType(type, node.name.text)) {
|
|
1834
|
+
out.push({
|
|
1835
|
+
file: sf,
|
|
1836
|
+
start: node.name.getStart(sf),
|
|
1837
|
+
length: node.name.getWidth(sf),
|
|
1838
|
+
category: ts.DiagnosticCategory.Error,
|
|
1839
|
+
code: 2339,
|
|
1840
|
+
messageText: `Property '${node.name.text}' does not exist on type '${checker.typeToString(type)}'.`,
|
|
1841
|
+
source: "fluixi"
|
|
1334
1842
|
});
|
|
1335
|
-
} else if (a.kind === "PropertyBinding") {
|
|
1336
|
-
entry(a.loc.start + 1, a.name, () => putHole(a.hole));
|
|
1337
1843
|
}
|
|
1338
1844
|
}
|
|
1339
|
-
|
|
1845
|
+
ts.forEachChild(node, visit);
|
|
1340
1846
|
};
|
|
1847
|
+
if (fn.body) visit(fn.body);
|
|
1341
1848
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
putHole(i);
|
|
1368
|
-
gen += ")";
|
|
1369
|
-
});
|
|
1370
|
-
} else if (role && role.kind === "event") {
|
|
1371
|
-
const key = EVENT_MAP_KEYS.has(role.event) ? role.event : role.event;
|
|
1372
|
-
calls.push(() => {
|
|
1373
|
-
gen += `__fxOn(${JSON.stringify(key)}, `;
|
|
1374
|
-
putHole(i);
|
|
1375
|
-
gen += ")";
|
|
1376
|
-
});
|
|
1377
|
-
} else if (role && role.kind === "ref" && isFn(ts, tpl.holes[i])) {
|
|
1378
|
-
const tag = role.tag;
|
|
1379
|
-
calls.push(() => {
|
|
1380
|
-
gen += `__fxRef(${JSON.stringify(tag)}, `;
|
|
1381
|
-
putHole(i);
|
|
1382
|
-
gen += ")";
|
|
1383
|
-
});
|
|
1384
|
-
} else {
|
|
1385
|
-
calls.push(() => {
|
|
1386
|
-
gen += "__fxExpr(";
|
|
1387
|
-
putHole(i);
|
|
1388
|
-
gen += ")";
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1849
|
+
cache.set(sf, out);
|
|
1850
|
+
return out;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// src/fixes.ts
|
|
1854
|
+
function templateCodeFixes(ts, ls, fileName, start, end, errorCodes) {
|
|
1855
|
+
if (!errorCodes.includes(ACCESSOR_NOT_CALLED)) return [];
|
|
1856
|
+
const out = [];
|
|
1857
|
+
for (const diagnostic of templateDiagnostics(ts, ls, fileName)) {
|
|
1858
|
+
if (diagnostic.code !== ACCESSOR_NOT_CALLED || diagnostic.start === void 0) continue;
|
|
1859
|
+
if (diagnostic.start > end || diagnostic.start + (diagnostic.length ?? 0) < start) continue;
|
|
1860
|
+
const at = diagnostic.start + (diagnostic.length ?? 0);
|
|
1861
|
+
const name = diagnostic.file?.text.slice(diagnostic.start, at) ?? "value";
|
|
1862
|
+
out.push({
|
|
1863
|
+
fixName: "fluixiCallAccessor",
|
|
1864
|
+
description: `Call '${name}'`,
|
|
1865
|
+
changes: [
|
|
1866
|
+
{
|
|
1867
|
+
fileName,
|
|
1868
|
+
textChanges: [{ span: { start: at, length: 0 }, newText: "()" }]
|
|
1869
|
+
}
|
|
1870
|
+
],
|
|
1871
|
+
// Every occurrence in the file is the same edit, so offer to do them at once.
|
|
1872
|
+
fixAllDescription: "Call every uncalled accessor in this file"
|
|
1873
|
+
});
|
|
1391
1874
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1875
|
+
return out;
|
|
1876
|
+
}
|
|
1877
|
+
function templateFixAll(ts, ls, fileName, errorCodes) {
|
|
1878
|
+
if (!errorCodes.includes(ACCESSOR_NOT_CALLED)) return void 0;
|
|
1879
|
+
const textChanges = [];
|
|
1880
|
+
for (const diagnostic of templateDiagnostics(ts, ls, fileName)) {
|
|
1881
|
+
if (diagnostic.code !== ACCESSOR_NOT_CALLED || diagnostic.start === void 0) continue;
|
|
1882
|
+
const at = diagnostic.start + (diagnostic.length ?? 0);
|
|
1883
|
+
textChanges.push({ span: { start: at, length: 0 }, newText: "()" });
|
|
1884
|
+
}
|
|
1885
|
+
if (textChanges.length === 0) return void 0;
|
|
1886
|
+
textChanges.sort((a, b) => b.span.start - a.span.start);
|
|
1887
|
+
return { changes: [{ fileName, textChanges }], commands: void 0 };
|
|
1399
1888
|
}
|
|
1400
1889
|
|
|
1401
|
-
// src/
|
|
1402
|
-
var
|
|
1403
|
-
function
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
/** Source files known to contain no templates, so we skip building a shadow. */
|
|
1420
|
-
barren = /* @__PURE__ */ new Map();
|
|
1421
|
-
/**
|
|
1422
|
-
* Teach the existing host about shadow paths. The language service holds this
|
|
1423
|
-
* host object, so overriding its methods in place is enough — no second service.
|
|
1424
|
-
*/
|
|
1425
|
-
patchHost() {
|
|
1426
|
-
const ts = this.ts;
|
|
1427
|
-
const host = this.host;
|
|
1428
|
-
const self = this;
|
|
1429
|
-
const originalNames = host.getScriptFileNames.bind(host);
|
|
1430
|
-
host.getScriptFileNames = () => {
|
|
1431
|
-
const names = originalNames();
|
|
1432
|
-
const out = names.slice();
|
|
1433
|
-
for (const name of names) {
|
|
1434
|
-
if (!isShadowPath(name) && self.build(name)) out.push(shadowPathFor(name));
|
|
1890
|
+
// src/navigation.ts
|
|
1891
|
+
var spansByFile = /* @__PURE__ */ new WeakMap();
|
|
1892
|
+
function tagSpans(ts, sf) {
|
|
1893
|
+
const hit = spansByFile.get(sf);
|
|
1894
|
+
if (hit) return hit;
|
|
1895
|
+
const out = [];
|
|
1896
|
+
for (const template of templatesIn(ts, sf)) {
|
|
1897
|
+
const { root } = parseCached(ts, sf, template.template);
|
|
1898
|
+
const walk = (nodes) => {
|
|
1899
|
+
for (const node of nodes) {
|
|
1900
|
+
if (node.kind === "Component" && node.tag) {
|
|
1901
|
+
const component = node;
|
|
1902
|
+
const name = component.tag.split(".")[0];
|
|
1903
|
+
out.push({ name, start: component.loc.start + 1, length: name.length });
|
|
1904
|
+
const closing = closingTagName(sf.text, component.loc.end, component.tag);
|
|
1905
|
+
if (closing !== void 0) out.push({ name, start: closing, length: name.length });
|
|
1906
|
+
}
|
|
1907
|
+
if ("children" in node && node.children) walk(node.children);
|
|
1435
1908
|
}
|
|
1436
|
-
return out;
|
|
1437
|
-
};
|
|
1438
|
-
const originalSnapshot = host.getScriptSnapshot.bind(host);
|
|
1439
|
-
host.getScriptSnapshot = (fileName) => {
|
|
1440
|
-
if (!isShadowPath(fileName)) return originalSnapshot(fileName);
|
|
1441
|
-
const built = self.build(sourcePathFor(fileName));
|
|
1442
|
-
return built ? ts.ScriptSnapshot.fromString(built.text) : void 0;
|
|
1443
1909
|
};
|
|
1444
|
-
|
|
1445
|
-
host.getScriptVersion = (fileName) => isShadowPath(fileName) ? originalVersion(sourcePathFor(fileName)) : originalVersion(fileName);
|
|
1446
|
-
if (typeof host.fileExists === "function") {
|
|
1447
|
-
const originalExists = host.fileExists.bind(host);
|
|
1448
|
-
host.fileExists = (fileName) => isShadowPath(fileName) ? !!self.build(sourcePathFor(fileName)) : originalExists(fileName);
|
|
1449
|
-
}
|
|
1910
|
+
walk(root.children);
|
|
1450
1911
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1912
|
+
spansByFile.set(sf, out);
|
|
1913
|
+
return out;
|
|
1914
|
+
}
|
|
1915
|
+
function closingTagName(text, end, tag) {
|
|
1916
|
+
const open = text.lastIndexOf("</", end);
|
|
1917
|
+
if (open === -1) return void 0;
|
|
1918
|
+
const close = text.indexOf(">", open);
|
|
1919
|
+
if (close === -1 || close >= end) return void 0;
|
|
1920
|
+
const index = text.indexOf(tag, open);
|
|
1921
|
+
return index !== -1 && index < close ? index : void 0;
|
|
1922
|
+
}
|
|
1923
|
+
function tagAt(ts, sf, position) {
|
|
1924
|
+
if (!templateAtCached(ts, sf, position)) return void 0;
|
|
1925
|
+
return tagSpans(ts, sf).find((s) => position >= s.start && position <= s.start + s.length);
|
|
1926
|
+
}
|
|
1927
|
+
function symbolFor(ts, program, host, location, name) {
|
|
1928
|
+
const checker = program.getTypeChecker();
|
|
1929
|
+
const flags = ts.SymbolFlags.Value | ts.SymbolFlags.Function | ts.SymbolFlags.Class | ts.SymbolFlags.Alias;
|
|
1930
|
+
const symbol = checker.getSymbolsInScope(location, flags).find((s) => s.name === name);
|
|
1931
|
+
if (!symbol) return autoImportedSymbol(ts, program, host, location.getSourceFile(), name);
|
|
1932
|
+
return symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
1933
|
+
}
|
|
1934
|
+
function declarationFor(ts, program, host, sf, tag) {
|
|
1935
|
+
const template = templateAtCached(ts, sf, tag.start);
|
|
1936
|
+
if (!template) return void 0;
|
|
1937
|
+
const symbol = symbolFor(ts, program, host, template, tag.name);
|
|
1938
|
+
return symbol?.valueDeclaration ?? symbol?.declarations?.[0];
|
|
1939
|
+
}
|
|
1940
|
+
function templateDefinition(ts, ls, host, fileName, position) {
|
|
1941
|
+
const program = ls.getProgram();
|
|
1942
|
+
const sf = program?.getSourceFile(fileName);
|
|
1943
|
+
if (!program || !sf) return void 0;
|
|
1944
|
+
const tag = tagAt(ts, sf, position);
|
|
1945
|
+
if (!tag) return void 0;
|
|
1946
|
+
const decl = declarationFor(ts, program, host, sf, tag);
|
|
1947
|
+
if (!decl) {
|
|
1948
|
+
const site = declarationSite(ts, program, host, sf, tag.name);
|
|
1949
|
+
if (!site) return void 0;
|
|
1950
|
+
return [
|
|
1951
|
+
{
|
|
1952
|
+
fileName: site.fileName,
|
|
1953
|
+
textSpan: { start: site.start, length: site.length },
|
|
1954
|
+
kind: ts.ScriptElementKind.unknown,
|
|
1955
|
+
name: tag.name,
|
|
1956
|
+
containerKind: ts.ScriptElementKind.unknown,
|
|
1957
|
+
containerName: ""
|
|
1958
|
+
}
|
|
1959
|
+
];
|
|
1960
|
+
}
|
|
1961
|
+
const target = decl.name ?? decl;
|
|
1962
|
+
const declFile = decl.getSourceFile();
|
|
1963
|
+
return [
|
|
1964
|
+
{
|
|
1965
|
+
fileName: declFile.fileName,
|
|
1966
|
+
textSpan: { start: target.getStart(declFile), length: target.getWidth(declFile) },
|
|
1967
|
+
kind: ts.ScriptElementKind.unknown,
|
|
1968
|
+
name: tag.name,
|
|
1969
|
+
containerKind: ts.ScriptElementKind.unknown,
|
|
1970
|
+
containerName: ""
|
|
1464
1971
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1972
|
+
];
|
|
1973
|
+
}
|
|
1974
|
+
function tagOccurrences(ts, program, host, symbol, name) {
|
|
1975
|
+
const out = [];
|
|
1976
|
+
for (const sf of program.getSourceFiles()) {
|
|
1977
|
+
if (sf.isDeclarationFile) continue;
|
|
1978
|
+
const text = sf.text;
|
|
1979
|
+
if (text.indexOf("html`") === -1 && text.indexOf("svg`") === -1) continue;
|
|
1980
|
+
if (text.indexOf(name) === -1) continue;
|
|
1981
|
+
for (const span of tagSpans(ts, sf)) {
|
|
1982
|
+
if (span.name !== name) continue;
|
|
1983
|
+
const template = templateAtCached(ts, sf, span.start);
|
|
1984
|
+
if (!template || symbolFor(ts, program, host, template, name) !== symbol) continue;
|
|
1985
|
+
out.push({ fileName: sf.fileName, span: { start: span.start, length: span.length } });
|
|
1471
1986
|
}
|
|
1472
|
-
const entry = {
|
|
1473
|
-
version,
|
|
1474
|
-
text: virtual.text + "\nexport {};\n",
|
|
1475
|
-
segments: virtual.segments
|
|
1476
|
-
};
|
|
1477
|
-
this.built.set(fileName, entry);
|
|
1478
|
-
return entry;
|
|
1479
|
-
}
|
|
1480
|
-
/** The shadow path for a source file, when it has one. */
|
|
1481
|
-
shadowFor(fileName) {
|
|
1482
|
-
return this.build(fileName) ? shadowPathFor(fileName) : void 0;
|
|
1483
|
-
}
|
|
1484
|
-
segmentsFor(fileName) {
|
|
1485
|
-
return this.build(fileName)?.segments;
|
|
1486
|
-
}
|
|
1487
|
-
toSrc(fileName, position) {
|
|
1488
|
-
const segments = this.segmentsFor(fileName);
|
|
1489
|
-
return segments ? toSource(segments, position) : -1;
|
|
1490
|
-
}
|
|
1491
|
-
inHole(fileName, position) {
|
|
1492
|
-
const segments = this.segmentsFor(fileName);
|
|
1493
|
-
return !!segments && inHole(segments, position);
|
|
1494
|
-
}
|
|
1495
|
-
inKey(fileName, position) {
|
|
1496
|
-
const segments = this.segmentsFor(fileName);
|
|
1497
|
-
return !!segments && inKey(segments, position);
|
|
1498
1987
|
}
|
|
1499
|
-
|
|
1988
|
+
return out;
|
|
1989
|
+
}
|
|
1990
|
+
function symbolAt(ts, program, host, sf, position) {
|
|
1991
|
+
const checker = program.getTypeChecker();
|
|
1992
|
+
const tag = tagAt(ts, sf, position);
|
|
1993
|
+
if (tag) {
|
|
1994
|
+
const template = templateAtCached(ts, sf, tag.start);
|
|
1995
|
+
const symbol2 = template && symbolFor(ts, program, host, template, tag.name);
|
|
1996
|
+
return symbol2 ? { symbol: symbol2, name: tag.name } : void 0;
|
|
1997
|
+
}
|
|
1998
|
+
const token = tokenAt(ts, sf, position);
|
|
1999
|
+
if (!token || !ts.isIdentifier(token)) return void 0;
|
|
2000
|
+
let symbol = checker.getSymbolAtLocation(token);
|
|
2001
|
+
if (symbol && symbol.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
|
|
2002
|
+
return symbol ? { symbol, name: token.text } : void 0;
|
|
2003
|
+
}
|
|
2004
|
+
function tokenAt(ts, sf, position) {
|
|
2005
|
+
let found;
|
|
2006
|
+
const visit = (node) => {
|
|
2007
|
+
if (position < node.getStart(sf) || position > node.getEnd()) return;
|
|
2008
|
+
if (node.getChildCount(sf) === 0) found = node;
|
|
2009
|
+
ts.forEachChild(node, visit);
|
|
2010
|
+
};
|
|
2011
|
+
visit(sf);
|
|
2012
|
+
return found;
|
|
2013
|
+
}
|
|
2014
|
+
function templateReferences(ts, ls, host, fileName, position) {
|
|
2015
|
+
const program = ls.getProgram();
|
|
2016
|
+
const sf = program?.getSourceFile(fileName);
|
|
2017
|
+
if (!program || !sf) return [];
|
|
2018
|
+
const hit = symbolAt(ts, program, host, sf, position);
|
|
2019
|
+
if (!hit) return [];
|
|
2020
|
+
return tagOccurrences(ts, program, host, hit.symbol, hit.name).map(({ fileName: f, span }) => ({
|
|
2021
|
+
fileName: f,
|
|
2022
|
+
textSpan: span,
|
|
2023
|
+
isWriteAccess: false,
|
|
2024
|
+
isDefinition: false
|
|
2025
|
+
}));
|
|
2026
|
+
}
|
|
2027
|
+
function templateRenameLocations(ts, ls, host, fileName, position) {
|
|
2028
|
+
const program = ls.getProgram();
|
|
2029
|
+
const sf = program?.getSourceFile(fileName);
|
|
2030
|
+
if (!program || !sf) return [];
|
|
2031
|
+
const hit = symbolAt(ts, program, host, sf, position);
|
|
2032
|
+
if (!hit) return [];
|
|
2033
|
+
return tagOccurrences(ts, program, host, hit.symbol, hit.name).map(({ fileName: f, span }) => ({
|
|
2034
|
+
fileName: f,
|
|
2035
|
+
textSpan: span
|
|
2036
|
+
}));
|
|
2037
|
+
}
|
|
1500
2038
|
|
|
1501
2039
|
// src/index.ts
|
|
1502
2040
|
function init(modules) {
|
|
@@ -1505,12 +2043,6 @@ function init(modules) {
|
|
|
1505
2043
|
create(info) {
|
|
1506
2044
|
const ls = info.languageService;
|
|
1507
2045
|
const host = info.languageServiceHost;
|
|
1508
|
-
let shadows;
|
|
1509
|
-
try {
|
|
1510
|
-
shadows = new ShadowFiles(ts, info.languageServiceHost);
|
|
1511
|
-
} catch {
|
|
1512
|
-
shadows = void 0;
|
|
1513
|
-
}
|
|
1514
2046
|
const proxy = /* @__PURE__ */ Object.create(null);
|
|
1515
2047
|
for (const key of Object.keys(ls)) {
|
|
1516
2048
|
const member = ls[key];
|
|
@@ -1520,9 +2052,24 @@ function init(modules) {
|
|
|
1520
2052
|
isGlobalCompletion: false,
|
|
1521
2053
|
isMemberCompletion: false,
|
|
1522
2054
|
isNewIdentifierLocation: true,
|
|
2055
|
+
// Re-ask on every keystroke instead of letting the editor filter what it already
|
|
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.
|
|
2058
|
+
// Without this the props appear only after enough typing to force a fresh
|
|
2059
|
+
// request, which reads as completion working intermittently.
|
|
2060
|
+
isIncomplete: true,
|
|
1523
2061
|
entries
|
|
1524
2062
|
});
|
|
1525
2063
|
const IMPLICIT_ANY = /* @__PURE__ */ new Set([7006, 7044]);
|
|
2064
|
+
const dedupe = (items) => {
|
|
2065
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2066
|
+
return items.filter((i) => {
|
|
2067
|
+
const key = `${i.fileName}:${i.textSpan.start}:${i.textSpan.length}`;
|
|
2068
|
+
if (seen.has(key)) return false;
|
|
2069
|
+
seen.add(key);
|
|
2070
|
+
return true;
|
|
2071
|
+
});
|
|
2072
|
+
};
|
|
1526
2073
|
const reliableFilter = (fileName, diagnostics) => {
|
|
1527
2074
|
const program = ls.getProgram();
|
|
1528
2075
|
const sourceFile = program?.getSourceFile(fileName);
|
|
@@ -1534,41 +2081,18 @@ function init(modules) {
|
|
|
1534
2081
|
return true;
|
|
1535
2082
|
});
|
|
1536
2083
|
};
|
|
1537
|
-
|
|
1538
|
-
const
|
|
1539
|
-
if (!shadow) return [];
|
|
2084
|
+
proxy.getSemanticDiagnostics = (fileName) => {
|
|
2085
|
+
const base = reliableFilter(fileName, ls.getSemanticDiagnostics(fileName));
|
|
1540
2086
|
try {
|
|
1541
|
-
|
|
1542
|
-
const out = [];
|
|
1543
|
-
for (const d of ls.getSemanticDiagnostics(shadow)) {
|
|
1544
|
-
if (d.start === void 0) continue;
|
|
1545
|
-
const start = shadows.toSrc(fileName, d.start);
|
|
1546
|
-
if (start === -1) continue;
|
|
1547
|
-
if (!shadows.inHole(fileName, start) && !shadows.inKey(fileName, start)) continue;
|
|
1548
|
-
out.push({ ...d, start, file: sourceFile });
|
|
1549
|
-
}
|
|
1550
|
-
return out;
|
|
2087
|
+
return base.concat(templateDiagnostics(ts, ls, fileName));
|
|
1551
2088
|
} catch {
|
|
1552
|
-
return
|
|
2089
|
+
return base;
|
|
1553
2090
|
}
|
|
1554
2091
|
};
|
|
1555
|
-
proxy.
|
|
1556
|
-
if (isShadowPath(fileName)) return [];
|
|
1557
|
-
const base = reliableFilter(fileName, ls.getSemanticDiagnostics(fileName));
|
|
1558
|
-
return base.concat(holeDiagnostics(fileName));
|
|
1559
|
-
};
|
|
1560
|
-
proxy.getSuggestionDiagnostics = (fileName) => isShadowPath(fileName) ? [] : reliableFilter(fileName, ls.getSuggestionDiagnostics(fileName));
|
|
1561
|
-
proxy.getSyntacticDiagnostics = (fileName) => isShadowPath(fileName) ? [] : ls.getSyntacticDiagnostics(fileName);
|
|
1562
|
-
const notShadow = (items) => items?.filter((i) => !isShadowPath(i.fileName));
|
|
1563
|
-
proxy.findRenameLocations = (fileName, position, findInStrings, findInComments, prefs) => notShadow(ls.findRenameLocations(fileName, position, findInStrings, findInComments, prefs));
|
|
1564
|
-
proxy.getReferencesAtPosition = (fileName, position) => notShadow(ls.getReferencesAtPosition(fileName, position));
|
|
1565
|
-
proxy.getDefinitionAtPosition = (fileName, position) => notShadow(ls.getDefinitionAtPosition(fileName, position));
|
|
1566
|
-
proxy.getImplementationAtPosition = (fileName, position) => notShadow(ls.getImplementationAtPosition(fileName, position));
|
|
1567
|
-
proxy.getDocumentHighlights = (fileName, position, filesToSearch) => ls.getDocumentHighlights(fileName, position, filesToSearch)?.filter((h) => !isShadowPath(h.fileName));
|
|
1568
|
-
proxy.findReferences = (fileName, position) => ls.findReferences(fileName, position)?.filter((r) => !isShadowPath(r.definition.fileName)).map((r) => ({ ...r, references: r.references.filter((x2) => !isShadowPath(x2.fileName)) }));
|
|
2092
|
+
proxy.getSuggestionDiagnostics = (fileName) => reliableFilter(fileName, ls.getSuggestionDiagnostics(fileName));
|
|
1569
2093
|
proxy.getQuickInfoAtPosition = (fileName, position) => {
|
|
1570
2094
|
try {
|
|
1571
|
-
const prop = componentPropHover(ts, ls, fileName, position);
|
|
2095
|
+
const prop = componentPropHover(ts, ls, host, fileName, position);
|
|
1572
2096
|
if (prop) return prop;
|
|
1573
2097
|
} catch {
|
|
1574
2098
|
}
|
|
@@ -1582,11 +2106,16 @@ function init(modules) {
|
|
|
1582
2106
|
if (inferred) return inferred;
|
|
1583
2107
|
} catch {
|
|
1584
2108
|
}
|
|
1585
|
-
|
|
2109
|
+
try {
|
|
2110
|
+
const tag = componentQuickInfo(ts, ls, host, fileName, position);
|
|
2111
|
+
if (tag) return tag;
|
|
2112
|
+
} catch {
|
|
2113
|
+
}
|
|
2114
|
+
return ls.getQuickInfoAtPosition(fileName, position);
|
|
1586
2115
|
};
|
|
1587
2116
|
proxy.getCompletionsAtPosition = (fileName, position, options, formatting) => {
|
|
1588
2117
|
try {
|
|
1589
|
-
const props = componentPropCompletions(ts, ls, fileName, position);
|
|
2118
|
+
const props = componentPropCompletions(ts, ls, host, fileName, position);
|
|
1590
2119
|
if (props) return completionList(props);
|
|
1591
2120
|
} catch {
|
|
1592
2121
|
}
|
|
@@ -1602,8 +2131,75 @@ function init(modules) {
|
|
|
1602
2131
|
}
|
|
1603
2132
|
return ls.getCompletionsAtPosition(fileName, position, options, formatting);
|
|
1604
2133
|
};
|
|
2134
|
+
proxy.getDefinitionAtPosition = (fileName, position) => {
|
|
2135
|
+
try {
|
|
2136
|
+
const tag = templateDefinition(ts, ls, host, fileName, position);
|
|
2137
|
+
if (tag) return tag;
|
|
2138
|
+
} catch {
|
|
2139
|
+
}
|
|
2140
|
+
return ls.getDefinitionAtPosition(fileName, position);
|
|
2141
|
+
};
|
|
2142
|
+
proxy.getDefinitionAndBoundSpan = (fileName, position) => {
|
|
2143
|
+
try {
|
|
2144
|
+
const definitions = templateDefinition(ts, ls, host, fileName, position);
|
|
2145
|
+
if (definitions) {
|
|
2146
|
+
const sf = ls.getProgram()?.getSourceFile(fileName);
|
|
2147
|
+
const tag = sf && tagAt(ts, sf, position);
|
|
2148
|
+
if (tag) {
|
|
2149
|
+
return { definitions, textSpan: { start: tag.start, length: tag.length } };
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
} catch {
|
|
2153
|
+
}
|
|
2154
|
+
return ls.getDefinitionAndBoundSpan(fileName, position);
|
|
2155
|
+
};
|
|
2156
|
+
proxy.getReferencesAtPosition = (fileName, position) => {
|
|
2157
|
+
const base = ls.getReferencesAtPosition(fileName, position) ?? [];
|
|
2158
|
+
try {
|
|
2159
|
+
const extra = templateReferences(ts, ls, host, fileName, position);
|
|
2160
|
+
return extra.length ? dedupe([...base, ...extra]) : base;
|
|
2161
|
+
} catch {
|
|
2162
|
+
return base;
|
|
2163
|
+
}
|
|
2164
|
+
};
|
|
2165
|
+
proxy.findRenameLocations = (fileName, position, findInStrings, findInComments, prefs) => {
|
|
2166
|
+
const base = ls.findRenameLocations(fileName, position, findInStrings, findInComments, prefs) ?? [];
|
|
2167
|
+
try {
|
|
2168
|
+
const extra = templateRenameLocations(ts, ls, host, fileName, position);
|
|
2169
|
+
if (!base.length || !extra.length) return base.length ? base : void 0;
|
|
2170
|
+
return dedupe([...base, ...extra]);
|
|
2171
|
+
} catch {
|
|
2172
|
+
return base;
|
|
2173
|
+
}
|
|
2174
|
+
};
|
|
2175
|
+
proxy.getCodeFixesAtPosition = (fileName, start, end, errorCodes, formatOptions, preferences) => {
|
|
2176
|
+
const base = ls.getCodeFixesAtPosition(
|
|
2177
|
+
fileName,
|
|
2178
|
+
start,
|
|
2179
|
+
end,
|
|
2180
|
+
errorCodes,
|
|
2181
|
+
formatOptions,
|
|
2182
|
+
preferences
|
|
2183
|
+
);
|
|
2184
|
+
try {
|
|
2185
|
+
const own = templateCodeFixes(ts, ls, fileName, start, end, errorCodes);
|
|
2186
|
+
return own.length ? [...base, ...own] : base;
|
|
2187
|
+
} catch {
|
|
2188
|
+
return base;
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
proxy.getCombinedCodeFix = (scope, fixId, formatOptions, preferences) => {
|
|
2192
|
+
if (fixId === "fluixiCallAccessor") {
|
|
2193
|
+
const fileName = scope.fileName;
|
|
2194
|
+
const combined = templateFixAll(ts, ls, fileName, [ACCESSOR_NOT_CALLED]);
|
|
2195
|
+
if (combined) return combined;
|
|
2196
|
+
}
|
|
2197
|
+
return ls.getCombinedCodeFix(scope, fixId, formatOptions, preferences);
|
|
2198
|
+
};
|
|
1605
2199
|
return proxy;
|
|
1606
2200
|
}
|
|
1607
2201
|
};
|
|
1608
2202
|
}
|
|
1609
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.86 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|