stimeo-ui 0.3.0 → 0.4.0
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/CHANGELOG.md +56 -0
- package/dist/controllers/alert_dialog_controller.js +32 -5
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +32 -6
- package/dist/controllers/announcer_controller.js +255 -20
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +2 -1
- package/dist/controllers/color_picker_controller.js +46 -0
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.js +32 -5
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.js +32 -5
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +19 -5
- package/dist/controllers/countdown_controller.js +112 -12
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +4 -1
- package/dist/controllers/date_range_picker_controller.js +50 -0
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.js +32 -5
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.js +32 -5
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +9 -4
- package/dist/controllers/empty_state_controller.js +24 -10
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/focus_controller.js +32 -5
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +14 -3
- package/dist/controllers/frame_loading_controller.js +177 -15
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +19 -3
- package/dist/controllers/local_time_controller.js +100 -6
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +22 -2
- package/dist/controllers/meter_controller.js +145 -26
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +13 -1
- package/dist/controllers/network_status_controller.js +28 -8
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +33 -6
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +16 -1
- package/dist/controllers/progress_controller.js +116 -9
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +4 -1
- package/dist/controllers/range_slider_controller.js +68 -3
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +4 -1
- package/dist/controllers/rating_controller.js +53 -0
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +13 -0
- package/dist/controllers/relative_time_controller.js +133 -12
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +1 -11
- package/dist/controllers/sidebar_controller.js +37 -8
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +7 -2
- package/dist/controllers/skeleton_controller.js +73 -20
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.js +17 -2
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +37 -4
- package/dist/controllers/spinner_controller.js +228 -27
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +12 -1
- package/dist/controllers/step_indicator_controller.js +82 -5
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +26 -2
- package/dist/controllers/stick_to_bottom_controller.js +60 -10
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/index.js +1056 -281
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.js +785 -76
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +785 -76
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +5 -5
- package/dist/inspector/manifest.json +21 -8
- package/package.json +1 -1
|
@@ -7,66 +7,8 @@ import { createInterface } from 'readline';
|
|
|
7
7
|
|
|
8
8
|
// src/inspector/cli_bin.ts
|
|
9
9
|
|
|
10
|
-
// src/inspector/erb.ts
|
|
11
|
-
function neutralizeErb(source) {
|
|
12
|
-
return source.replace(/<%[\s\S]*?%>/g, (match) => match.replace(/[^\n]/g, " "));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// src/inspector/extract.ts
|
|
16
|
-
var NAMESPACE_PREFIX = "stimeo--";
|
|
17
|
-
function dasherize(value) {
|
|
18
|
-
return value.replace(/([A-Z])/g, (_match, char) => `-${char.toLowerCase()}`);
|
|
19
|
-
}
|
|
20
|
-
function controllerIdentifiers(dataControllerValue) {
|
|
21
|
-
const seen = /* @__PURE__ */ new Set();
|
|
22
|
-
for (const token of dataControllerValue.split(/\s+/)) {
|
|
23
|
-
if (token.startsWith(NAMESPACE_PREFIX)) seen.add(token);
|
|
24
|
-
}
|
|
25
|
-
return [...seen];
|
|
26
|
-
}
|
|
27
|
-
function parseTargetAttr(attrName) {
|
|
28
|
-
const match = /^data-(stimeo--[a-z0-9][a-z0-9-]*)-target$/.exec(attrName);
|
|
29
|
-
return match?.[1] ?? null;
|
|
30
|
-
}
|
|
31
|
-
function parseValueAttr(attrName, knownIdentifiers) {
|
|
32
|
-
const match = /^data-(stimeo--[a-z0-9-]+)-value$/.exec(attrName);
|
|
33
|
-
if (!match) return null;
|
|
34
|
-
const inner = match[1] ?? "";
|
|
35
|
-
let best = null;
|
|
36
|
-
for (const id of knownIdentifiers) {
|
|
37
|
-
if (inner.startsWith(`${id}-`)) {
|
|
38
|
-
const valueToken = inner.slice(id.length + 1);
|
|
39
|
-
if (valueToken.length > 0 && (!best || id.length > best.identifier.length)) {
|
|
40
|
-
best = { identifier: id, valueToken };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (best) return best;
|
|
45
|
-
const rest = inner.slice(NAMESPACE_PREFIX.length);
|
|
46
|
-
const dash = rest.indexOf("-");
|
|
47
|
-
const identifierGuess = dash === -1 ? inner : `${NAMESPACE_PREFIX}${rest.slice(0, dash)}`;
|
|
48
|
-
const valueGuess = dash === -1 ? "" : rest.slice(dash + 1);
|
|
49
|
-
return { identifier: null, valueToken: valueGuess || identifierGuess };
|
|
50
|
-
}
|
|
51
|
-
function actionDescriptors(dataActionValue) {
|
|
52
|
-
const descriptors = [];
|
|
53
|
-
for (const descriptor of dataActionValue.split(/\s+/)) {
|
|
54
|
-
const hash = descriptor.indexOf("#");
|
|
55
|
-
if (hash === -1) continue;
|
|
56
|
-
let lhs = descriptor.slice(0, hash);
|
|
57
|
-
const arrow = lhs.indexOf("->");
|
|
58
|
-
if (arrow !== -1) lhs = lhs.slice(arrow + 2);
|
|
59
|
-
if (!lhs.startsWith(NAMESPACE_PREFIX)) continue;
|
|
60
|
-
const method = /^[a-zA-Z_$][\w$]*/.exec(descriptor.slice(hash + 1))?.[0] ?? "";
|
|
61
|
-
descriptors.push({ identifier: lhs, method });
|
|
62
|
-
}
|
|
63
|
-
return descriptors;
|
|
64
|
-
}
|
|
65
|
-
function isStimeoDataAttr(attrName) {
|
|
66
|
-
return attrName.startsWith(`data-${NAMESPACE_PREFIX}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
10
|
// src/inspector/html_parser.ts
|
|
11
|
+
var ERB_ELEMENT_TAG = "#erb";
|
|
70
12
|
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
71
13
|
"area",
|
|
72
14
|
"base",
|
|
@@ -103,7 +45,7 @@ var PositionIndex = class {
|
|
|
103
45
|
return { line: lo + 1, column: offset - (this.#lineStarts[lo] ?? 0) + 1 };
|
|
104
46
|
}
|
|
105
47
|
};
|
|
106
|
-
function parseHtml(source) {
|
|
48
|
+
function parseHtml(source, synthetics = []) {
|
|
107
49
|
const pos = new PositionIndex(source);
|
|
108
50
|
const len = source.length;
|
|
109
51
|
const ch = (k) => k >= 0 && k < len ? source[k] : "";
|
|
@@ -113,12 +55,57 @@ function parseHtml(source) {
|
|
|
113
55
|
children: [],
|
|
114
56
|
parent: null,
|
|
115
57
|
line: 1,
|
|
116
|
-
column: 1
|
|
58
|
+
column: 1,
|
|
59
|
+
origin: "markup",
|
|
60
|
+
opaque: false
|
|
117
61
|
};
|
|
118
62
|
const stack = [root];
|
|
119
63
|
let i = 0;
|
|
120
64
|
const top = () => stack[stack.length - 1];
|
|
65
|
+
let pending = 0;
|
|
66
|
+
const closesAt = /* @__PURE__ */ new Map();
|
|
67
|
+
const takeSynthetics = (offset, splice) => {
|
|
68
|
+
while (pending < synthetics.length) {
|
|
69
|
+
const synthetic = synthetics[pending];
|
|
70
|
+
if (synthetic.start > offset) return;
|
|
71
|
+
pending++;
|
|
72
|
+
if (!splice) continue;
|
|
73
|
+
const at = pos.at(synthetic.start);
|
|
74
|
+
const node = {
|
|
75
|
+
tag: synthetic.tag,
|
|
76
|
+
attrs: synthetic.attrs,
|
|
77
|
+
children: [],
|
|
78
|
+
parent: top(),
|
|
79
|
+
line: at.line,
|
|
80
|
+
column: at.column,
|
|
81
|
+
sourceLength: synthetic.sourceLength,
|
|
82
|
+
origin: "erb",
|
|
83
|
+
opaque: synthetic.opaque
|
|
84
|
+
};
|
|
85
|
+
top().children.push(node);
|
|
86
|
+
if (synthetic.container) {
|
|
87
|
+
closesAt.set(node, synthetic.end);
|
|
88
|
+
stack.push(node);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const closeSynthetics = (offset) => {
|
|
93
|
+
for (; ; ) {
|
|
94
|
+
let expired = -1;
|
|
95
|
+
for (let depth = stack.length - 1; depth >= 1; depth--) {
|
|
96
|
+
const end = closesAt.get(stack[depth]);
|
|
97
|
+
if (end !== void 0 && end <= offset) {
|
|
98
|
+
expired = depth;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (expired < 0) return;
|
|
103
|
+
stack.length = expired;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
121
106
|
while (i < len) {
|
|
107
|
+
closeSynthetics(i);
|
|
108
|
+
takeSynthetics(i, true);
|
|
122
109
|
if (ch(i) !== "<") {
|
|
123
110
|
i++;
|
|
124
111
|
continue;
|
|
@@ -127,11 +114,13 @@ function parseHtml(source) {
|
|
|
127
114
|
if (source.startsWith("<!--", i)) {
|
|
128
115
|
const end = source.indexOf("-->", i + 4);
|
|
129
116
|
i = end === -1 ? len : end + 3;
|
|
117
|
+
takeSynthetics(i - 1, false);
|
|
130
118
|
continue;
|
|
131
119
|
}
|
|
132
120
|
if (next === "!" || next === "?") {
|
|
133
121
|
const end = source.indexOf(">", i);
|
|
134
122
|
i = end === -1 ? len : end + 1;
|
|
123
|
+
takeSynthetics(i - 1, false);
|
|
135
124
|
continue;
|
|
136
125
|
}
|
|
137
126
|
if (next === "/") {
|
|
@@ -143,6 +132,7 @@ function parseHtml(source) {
|
|
|
143
132
|
}
|
|
144
133
|
const end = source.indexOf(">", j2);
|
|
145
134
|
i = end === -1 ? len : end + 1;
|
|
135
|
+
takeSynthetics(i - 1, false);
|
|
146
136
|
closeTag(stack, name.toLowerCase());
|
|
147
137
|
continue;
|
|
148
138
|
}
|
|
@@ -223,10 +213,13 @@ function parseHtml(source) {
|
|
|
223
213
|
children: [],
|
|
224
214
|
parent: top(),
|
|
225
215
|
line: startPos.line,
|
|
226
|
-
column: startPos.column
|
|
216
|
+
column: startPos.column,
|
|
217
|
+
origin: "markup",
|
|
218
|
+
opaque: false
|
|
227
219
|
};
|
|
228
220
|
top().children.push(node);
|
|
229
221
|
i = j;
|
|
222
|
+
takeSynthetics(i - 1, false);
|
|
230
223
|
if (selfClosing || VOID_ELEMENTS.has(lowerTag)) {
|
|
231
224
|
continue;
|
|
232
225
|
}
|
|
@@ -239,10 +232,13 @@ function parseHtml(source) {
|
|
|
239
232
|
const gt = source.indexOf(">", idx);
|
|
240
233
|
i = gt === -1 ? len : gt + 1;
|
|
241
234
|
}
|
|
235
|
+
takeSynthetics(i - 1, false);
|
|
242
236
|
continue;
|
|
243
237
|
}
|
|
244
238
|
stack.push(node);
|
|
245
239
|
}
|
|
240
|
+
closeSynthetics(len);
|
|
241
|
+
takeSynthetics(len, true);
|
|
246
242
|
return root;
|
|
247
243
|
}
|
|
248
244
|
function closeTag(stack, name) {
|
|
@@ -260,6 +256,673 @@ function walk(root, visit) {
|
|
|
260
256
|
}
|
|
261
257
|
}
|
|
262
258
|
|
|
259
|
+
// src/inspector/ruby_scan.ts
|
|
260
|
+
var SPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\f"]);
|
|
261
|
+
var IDENT_START = /[A-Za-z_]/;
|
|
262
|
+
var IDENT_CHAR = /[A-Za-z0-9_]/;
|
|
263
|
+
var ESCAPES = {
|
|
264
|
+
n: "\n",
|
|
265
|
+
t: " ",
|
|
266
|
+
r: "\r",
|
|
267
|
+
f: "\f",
|
|
268
|
+
v: "\v",
|
|
269
|
+
b: "\b",
|
|
270
|
+
a: "\x07",
|
|
271
|
+
e: "\x1B",
|
|
272
|
+
s: " "
|
|
273
|
+
};
|
|
274
|
+
var UNDECODED_ESCAPE = /[uxcCM0-7]/;
|
|
275
|
+
function readString(code, start) {
|
|
276
|
+
const quote = code[start];
|
|
277
|
+
const single = quote === "'";
|
|
278
|
+
const contentStart = start + 1;
|
|
279
|
+
let i = contentStart;
|
|
280
|
+
let value = "";
|
|
281
|
+
let verbatim = true;
|
|
282
|
+
while (i < code.length) {
|
|
283
|
+
const char = code[i];
|
|
284
|
+
if (char === quote) {
|
|
285
|
+
return { end: i + 1, value, literal: true, verbatim, contentStart, contentEnd: i };
|
|
286
|
+
}
|
|
287
|
+
if (char === "\\") {
|
|
288
|
+
const escaped = code[i + 1];
|
|
289
|
+
if (escaped === void 0) break;
|
|
290
|
+
verbatim = false;
|
|
291
|
+
if (single) {
|
|
292
|
+
value += escaped === "'" || escaped === "\\" ? escaped : `\\${escaped}`;
|
|
293
|
+
} else if (UNDECODED_ESCAPE.test(escaped)) {
|
|
294
|
+
return {
|
|
295
|
+
end: closingQuote(code, i + 2, quote),
|
|
296
|
+
value,
|
|
297
|
+
literal: false,
|
|
298
|
+
verbatim: false,
|
|
299
|
+
contentStart,
|
|
300
|
+
contentEnd: i
|
|
301
|
+
};
|
|
302
|
+
} else if (escaped !== "\n") {
|
|
303
|
+
value += ESCAPES[escaped] ?? escaped;
|
|
304
|
+
}
|
|
305
|
+
i += 2;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (!single && char === "#" && code[i + 1] === "{") {
|
|
309
|
+
const close = skipBraces(code, i + 1);
|
|
310
|
+
return {
|
|
311
|
+
end: closingQuote(code, close, quote),
|
|
312
|
+
value,
|
|
313
|
+
literal: false,
|
|
314
|
+
verbatim: false,
|
|
315
|
+
contentStart,
|
|
316
|
+
contentEnd: i
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
value += char;
|
|
320
|
+
i++;
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
end: code.length,
|
|
324
|
+
value,
|
|
325
|
+
literal: false,
|
|
326
|
+
verbatim: false,
|
|
327
|
+
contentStart,
|
|
328
|
+
contentEnd: code.length
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function closingQuote(code, from, quote) {
|
|
332
|
+
for (let i = from; i < code.length; i++) {
|
|
333
|
+
if (code[i] === "\\") {
|
|
334
|
+
i++;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (code[i] === quote) return i + 1;
|
|
338
|
+
}
|
|
339
|
+
return code.length;
|
|
340
|
+
}
|
|
341
|
+
function skipBraces(code, open) {
|
|
342
|
+
let depth = 0;
|
|
343
|
+
let i = open;
|
|
344
|
+
while (i < code.length) {
|
|
345
|
+
const char = code[i];
|
|
346
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
347
|
+
i = readString(code, i).end;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (char === "{") depth++;
|
|
351
|
+
else if (char === "}") {
|
|
352
|
+
depth--;
|
|
353
|
+
if (depth === 0) return i + 1;
|
|
354
|
+
}
|
|
355
|
+
i++;
|
|
356
|
+
}
|
|
357
|
+
return code.length;
|
|
358
|
+
}
|
|
359
|
+
function endOfLine(code, at) {
|
|
360
|
+
const newline = code.indexOf("\n", at);
|
|
361
|
+
return newline === -1 ? code.length : newline + 1;
|
|
362
|
+
}
|
|
363
|
+
function skipTrivia(code, at) {
|
|
364
|
+
let i = at;
|
|
365
|
+
while (i < code.length) {
|
|
366
|
+
const char = code[i];
|
|
367
|
+
if (SPACE.has(char)) i++;
|
|
368
|
+
else if (char === "#") i = endOfLine(code, i);
|
|
369
|
+
else break;
|
|
370
|
+
}
|
|
371
|
+
return i;
|
|
372
|
+
}
|
|
373
|
+
function previousSignificant(code, at) {
|
|
374
|
+
let i = at - 1;
|
|
375
|
+
while (i >= 0 && (code[i] === " " || code[i] === " ")) i--;
|
|
376
|
+
return i >= 0 ? code[i] : "";
|
|
377
|
+
}
|
|
378
|
+
var EXPRESSION_LEAD = /* @__PURE__ */ new Set([
|
|
379
|
+
"",
|
|
380
|
+
"\n",
|
|
381
|
+
"\r",
|
|
382
|
+
";",
|
|
383
|
+
"(",
|
|
384
|
+
"[",
|
|
385
|
+
"{",
|
|
386
|
+
",",
|
|
387
|
+
":",
|
|
388
|
+
"=",
|
|
389
|
+
"<",
|
|
390
|
+
">",
|
|
391
|
+
"!",
|
|
392
|
+
"&",
|
|
393
|
+
"|",
|
|
394
|
+
"^",
|
|
395
|
+
"~",
|
|
396
|
+
"+",
|
|
397
|
+
"-",
|
|
398
|
+
"*",
|
|
399
|
+
"/",
|
|
400
|
+
"%",
|
|
401
|
+
"?"
|
|
402
|
+
]);
|
|
403
|
+
function opensExpression(code, at) {
|
|
404
|
+
return EXPRESSION_LEAD.has(previousSignificant(code, at));
|
|
405
|
+
}
|
|
406
|
+
var PERCENT_TYPES = "qQwWiIrsx";
|
|
407
|
+
var DELIMITER_PAIRS = { "(": ")", "[": "]", "{": "}", "<": ">" };
|
|
408
|
+
function skipLiteral(code, at) {
|
|
409
|
+
const char = code[at];
|
|
410
|
+
if (char === void 0) return -1;
|
|
411
|
+
if (char === "%") {
|
|
412
|
+
const typed = PERCENT_TYPES.includes(code[at + 1] ?? "");
|
|
413
|
+
const open = typed ? code[at + 2] : code[at + 1];
|
|
414
|
+
if (open === void 0 || IDENT_CHAR.test(open) || SPACE.has(open)) return -1;
|
|
415
|
+
if (!typed && !opensExpression(code, at) && !SPACE.has(code[at - 1] ?? "")) return -1;
|
|
416
|
+
const end = closeDelimited(code, typed ? at + 3 : at + 2, open);
|
|
417
|
+
return end < 0 ? code.length : end;
|
|
418
|
+
}
|
|
419
|
+
if (char === "/" && opensExpression(code, at)) {
|
|
420
|
+
const end = closeDelimited(code, at + 1, "/");
|
|
421
|
+
if (end < 0) return -1;
|
|
422
|
+
let i = end;
|
|
423
|
+
while (i < code.length && /[imxounse]/.test(code[i])) i++;
|
|
424
|
+
return i;
|
|
425
|
+
}
|
|
426
|
+
if (char === "?" && opensExpression(code, at)) {
|
|
427
|
+
const next = code[at + 1];
|
|
428
|
+
if (next === void 0 || SPACE.has(next)) return -1;
|
|
429
|
+
if (next === "\\") return at + 3;
|
|
430
|
+
if (IDENT_CHAR.test(next) && IDENT_CHAR.test(code[at + 2] ?? "")) return -1;
|
|
431
|
+
return at + 2;
|
|
432
|
+
}
|
|
433
|
+
return -1;
|
|
434
|
+
}
|
|
435
|
+
function closeDelimited(code, from, open) {
|
|
436
|
+
const close = DELIMITER_PAIRS[open] ?? open;
|
|
437
|
+
const nests = close !== open;
|
|
438
|
+
let depth = 1;
|
|
439
|
+
for (let i = from; i < code.length; i++) {
|
|
440
|
+
const char = code[i];
|
|
441
|
+
if (char === "\\") {
|
|
442
|
+
i++;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (nests && char === open) depth++;
|
|
446
|
+
else if (char === close) {
|
|
447
|
+
depth--;
|
|
448
|
+
if (depth === 0) return i + 1;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return -1;
|
|
452
|
+
}
|
|
453
|
+
var HEREDOC_OPEN = /^<<([-~])?(["'`])?([A-Za-z_]\w*)/;
|
|
454
|
+
function maskHeredocs(code) {
|
|
455
|
+
let masked = code;
|
|
456
|
+
let i = 0;
|
|
457
|
+
while (i < masked.length) {
|
|
458
|
+
const char = masked[i];
|
|
459
|
+
if (char === "#") {
|
|
460
|
+
i = endOfLine(masked, i);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
464
|
+
i = readString(masked, i).end;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const literal = skipLiteral(masked, i);
|
|
468
|
+
if (literal > i) {
|
|
469
|
+
i = literal;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (char === "<" && masked[i + 1] === "<") {
|
|
473
|
+
const [matched, dash, quote, identifier] = HEREDOC_OPEN.exec(masked.slice(i)) ?? [];
|
|
474
|
+
const opensBody = identifier !== void 0 && (dash !== void 0 || quote !== void 0 || /^[A-Z]/.test(identifier) && opensExpression(masked, i));
|
|
475
|
+
const consumed = (matched?.length ?? 0) + (quote === void 0 ? 0 : 1);
|
|
476
|
+
if (opensBody && (quote === void 0 || masked[i + consumed - 1] === quote)) {
|
|
477
|
+
const bodyStart = endOfLine(masked, i);
|
|
478
|
+
const bodyEnd = heredocEnd(masked, bodyStart, identifier);
|
|
479
|
+
masked = masked.slice(0, bodyStart) + masked.slice(bodyStart, bodyEnd).replace(/[^\n]/g, " ") + masked.slice(bodyEnd);
|
|
480
|
+
i += consumed;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
i++;
|
|
485
|
+
}
|
|
486
|
+
return masked;
|
|
487
|
+
}
|
|
488
|
+
function heredocEnd(code, bodyStart, identifier) {
|
|
489
|
+
let line = bodyStart;
|
|
490
|
+
while (line < code.length) {
|
|
491
|
+
const next = endOfLine(code, line);
|
|
492
|
+
if (code.slice(line, next).trim() === identifier) return next;
|
|
493
|
+
line = next;
|
|
494
|
+
}
|
|
495
|
+
return code.length;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/inspector/ruby_hash.ts
|
|
499
|
+
function readDataOption(source, base) {
|
|
500
|
+
const code = maskHeredocs(source);
|
|
501
|
+
const attrs = [];
|
|
502
|
+
let found = false;
|
|
503
|
+
let opaque = false;
|
|
504
|
+
let i = 0;
|
|
505
|
+
while (i < code.length) {
|
|
506
|
+
const char = code[i];
|
|
507
|
+
if (char === "#") {
|
|
508
|
+
i = endOfLine(code, i);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
512
|
+
const literal = readString(code, i);
|
|
513
|
+
const value = char === "`" ? null : keySeparator(code, literal.end);
|
|
514
|
+
if (literal.literal && literal.value === "data" && value !== null) {
|
|
515
|
+
i = value;
|
|
516
|
+
} else {
|
|
517
|
+
i = literal.end;
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
} else if (char === ":" && IDENT_START.test(code[i + 1] ?? "")) {
|
|
521
|
+
const name = readIdentifier(code, i + 1);
|
|
522
|
+
const value = hashRocket(code, name.end);
|
|
523
|
+
if (name.text === "data" && value !== null) {
|
|
524
|
+
i = value;
|
|
525
|
+
} else {
|
|
526
|
+
i = name.end;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
} else if (IDENT_START.test(char) && !IDENT_CHAR.test(code[i - 1] ?? "")) {
|
|
530
|
+
const name = readIdentifier(code, i);
|
|
531
|
+
const keyed = name.text === "data" && code[name.end] === ":" && code[name.end + 1] !== ":";
|
|
532
|
+
if (keyed) {
|
|
533
|
+
i = name.end + 1;
|
|
534
|
+
} else {
|
|
535
|
+
i = name.end;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
const literal = skipLiteral(code, i);
|
|
540
|
+
i = literal < 0 ? i + 1 : literal;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
found = true;
|
|
544
|
+
const start = skipTrivia(code, i);
|
|
545
|
+
if (code[start] === "{") {
|
|
546
|
+
const hash = readHash(code, start, base);
|
|
547
|
+
attrs.push(...hash.attrs);
|
|
548
|
+
if (hash.partial || !optionEnds(code, hash.end)) opaque = true;
|
|
549
|
+
i = hash.end;
|
|
550
|
+
} else {
|
|
551
|
+
opaque = true;
|
|
552
|
+
i = start;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return { attrs, found, opaque };
|
|
556
|
+
}
|
|
557
|
+
var OPTION_TAIL = /^(?:do|if|unless|while|until|rescue|then|and|or|end)\b/;
|
|
558
|
+
function optionEnds(code, at) {
|
|
559
|
+
const i = skipTrivia(code, at);
|
|
560
|
+
if (i >= code.length) return true;
|
|
561
|
+
const char = code[i];
|
|
562
|
+
if (char === "," || char === "}" || char === ")" || char === "]" || char === ";") return true;
|
|
563
|
+
return OPTION_TAIL.test(code.slice(i));
|
|
564
|
+
}
|
|
565
|
+
function readHash(code, open, base) {
|
|
566
|
+
const attrs = [];
|
|
567
|
+
let partial = false;
|
|
568
|
+
let i = open + 1;
|
|
569
|
+
for (; ; ) {
|
|
570
|
+
i = skipTrivia(code, i);
|
|
571
|
+
if (i >= code.length) {
|
|
572
|
+
partial = true;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
if (code[i] === "}") {
|
|
576
|
+
i++;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
if (code[i] === ",") {
|
|
580
|
+
i++;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
const entry = readEntry(code, i, base);
|
|
584
|
+
if (entry.attr) attrs.push(entry.attr);
|
|
585
|
+
else partial = true;
|
|
586
|
+
i = entry.end;
|
|
587
|
+
}
|
|
588
|
+
return { attrs, partial, end: i };
|
|
589
|
+
}
|
|
590
|
+
function readEntry(code, start, base) {
|
|
591
|
+
const key = readKey(code, start);
|
|
592
|
+
if (key === null) return { attr: null, end: skipValue(code, start) };
|
|
593
|
+
const valueStart = skipTrivia(code, key.valueAt);
|
|
594
|
+
const name = attributeName(key.name);
|
|
595
|
+
if (name === null) return { attr: null, end: skipValue(code, valueStart) };
|
|
596
|
+
const anchor = { keyStart: base + start, keyEnd: base + key.end };
|
|
597
|
+
const quote = code[valueStart];
|
|
598
|
+
if (quote === '"' || quote === "'") {
|
|
599
|
+
const literal = readString(code, valueStart);
|
|
600
|
+
const after = skipTrivia(code, literal.end);
|
|
601
|
+
const whole = after >= code.length || code[after] === "," || code[after] === "}";
|
|
602
|
+
if (literal.literal && whole) {
|
|
603
|
+
return {
|
|
604
|
+
attr: {
|
|
605
|
+
name,
|
|
606
|
+
value: literal.value,
|
|
607
|
+
...anchor,
|
|
608
|
+
valueStart: literal.verbatim ? base + literal.contentStart : void 0,
|
|
609
|
+
valueEnd: literal.verbatim ? base + literal.contentEnd : void 0
|
|
610
|
+
},
|
|
611
|
+
end: literal.end
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return { attr: { name, value: null, ...anchor }, end: skipValue(code, valueStart) };
|
|
616
|
+
}
|
|
617
|
+
function readKey(code, start) {
|
|
618
|
+
const char = code[start];
|
|
619
|
+
if (char === '"' || char === "'") {
|
|
620
|
+
const literal = readString(code, start);
|
|
621
|
+
const valueAt = keySeparator(code, literal.end);
|
|
622
|
+
if (!literal.literal || valueAt === null) return null;
|
|
623
|
+
return { name: literal.value, end: literal.end, valueAt };
|
|
624
|
+
}
|
|
625
|
+
if (char === ":" && IDENT_START.test(code[start + 1] ?? "")) {
|
|
626
|
+
const name = readIdentifier(code, start + 1);
|
|
627
|
+
const valueAt = hashRocket(code, name.end);
|
|
628
|
+
return valueAt === null ? null : { name: name.text, end: name.end, valueAt };
|
|
629
|
+
}
|
|
630
|
+
if (char !== void 0 && IDENT_START.test(char)) {
|
|
631
|
+
const name = readIdentifier(code, start);
|
|
632
|
+
if (code[name.end] !== ":" || code[name.end + 1] === ":") return null;
|
|
633
|
+
return { name: name.text, end: name.end, valueAt: name.end + 1 };
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
function attributeName(key) {
|
|
638
|
+
const name = key.replaceAll("_", "-").toLowerCase();
|
|
639
|
+
return name.length > 0 && !/[\s"'=<>/]/.test(name) ? `data-${name}` : null;
|
|
640
|
+
}
|
|
641
|
+
function keySeparator(code, at) {
|
|
642
|
+
if (code[at] === ":" && code[at + 1] !== ":") return at + 1;
|
|
643
|
+
return hashRocket(code, at);
|
|
644
|
+
}
|
|
645
|
+
function hashRocket(code, at) {
|
|
646
|
+
const arrow = skipTrivia(code, at);
|
|
647
|
+
return code.startsWith("=>", arrow) ? arrow + 2 : null;
|
|
648
|
+
}
|
|
649
|
+
function readIdentifier(code, start) {
|
|
650
|
+
let end = start;
|
|
651
|
+
while (end < code.length && IDENT_CHAR.test(code[end])) end++;
|
|
652
|
+
return { text: code.slice(start, end), end };
|
|
653
|
+
}
|
|
654
|
+
function skipValue(code, start) {
|
|
655
|
+
let depth = 0;
|
|
656
|
+
let i = start;
|
|
657
|
+
while (i < code.length) {
|
|
658
|
+
const char = code[i];
|
|
659
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
660
|
+
i = readString(code, i).end;
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (char === "#") {
|
|
664
|
+
i = endOfLine(code, i);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
const literal = skipLiteral(code, i);
|
|
668
|
+
if (literal >= 0) {
|
|
669
|
+
i = literal;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (char === "(" || char === "[" || char === "{") depth++;
|
|
673
|
+
else if (char === ")" || char === "]") depth--;
|
|
674
|
+
else if (char === "}") {
|
|
675
|
+
if (depth === 0) return i;
|
|
676
|
+
depth--;
|
|
677
|
+
} else if (char === "," && depth === 0) return i;
|
|
678
|
+
i++;
|
|
679
|
+
}
|
|
680
|
+
return code.length;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/inspector/erb.ts
|
|
684
|
+
var ERB_TAG = /<%[\s\S]*?%>/g;
|
|
685
|
+
function neutralizeErb(source) {
|
|
686
|
+
return source.replace(ERB_TAG, (match) => match.replace(/[^\n]/g, " "));
|
|
687
|
+
}
|
|
688
|
+
function erbRanges(source) {
|
|
689
|
+
return [...source.matchAll(ERB_TAG)].map(
|
|
690
|
+
(match) => [match.index ?? 0, (match.index ?? 0) + match[0].length]
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
function erbElements(source) {
|
|
694
|
+
const tags = readErbTags(source);
|
|
695
|
+
const blockEnds = matchBlocks(tags);
|
|
696
|
+
const index = new PositionIndex(source);
|
|
697
|
+
const elements = [];
|
|
698
|
+
for (const [ordinal, tag] of tags.entries()) {
|
|
699
|
+
if (!tag.output) continue;
|
|
700
|
+
const option = readDataOption(tag.code, tag.codeStart);
|
|
701
|
+
if (!option.found) continue;
|
|
702
|
+
const end = blockEnds.get(ordinal) ?? tag.end;
|
|
703
|
+
elements.push({
|
|
704
|
+
tag: ERB_ELEMENT_TAG,
|
|
705
|
+
attrs: option.attrs.map((attr) => toParsedAttr(attr, index)),
|
|
706
|
+
start: tag.start,
|
|
707
|
+
// The token a reader can act on is the delimiter that opens the call.
|
|
708
|
+
sourceLength: tag.codeStart - tag.start,
|
|
709
|
+
end,
|
|
710
|
+
container: end > tag.end,
|
|
711
|
+
opaque: option.opaque
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return elements;
|
|
715
|
+
}
|
|
716
|
+
function toParsedAttr(attr, index) {
|
|
717
|
+
const at = index.at(attr.keyStart);
|
|
718
|
+
return {
|
|
719
|
+
name: attr.name,
|
|
720
|
+
value: attr.value ?? "",
|
|
721
|
+
line: at.line,
|
|
722
|
+
column: at.column,
|
|
723
|
+
sourceLength: attr.keyEnd - attr.keyStart,
|
|
724
|
+
valueStart: attr.valueStart,
|
|
725
|
+
valueEnd: attr.valueEnd,
|
|
726
|
+
dynamicValue: attr.value === null
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function readErbTags(source) {
|
|
730
|
+
const tags = [];
|
|
731
|
+
for (const match of source.matchAll(ERB_TAG)) {
|
|
732
|
+
const raw = match[0];
|
|
733
|
+
const start = match.index ?? 0;
|
|
734
|
+
let open = 2;
|
|
735
|
+
let output = false;
|
|
736
|
+
if (raw[open] === "#") continue;
|
|
737
|
+
if (raw[open] === "=") {
|
|
738
|
+
output = true;
|
|
739
|
+
open++;
|
|
740
|
+
if (raw[open] === "=") open++;
|
|
741
|
+
} else if (raw[open] === "-") {
|
|
742
|
+
open++;
|
|
743
|
+
}
|
|
744
|
+
let close = raw.length - 2;
|
|
745
|
+
if (raw[close - 1] === "-") close--;
|
|
746
|
+
if (close < open) close = open;
|
|
747
|
+
tags.push({
|
|
748
|
+
start,
|
|
749
|
+
end: start + raw.length,
|
|
750
|
+
output,
|
|
751
|
+
code: raw.slice(open, close),
|
|
752
|
+
codeStart: start + open
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
return tags;
|
|
756
|
+
}
|
|
757
|
+
function matchBlocks(tags) {
|
|
758
|
+
const ends = /* @__PURE__ */ new Map();
|
|
759
|
+
const open = [];
|
|
760
|
+
for (const [ordinal, tag] of tags.entries()) {
|
|
761
|
+
for (const event of blockEvents(tag.code)) {
|
|
762
|
+
if (event === "open") {
|
|
763
|
+
open.push(ordinal);
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
const opener = open.pop();
|
|
767
|
+
if (opener !== void 0) ends.set(opener, tag.end);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return ends;
|
|
771
|
+
}
|
|
772
|
+
var BLOCK_OPENERS = /* @__PURE__ */ new Set(["case", "begin", "def", "class", "module", "for"]);
|
|
773
|
+
var BLOCK_MODIFIERS = /* @__PURE__ */ new Set(["if", "unless", "while", "until"]);
|
|
774
|
+
var BLOCK_CONTINUERS = /* @__PURE__ */ new Set(["elsif", "else", "when", "in", "then", "rescue", "ensure"]);
|
|
775
|
+
function blockEvents(source) {
|
|
776
|
+
const code = maskHeredocs(source);
|
|
777
|
+
const events = [];
|
|
778
|
+
let previous = "";
|
|
779
|
+
let loopHeader = false;
|
|
780
|
+
let i = 0;
|
|
781
|
+
while (i < code.length) {
|
|
782
|
+
const char = code[i];
|
|
783
|
+
if (char === "\n" || char === ";") {
|
|
784
|
+
previous = char;
|
|
785
|
+
loopHeader = false;
|
|
786
|
+
i++;
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (SPACE.has(char)) {
|
|
790
|
+
i++;
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (char === "#") {
|
|
794
|
+
i = endOfLine(code, i);
|
|
795
|
+
previous = "\n";
|
|
796
|
+
loopHeader = false;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
800
|
+
i = readString(code, i).end;
|
|
801
|
+
previous = "value";
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (char === ":" && IDENT_START.test(code[i + 1] ?? "")) {
|
|
805
|
+
i = identifierEnd(code, i + 1);
|
|
806
|
+
previous = "value";
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
const literal = skipLiteral(code, i);
|
|
810
|
+
if (literal >= 0) {
|
|
811
|
+
i = literal;
|
|
812
|
+
previous = "value";
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (char === "{") {
|
|
816
|
+
events.push("open");
|
|
817
|
+
previous = "{";
|
|
818
|
+
i++;
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (char === "}") {
|
|
822
|
+
events.push("close");
|
|
823
|
+
previous = "value";
|
|
824
|
+
i++;
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
if (char === ")" || char === "]") {
|
|
828
|
+
previous = "value";
|
|
829
|
+
i++;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (!IDENT_START.test(char)) {
|
|
833
|
+
previous = char;
|
|
834
|
+
i++;
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
const end = identifierEnd(code, i);
|
|
838
|
+
const word = code.slice(i, end);
|
|
839
|
+
const keyword = !(code[end] === ":" && code[end + 1] !== ":") && code[i - 1] !== ".";
|
|
840
|
+
i = end;
|
|
841
|
+
if (keyword && word === "end") {
|
|
842
|
+
events.push("close");
|
|
843
|
+
previous = "value";
|
|
844
|
+
} else if (keyword && word === "do") {
|
|
845
|
+
if (loopHeader) loopHeader = false;
|
|
846
|
+
else events.push("open");
|
|
847
|
+
previous = "do";
|
|
848
|
+
} else if (keyword && BLOCK_OPENERS.has(word)) {
|
|
849
|
+
events.push("open");
|
|
850
|
+
loopHeader = word === "for";
|
|
851
|
+
previous = word;
|
|
852
|
+
} else if (keyword && BLOCK_MODIFIERS.has(word)) {
|
|
853
|
+
if (previous !== "value") {
|
|
854
|
+
events.push("open");
|
|
855
|
+
loopHeader = word !== "if" && word !== "unless";
|
|
856
|
+
}
|
|
857
|
+
previous = word;
|
|
858
|
+
} else if (keyword && BLOCK_CONTINUERS.has(word)) {
|
|
859
|
+
previous = word;
|
|
860
|
+
} else {
|
|
861
|
+
previous = "value";
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return events;
|
|
865
|
+
}
|
|
866
|
+
function identifierEnd(code, start) {
|
|
867
|
+
let end = start;
|
|
868
|
+
while (end < code.length && IDENT_CHAR.test(code[end])) end++;
|
|
869
|
+
return end;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/inspector/extract.ts
|
|
873
|
+
var NAMESPACE_PREFIX = "stimeo--";
|
|
874
|
+
function dasherize(value) {
|
|
875
|
+
return value.replace(/([A-Z])/g, (_match, char) => `-${char.toLowerCase()}`);
|
|
876
|
+
}
|
|
877
|
+
function controllerIdentifiers(dataControllerValue) {
|
|
878
|
+
const seen = /* @__PURE__ */ new Set();
|
|
879
|
+
for (const token of dataControllerValue.split(/\s+/)) {
|
|
880
|
+
if (token.startsWith(NAMESPACE_PREFIX)) seen.add(token);
|
|
881
|
+
}
|
|
882
|
+
return [...seen];
|
|
883
|
+
}
|
|
884
|
+
function parseTargetAttr(attrName) {
|
|
885
|
+
const match = /^data-(stimeo--[a-z0-9][a-z0-9-]*)-target$/.exec(attrName);
|
|
886
|
+
return match?.[1] ?? null;
|
|
887
|
+
}
|
|
888
|
+
function parseValueAttr(attrName, knownIdentifiers) {
|
|
889
|
+
const match = /^data-(stimeo--[a-z0-9-]+)-value$/.exec(attrName);
|
|
890
|
+
if (!match) return null;
|
|
891
|
+
const inner = match[1] ?? "";
|
|
892
|
+
let best = null;
|
|
893
|
+
for (const id of knownIdentifiers) {
|
|
894
|
+
if (inner.startsWith(`${id}-`)) {
|
|
895
|
+
const valueToken = inner.slice(id.length + 1);
|
|
896
|
+
if (valueToken.length > 0 && (!best || id.length > best.identifier.length)) {
|
|
897
|
+
best = { identifier: id, valueToken };
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (best) return best;
|
|
902
|
+
const rest = inner.slice(NAMESPACE_PREFIX.length);
|
|
903
|
+
const dash = rest.indexOf("-");
|
|
904
|
+
const identifierGuess = dash === -1 ? inner : `${NAMESPACE_PREFIX}${rest.slice(0, dash)}`;
|
|
905
|
+
const valueGuess = dash === -1 ? "" : rest.slice(dash + 1);
|
|
906
|
+
return { identifier: null, valueToken: valueGuess || identifierGuess };
|
|
907
|
+
}
|
|
908
|
+
function actionDescriptors(dataActionValue) {
|
|
909
|
+
const descriptors = [];
|
|
910
|
+
for (const descriptor of dataActionValue.split(/\s+/)) {
|
|
911
|
+
const hash = descriptor.indexOf("#");
|
|
912
|
+
if (hash === -1) continue;
|
|
913
|
+
let lhs = descriptor.slice(0, hash);
|
|
914
|
+
const arrow = lhs.indexOf("->");
|
|
915
|
+
if (arrow !== -1) lhs = lhs.slice(arrow + 2);
|
|
916
|
+
if (!lhs.startsWith(NAMESPACE_PREFIX)) continue;
|
|
917
|
+
const method = /^[a-zA-Z_$][\w$]*/.exec(descriptor.slice(hash + 1))?.[0] ?? "";
|
|
918
|
+
descriptors.push({ identifier: lhs, method });
|
|
919
|
+
}
|
|
920
|
+
return descriptors;
|
|
921
|
+
}
|
|
922
|
+
function isStimeoDataAttr(attrName) {
|
|
923
|
+
return attrName.startsWith(`data-${NAMESPACE_PREFIX}`);
|
|
924
|
+
}
|
|
925
|
+
|
|
263
926
|
// src/inspector/types.ts
|
|
264
927
|
var DIAGNOSTIC_CODES = [
|
|
265
928
|
"unknown-controller",
|
|
@@ -286,16 +949,15 @@ var DIAGNOSTIC_CODES = [
|
|
|
286
949
|
// src/inspector/check.ts
|
|
287
950
|
function checkSource(source, manifest) {
|
|
288
951
|
const diagnostics = [];
|
|
289
|
-
const tree = parseHtml(neutralizeErb(source));
|
|
952
|
+
const tree = parseHtml(neutralizeErb(source), erbElements(source));
|
|
290
953
|
const known = manifest.controllers;
|
|
291
954
|
const knownIdentifiers = Object.keys(known);
|
|
292
|
-
const
|
|
293
|
-
(m) => [m.index ?? 0, (m.index ?? 0) + m[0].length]
|
|
294
|
-
);
|
|
955
|
+
const ranges = erbRanges(source);
|
|
295
956
|
const isDynamicValue = (attr) => {
|
|
957
|
+
if (attr.dynamicValue !== void 0) return attr.dynamicValue;
|
|
296
958
|
const { valueStart, valueEnd } = attr;
|
|
297
959
|
if (valueStart === void 0 || valueEnd === void 0) return false;
|
|
298
|
-
return
|
|
960
|
+
return ranges.some(([start, end]) => start < valueEnd && end > valueStart);
|
|
299
961
|
};
|
|
300
962
|
const conditionHolds = (scope, identifier, when) => {
|
|
301
963
|
if (!when) return true;
|
|
@@ -312,7 +974,9 @@ function checkSource(source, manifest) {
|
|
|
312
974
|
const ignore = current.attrs.find((a) => a.name === IGNORE_ATTR);
|
|
313
975
|
if (ignore) {
|
|
314
976
|
const codes = ignore.value.trim();
|
|
315
|
-
if (codes.length === 0
|
|
977
|
+
if (codes.length === 0) {
|
|
978
|
+
if (!isDynamicValue(ignore)) return true;
|
|
979
|
+
} else if (codes.split(/\s+/).includes(code)) return true;
|
|
316
980
|
}
|
|
317
981
|
current = current.parent;
|
|
318
982
|
}
|
|
@@ -321,6 +985,26 @@ function checkSource(source, manifest) {
|
|
|
321
985
|
const report = (owner, code, severity, message, at, suggestion, fix) => {
|
|
322
986
|
if (!suppressed(owner, code)) push(diagnostics, code, severity, message, at, suggestion, fix);
|
|
323
987
|
};
|
|
988
|
+
const unresolved = /* @__PURE__ */ new Set();
|
|
989
|
+
const unnamedTargets = [];
|
|
990
|
+
const targetsUnreadable = (container, scope, identifier) => {
|
|
991
|
+
for (const node of unresolved) {
|
|
992
|
+
if (isWithin(node, container) && findOwner(node, identifier) === scope) return true;
|
|
993
|
+
}
|
|
994
|
+
for (const { node, identifier: hidden } of unnamedTargets) {
|
|
995
|
+
if (hidden !== identifier) continue;
|
|
996
|
+
if (isWithin(node, container) && findOwner(node, identifier) === scope) return true;
|
|
997
|
+
}
|
|
998
|
+
return false;
|
|
999
|
+
};
|
|
1000
|
+
const unresolvedAbove = (node) => {
|
|
1001
|
+
let current = node;
|
|
1002
|
+
while (current && current.tag !== "#root") {
|
|
1003
|
+
if (unresolved.has(current)) return true;
|
|
1004
|
+
current = current.parent;
|
|
1005
|
+
}
|
|
1006
|
+
return false;
|
|
1007
|
+
};
|
|
324
1008
|
const scopes = [];
|
|
325
1009
|
const presence = /* @__PURE__ */ new Map();
|
|
326
1010
|
const targetNodes = /* @__PURE__ */ new Map();
|
|
@@ -375,6 +1059,10 @@ function checkSource(source, manifest) {
|
|
|
375
1059
|
}
|
|
376
1060
|
const declarationCandidates = [];
|
|
377
1061
|
walk(tree, (node) => {
|
|
1062
|
+
const declaredController = node.attrs.find((a) => a.name === "data-controller");
|
|
1063
|
+
if (node.opaque || declaredController && isDynamicValue(declaredController)) {
|
|
1064
|
+
unresolved.add(node);
|
|
1065
|
+
}
|
|
378
1066
|
if (declarationAttrs.size > 0 && node.attrs.some((a) => declarationAttrs.has(a.name))) {
|
|
379
1067
|
declarationCandidates.push(node);
|
|
380
1068
|
}
|
|
@@ -482,14 +1170,18 @@ function checkSource(source, manifest) {
|
|
|
482
1170
|
if (targetName.length > 0) {
|
|
483
1171
|
recordPresence(owner, targetIdentifier, targetName);
|
|
484
1172
|
recordTargetNode(owner, targetIdentifier, targetName, node);
|
|
1173
|
+
} else if (isDynamicValue(attr)) {
|
|
1174
|
+
unnamedTargets.push({ node, identifier: targetIdentifier });
|
|
485
1175
|
}
|
|
486
1176
|
} else if (!inDeclaredFragment(node, targetIdentifier)) {
|
|
1177
|
+
const uncertain = unresolvedAbove(node);
|
|
487
1178
|
report(
|
|
488
1179
|
node,
|
|
489
1180
|
"orphan-target",
|
|
490
|
-
"error",
|
|
1181
|
+
uncertain ? "warning" : "error",
|
|
491
1182
|
`Target "${attr.name}" has no enclosing controller "${targetIdentifier}".`,
|
|
492
|
-
attr
|
|
1183
|
+
attr,
|
|
1184
|
+
uncertain ? UNRESOLVED_HOST_HINT : void 0
|
|
493
1185
|
);
|
|
494
1186
|
}
|
|
495
1187
|
continue;
|
|
@@ -552,18 +1244,21 @@ function checkSource(source, manifest) {
|
|
|
552
1244
|
}
|
|
553
1245
|
}
|
|
554
1246
|
});
|
|
1247
|
+
const scopeIsComplete = (container, scope, identifier) => container.origin === "markup" && !targetsUnreadable(container, scope, identifier);
|
|
555
1248
|
for (const { node, identifier } of scopes) {
|
|
556
1249
|
const controller = known[identifier];
|
|
557
1250
|
if (!controller) continue;
|
|
1251
|
+
const complete = scopeIsComplete(node, node, identifier);
|
|
558
1252
|
const present = presence.get(node)?.get(identifier) ?? /* @__PURE__ */ new Set();
|
|
559
1253
|
for (const required of controller.requiredTargets) {
|
|
560
1254
|
if (!present.has(required)) {
|
|
561
1255
|
report(
|
|
562
1256
|
node,
|
|
563
1257
|
"missing-required-target",
|
|
564
|
-
"error",
|
|
1258
|
+
complete ? "error" : "warning",
|
|
565
1259
|
`"${identifier}" is missing required target "${required}".`,
|
|
566
|
-
node
|
|
1260
|
+
node,
|
|
1261
|
+
complete ? void 0 : UNRESOLVED_TARGET_HINT
|
|
567
1262
|
);
|
|
568
1263
|
}
|
|
569
1264
|
}
|
|
@@ -571,6 +1266,7 @@ function checkSource(source, manifest) {
|
|
|
571
1266
|
for (const { node, identifier } of scopes) {
|
|
572
1267
|
const controller = known[identifier];
|
|
573
1268
|
if (!controller) continue;
|
|
1269
|
+
const complete = scopeIsComplete(node, node, identifier);
|
|
574
1270
|
const present = presence.get(node)?.get(identifier) ?? /* @__PURE__ */ new Set();
|
|
575
1271
|
for (const rule of controller.conditionalTargets) {
|
|
576
1272
|
if (!present.has(rule.whenPresent)) continue;
|
|
@@ -579,10 +1275,10 @@ function checkSource(source, manifest) {
|
|
|
579
1275
|
report(
|
|
580
1276
|
node,
|
|
581
1277
|
"missing-conditional-target",
|
|
582
|
-
"error",
|
|
1278
|
+
complete ? "error" : "warning",
|
|
583
1279
|
`"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
|
|
584
1280
|
node,
|
|
585
|
-
rule.suggestion
|
|
1281
|
+
complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
|
|
586
1282
|
);
|
|
587
1283
|
}
|
|
588
1284
|
}
|
|
@@ -595,6 +1291,7 @@ function checkSource(source, manifest) {
|
|
|
595
1291
|
if (req.whenDocument && !documentHolds(req.whenDocument)) continue;
|
|
596
1292
|
const severity = documentHolds(req.escalateWhen) ? "error" : req.severity ?? "error";
|
|
597
1293
|
for (const el of elements) {
|
|
1294
|
+
if (!hasReadableMarkup(el)) continue;
|
|
598
1295
|
if (!contentHolds(node, identifier, el, req.whenContains)) continue;
|
|
599
1296
|
if (!elementHolds(el, req.whenElement)) continue;
|
|
600
1297
|
checkA11y(report, el, identifier, req, severity);
|
|
@@ -608,6 +1305,7 @@ function checkSource(source, manifest) {
|
|
|
608
1305
|
const reach = req.reach ?? "tab";
|
|
609
1306
|
const elements = targetNodes.get(node)?.get(identifier)?.get(req.target) ?? [];
|
|
610
1307
|
for (const el of elements) {
|
|
1308
|
+
if (!hasReadableMarkup(el)) continue;
|
|
611
1309
|
if (!isFocusable(el, reach)) {
|
|
612
1310
|
const problem = reach === "tab" ? 'not in the Tab order (needs a native control or tabindex="0")' : "not focusable (needs a native control or tabindex)";
|
|
613
1311
|
report(
|
|
@@ -629,6 +1327,7 @@ function checkSource(source, manifest) {
|
|
|
629
1327
|
const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
|
|
630
1328
|
const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
|
|
631
1329
|
for (const el of elements) {
|
|
1330
|
+
if (!hasReadableMarkup(el)) continue;
|
|
632
1331
|
for (const name of rule.attrs) {
|
|
633
1332
|
const attr = el.attrs.find((a) => a.name === name);
|
|
634
1333
|
if (!attr) continue;
|
|
@@ -651,6 +1350,7 @@ function checkSource(source, manifest) {
|
|
|
651
1350
|
const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
|
|
652
1351
|
const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
|
|
653
1352
|
for (const el of elements) {
|
|
1353
|
+
if (!hasReadableMarkup(el)) continue;
|
|
654
1354
|
if (!contentHolds(node, identifier, el, rule.whenContains)) continue;
|
|
655
1355
|
for (const name of rule.attrs) {
|
|
656
1356
|
const attr = el.attrs.find((a) => a.name === name);
|
|
@@ -681,6 +1381,7 @@ function checkSource(source, manifest) {
|
|
|
681
1381
|
if (conditionHolds(node, identifier, when) !== true) continue;
|
|
682
1382
|
const hostContext = when ? ` (${when.value} ${list(when.equals)})` : "";
|
|
683
1383
|
for (const el of elements) {
|
|
1384
|
+
if (el.opaque) continue;
|
|
684
1385
|
const declared = el.attrs.find((a) => a.name === "data-controller");
|
|
685
1386
|
if (!declared || !controllerIdentifiers(declared.value).includes(rule.coController)) {
|
|
686
1387
|
continue;
|
|
@@ -721,6 +1422,7 @@ function checkSource(source, manifest) {
|
|
|
721
1422
|
const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
|
|
722
1423
|
const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
|
|
723
1424
|
for (const el of elements) {
|
|
1425
|
+
if (el.opaque) continue;
|
|
724
1426
|
const declared = el.attrs.find((a) => a.name === "data-controller");
|
|
725
1427
|
if (declared && isDynamicValue(declared)) continue;
|
|
726
1428
|
if (declared && controllerIdentifiers(declared.value).includes(rule.controller)) continue;
|
|
@@ -809,13 +1511,14 @@ function checkSource(source, manifest) {
|
|
|
809
1511
|
}
|
|
810
1512
|
}
|
|
811
1513
|
if (rule.min !== void 0 && matched.length < rule.min) {
|
|
1514
|
+
const complete = scopeIsComplete(container, node, identifier);
|
|
812
1515
|
report(
|
|
813
1516
|
container,
|
|
814
1517
|
"cardinality-violation",
|
|
815
|
-
"error",
|
|
1518
|
+
complete ? "error" : "warning",
|
|
816
1519
|
`"${identifier}" requires at least ${rule.min} ${what} per ${where}, but found ${matched.length}.`,
|
|
817
1520
|
container,
|
|
818
|
-
rule.suggestion
|
|
1521
|
+
complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
|
|
819
1522
|
);
|
|
820
1523
|
}
|
|
821
1524
|
}
|
|
@@ -858,6 +1561,11 @@ function checkSource(source, manifest) {
|
|
|
858
1561
|
diagnostics.sort((a, b) => a.line - b.line || a.column - b.column);
|
|
859
1562
|
return diagnostics;
|
|
860
1563
|
}
|
|
1564
|
+
var UNRESOLVED_TARGET_HINT = "Part of this scope is rendered by a helper or a non-literal data: hash, so the target may exist at runtime.";
|
|
1565
|
+
var UNRESOLVED_HOST_HINT = "An enclosing helper's data: option is not a literal hash, so the host controller may be declared at runtime.";
|
|
1566
|
+
function hasReadableMarkup(el) {
|
|
1567
|
+
return el.origin === "markup";
|
|
1568
|
+
}
|
|
861
1569
|
function checkA11y(report, el, identifier, req, severity) {
|
|
862
1570
|
const groups = [
|
|
863
1571
|
{ attrs: req.attrs, values: req.values },
|
|
@@ -986,6 +1694,7 @@ function inDeclaredFragment(node, identifier) {
|
|
|
986
1694
|
return false;
|
|
987
1695
|
}
|
|
988
1696
|
function anchorLength(at) {
|
|
1697
|
+
if (at.sourceLength !== void 0) return at.sourceLength;
|
|
989
1698
|
return "name" in at ? at.name.length : at.tag.length + 1;
|
|
990
1699
|
}
|
|
991
1700
|
function push(out, code, severity, message, at, suggestion, fix) {
|