synthesisui 0.16.93 → 0.16.94
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/commands/import.js +25 -2
- package/dist/doctor/sketch.js +55 -0
- package/dist/doctor/transcribe.js +29 -4
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -396,7 +396,30 @@ export async function takeCensus(root, opts) {
|
|
|
396
396
|
// component's own look, and everything below it is a part this slice does
|
|
397
397
|
// not attempt.
|
|
398
398
|
if (found.length > 0) {
|
|
399
|
-
|
|
399
|
+
/**
|
|
400
|
+
* EVERY COMPONENT THE FILE DEFINES, not just the first.
|
|
401
|
+
*
|
|
402
|
+
* One file in a real library defines two - `RadioCard` and
|
|
403
|
+
* `RadioCardGroup` - and the second arrived with an empty base while its
|
|
404
|
+
* `flex flex-col gap-3` sat six lines below the first component's return
|
|
405
|
+
* (audit, dono, 01/08). The file-level readings (the CSS module, the
|
|
406
|
+
* variant tables) belong to the PRIMARY definition; the root classes and
|
|
407
|
+
* the root tag are per component, scoped by name.
|
|
408
|
+
*/
|
|
409
|
+
for (const extra of found.slice(1)) {
|
|
410
|
+
const et = transcribe(rootClasses(src, extra.name), declaredValues);
|
|
411
|
+
const etag = rootTag(src, extra.name);
|
|
412
|
+
const esize = Object.keys(et.base).length +
|
|
413
|
+
Object.keys(et.dark).length +
|
|
414
|
+
Object.keys(et.states).length;
|
|
415
|
+
if (esize > 0 || etag) {
|
|
416
|
+
looks[extra.name] = {
|
|
417
|
+
...et,
|
|
418
|
+
...(etag ? { rootTag: etag } : {}),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const t = transcribe(rootClasses(src, found[0].name), declaredValues);
|
|
400
423
|
/**
|
|
401
424
|
* THE VARIANT STYLES, FOLDED IN.
|
|
402
425
|
*
|
|
@@ -480,7 +503,7 @@ export async function takeCensus(root, opts) {
|
|
|
480
503
|
Object.keys(states).length +
|
|
481
504
|
Object.keys(parts).length +
|
|
482
505
|
layers.length;
|
|
483
|
-
const tag = rootTag(src);
|
|
506
|
+
const tag = rootTag(src, found[0].name);
|
|
484
507
|
/**
|
|
485
508
|
* THE SKETCH travels with the look, so the reading phase never opens this
|
|
486
509
|
* file again: naming parts is a sentence over data the census already holds,
|
package/dist/doctor/sketch.js
CHANGED
|
@@ -23,6 +23,56 @@ import { scanTags } from "./css-modules.js";
|
|
|
23
23
|
const MAX_NODES = 60;
|
|
24
24
|
/** `className="..."` or className={"..."} / {`...`} with no interpolation. */
|
|
25
25
|
const CLASS_ATTR = /className=\{?["'`]([^"'`{}$]*)["'`]\}?/;
|
|
26
|
+
/**
|
|
27
|
+
* `className={cn("a b", "c", cond && "d", record[x])}` - the STATIC strings.
|
|
28
|
+
*
|
|
29
|
+
* The plain-attr regex above sees a call expression and reads nothing, and
|
|
30
|
+
* 19 of 36 components in a real library style exactly this way - so their
|
|
31
|
+
* elements arrived in the sketch with no classes at all, and the Tooltip's
|
|
32
|
+
* popup carried none when the base had to be transcribed by hand
|
|
33
|
+
* (not-expressed.md, dono, 01/08). The always-on string arguments ARE the
|
|
34
|
+
* element's resting classes; the conditional halves are variants, which the
|
|
35
|
+
* variant readers already read - so only TOP-LEVEL string literals count.
|
|
36
|
+
* Template literals with `${}` and computed values stay out: a class we
|
|
37
|
+
* cannot see in full is a class we would be guessing at.
|
|
38
|
+
*
|
|
39
|
+
* THE NAMESPACE COUNTS. Measured on the real library: 64 calls are a bare
|
|
40
|
+
* `cn(`, 11 are `twUtils.cn(` and 2 are `clsx(` - and the first version of this
|
|
41
|
+
* regex read only the bare form, so a Modal's whole surface (its fill, border,
|
|
42
|
+
* radius and padding) stayed invisible while its siblings read fine
|
|
43
|
+
* (e2e, dono, 01/08).
|
|
44
|
+
*/
|
|
45
|
+
const CLASS_CALL = /className=\{\s*(?:[A-Za-z_$][\w$]*\.)?(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(/;
|
|
46
|
+
function staticCallClasses(body) {
|
|
47
|
+
const call = CLASS_CALL.exec(body);
|
|
48
|
+
if (!call)
|
|
49
|
+
return null;
|
|
50
|
+
const start = call.index + call[0].length;
|
|
51
|
+
// Walk the call's arguments at depth 0, collecting bare string literals.
|
|
52
|
+
let depth = 1;
|
|
53
|
+
let argStart = start;
|
|
54
|
+
const args = [];
|
|
55
|
+
for (let i = start; i < body.length && depth > 0; i++) {
|
|
56
|
+
const ch = body[i];
|
|
57
|
+
if (ch === "(" || ch === "{" || ch === "[")
|
|
58
|
+
depth += 1;
|
|
59
|
+
else if (ch === ")" || ch === "}" || ch === "]") {
|
|
60
|
+
depth -= 1;
|
|
61
|
+
if (depth === 0)
|
|
62
|
+
args.push(body.slice(argStart, i));
|
|
63
|
+
}
|
|
64
|
+
else if (ch === "," && depth === 1) {
|
|
65
|
+
args.push(body.slice(argStart, i));
|
|
66
|
+
argStart = i + 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const classes = args
|
|
70
|
+
.map((a) => a.trim())
|
|
71
|
+
.filter((a) => /^(["'`]).*\1$/.test(a) && !a.includes("${"))
|
|
72
|
+
.map((a) => a.slice(1, -1).trim())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
return classes.length > 0 ? classes.join(" ") : null;
|
|
75
|
+
}
|
|
26
76
|
/**
|
|
27
77
|
* The sketch of one component file.
|
|
28
78
|
*
|
|
@@ -47,6 +97,11 @@ export function sketchOf(source) {
|
|
|
47
97
|
const cls = CLASS_ATTR.exec(event.body);
|
|
48
98
|
if (cls?.[1].trim())
|
|
49
99
|
node.classes = cls[1].trim();
|
|
100
|
+
else {
|
|
101
|
+
const called = staticCallClasses(event.body);
|
|
102
|
+
if (called)
|
|
103
|
+
node.classes = called;
|
|
104
|
+
}
|
|
50
105
|
// Literal text: the slice between this open and the next event, when it is prose.
|
|
51
106
|
const next = events[i + 1];
|
|
52
107
|
if (next) {
|
|
@@ -1090,15 +1090,40 @@ export function transcribe(classes, declared) {
|
|
|
1090
1090
|
* which is still a signal, just a weaker one, so it travels as written and the
|
|
1091
1091
|
* platform decides what to do with it.
|
|
1092
1092
|
*/
|
|
1093
|
-
|
|
1094
|
-
|
|
1093
|
+
/**
|
|
1094
|
+
* WHERE *THIS* COMPONENT'S `return (` IS.
|
|
1095
|
+
*
|
|
1096
|
+
* Both readers below took the FIRST `return (` in the file, which is right for
|
|
1097
|
+
* the 34 files that define one component and wrong for the one that defines
|
|
1098
|
+
* two: `RadioCard.tsx` exports `RadioCard` and `RadioCardGroup`, and the group
|
|
1099
|
+
* - a real `flex flex-col gap-3` - arrived with an empty base and the screen
|
|
1100
|
+
* said "Not written yet" over it (audit, dono, 01/08).
|
|
1101
|
+
*
|
|
1102
|
+
* Scoped by NAME when a name is given: find that declaration, then its return.
|
|
1103
|
+
* Absent or unfound, the old behaviour stands exactly - which keeps every
|
|
1104
|
+
* single-component file reading byte-identically.
|
|
1105
|
+
*/
|
|
1106
|
+
function returnAt(source, name) {
|
|
1107
|
+
if (name) {
|
|
1108
|
+
const decl = new RegExp(`(?:function|const|let|var)\\s+${name}\\b|${name}\\s*[:=]\\s*(?:function|\\()`).exec(source);
|
|
1109
|
+
if (decl) {
|
|
1110
|
+
const from = source.slice(decl.index);
|
|
1111
|
+
const ret = from.search(/return\s*\(/);
|
|
1112
|
+
if (ret !== -1)
|
|
1113
|
+
return decl.index + ret;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return source.search(/return\s*\(/);
|
|
1117
|
+
}
|
|
1118
|
+
export function rootTag(source, name) {
|
|
1119
|
+
const ret = returnAt(source, name);
|
|
1095
1120
|
if (ret === -1)
|
|
1096
1121
|
return null;
|
|
1097
1122
|
const m = /<([A-Za-z][A-Za-z0-9.]*)/.exec(source.slice(ret));
|
|
1098
1123
|
return m ? m[1] : null;
|
|
1099
1124
|
}
|
|
1100
|
-
export function rootClasses(source) {
|
|
1101
|
-
const ret = source
|
|
1125
|
+
export function rootClasses(source, name) {
|
|
1126
|
+
const ret = returnAt(source, name);
|
|
1102
1127
|
if (ret === -1)
|
|
1103
1128
|
return [];
|
|
1104
1129
|
const open = source.indexOf("<", ret);
|
package/package.json
CHANGED