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.
- package/dist/cli/cli.cjs +115 -14
- package/dist/cli/cli.cjs.map +1 -1
- package/dist/headless/index.cjs +3 -0
- package/dist/headless/index.cjs.map +1 -1
- package/dist/headless/index.js +3 -0
- package/dist/headless/index.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +3 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +110 -9
- package/dist/scan/index.cjs.map +1 -1
- package/dist/scan/index.d.cts +1 -1
- package/dist/scan/index.d.ts +1 -1
- package/dist/scan/index.js +110 -9
- package/dist/scan/index.js.map +1 -1
- package/package.json +19 -26
package/dist/cli/cli.cjs
CHANGED
|
@@ -739,17 +739,38 @@ function audit(opts) {
|
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
741
|
if (lint) {
|
|
742
|
+
const dynamicAttrRe = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*\{/g;
|
|
742
743
|
for (const f of files) {
|
|
743
|
-
const lines = f.content.split("\n");
|
|
744
|
-
const candidateRe = /<(button|a|input|select|textarea)(?=[\s/>])([^>]*)>/g;
|
|
745
744
|
let m;
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const idx = m.index;
|
|
745
|
+
dynamicAttrRe.lastIndex = 0;
|
|
746
|
+
while ((m = dynamicAttrRe.exec(f.content)) !== null) {
|
|
747
|
+
const kind = m[1] ?? "element";
|
|
750
748
|
let line = 1;
|
|
751
|
-
for (let i = 0; i <
|
|
752
|
-
|
|
749
|
+
for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
|
|
750
|
+
const attrName = m[1] ? `data-uidex-${m[1]}` : "data-uidex";
|
|
751
|
+
diagnostics.push({
|
|
752
|
+
code: "dynamic-attr",
|
|
753
|
+
severity: "warning",
|
|
754
|
+
message: `\`${attrName}={\u2026}\` uses a dynamic expression; the scanner cannot resolve the ${kind} id statically`,
|
|
755
|
+
file: f.displayPath,
|
|
756
|
+
line,
|
|
757
|
+
hint: dynamicAttrHint(kind)
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (lint) {
|
|
763
|
+
for (const f of files) {
|
|
764
|
+
const tagRe = /<(button|a|input|select|textarea)(?=[\s/>])/g;
|
|
765
|
+
let m;
|
|
766
|
+
while ((m = tagRe.exec(f.content)) !== null) {
|
|
767
|
+
const afterTag = m.index + m[0].length;
|
|
768
|
+
const closeIdx = findJsxOpeningEnd(f.content, afterTag);
|
|
769
|
+
if (closeIdx === -1) continue;
|
|
770
|
+
const attrs = f.content.slice(afterTag, closeIdx);
|
|
771
|
+
if (attrs.includes("data-uidex")) continue;
|
|
772
|
+
let line = 1;
|
|
773
|
+
for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
|
|
753
774
|
diagnostics.push({
|
|
754
775
|
code: "missing-element-annotation",
|
|
755
776
|
severity: "info",
|
|
@@ -959,6 +980,63 @@ function extractEntitiesArray(source) {
|
|
|
959
980
|
}
|
|
960
981
|
return null;
|
|
961
982
|
}
|
|
983
|
+
function findJsxOpeningEnd(src, start) {
|
|
984
|
+
let i = start;
|
|
985
|
+
while (i < src.length) {
|
|
986
|
+
const ch = src[i];
|
|
987
|
+
if (ch === ">" || ch === "/" && src[i + 1] === ">") return i;
|
|
988
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
989
|
+
i = skipString(src, i);
|
|
990
|
+
} else if (ch === "{") {
|
|
991
|
+
i = skipBraces(src, i);
|
|
992
|
+
} else {
|
|
993
|
+
i++;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return -1;
|
|
997
|
+
}
|
|
998
|
+
function skipString(src, start) {
|
|
999
|
+
const quote = src[start];
|
|
1000
|
+
let i = start + 1;
|
|
1001
|
+
while (i < src.length) {
|
|
1002
|
+
if (src[i] === "\\" && quote !== "`") {
|
|
1003
|
+
i += 2;
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (quote === "`" && src[i] === "$" && src[i + 1] === "{") {
|
|
1007
|
+
i = skipBraces(src, i + 1);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (src[i] === quote) return i + 1;
|
|
1011
|
+
i++;
|
|
1012
|
+
}
|
|
1013
|
+
return i;
|
|
1014
|
+
}
|
|
1015
|
+
function skipBraces(src, start) {
|
|
1016
|
+
let depth = 1;
|
|
1017
|
+
let i = start + 1;
|
|
1018
|
+
while (i < src.length && depth > 0) {
|
|
1019
|
+
const ch = src[i];
|
|
1020
|
+
if (ch === "{") {
|
|
1021
|
+
depth++;
|
|
1022
|
+
i++;
|
|
1023
|
+
} else if (ch === "}") {
|
|
1024
|
+
depth--;
|
|
1025
|
+
i++;
|
|
1026
|
+
} else if (ch === '"' || ch === "'" || ch === "`") {
|
|
1027
|
+
i = skipString(src, i);
|
|
1028
|
+
} else {
|
|
1029
|
+
i++;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return i;
|
|
1033
|
+
}
|
|
1034
|
+
function dynamicAttrHint(kind) {
|
|
1035
|
+
if (kind === "region") {
|
|
1036
|
+
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`;
|
|
1037
|
+
}
|
|
1038
|
+
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`;
|
|
1039
|
+
}
|
|
962
1040
|
function stableStringify(value) {
|
|
963
1041
|
return JSON.stringify(value, stableReplacer);
|
|
964
1042
|
}
|
|
@@ -1112,6 +1190,11 @@ function emit(opts) {
|
|
|
1112
1190
|
lines.push(" acceptance?: readonly string[]");
|
|
1113
1191
|
lines.push(" description?: string");
|
|
1114
1192
|
lines.push(" }");
|
|
1193
|
+
lines.push(" export interface Region {");
|
|
1194
|
+
lines.push(" region: RegionId | false");
|
|
1195
|
+
lines.push(" name?: string");
|
|
1196
|
+
lines.push(" description?: string");
|
|
1197
|
+
lines.push(" }");
|
|
1115
1198
|
lines.push(" export interface Flow {");
|
|
1116
1199
|
lines.push(" flow: FlowId");
|
|
1117
1200
|
lines.push(" name?: string");
|
|
@@ -1160,6 +1243,7 @@ var KIND_DISCRIMINATORS = [
|
|
|
1160
1243
|
"feature",
|
|
1161
1244
|
"primitive",
|
|
1162
1245
|
"widget",
|
|
1246
|
+
"region",
|
|
1163
1247
|
"flow",
|
|
1164
1248
|
"notFlow"
|
|
1165
1249
|
];
|
|
@@ -1181,12 +1265,14 @@ var ALLOWED_FIELDS = {
|
|
|
1181
1265
|
]),
|
|
1182
1266
|
primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
|
|
1183
1267
|
widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
|
|
1268
|
+
region: /* @__PURE__ */ new Set(["region", "name", "description"]),
|
|
1184
1269
|
flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
|
|
1185
1270
|
};
|
|
1186
1271
|
var FALSEABLE = /* @__PURE__ */ new Set([
|
|
1187
1272
|
"page",
|
|
1188
1273
|
"feature",
|
|
1189
|
-
"primitive"
|
|
1274
|
+
"primitive",
|
|
1275
|
+
"region"
|
|
1190
1276
|
]);
|
|
1191
1277
|
var ExtractError = class extends Error {
|
|
1192
1278
|
code;
|
|
@@ -2053,7 +2139,7 @@ function collectJSXAncestry(content) {
|
|
|
2053
2139
|
continue;
|
|
2054
2140
|
}
|
|
2055
2141
|
if (c === '"' || c === "'") {
|
|
2056
|
-
const next =
|
|
2142
|
+
const next = skipString2(content, i, c);
|
|
2057
2143
|
advanceLines(i, next);
|
|
2058
2144
|
i = next;
|
|
2059
2145
|
continue;
|
|
@@ -2113,7 +2199,7 @@ function collectJSXAncestry(content) {
|
|
|
2113
2199
|
}
|
|
2114
2200
|
return out2;
|
|
2115
2201
|
}
|
|
2116
|
-
function
|
|
2202
|
+
function skipString2(content, start, quote) {
|
|
2117
2203
|
const N = content.length;
|
|
2118
2204
|
let i = start + 1;
|
|
2119
2205
|
while (i < N) {
|
|
@@ -2143,7 +2229,7 @@ function skipTemplate(content, start) {
|
|
|
2143
2229
|
while (i < N && depth > 0) {
|
|
2144
2230
|
const cj = content[i];
|
|
2145
2231
|
if (cj === '"' || cj === "'") {
|
|
2146
|
-
i =
|
|
2232
|
+
i = skipString2(content, i, cj);
|
|
2147
2233
|
continue;
|
|
2148
2234
|
}
|
|
2149
2235
|
if (cj === "`") {
|
|
@@ -2166,7 +2252,7 @@ function findTagEnd(content, start) {
|
|
|
2166
2252
|
while (i < N) {
|
|
2167
2253
|
const c = content[i];
|
|
2168
2254
|
if (c === '"' || c === "'") {
|
|
2169
|
-
i =
|
|
2255
|
+
i = skipString2(content, i, c);
|
|
2170
2256
|
continue;
|
|
2171
2257
|
}
|
|
2172
2258
|
if (c === "`") {
|
|
@@ -2179,7 +2265,7 @@ function findTagEnd(content, start) {
|
|
|
2179
2265
|
while (i < N && depth > 0) {
|
|
2180
2266
|
const cj = content[i];
|
|
2181
2267
|
if (cj === '"' || cj === "'") {
|
|
2182
|
-
i =
|
|
2268
|
+
i = skipString2(content, i, cj);
|
|
2183
2269
|
continue;
|
|
2184
2270
|
}
|
|
2185
2271
|
if (cj === "`") {
|
|
@@ -2834,6 +2920,21 @@ function resolve3(ctx) {
|
|
|
2834
2920
|
};
|
|
2835
2921
|
registry.add(region);
|
|
2836
2922
|
}
|
|
2923
|
+
for (const ef of ctx.extracted) {
|
|
2924
|
+
const exp = exportFor(ef.file.displayPath, "region");
|
|
2925
|
+
if (!exp) continue;
|
|
2926
|
+
if (exp.id === false) continue;
|
|
2927
|
+
if (typeof exp.id === "string" && !registry.get("region", exp.id)) {
|
|
2928
|
+
const meta = buildMetaFromExport(exp);
|
|
2929
|
+
const region = {
|
|
2930
|
+
kind: "region",
|
|
2931
|
+
id: exp.id,
|
|
2932
|
+
loc: { file: ef.file.displayPath, line: exp.loc.line },
|
|
2933
|
+
...meta ? { meta } : {}
|
|
2934
|
+
};
|
|
2935
|
+
registry.add(region);
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2837
2938
|
const primitiveConventions = conventions.primitives;
|
|
2838
2939
|
for (const ef of ctx.extracted) {
|
|
2839
2940
|
const file = ef.file.displayPath;
|