synthesisui 0.16.154 → 0.16.155
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 +34 -0
- package/dist/doctor/variant-read.js +86 -12
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -857,6 +857,16 @@ export async function takeCensus(root, opts) {
|
|
|
857
857
|
...(v.unslotted.length > 0
|
|
858
858
|
? { unplaced: [...new Set(v.unslotted)] }
|
|
859
859
|
: {}),
|
|
860
|
+
/**
|
|
861
|
+
* AS NOTAS SÃO OUTRA LISTA - ver `notes` em `variant-read.ts`.
|
|
862
|
+
*
|
|
863
|
+
* Um layer que FICOU e precisa de um `data-*` não é um look sem lugar: ele está na receita.
|
|
864
|
+
* As duas viajavam em `unplaced`, e a tela de causas cobrava conserto de 16 linhas que
|
|
865
|
+
* diziam, no próprio texto, que nada tinha sido perdido (dono, 06/08).
|
|
866
|
+
*/
|
|
867
|
+
...(v.notes.length > 0
|
|
868
|
+
? { renderNotes: [...new Set(v.notes)] }
|
|
869
|
+
: {}),
|
|
860
870
|
...(Object.keys(v.axes).length > 0 ? { declaredAxes: v.axes } : {}),
|
|
861
871
|
...(Object.keys(defaults).length > 0 ? { defaults } : {}),
|
|
862
872
|
};
|
|
@@ -1253,6 +1263,30 @@ export async function takeCensus(root, opts) {
|
|
|
1253
1263
|
console.log(body(paint.faint(` and ${unplacedByComponent.length - 8} more components`)));
|
|
1254
1264
|
}
|
|
1255
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* E AS NOTAS DE RENDERIZAÇÃO, que são o oposto de uma perda.
|
|
1268
|
+
*
|
|
1269
|
+
* O layer ESTÁ na receita: o que falta é um atributo no elemento para ele aplicar. Até 06/08 estas
|
|
1270
|
+
* linhas viajavam junto com as de "não achei onde pôr" - 16 de 33 num repo real -, e a tela de causas
|
|
1271
|
+
* cobrava conserto de quem não tinha o que consertar. Separadas, elas viram o que sempre foram: uma
|
|
1272
|
+
* instrução para quem for renderizar.
|
|
1273
|
+
*/
|
|
1274
|
+
const notesByComponent = Object.entries(looks)
|
|
1275
|
+
.filter(([, look]) => (look.renderNotes ?? []).length > 0)
|
|
1276
|
+
.map(([component, look]) => ({
|
|
1277
|
+
component,
|
|
1278
|
+
lines: look.renderNotes ?? [],
|
|
1279
|
+
}));
|
|
1280
|
+
if (notesByComponent.length > 0) {
|
|
1281
|
+
const total = notesByComponent.reduce((n, u) => n + u.lines.length, 0);
|
|
1282
|
+
console.log("");
|
|
1283
|
+
console.log(section("Looks that are in the recipe and need an attribute"));
|
|
1284
|
+
console.log(body(`${total} decision${total === 1 ? "" : "s"} across ${notesByComponent.length} component${notesByComponent.length === 1 ? "" : "s"} were read and KEPT - they apply once the element carries the attribute the line names:`));
|
|
1285
|
+
for (const u of notesByComponent.slice(0, 6))
|
|
1286
|
+
console.log(body(paint.faint(` ${u.component}: ${u.lines[0]}`)));
|
|
1287
|
+
if (notesByComponent.length > 6)
|
|
1288
|
+
console.log(body(paint.faint(` and ${notesByComponent.length - 6} more components`)));
|
|
1289
|
+
}
|
|
1256
1290
|
/**
|
|
1257
1291
|
* THE DROPS, SAID OUT LOUD. `p-spacing-sm` on a real menu popup read as nothing and
|
|
1258
1292
|
* the popup shipped with no padding, invisibly - "if the compiler drops them, the drop
|
|
@@ -79,9 +79,16 @@ export function conditionOf(modifiers) {
|
|
|
79
79
|
const out = {};
|
|
80
80
|
for (const raw of modifiers) {
|
|
81
81
|
if (raw === "dark") {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
/**
|
|
83
|
+
* O ESQUEMA É UMA CONDIÇÃO, e o layer tem slot para ele.
|
|
84
|
+
*
|
|
85
|
+
* O comentário anterior dizia "the dark half has its own home in the transcription and no slot
|
|
86
|
+
* inside a layer" - verdade quando foi escrito, e falso desde que o layer passou a carregar
|
|
87
|
+
* `scheme`. A "casa na transcrição" só existe para a classe INCONDICIONAL: uma `dark:` dentro de
|
|
88
|
+
* um record por variante passava por aqui e morria, e é assim que 9 utilities do Button e do
|
|
89
|
+
* Toggle sumiram com uma linha de aviso e outras 66 sem nenhuma (dono, 06/08).
|
|
90
|
+
*/
|
|
91
|
+
out.scheme = "dark";
|
|
85
92
|
continue;
|
|
86
93
|
}
|
|
87
94
|
const relational = RELATIONAL.exec(raw);
|
|
@@ -121,7 +128,8 @@ function keyOf(c, variant) {
|
|
|
121
128
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
122
129
|
.map(([k, o]) => `${k}=${o}`)
|
|
123
130
|
.join("&");
|
|
124
|
-
|
|
131
|
+
/** O ESQUEMA ENTRA NA CHAVE: sem ele, `bg-white` e `dark:bg-black` caem no mesmo layer e um vence. */
|
|
132
|
+
return `${v}|${c.state ?? ""}|${c.within ?? ""}|${c.at ?? ""}|${c.scheme ?? ""}`;
|
|
125
133
|
}
|
|
126
134
|
/**
|
|
127
135
|
* Split a class list into the base and the conditional layers it contains.
|
|
@@ -157,12 +165,13 @@ export function splitByCondition(classes, variant) {
|
|
|
157
165
|
const key = keyOf(cond, variant);
|
|
158
166
|
const layer = layers.get(key) ??
|
|
159
167
|
{
|
|
160
|
-
...(variant || cond.state || cond.within
|
|
168
|
+
...(variant || cond.state || cond.within || cond.scheme
|
|
161
169
|
? {
|
|
162
170
|
when: {
|
|
163
171
|
...(variant ? { variant } : {}),
|
|
164
172
|
...(cond.state ? { state: cond.state } : {}),
|
|
165
173
|
...(cond.within ? { within: cond.within } : {}),
|
|
174
|
+
...(cond.scheme ? { scheme: cond.scheme } : {}),
|
|
166
175
|
},
|
|
167
176
|
}
|
|
168
177
|
: {}),
|
|
@@ -623,6 +632,31 @@ function objectFields(value) {
|
|
|
623
632
|
return [];
|
|
624
633
|
return topLevelEntries(trimmed.slice(1, end - 1));
|
|
625
634
|
}
|
|
635
|
+
/**
|
|
636
|
+
* TEM A FORMA DE UMA LISTA DE CLASSES?
|
|
637
|
+
*
|
|
638
|
+
* Usada só no resíduo: quando nenhuma prop de classe consome o campo, esta é a última pergunta antes de
|
|
639
|
+
* afirmar que ele é estilo. Ela é deliberadamente TÍMIDA - dois tokens no mínimo, tudo minúsculo, nada
|
|
640
|
+
* começando com `#`:
|
|
641
|
+
*
|
|
642
|
+
* "px-2 py-2 gap-0.5" 3 tokens, minusculo -> lista de classes
|
|
643
|
+
* "overline" 1 token -> silencio (e o `variant` de um <Text>)
|
|
644
|
+
* "body-md" 1 token -> silencio, e indistinguivel de `text-xs`
|
|
645
|
+
* "#1394DC" comeca com # -> silencio (cor de grafico)
|
|
646
|
+
* "Waiting" maiuscula -> silencio (conteudo)
|
|
647
|
+
*
|
|
648
|
+
* Ela erra CALANDO sobre uma classe única e não-consumida, e esse é o lado certo de errar: uma linha
|
|
649
|
+
* inventada num relatório custa a confiança na linha seguinte, que estava certa.
|
|
650
|
+
*/
|
|
651
|
+
function looksLikeClassList(value) {
|
|
652
|
+
const text = value.trim().replace(/^[`'"]|[`'"]$/g, "");
|
|
653
|
+
if (!text || text.startsWith("#"))
|
|
654
|
+
return false;
|
|
655
|
+
const tokens = text.split(/\s+/).filter(Boolean);
|
|
656
|
+
if (tokens.length < 2)
|
|
657
|
+
return false;
|
|
658
|
+
return tokens.every((t) => /^[a-z0-9[\]()/:._!-]+$/.test(t));
|
|
659
|
+
}
|
|
626
660
|
/**
|
|
627
661
|
* THE STATIC CLASSES OF THE `className` CALL THAT CONSUMES A LOOKUP.
|
|
628
662
|
*
|
|
@@ -636,12 +670,34 @@ function objectFields(value) {
|
|
|
636
670
|
* local first (`const config = statusConfig[status]`); a record whose value is a plain
|
|
637
671
|
* string has no field, and there the indexing expression is what is written at the call
|
|
638
672
|
* site (`variantColors[variant]`), so the SYMBOL is the reliable half.
|
|
673
|
+
*
|
|
674
|
+
* E DUAS COISAS QUE FALTAVAM, medidas em 8 de 8 casos de um repo real (dono, 06/08) - nenhum deles
|
|
675
|
+
* escrevia `simbolo.campo` no ponto de consumo:
|
|
676
|
+
*
|
|
677
|
+
* 1. A PROP NÃO É SÓ `className`. O MetricCard deles passa o campo em
|
|
678
|
+
* `contentClassName={cn("p-0 flex flex-col", currentSize.container)}` - é o Card deles que recebe
|
|
679
|
+
* a classe por outro nome, e exigir `className` literal descartava a linha inteira.
|
|
680
|
+
* 2. O CAMPO PODE VIR DESESTRUTURADO. O CircularProgress escreve
|
|
681
|
+
* `const { diameter, strokeWidth, fontSize } = sizeConfig[size]` e depois usa `fontSize` NU dentro
|
|
682
|
+
* do `cn()` - sem ponto nenhum, então a forma `<algo>.campo` não casa.
|
|
683
|
+
*
|
|
684
|
+
* As duas juntas são a diferença entre colocar o look no elemento certo e reportá-lo como "não há
|
|
685
|
+
* elemento onde pôr".
|
|
639
686
|
*/
|
|
640
687
|
function callSiteClasses(source, symbol, field) {
|
|
688
|
+
/** Desestruturado da própria tabela: `const { fontSize } = sizeConfig[size]`. */
|
|
689
|
+
const destructured = field != null &&
|
|
690
|
+
new RegExp(`\\{[^}]*\\b${field}\\b[^}]*\\}\\s*=\\s*${symbol}\\s*\\[`).test(source);
|
|
641
691
|
const use = field
|
|
642
|
-
? new RegExp(
|
|
692
|
+
? new RegExp(destructured
|
|
693
|
+
? `\\b${field}\\b`
|
|
694
|
+
: `\\b[A-Za-z_$][\\w$]*(?:\\[[^\\]]*\\])?\\.${field}\\b`)
|
|
643
695
|
: new RegExp(`\\b${symbol}\\s*\\[`);
|
|
644
|
-
|
|
696
|
+
/**
|
|
697
|
+
* `className` E QUALQUER `*ClassName`, que é como um componente deles recebe a classe de um filho.
|
|
698
|
+
* A borda `(?:^|[^A-Za-z])` existe para não casar no meio de um identificador maior.
|
|
699
|
+
*/
|
|
700
|
+
for (const m of source.matchAll(/(?:^|[^A-Za-z])[A-Za-z]*[Cc]lassName=\{\s*(?:[A-Za-z_$][\w$]*\.)?(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(/g)) {
|
|
645
701
|
const open = (m.index ?? 0) + m[0].length - 1;
|
|
646
702
|
const end = endOf(source, open);
|
|
647
703
|
if (end === -1)
|
|
@@ -787,11 +843,23 @@ unattributed) {
|
|
|
787
843
|
const on = callSiteClasses(clean, symbol, field.key);
|
|
788
844
|
if (!on) {
|
|
789
845
|
/**
|
|
790
|
-
* NOWHERE TO PUT IT IS NOT THE ROOT. A field no
|
|
791
|
-
*
|
|
792
|
-
*
|
|
846
|
+
* NOWHERE TO PUT IT IS NOT THE ROOT. A field no class prop claims is a decision we cannot
|
|
847
|
+
* place, and placing it on the component would paint the wrong element - which is the whole
|
|
848
|
+
* bug this reads around.
|
|
849
|
+
*
|
|
850
|
+
* MAS SÓ SE FOR UMA LISTA DE CLASSES. Medido em 8 de 8 casos de um repo real (dono, 06/08),
|
|
851
|
+
* SEIS dos campos não eram estilo nenhum: `labelVariant: "overline"` e `valueVariant:
|
|
852
|
+
* "body-md"` são o `variant` de um `<Text>`, `text: "Waiting"` é conteúdo, e
|
|
853
|
+
* `start: "#1394DC"` é a cor de um gráfico. Chamá-los de "look que não pôde ser colocado" é
|
|
854
|
+
* inventar um problema, e foi o que fez uma causa de 33 ocorrências ter 6 falsos positivos.
|
|
855
|
+
*
|
|
856
|
+
* A regra é o que sobra depois do consumo: se NENHUMA prop de classe consome o campo E o
|
|
857
|
+
* valor não tem a forma de uma lista de classes, a gente não sabe que é estilo - e o que não
|
|
858
|
+
* se sabe não se afirma. Um valor de um token só fica em silêncio de propósito: `overline` e
|
|
859
|
+
* `text-xs` são indistinguíveis pela forma, e o consumo era a única prova.
|
|
793
860
|
*/
|
|
794
|
-
|
|
861
|
+
if (looksLikeClassList(field.value))
|
|
862
|
+
unattributed?.push(`\`${symbol}.${field.key}\` is a field of a per-element table that no class prop consumes, so there is no element to put it on`);
|
|
795
863
|
continue;
|
|
796
864
|
}
|
|
797
865
|
const per = splitByCondition(classes, { [axis]: option.key });
|
|
@@ -856,6 +924,7 @@ export function transcribeVariants(source, declared) {
|
|
|
856
924
|
const reads = readVariants(source);
|
|
857
925
|
const fromRecords = {};
|
|
858
926
|
const unslotted = [];
|
|
927
|
+
const notes = [];
|
|
859
928
|
const inline = [
|
|
860
929
|
...readInlineConditions(source),
|
|
861
930
|
// The two shapes the enumeration found to be five times more common than `cva`.
|
|
@@ -925,7 +994,11 @@ export function transcribeVariants(source, declared) {
|
|
|
925
994
|
continue;
|
|
926
995
|
}
|
|
927
996
|
const attr = axis.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
928
|
-
|
|
997
|
+
/**
|
|
998
|
+
* NOTA, NÃO PERDA. O layer está na receita; o que falta é um atributo no elemento para ele aplicar.
|
|
999
|
+
* Enquanto isto viajava em `unslotted`, a tela contava 16 destes como pendência de conserto.
|
|
1000
|
+
*/
|
|
1001
|
+
notes.push(named.length === 0
|
|
929
1002
|
? `\`${axis}\` gates a look and is a boolean rather than an axis - the layer is kept, and \`data-${attr}\` has to be set for it to apply`
|
|
930
1003
|
: `\`${axis}\` gates a look on one option (\`${named[0]}\`) - one option is not an axis, so the layer is kept and \`data-${attr}="${named[0]}"\` has to be set for it to apply`);
|
|
931
1004
|
}
|
|
@@ -957,5 +1030,6 @@ export function transcribeVariants(source, declared) {
|
|
|
957
1030
|
layers,
|
|
958
1031
|
base: transcribe(baseClasses, declared),
|
|
959
1032
|
unslotted,
|
|
1033
|
+
notes,
|
|
960
1034
|
};
|
|
961
1035
|
}
|
package/package.json
CHANGED