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.
Files changed (82) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/controllers/alert_dialog_controller.js +32 -5
  3. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  4. package/dist/controllers/announcer_controller.d.ts +32 -6
  5. package/dist/controllers/announcer_controller.js +255 -20
  6. package/dist/controllers/announcer_controller.js.map +1 -1
  7. package/dist/controllers/color_picker_controller.d.ts +2 -1
  8. package/dist/controllers/color_picker_controller.js +46 -0
  9. package/dist/controllers/color_picker_controller.js.map +1 -1
  10. package/dist/controllers/command_palette_controller.js +32 -5
  11. package/dist/controllers/command_palette_controller.js.map +1 -1
  12. package/dist/controllers/confirm_controller.js +32 -5
  13. package/dist/controllers/confirm_controller.js.map +1 -1
  14. package/dist/controllers/countdown_controller.d.ts +19 -5
  15. package/dist/controllers/countdown_controller.js +112 -12
  16. package/dist/controllers/countdown_controller.js.map +1 -1
  17. package/dist/controllers/date_range_picker_controller.d.ts +4 -1
  18. package/dist/controllers/date_range_picker_controller.js +50 -0
  19. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  20. package/dist/controllers/dialog_controller.js +32 -5
  21. package/dist/controllers/dialog_controller.js.map +1 -1
  22. package/dist/controllers/dismissible_controller.js.map +1 -1
  23. package/dist/controllers/drawer_controller.js +32 -5
  24. package/dist/controllers/drawer_controller.js.map +1 -1
  25. package/dist/controllers/empty_state_controller.d.ts +9 -4
  26. package/dist/controllers/empty_state_controller.js +24 -10
  27. package/dist/controllers/empty_state_controller.js.map +1 -1
  28. package/dist/controllers/focus_controller.js +32 -5
  29. package/dist/controllers/focus_controller.js.map +1 -1
  30. package/dist/controllers/form_validation_controller.js.map +1 -1
  31. package/dist/controllers/frame_loading_controller.d.ts +14 -3
  32. package/dist/controllers/frame_loading_controller.js +177 -15
  33. package/dist/controllers/frame_loading_controller.js.map +1 -1
  34. package/dist/controllers/local_time_controller.d.ts +19 -3
  35. package/dist/controllers/local_time_controller.js +100 -6
  36. package/dist/controllers/local_time_controller.js.map +1 -1
  37. package/dist/controllers/meter_controller.d.ts +22 -2
  38. package/dist/controllers/meter_controller.js +145 -26
  39. package/dist/controllers/meter_controller.js.map +1 -1
  40. package/dist/controllers/network_status_controller.d.ts +13 -1
  41. package/dist/controllers/network_status_controller.js +28 -8
  42. package/dist/controllers/network_status_controller.js.map +1 -1
  43. package/dist/controllers/overflow_menu_controller.js +33 -6
  44. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  45. package/dist/controllers/progress_controller.d.ts +16 -1
  46. package/dist/controllers/progress_controller.js +116 -9
  47. package/dist/controllers/progress_controller.js.map +1 -1
  48. package/dist/controllers/range_slider_controller.d.ts +4 -1
  49. package/dist/controllers/range_slider_controller.js +68 -3
  50. package/dist/controllers/range_slider_controller.js.map +1 -1
  51. package/dist/controllers/rating_controller.d.ts +4 -1
  52. package/dist/controllers/rating_controller.js +53 -0
  53. package/dist/controllers/rating_controller.js.map +1 -1
  54. package/dist/controllers/relative_time_controller.d.ts +13 -0
  55. package/dist/controllers/relative_time_controller.js +133 -12
  56. package/dist/controllers/relative_time_controller.js.map +1 -1
  57. package/dist/controllers/sidebar_controller.d.ts +1 -11
  58. package/dist/controllers/sidebar_controller.js +37 -8
  59. package/dist/controllers/sidebar_controller.js.map +1 -1
  60. package/dist/controllers/skeleton_controller.d.ts +7 -2
  61. package/dist/controllers/skeleton_controller.js +73 -20
  62. package/dist/controllers/skeleton_controller.js.map +1 -1
  63. package/dist/controllers/slider_controller.js +17 -2
  64. package/dist/controllers/slider_controller.js.map +1 -1
  65. package/dist/controllers/spinner_controller.d.ts +37 -4
  66. package/dist/controllers/spinner_controller.js +228 -27
  67. package/dist/controllers/spinner_controller.js.map +1 -1
  68. package/dist/controllers/step_indicator_controller.d.ts +12 -1
  69. package/dist/controllers/step_indicator_controller.js +82 -5
  70. package/dist/controllers/step_indicator_controller.js.map +1 -1
  71. package/dist/controllers/stick_to_bottom_controller.d.ts +26 -2
  72. package/dist/controllers/stick_to_bottom_controller.js +60 -10
  73. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  74. package/dist/index.js +1056 -281
  75. package/dist/index.js.map +1 -1
  76. package/dist/inspector/cli.js +785 -76
  77. package/dist/inspector/cli.js.map +1 -1
  78. package/dist/inspector/cli_bin.js +785 -76
  79. package/dist/inspector/cli_bin.js.map +1 -1
  80. package/dist/inspector/examples.json +5 -5
  81. package/dist/inspector/manifest.json +21 -8
  82. 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 erbRanges = [...source.matchAll(/<%[\s\S]*?%>/g)].map(
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 erbRanges.some(([start, end]) => start < valueEnd && end > valueStart);
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 || codes.split(/\s+/).includes(code)) return true;
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) {