uidex 0.5.0 → 0.5.1

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.
@@ -412,6 +412,7 @@ var KIND_DISCRIMINATORS = [
412
412
  "feature",
413
413
  "primitive",
414
414
  "widget",
415
+ "region",
415
416
  "flow",
416
417
  "notFlow"
417
418
  ];
@@ -433,12 +434,14 @@ var ALLOWED_FIELDS = {
433
434
  ]),
434
435
  primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
435
436
  widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
437
+ region: /* @__PURE__ */ new Set(["region", "name", "description"]),
436
438
  flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
437
439
  };
438
440
  var FALSEABLE = /* @__PURE__ */ new Set([
439
441
  "page",
440
442
  "feature",
441
- "primitive"
443
+ "primitive",
444
+ "region"
442
445
  ]);
443
446
  var ExtractError = class extends Error {
444
447
  code;
@@ -2070,6 +2073,21 @@ function resolve2(ctx) {
2070
2073
  };
2071
2074
  registry.add(region);
2072
2075
  }
2076
+ for (const ef of ctx.extracted) {
2077
+ const exp = exportFor(ef.file.displayPath, "region");
2078
+ if (!exp) continue;
2079
+ if (exp.id === false) continue;
2080
+ if (typeof exp.id === "string" && !registry.get("region", exp.id)) {
2081
+ const meta = buildMetaFromExport(exp);
2082
+ const region = {
2083
+ kind: "region",
2084
+ id: exp.id,
2085
+ loc: { file: ef.file.displayPath, line: exp.loc.line },
2086
+ ...meta ? { meta } : {}
2087
+ };
2088
+ registry.add(region);
2089
+ }
2090
+ }
2073
2091
  const primitiveConventions = conventions.primitives;
2074
2092
  for (const ef of ctx.extracted) {
2075
2093
  const file = ef.file.displayPath;
@@ -2378,17 +2396,38 @@ function audit(opts) {
2378
2396
  }
2379
2397
  }
2380
2398
  if (lint) {
2399
+ const dynamicAttrRe = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*\{/g;
2381
2400
  for (const f of files) {
2382
- const lines = f.content.split("\n");
2383
- const candidateRe = /<(button|a|input|select|textarea)(?=[\s/>])([^>]*)>/g;
2384
2401
  let m;
2385
- while ((m = candidateRe.exec(f.content)) !== null) {
2386
- const attrs = m[2];
2387
- if (attrs && attrs.includes("data-uidex")) continue;
2388
- const idx = m.index;
2402
+ dynamicAttrRe.lastIndex = 0;
2403
+ while ((m = dynamicAttrRe.exec(f.content)) !== null) {
2404
+ const kind = m[1] ?? "element";
2389
2405
  let line = 1;
2390
- for (let i = 0; i < idx; i++) if (f.content[i] === "\n") line++;
2391
- void lines;
2406
+ for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
2407
+ const attrName = m[1] ? `data-uidex-${m[1]}` : "data-uidex";
2408
+ diagnostics.push({
2409
+ code: "dynamic-attr",
2410
+ severity: "warning",
2411
+ message: `\`${attrName}={\u2026}\` uses a dynamic expression; the scanner cannot resolve the ${kind} id statically`,
2412
+ file: f.displayPath,
2413
+ line,
2414
+ hint: dynamicAttrHint(kind)
2415
+ });
2416
+ }
2417
+ }
2418
+ }
2419
+ if (lint) {
2420
+ for (const f of files) {
2421
+ const tagRe = /<(button|a|input|select|textarea)(?=[\s/>])/g;
2422
+ let m;
2423
+ while ((m = tagRe.exec(f.content)) !== null) {
2424
+ const afterTag = m.index + m[0].length;
2425
+ const closeIdx = findJsxOpeningEnd(f.content, afterTag);
2426
+ if (closeIdx === -1) continue;
2427
+ const attrs = f.content.slice(afterTag, closeIdx);
2428
+ if (attrs.includes("data-uidex")) continue;
2429
+ let line = 1;
2430
+ for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
2392
2431
  diagnostics.push({
2393
2432
  code: "missing-element-annotation",
2394
2433
  severity: "info",
@@ -2598,6 +2637,63 @@ function extractEntitiesArray(source) {
2598
2637
  }
2599
2638
  return null;
2600
2639
  }
2640
+ function findJsxOpeningEnd(src, start) {
2641
+ let i = start;
2642
+ while (i < src.length) {
2643
+ const ch = src[i];
2644
+ if (ch === ">" || ch === "/" && src[i + 1] === ">") return i;
2645
+ if (ch === '"' || ch === "'" || ch === "`") {
2646
+ i = skipString2(src, i);
2647
+ } else if (ch === "{") {
2648
+ i = skipBraces(src, i);
2649
+ } else {
2650
+ i++;
2651
+ }
2652
+ }
2653
+ return -1;
2654
+ }
2655
+ function skipString2(src, start) {
2656
+ const quote = src[start];
2657
+ let i = start + 1;
2658
+ while (i < src.length) {
2659
+ if (src[i] === "\\" && quote !== "`") {
2660
+ i += 2;
2661
+ continue;
2662
+ }
2663
+ if (quote === "`" && src[i] === "$" && src[i + 1] === "{") {
2664
+ i = skipBraces(src, i + 1);
2665
+ continue;
2666
+ }
2667
+ if (src[i] === quote) return i + 1;
2668
+ i++;
2669
+ }
2670
+ return i;
2671
+ }
2672
+ function skipBraces(src, start) {
2673
+ let depth = 1;
2674
+ let i = start + 1;
2675
+ while (i < src.length && depth > 0) {
2676
+ const ch = src[i];
2677
+ if (ch === "{") {
2678
+ depth++;
2679
+ i++;
2680
+ } else if (ch === "}") {
2681
+ depth--;
2682
+ i++;
2683
+ } else if (ch === '"' || ch === "'" || ch === "`") {
2684
+ i = skipString2(src, i);
2685
+ } else {
2686
+ i++;
2687
+ }
2688
+ }
2689
+ return i;
2690
+ }
2691
+ function dynamicAttrHint(kind) {
2692
+ if (kind === "region") {
2693
+ return `Use a string literal: \`data-uidex-region="id"\`, or declare the region via \`export const uidex = { region: "id" } as const satisfies Uidex.Region\` on the file that passes the region value`;
2694
+ }
2695
+ return `The scanner requires string-literal attribute values. If this component forwards the annotation via a prop, restructure so the caller provides the annotated element directly (e.g. via a slot or render prop) with a string-literal \`data-uidex\` attribute`;
2696
+ }
2601
2697
  function stableStringify(value) {
2602
2698
  return JSON.stringify(value, stableReplacer);
2603
2699
  }
@@ -2751,6 +2847,11 @@ function emit(opts) {
2751
2847
  lines.push(" acceptance?: readonly string[]");
2752
2848
  lines.push(" description?: string");
2753
2849
  lines.push(" }");
2850
+ lines.push(" export interface Region {");
2851
+ lines.push(" region: RegionId | false");
2852
+ lines.push(" name?: string");
2853
+ lines.push(" description?: string");
2854
+ lines.push(" }");
2754
2855
  lines.push(" export interface Flow {");
2755
2856
  lines.push(" flow: FlowId");
2756
2857
  lines.push(" name?: string");