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.
@@ -155,7 +155,7 @@ interface Annotation {
155
155
  /** JSX ancestor chain, outermost to innermost. Only populated for DOM-attribute kinds. */
156
156
  ancestors?: AnnotationAncestor[];
157
157
  }
158
- type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "flow";
158
+ type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "region" | "flow";
159
159
  interface MetadataExport {
160
160
  source: "ts-export";
161
161
  kind: MetadataExportKind;
@@ -155,7 +155,7 @@ interface Annotation {
155
155
  /** JSX ancestor chain, outermost to innermost. Only populated for DOM-attribute kinds. */
156
156
  ancestors?: AnnotationAncestor[];
157
157
  }
158
- type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "flow";
158
+ type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "region" | "flow";
159
159
  interface MetadataExport {
160
160
  source: "ts-export";
161
161
  kind: MetadataExportKind;
@@ -356,6 +356,7 @@ var KIND_DISCRIMINATORS = [
356
356
  "feature",
357
357
  "primitive",
358
358
  "widget",
359
+ "region",
359
360
  "flow",
360
361
  "notFlow"
361
362
  ];
@@ -377,12 +378,14 @@ var ALLOWED_FIELDS = {
377
378
  ]),
378
379
  primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
379
380
  widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
381
+ region: /* @__PURE__ */ new Set(["region", "name", "description"]),
380
382
  flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
381
383
  };
382
384
  var FALSEABLE = /* @__PURE__ */ new Set([
383
385
  "page",
384
386
  "feature",
385
- "primitive"
387
+ "primitive",
388
+ "region"
386
389
  ]);
387
390
  var ExtractError = class extends Error {
388
391
  code;
@@ -2014,6 +2017,21 @@ function resolve2(ctx) {
2014
2017
  };
2015
2018
  registry.add(region);
2016
2019
  }
2020
+ for (const ef of ctx.extracted) {
2021
+ const exp = exportFor(ef.file.displayPath, "region");
2022
+ if (!exp) continue;
2023
+ if (exp.id === false) continue;
2024
+ if (typeof exp.id === "string" && !registry.get("region", exp.id)) {
2025
+ const meta = buildMetaFromExport(exp);
2026
+ const region = {
2027
+ kind: "region",
2028
+ id: exp.id,
2029
+ loc: { file: ef.file.displayPath, line: exp.loc.line },
2030
+ ...meta ? { meta } : {}
2031
+ };
2032
+ registry.add(region);
2033
+ }
2034
+ }
2017
2035
  const primitiveConventions = conventions.primitives;
2018
2036
  for (const ef of ctx.extracted) {
2019
2037
  const file = ef.file.displayPath;
@@ -2322,17 +2340,38 @@ function audit(opts) {
2322
2340
  }
2323
2341
  }
2324
2342
  if (lint) {
2343
+ const dynamicAttrRe = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*\{/g;
2325
2344
  for (const f of files) {
2326
- const lines = f.content.split("\n");
2327
- const candidateRe = /<(button|a|input|select|textarea)(?=[\s/>])([^>]*)>/g;
2328
2345
  let m;
2329
- while ((m = candidateRe.exec(f.content)) !== null) {
2330
- const attrs = m[2];
2331
- if (attrs && attrs.includes("data-uidex")) continue;
2332
- const idx = m.index;
2346
+ dynamicAttrRe.lastIndex = 0;
2347
+ while ((m = dynamicAttrRe.exec(f.content)) !== null) {
2348
+ const kind = m[1] ?? "element";
2333
2349
  let line = 1;
2334
- for (let i = 0; i < idx; i++) if (f.content[i] === "\n") line++;
2335
- void lines;
2350
+ for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
2351
+ const attrName = m[1] ? `data-uidex-${m[1]}` : "data-uidex";
2352
+ diagnostics.push({
2353
+ code: "dynamic-attr",
2354
+ severity: "warning",
2355
+ message: `\`${attrName}={\u2026}\` uses a dynamic expression; the scanner cannot resolve the ${kind} id statically`,
2356
+ file: f.displayPath,
2357
+ line,
2358
+ hint: dynamicAttrHint(kind)
2359
+ });
2360
+ }
2361
+ }
2362
+ }
2363
+ if (lint) {
2364
+ for (const f of files) {
2365
+ const tagRe = /<(button|a|input|select|textarea)(?=[\s/>])/g;
2366
+ let m;
2367
+ while ((m = tagRe.exec(f.content)) !== null) {
2368
+ const afterTag = m.index + m[0].length;
2369
+ const closeIdx = findJsxOpeningEnd(f.content, afterTag);
2370
+ if (closeIdx === -1) continue;
2371
+ const attrs = f.content.slice(afterTag, closeIdx);
2372
+ if (attrs.includes("data-uidex")) continue;
2373
+ let line = 1;
2374
+ for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
2336
2375
  diagnostics.push({
2337
2376
  code: "missing-element-annotation",
2338
2377
  severity: "info",
@@ -2542,6 +2581,63 @@ function extractEntitiesArray(source) {
2542
2581
  }
2543
2582
  return null;
2544
2583
  }
2584
+ function findJsxOpeningEnd(src, start) {
2585
+ let i = start;
2586
+ while (i < src.length) {
2587
+ const ch = src[i];
2588
+ if (ch === ">" || ch === "/" && src[i + 1] === ">") return i;
2589
+ if (ch === '"' || ch === "'" || ch === "`") {
2590
+ i = skipString2(src, i);
2591
+ } else if (ch === "{") {
2592
+ i = skipBraces(src, i);
2593
+ } else {
2594
+ i++;
2595
+ }
2596
+ }
2597
+ return -1;
2598
+ }
2599
+ function skipString2(src, start) {
2600
+ const quote = src[start];
2601
+ let i = start + 1;
2602
+ while (i < src.length) {
2603
+ if (src[i] === "\\" && quote !== "`") {
2604
+ i += 2;
2605
+ continue;
2606
+ }
2607
+ if (quote === "`" && src[i] === "$" && src[i + 1] === "{") {
2608
+ i = skipBraces(src, i + 1);
2609
+ continue;
2610
+ }
2611
+ if (src[i] === quote) return i + 1;
2612
+ i++;
2613
+ }
2614
+ return i;
2615
+ }
2616
+ function skipBraces(src, start) {
2617
+ let depth = 1;
2618
+ let i = start + 1;
2619
+ while (i < src.length && depth > 0) {
2620
+ const ch = src[i];
2621
+ if (ch === "{") {
2622
+ depth++;
2623
+ i++;
2624
+ } else if (ch === "}") {
2625
+ depth--;
2626
+ i++;
2627
+ } else if (ch === '"' || ch === "'" || ch === "`") {
2628
+ i = skipString2(src, i);
2629
+ } else {
2630
+ i++;
2631
+ }
2632
+ }
2633
+ return i;
2634
+ }
2635
+ function dynamicAttrHint(kind) {
2636
+ if (kind === "region") {
2637
+ 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`;
2638
+ }
2639
+ 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`;
2640
+ }
2545
2641
  function stableStringify(value) {
2546
2642
  return JSON.stringify(value, stableReplacer);
2547
2643
  }
@@ -2695,6 +2791,11 @@ function emit(opts) {
2695
2791
  lines.push(" acceptance?: readonly string[]");
2696
2792
  lines.push(" description?: string");
2697
2793
  lines.push(" }");
2794
+ lines.push(" export interface Region {");
2795
+ lines.push(" region: RegionId | false");
2796
+ lines.push(" name?: string");
2797
+ lines.push(" description?: string");
2798
+ lines.push(" }");
2698
2799
  lines.push(" export interface Flow {");
2699
2800
  lines.push(" flow: FlowId");
2700
2801
  lines.push(" name?: string");