synthesisui 0.16.286 → 0.16.289
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/anatomy-read.js +20 -5
- package/dist/commands/import.js +92 -16
- package/dist/doctor/broken-refs.js +15 -1
- package/dist/doctor/component-gate.js +59 -2
- package/dist/doctor/css-modules.js +77 -47
- package/dist/doctor/definitions-scan.js +59 -3
- package/dist/doctor/transcribe.js +9 -51
- package/dist/doctor/variant-read.js +31 -5
- package/dist/global-wear.js +44 -0
- package/dist/install-marks.js +1 -1
- package/dist/namespace-pairs.js +80 -0
- package/dist/not-expressed.js +76 -0
- package/dist/token-ref.js +93 -0
- package/package.json +1 -1
package/dist/anatomy-read.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import { readSxProps } from "./doctor/style-props.js";
|
|
33
33
|
import { transcribe, } from "./doctor/transcribe.js";
|
|
34
34
|
import { frontierKind, packageRoot } from "./frontier-kind.js";
|
|
35
|
+
import { withGlobal } from "./global-wear.js";
|
|
35
36
|
/** The ten forms, closed. A renderer that accepts any tag draws anything. */
|
|
36
37
|
const FORMS = new Set([
|
|
37
38
|
"image",
|
|
@@ -252,7 +253,18 @@ sketch,
|
|
|
252
253
|
* A escala de espaçamento que o projeto DELES declara para o `sx`. Ausente = o default
|
|
253
254
|
* documentado do MUI, que é fato da biblioteca que eles escolheram - ver `spacingOf`.
|
|
254
255
|
*/
|
|
255
|
-
sxSpacing
|
|
256
|
+
sxSpacing,
|
|
257
|
+
/**
|
|
258
|
+
* AS CLASSES DA FOLHA GLOBAL DELE - ver `Census.globalClasses` e `wearGlobal`.
|
|
259
|
+
*
|
|
260
|
+
* O QUE ISSO ALCANÇA QUE NADA MAIS ALCANÇAVA: uma utility que ele escreve num NÓ FILHO. O
|
|
261
|
+
* `LeaderboardRow` do `codelevel-ui` põe `text-grad-xp` num `<span>` dentro de um ternário, e o
|
|
262
|
+
* `QuestItem` põe `text-grad` numa tabela que pertence ao filho - as duas passam por aqui, e não
|
|
263
|
+
* pela raiz nem pela tabela de variantes do componente.
|
|
264
|
+
*
|
|
265
|
+
* Ausente é a resposta de quem não tem folha global, e nesse caso nada muda.
|
|
266
|
+
*/
|
|
267
|
+
globals) {
|
|
256
268
|
/**
|
|
257
269
|
* THE PACKAGE THE RETURNED ELEMENT CAME FROM, measured rather than spelled.
|
|
258
270
|
* Node 0 of the sketch IS what the component returns, and the reader stamped its
|
|
@@ -474,7 +486,7 @@ sxSpacing) {
|
|
|
474
486
|
? (sketch?.[node.at]?.classes ?? "")
|
|
475
487
|
: "";
|
|
476
488
|
const classes = raw.split(/\s+/).filter(Boolean);
|
|
477
|
-
const t = transcribe(classes, declared);
|
|
489
|
+
const t = withGlobal(transcribe(classes, declared), classes, globals);
|
|
478
490
|
/**
|
|
479
491
|
* A PROP DE ESTILO DESTE NÓ, sob a classe dele.
|
|
480
492
|
*
|
|
@@ -618,7 +630,7 @@ sxSpacing) {
|
|
|
618
630
|
* otherwise. So a drawn root sends nothing here at all.
|
|
619
631
|
*/
|
|
620
632
|
const rootStyle = rootPainted
|
|
621
|
-
? transcribe(rootPainted.split(/\s+/).filter(Boolean), declared)
|
|
633
|
+
? withGlobal(transcribe(rootPainted.split(/\s+/).filter(Boolean), declared), rootPainted.split(/\s+/).filter(Boolean), globals)
|
|
622
634
|
: null;
|
|
623
635
|
const paints = rootStyle && Object.keys(rootStyle.base).length > 0
|
|
624
636
|
? { style: rootStyle.base }
|
|
@@ -710,13 +722,16 @@ sxSpacing) {
|
|
|
710
722
|
* correctly, and the absence of a tree means the preview lays them out in a row
|
|
711
723
|
* exactly as it did before.
|
|
712
724
|
*/
|
|
713
|
-
export function resolveFlatParts(read, declared
|
|
725
|
+
export function resolveFlatParts(read, declared,
|
|
726
|
+
/** As classes da folha global dele - ver `wearGlobal`. */
|
|
727
|
+
globals) {
|
|
714
728
|
const parts = {};
|
|
715
729
|
for (const part of read) {
|
|
716
730
|
const name = safePartName(String(part?.name ?? ""));
|
|
717
731
|
if (!name || typeof part.classes !== "string")
|
|
718
732
|
continue;
|
|
719
|
-
const
|
|
733
|
+
const partClasses = part.classes.split(/\s+/).filter(Boolean);
|
|
734
|
+
const t = withGlobal(transcribe(partClasses, declared), partClasses, globals);
|
|
720
735
|
parts[name] = { base: t.base, dark: t.dark, states: t.states };
|
|
721
736
|
}
|
|
722
737
|
return { parts, tree: [], composes: [], external: [], notes: [], partAt: {} };
|
package/dist/commands/import.js
CHANGED
|
@@ -40,7 +40,9 @@ import { keyframesInSheets } from "../global-keyframes.js";
|
|
|
40
40
|
import { withLibraryStructure } from "../library-structure.js";
|
|
41
41
|
import { mergeCensus } from "../merge-census.js";
|
|
42
42
|
import { claimName } from "../name-claim.js";
|
|
43
|
+
import { mergeNamespacePairs } from "../namespace-pairs.js";
|
|
43
44
|
import { namingQueue } from "../naming-queue.js";
|
|
45
|
+
import { notExpressed } from "../not-expressed.js";
|
|
44
46
|
import { body, paint, section } from "../output.js";
|
|
45
47
|
import { outsideScope } from "../outside-scope.js";
|
|
46
48
|
import { phase, startProgress } from "../progress.js";
|
|
@@ -367,6 +369,8 @@ function screenOf(rel) {
|
|
|
367
369
|
const after = parts.slice(i + 1).filter((p) => !p.startsWith("("));
|
|
368
370
|
return `${parts[i]}/${after.slice(0, 2).join("/")}`;
|
|
369
371
|
}
|
|
372
|
+
/** Quantas classes da folha global viajam no censo. Ver o aviso em `takeCensus`. */
|
|
373
|
+
const MAX_GLOBAL_CLASSES = 300;
|
|
370
374
|
export async function takeCensus(root, opts) {
|
|
371
375
|
/**
|
|
372
376
|
* O RELATÓRIO SÓ SAI QUANDO ALGUÉM O PEDIU - `say` cala quando o chamador é uma RE-medição.
|
|
@@ -450,6 +454,17 @@ export async function takeCensus(root, opts) {
|
|
|
450
454
|
* porque é isso que o juiz do ledger precisa para chamar a classe e a regra de LIDAS.
|
|
451
455
|
*/
|
|
452
456
|
const globalClasses = readGlobalClasses(globalSheets, declaredValues);
|
|
457
|
+
/**
|
|
458
|
+
* TETO DAS CLASSES GLOBAIS QUE VIAJAM NO CENSO - e quando ele corta, ele fala.
|
|
459
|
+
*
|
|
460
|
+
* Medido nas três populações: 24 classes na folha do `codelevel-ui`, 22 na nossa, 0 no
|
|
461
|
+
* `apps/web-dashboard` do repo real, que é CSS Modules. O teto é para a folha de um app que
|
|
462
|
+
* escreva utilitário por atacado, e existe porque um censo que dobra de tamanho em silêncio é o
|
|
463
|
+
* tipo de surpresa que ninguém pede.
|
|
464
|
+
*/
|
|
465
|
+
if (globalClasses.size > MAX_GLOBAL_CLASSES) {
|
|
466
|
+
console.log(body(paint.faint(`${globalClasses.size} classes in your global sheet, and ${globalClasses.size - MAX_GLOBAL_CLASSES} of them are not travelling with this census - the first ${MAX_GLOBAL_CLASSES} are.`)));
|
|
467
|
+
}
|
|
453
468
|
/**
|
|
454
469
|
* OS KEYFRAMES DA FOLHA DELE, colhidos onde as folhas globais já estão na mão.
|
|
455
470
|
*
|
|
@@ -735,16 +750,18 @@ export async function takeCensus(root, opts) {
|
|
|
735
750
|
/**
|
|
736
751
|
* Ver `CensusLook.aside` - o segundo componente do arquivo também.
|
|
737
752
|
*
|
|
738
|
-
*
|
|
739
|
-
* componente do arquivo,
|
|
740
|
-
*
|
|
741
|
-
* `
|
|
753
|
+
* E COM OS EIXOS QUE O TIPO DELE DECLARA. `transcribeVariants` roda para o primeiro
|
|
754
|
+
* componente do arquivo, então aqui não há tabela de variantes - mas `extra.axes` é a
|
|
755
|
+
* declaração da interface, lida por `scanDefinitions` para TODA definição do arquivo.
|
|
756
|
+
* Era `undefined`, e isso pôs de lado o `ToastIcon` dele: terceiro componente do
|
|
757
|
+
* `Toast.tsx`, cinco tons declarados no tipo, um gradiente para cada. Ver
|
|
758
|
+
* `classifyAside`.
|
|
742
759
|
*/
|
|
743
|
-
...(classifyAside({ name: extra.name, declaredAxes:
|
|
760
|
+
...(classifyAside({ name: extra.name, declaredAxes: extra.axes })
|
|
744
761
|
? {
|
|
745
762
|
aside: classifyAside({
|
|
746
763
|
name: extra.name,
|
|
747
|
-
declaredAxes:
|
|
764
|
+
declaredAxes: extra.axes,
|
|
748
765
|
}),
|
|
749
766
|
}
|
|
750
767
|
: {}),
|
|
@@ -782,7 +799,7 @@ export async function takeCensus(root, opts) {
|
|
|
782
799
|
* for the result, which is the "valid and unread" failure this pipeline has
|
|
783
800
|
* paid for twice already.
|
|
784
801
|
*/
|
|
785
|
-
const v = transcribeVariants(src, declaredValues);
|
|
802
|
+
const v = transcribeVariants(src, declaredValues, globalClasses);
|
|
786
803
|
/**
|
|
787
804
|
* THE RESTING OPTION, from the definition itself. `cva` says it in
|
|
788
805
|
* `defaultVariants`; a lookup-record component says it in the destructuring -
|
|
@@ -1040,14 +1057,20 @@ export async function takeCensus(root, opts) {
|
|
|
1040
1057
|
/**
|
|
1041
1058
|
* Ver `CensusLook.aside`: o fato viaja, a regra fica de um lado só.
|
|
1042
1059
|
*
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1060
|
+
* AS DUAS FONTES DE EIXO, e é a mesma decisão do autor nas duas. `found[0].axes` é o que
|
|
1061
|
+
* o TIPO declara (`interface ToastIconProps { tone?: "gold" | "cool" }`); `v.axes` é o
|
|
1062
|
+
* que a TABELA declara (`cva({ variants: … })`). Consultar só a segunda pôs de lado um
|
|
1063
|
+
* componente que pinta por `Record<tone, gradiente>` - forma que a tabela não vê.
|
|
1064
|
+
* Ver `classifyAside`.
|
|
1045
1065
|
*/
|
|
1046
|
-
...(classifyAside({
|
|
1066
|
+
...(classifyAside({
|
|
1067
|
+
name: found[0].name,
|
|
1068
|
+
declaredAxes: { ...found[0].axes, ...v.axes },
|
|
1069
|
+
})
|
|
1047
1070
|
? {
|
|
1048
1071
|
aside: classifyAside({
|
|
1049
1072
|
name: found[0].name,
|
|
1050
|
-
declaredAxes: v.axes,
|
|
1073
|
+
declaredAxes: { ...found[0].axes, ...v.axes },
|
|
1051
1074
|
}),
|
|
1052
1075
|
}
|
|
1053
1076
|
: {}),
|
|
@@ -1864,7 +1887,14 @@ export async function takeCensus(root, opts) {
|
|
|
1864
1887
|
list.push(kebab(pair.child));
|
|
1865
1888
|
companionsOf.set(pair.component, list);
|
|
1866
1889
|
}
|
|
1867
|
-
|
|
1890
|
+
/**
|
|
1891
|
+
* `Modal.Header` E `ModalHeader` CONTADOS UMA VEZ - ver `mergeNamespacePairs`.
|
|
1892
|
+
*
|
|
1893
|
+
* Antes do enriquecimento porque veredito, leis e companheiros são indexados por NOME: fundir
|
|
1894
|
+
* depois deixaria metade do que se sabe sobre a peça pendurado no nome que deixou de existir.
|
|
1895
|
+
*/
|
|
1896
|
+
const paired = mergeNamespacePairs(merged, new Set(defined.map((d) => d.name)));
|
|
1897
|
+
const components = paired.map((c) => ({
|
|
1868
1898
|
...c,
|
|
1869
1899
|
...(verdicts.get(idOf(c)) ?? {}),
|
|
1870
1900
|
...(laws.has(c.name) ? { laws: laws.get(c.name) } : {}),
|
|
@@ -2071,6 +2101,25 @@ export async function takeCensus(root, opts) {
|
|
|
2071
2101
|
}
|
|
2072
2102
|
: {}),
|
|
2073
2103
|
...(conventions.length > 0 ? { conventions } : {}),
|
|
2104
|
+
/**
|
|
2105
|
+
* AS CLASSES DA FOLHA GLOBAL DELE - ver `Census.globalClasses`.
|
|
2106
|
+
*
|
|
2107
|
+
* TODAS AS QUE A FOLHA DECLARA, e não só as que a raiz vestiu. A primeira tentativa levou apenas
|
|
2108
|
+
* as reivindicadas por `globalWear`, que só roda na raiz do componente - e as utilities usadas
|
|
2109
|
+
* numa tabela de variantes ou num nó filho, que são a maioria, ficavam de fora justamente do
|
|
2110
|
+
* campo que existe para alcançá-las. Duas de 24 chegavam.
|
|
2111
|
+
*
|
|
2112
|
+
* O CUSTO ESTÁ MEDIDO: a folha do `codelevel-ui` declara 24 classes, a nossa 22, e o
|
|
2113
|
+
* `apps/web-dashboard` do repo real declara 0 - ele é CSS Modules. Nas três populações isso é
|
|
2114
|
+
* ruído no tamanho do censo. O teto de `MAX_GLOBAL_CLASSES` existe para a folha de um app que
|
|
2115
|
+
* escreva utilitário por atacado, e quando ele corta, o número cortado é DITO em vez de
|
|
2116
|
+
* desaparecer.
|
|
2117
|
+
*/
|
|
2118
|
+
...(globalClasses.size > 0
|
|
2119
|
+
? {
|
|
2120
|
+
globalClasses: Object.fromEntries([...globalClasses.entries()].slice(0, MAX_GLOBAL_CLASSES)),
|
|
2121
|
+
}
|
|
2122
|
+
: {}),
|
|
2074
2123
|
classStyle,
|
|
2075
2124
|
...(Object.keys(looks).length > 0 ? { looks } : {}),
|
|
2076
2125
|
...(naming.components > 0 ? { naming } : {}),
|
|
@@ -3166,6 +3215,16 @@ export function rootBehaviour(tag, sketch) {
|
|
|
3166
3215
|
export async function resolveReadParts(census, root,
|
|
3167
3216
|
/** `true` numa re-medição: o resumo desta etapa fica para o `sync --full` - ver `say`. */
|
|
3168
3217
|
quiet = false) {
|
|
3218
|
+
/**
|
|
3219
|
+
* AS CLASSES DA FOLHA GLOBAL DELE, DO CENSO - ver `Census.globalClasses`.
|
|
3220
|
+
*
|
|
3221
|
+
* Esta etapa roda depois da medição, e também no `sync` e do lado da plataforma, sobre censo já
|
|
3222
|
+
* guardado - então ela não tem como voltar à folha no disco. Vindo do censo, uma utility que ele
|
|
3223
|
+
* escreve num NÓ FILHO chega ao look: o `LeaderboardRow` põe `text-grad-xp` num `<span>` dentro de
|
|
3224
|
+
* um ternário, e é aqui que aquele nó é lido.
|
|
3225
|
+
*/
|
|
3226
|
+
const globalClasses = new Map(Object.entries(census
|
|
3227
|
+
.globalClasses ?? {}));
|
|
3169
3228
|
const say = quiet
|
|
3170
3229
|
? () => { }
|
|
3171
3230
|
: (line) => {
|
|
@@ -3278,13 +3337,13 @@ quiet = false) {
|
|
|
3278
3337
|
? resolveAnatomy(framed, declared, deps, resolveRef, entry?.root,
|
|
3279
3338
|
// The component's own sketch, so a node naming itself by index reads
|
|
3280
3339
|
// the exact class string the census measured (dono, 01/08).
|
|
3281
|
-
looks[component]?.sketch, census.sxSpacing)
|
|
3340
|
+
looks[component]?.sketch, census.sxSpacing, globalClasses)
|
|
3282
3341
|
: patched
|
|
3283
|
-
? resolveAnatomy(patched.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing)
|
|
3342
|
+
? resolveAnatomy(patched.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing, globalClasses)
|
|
3284
3343
|
: derived && derived.read.length > 0
|
|
3285
|
-
? resolveAnatomy(derived.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing)
|
|
3344
|
+
? resolveAnatomy(derived.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing, globalClasses)
|
|
3286
3345
|
: Array.isArray(entry?.parts) && entry.parts.length > 0
|
|
3287
|
-
? resolveFlatParts(entry.parts, declared)
|
|
3346
|
+
? resolveFlatParts(entry.parts, declared, globalClasses)
|
|
3288
3347
|
: null;
|
|
3289
3348
|
if (!resolved)
|
|
3290
3349
|
continue;
|
|
@@ -3699,6 +3758,23 @@ export async function runImport(opts) {
|
|
|
3699
3758
|
const out = opts.census ?? join(root, "_synthesisui", "census.json");
|
|
3700
3759
|
await mkdir(dirname(out), { recursive: true });
|
|
3701
3760
|
await writeFile(out, `${JSON.stringify(census, null, 2)}\n`, "utf8");
|
|
3761
|
+
/**
|
|
3762
|
+
* O QUE NÃO CHEGOU, DECLARADO PELA ESTEIRA - ver `notExpressed`.
|
|
3763
|
+
*
|
|
3764
|
+
* Escrito ao lado do censo e na mesma rodada, porque a lacuna e a medição são a mesma verdade
|
|
3765
|
+
* vista dos dois lados. Era o agente que escrevia este arquivo, instruído por uma frase dentro da
|
|
3766
|
+
* descrição de uma tool - e em 23/08 o mesmo repositório foi importado duas vezes, a primeira
|
|
3767
|
+
* declarando 37 linhas de lacuna e a segunda nenhuma, com as lacunas todas no lugar. Um pedido a
|
|
3768
|
+
* um modelo não é um artefato.
|
|
3769
|
+
*
|
|
3770
|
+
* O agente ainda ACRESCENTA o que só ele vê. O piso deixou de depender dele.
|
|
3771
|
+
*/
|
|
3772
|
+
const gaps = notExpressed(census);
|
|
3773
|
+
if (gaps) {
|
|
3774
|
+
const gapFile = join(dirname(out), "not-expressed.md");
|
|
3775
|
+
await writeFile(gapFile, gaps, "utf8");
|
|
3776
|
+
console.log(body(`Gaps written to ${paint.strong(relative(root, gapFile))}`));
|
|
3777
|
+
}
|
|
3702
3778
|
console.log("");
|
|
3703
3779
|
console.log(body(`Written to ${paint.strong(relative(root, out))}`));
|
|
3704
3780
|
if (opts.dry) {
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { frontierKind } from "../frontier-kind.js";
|
|
21
21
|
import { importMap } from "./imports.js";
|
|
22
|
+
/** Quantos lugares viajam por referência quebrada. Ver `BrokenRef.at`. */
|
|
23
|
+
const MAX_PLACES = 3;
|
|
22
24
|
/** `var(--x)`, including inside a Tailwind arbitrary value: `bg-[var(--x)]`. */
|
|
23
25
|
const VAR_REF = /var\(\s*(--[a-zA-Z0-9_-]+)/g;
|
|
24
26
|
/**
|
|
@@ -96,9 +98,20 @@ export function findBrokenRefs(sources, declared) {
|
|
|
96
98
|
continue;
|
|
97
99
|
if (runtime && (RUNTIME_ANCHOR.has(name) || RUNTIME_PREFIX.test(name)))
|
|
98
100
|
continue;
|
|
99
|
-
const hit = seen.get(name) ?? {
|
|
101
|
+
const hit = seen.get(name) ?? {
|
|
102
|
+
count: 0,
|
|
103
|
+
files: new Set(),
|
|
104
|
+
at: [],
|
|
105
|
+
};
|
|
100
106
|
hit.count += 1;
|
|
101
107
|
hit.files.add(file);
|
|
108
|
+
/** A linha contada do índice do match - ver `BrokenRef.at` para o teto. */
|
|
109
|
+
if (hit.at.length < MAX_PLACES) {
|
|
110
|
+
hit.at.push({
|
|
111
|
+
file,
|
|
112
|
+
line: source.slice(0, m.index ?? 0).split("\n").length,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
102
115
|
seen.set(name, hit);
|
|
103
116
|
}
|
|
104
117
|
}
|
|
@@ -111,6 +124,7 @@ export function findBrokenRefs(sources, declared) {
|
|
|
111
124
|
count: hit.count,
|
|
112
125
|
files: hit.files.size,
|
|
113
126
|
...(meant ? { meant } : {}),
|
|
127
|
+
...(hit.at.length > 0 ? { at: hit.at } : {}),
|
|
114
128
|
});
|
|
115
129
|
}
|
|
116
130
|
// A broken reference somebody typed nine times is worse than one typed once,
|
|
@@ -61,6 +61,8 @@ const PROVIDER_SUFFIX = /(Provider|Providers|Context|Boundary|Guard|Wrapper)$/;
|
|
|
61
61
|
/** Any JSX at all in the file. A capitalised export with none is a constant, a
|
|
62
62
|
* config object or a helper - `export const ROUTES = {…}` is not a component. */
|
|
63
63
|
const HAS_JSX = /<[A-Za-z][^>]*>|<>/;
|
|
64
|
+
/** `ICON_SIZE`, `AURORA_PALETTES`, `ARTICLE_TAB` - a convenção universal para valor, não para peça. */
|
|
65
|
+
const SCREAMING_SNAKE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/;
|
|
64
66
|
/**
|
|
65
67
|
* Any sign that this file decides how something LOOKS.
|
|
66
68
|
*
|
|
@@ -71,7 +73,30 @@ const HAS_JSX = /<[A-Za-z][^>]*>|<>/;
|
|
|
71
73
|
const HAS_STYLING = /className|class=|style=|styled[.(]|css`|cva\(|\btv\(|makeStyles|sx=|tw`/;
|
|
72
74
|
/** `createContext` + a `.Provider` in the return is a provider whatever it is
|
|
73
75
|
* called - `RootWrapper` and `CommonProviders` both read this way. */
|
|
74
|
-
|
|
76
|
+
/**
|
|
77
|
+
* QUEM RECEBE O `createContext` - o nome, e não o arquivo em volta dele.
|
|
78
|
+
*
|
|
79
|
+
* O QUE O CLIENTE PERDIA: o `Tabs` inteiro. `Tabs.tsx` declara `const TabsCtx = createContext(…)`, e
|
|
80
|
+
* a pergunta era se o ARQUIVO tem contexto - então `Tabs`, `TabsList`, `TabsTrigger` e `TabsPanel`
|
|
81
|
+
* saíram juntos, com `cn("flex flex-col gap-4")` escrito neles (`codelevel-ui`, 23/08). Composto com
|
|
82
|
+
* contexto é o padrão dominante de React - Tabs, Accordion, Select, Dropdown -, então recusar o
|
|
83
|
+
* arquivo recusa a família toda.
|
|
84
|
+
*
|
|
85
|
+
* É a mesma falha de forma que `HAS_JSX` já cometeu do outro lado: uma pergunta sobre o arquivo
|
|
86
|
+
* respondendo sobre o export. Um provider é UM nome; os vizinhos respondem por si.
|
|
87
|
+
*
|
|
88
|
+
* Medido em três populações - `codelevel-ui` recupera 4, o dashboard do `frontend-hub` recupera 7
|
|
89
|
+
* (o `Pagination` inteiro), e os 6 que terminam em `Provider` continuam fora pelo sufixo.
|
|
90
|
+
*/
|
|
91
|
+
const CONTEXT_OWNER = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]*)?=\s*(?:React\.)?createContext\s*[<(]/g;
|
|
92
|
+
/** Os nomes que ESTE arquivo atribui a um `createContext`. */
|
|
93
|
+
function contextNames(source) {
|
|
94
|
+
CONTEXT_OWNER.lastIndex = 0;
|
|
95
|
+
const out = new Set();
|
|
96
|
+
for (const m of source.matchAll(CONTEXT_OWNER))
|
|
97
|
+
out.add(m[1]);
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
75
100
|
/** A lowercase JSX tag: an html element this file renders ITSELF. */
|
|
76
101
|
const OWN_ELEMENT = /<(?:[a-z][a-z0-9]*)(\s|>|\/)/;
|
|
77
102
|
/** Every capitalised JSX tag the file opens, dots included. */
|
|
@@ -205,7 +230,7 @@ export function gateComponent(input) {
|
|
|
205
230
|
because: `\`${name}\` names a screen, and composes ${shape.composes} piece${shape.composes === 1 ? "" : "s"} across ${shape.nodes} element${shape.nodes === 1 ? "" : "s"} - too thin to be a template of yours. A design system is made of the pieces a screen is built FROM`,
|
|
206
231
|
};
|
|
207
232
|
}
|
|
208
|
-
if (PROVIDER_SUFFIX.test(name) ||
|
|
233
|
+
if (PROVIDER_SUFFIX.test(name) || contextNames(source).has(name)) {
|
|
209
234
|
return {
|
|
210
235
|
ok: false,
|
|
211
236
|
why: "provider",
|
|
@@ -232,6 +257,38 @@ export function gateComponent(input) {
|
|
|
232
257
|
*/
|
|
233
258
|
if (STYLED_FACTORY.test(source))
|
|
234
259
|
return { ok: true };
|
|
260
|
+
/**
|
|
261
|
+
* UMA CONSTANTE NÃO É COMPONENTE, e o arquivo em volta dela não muda isso.
|
|
262
|
+
*
|
|
263
|
+
* O QUE O CLIENTE VIA: `ICON_SIZE` e `AURORA_PALETTES` ocupando vaga na vitrine do
|
|
264
|
+
* `codelevel-ui`, e uma delas com forma desenhada - um mapa de tamanhos renderizado como se fosse
|
|
265
|
+
* peça. Ele contava 59 componentes no design system dele e dois não existiam.
|
|
266
|
+
*
|
|
267
|
+
* O teste de markup logo abaixo já recusa constante, e ele pergunta pelo ARQUIVO INTEIRO:
|
|
268
|
+
* `ICON_SIZE` mora dentro do `Icon.tsx`, que tem JSX de sobra, então ela entrava pela porta do
|
|
269
|
+
* vizinho. Vem ANTES dele por isso - é a mesma falha de forma que o teste de contexto acima
|
|
270
|
+
* corrigiu do outro lado.
|
|
271
|
+
*
|
|
272
|
+
* MEDIDO EM QUATRO POPULAÇÕES, e as três encontradas são objeto literal no código real:
|
|
273
|
+
*
|
|
274
|
+
* codelevel-ui packages/ui 75 nomes · 2 (ICON_SIZE, AURORA_PALETTES)
|
|
275
|
+
* frontend-hub packages/ui 69 nomes · 0
|
|
276
|
+
* frontend-hub apps/web-dashboard 526 nomes · 1 (ARTICLE_TAB, um enum de aba)
|
|
277
|
+
* synthesisui apps/web · 0
|
|
278
|
+
*
|
|
279
|
+
* Nenhum componente das quatro usa este formato de nome - JSX escreve `<Button>` e nunca
|
|
280
|
+
* `<ICON_SIZE>` -, então a regra não recusa peça de ninguém. E ela só pôde entrar junto com o
|
|
281
|
+
* leitor de `export default` em linha própria: sozinha, ela deixava órfãs as 294 declarações de
|
|
282
|
+
* CSS Modules do único arquivo do dashboard onde a constante era a ÚNICA coisa que o leitor via.
|
|
283
|
+
* O dono daquele arquivo é o `CurateContentSection`, que agora é lido.
|
|
284
|
+
*/
|
|
285
|
+
if (SCREAMING_SNAKE.test(name)) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
why: "config",
|
|
289
|
+
because: `\`${name}\` is spelled as a constant - a value the code reads, not a component anything renders`,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
235
292
|
if (!HAS_JSX.test(source)) {
|
|
236
293
|
return {
|
|
237
294
|
ok: false,
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* and both branches naming a class in this file. Exactly one is a ternary between two
|
|
39
39
|
* string literals. So this reader covers a family I had written off as impossible.
|
|
40
40
|
*/
|
|
41
|
+
import { tokenRefFor } from "../token-ref.js";
|
|
41
42
|
/** A CSS pseudo-class or attribute, mapped to the state name the contract uses. */
|
|
42
43
|
const STATE_OF = [
|
|
43
44
|
[/:hover\b/, "hover"],
|
|
@@ -58,8 +59,19 @@ const STATE_OF = [
|
|
|
58
59
|
[/:last-child\b/, "last"],
|
|
59
60
|
[/:empty\b/, "empty"],
|
|
60
61
|
];
|
|
61
|
-
/**
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* `[data-theme="dark"]`, `.dark`, `html.dark`, `[data-scheme=dark]` - como um projeto diz escuro.
|
|
64
|
+
*
|
|
65
|
+
* O QUE O CLIENTE PERDIA: o esquema escuro escrito na folha dele. O padrão exigia que `.dark`
|
|
66
|
+
* viesse no início do seletor ou depois de um espaço, e ele escreve `html.dark .stroke-text` - a
|
|
67
|
+
* classe colada na tag, que é a forma que o Tailwind com `darkMode: "class"` produz quando o
|
|
68
|
+
* atributo vive no `<html>`. Cinco classes da folha dele caíam nisso, entre elas o `stroke-text` e o
|
|
69
|
+
* `holo-card`, e a regra escura delas era lida como se fosse a regra de repouso: o contorno claro do
|
|
70
|
+
* texto vinha pintado com a cor do tema escuro.
|
|
71
|
+
*
|
|
72
|
+
* `(?![\w-])` para não casar `.darkroom`, que é uma classe de alguém e não um esquema.
|
|
73
|
+
*/
|
|
74
|
+
const DARK = /\[data-theme=["']?dark["']?\]|\[data-scheme=["']?dark["']?\]|(?:^|[\s>+~]|[a-zA-Z0-9\]])\.dark(?![\w-])/;
|
|
63
75
|
/** `kebab-case` → `camelCase`, which is the only spelling the contract accepts. */
|
|
64
76
|
export function camel(prop) {
|
|
65
77
|
return prop.trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -274,7 +286,21 @@ export function readModuleCss(css) {
|
|
|
274
286
|
const selector = one.trim();
|
|
275
287
|
if (!selector)
|
|
276
288
|
continue;
|
|
277
|
-
|
|
289
|
+
/**
|
|
290
|
+
* O SELETOR DE ESQUEMA NÃO É UM ANCESTRAL DE ANATOMIA - e era registrado como um.
|
|
291
|
+
*
|
|
292
|
+
* `html.dark .stroke-text` tem duas classes, e a leitura tomava `dark` como PAI de
|
|
293
|
+
* `stroke-text`. Duas consequências, as duas na tela dele: a classe entrava na lista de
|
|
294
|
+
* "filho que só existe num contexto" e era descartada inteira - `stroke-text`,
|
|
295
|
+
* `stroke-text-soft`, `stroke-text-grad`, `stage-3d` e `holo-card`, cinco das 24 da folha
|
|
296
|
+
* dele -, e a árvore ganhava um nó chamado `dark` que não existe em marcação nenhuma.
|
|
297
|
+
*
|
|
298
|
+
* `.dark` é COMO o projeto diz escuro, não onde um elemento mora. Tirado dos nomes antes de
|
|
299
|
+
* decidir alvo e pai; o `isDark` abaixo continua lendo o seletor inteiro e mandando o bloco
|
|
300
|
+
* para `dark`, que é onde ele pertence.
|
|
301
|
+
*/
|
|
302
|
+
const scheme = DARK.test(selector);
|
|
303
|
+
const names = classesIn(selector).filter((n) => !(scheme && (n === "dark" || n === "light")));
|
|
278
304
|
if (names.length === 0) {
|
|
279
305
|
// An element or `:root` selector styles something this reader cannot attach to
|
|
280
306
|
// a component. Said out loud rather than dropped.
|
|
@@ -283,6 +309,9 @@ export function readModuleCss(css) {
|
|
|
283
309
|
}
|
|
284
310
|
const target = names[names.length - 1];
|
|
285
311
|
const entry = out.classes[target] ?? emptyClass();
|
|
312
|
+
/** Alvo sozinho no seletor: é regra dela, e não regra dentro de outra. Ver `ModuleClass.own`. */
|
|
313
|
+
if (names.length === 1)
|
|
314
|
+
entry.own = true;
|
|
286
315
|
out.classes[target] = entry;
|
|
287
316
|
// A descendant selector records the RELATION as well as the styles: `.panel
|
|
288
317
|
// .title` says title lives inside panel, which is the anatomy for free.
|
|
@@ -296,7 +325,7 @@ export function readModuleCss(css) {
|
|
|
296
325
|
const block = declarations(rule.body);
|
|
297
326
|
if (Object.keys(block).length === 0)
|
|
298
327
|
continue;
|
|
299
|
-
const isDark =
|
|
328
|
+
const isDark = scheme;
|
|
300
329
|
const state = STATE_OF.find(([re]) => re.test(selector))?.[1] ?? null;
|
|
301
330
|
const width = minWidthOf(rule.media);
|
|
302
331
|
if (width) {
|
|
@@ -526,7 +555,7 @@ export function transcribeModule(read, usage, declared) {
|
|
|
526
555
|
const resolve = (block) => {
|
|
527
556
|
const out = {};
|
|
528
557
|
for (const [prop, value] of Object.entries(block)) {
|
|
529
|
-
out[prop] = value.replace(/var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]*)?\)/g, (whole, name) => declared.has(name) ? tokenRefFor(name) : whole);
|
|
558
|
+
out[prop] = value.replace(/var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]*)?\)/g, (whole, name) => declared.has(name) ? tokenRefFor(name, declared) : whole);
|
|
530
559
|
}
|
|
531
560
|
return out;
|
|
532
561
|
};
|
|
@@ -645,44 +674,6 @@ function formFor(tag) {
|
|
|
645
674
|
* A declared custom property as a token ref in the document's namespace. Their name is
|
|
646
675
|
* the path, which is "your names travel unchanged" one level deeper.
|
|
647
676
|
*/
|
|
648
|
-
function tokenRefFor(name) {
|
|
649
|
-
const bare = name.replace(/^--/, "");
|
|
650
|
-
if (bare.startsWith("color-")) {
|
|
651
|
-
const rest = bare.slice("color-".length);
|
|
652
|
-
const m = /^(.*)-(\d{2,4})$/.exec(rest);
|
|
653
|
-
return m ? `{color.${m[1]}.${m[2]}}` : `{color.${rest}}`;
|
|
654
|
-
}
|
|
655
|
-
if (bare.startsWith("radius-"))
|
|
656
|
-
return `{radius.${bare.slice(7)}}`;
|
|
657
|
-
if (bare.startsWith("spacing-"))
|
|
658
|
-
return `{spacing.${bare.slice(8)}}`;
|
|
659
|
-
if (bare.startsWith("shadow-"))
|
|
660
|
-
return `{shadow.${bare.slice(7)}}`;
|
|
661
|
-
if (bare.startsWith("background-image-gradient-")) {
|
|
662
|
-
return `{gradients.${bare.slice("background-image-gradient-".length)}}`;
|
|
663
|
-
}
|
|
664
|
-
if (bare.startsWith("gradient-"))
|
|
665
|
-
return `{gradients.${bare.slice(9)}}`;
|
|
666
|
-
if (bare.startsWith("text-")) {
|
|
667
|
-
// The companion token: `--text-body-s--line-height` names the line-height OF a
|
|
668
|
-
// step, and a double dash inside a ref is a grammar nothing accepts (test13).
|
|
669
|
-
const companion = /^text-(.+?)--(line-height|letter-spacing|font-weight)$/.exec(bare);
|
|
670
|
-
if (companion) {
|
|
671
|
-
const prop = {
|
|
672
|
-
"line-height": "lineHeight",
|
|
673
|
-
"letter-spacing": "letterSpacing",
|
|
674
|
-
"font-weight": "weight",
|
|
675
|
-
}[companion[2]];
|
|
676
|
-
return `{typography.scale.${companion[1]}.${prop}}`;
|
|
677
|
-
}
|
|
678
|
-
if (bare.slice(5).includes("--"))
|
|
679
|
-
return `var(${name})`;
|
|
680
|
-
return `{typography.scale.${bare.slice(5)}.fontSize}`;
|
|
681
|
-
}
|
|
682
|
-
if (bare.startsWith("font-"))
|
|
683
|
-
return `{typography.families.${bare.slice(5)}}`;
|
|
684
|
-
return `var(${name})`;
|
|
685
|
-
}
|
|
686
677
|
/**
|
|
687
678
|
* AS REGRAS DE CLASSE DE UMA FOLHA GLOBAL, prontas para o componente que as veste.
|
|
688
679
|
*
|
|
@@ -700,21 +691,60 @@ function tokenRefFor(name) {
|
|
|
700
691
|
* descendente (`.a .b` - o contexto é do pai) e seletor composto, que `readModuleCss`
|
|
701
692
|
* já reporta em `unslotted`.
|
|
702
693
|
*/
|
|
694
|
+
/**
|
|
695
|
+
* `@utility x { … }` LIDA COMO `.x { … }` - a porta que o Tailwind v4 abriu, e nada mais.
|
|
696
|
+
*
|
|
697
|
+
* O QUE O CLIENTE PERDIA: as utilities ASSINATURA dele. No `codelevel-ui` são 23, e entre elas
|
|
698
|
+
* `text-grad`, `text-grad-fire`, `text-grad-xp` e `text-grad-gold` - os gradientes de texto que dão
|
|
699
|
+
* a cara do produto. O componente que escreve `text-grad` viajava sem o gradiente e o título dele
|
|
700
|
+
* chegava em cor lisa; das 198 declarações do `globals.css` que não alcançavam receita nenhuma, é
|
|
701
|
+
* aqui que a maior parte morava.
|
|
702
|
+
*
|
|
703
|
+
* É NORMALIZAÇÃO DE SINTAXE, E NÃO MOTOR NOVO. Uma `@utility` é uma classe com outro nome de porta:
|
|
704
|
+
* mesmo corpo, mesmas declarações, mesmo cascade. A máquina que resolve o `var()` dele para o token
|
|
705
|
+
* que ELE nomeou, que lê estado e `dark`, e que veste a classe na raiz do componente já existe
|
|
706
|
+
* inteira - só não conhecia a porta.
|
|
707
|
+
*
|
|
708
|
+
* MEDIDO EM TRÊS POPULAÇÕES: `codelevel-ui` 23 em 6 folhas, `synthesisui/web` 7 em 1 folha
|
|
709
|
+
* (`bg-ember`, `text-ember`, `mask-fade-x`…), `frontend-hub` 0 em 722 - ele é CSS Modules e não usa
|
|
710
|
+
* a sintaxe. Duas populações independentes com a forma, e uma sem, que é o que prova que a régua não
|
|
711
|
+
* assume o formato de um repositório só.
|
|
712
|
+
*
|
|
713
|
+
* A FUNCIONAL FICA DE FORA, e é o único caso: `@utility tab-* { tab-size: --value(integer) }` tem
|
|
714
|
+
* molde no lugar do nome, então não há classe fixa que uma marcação possa vestir. Nenhuma das 30
|
|
715
|
+
* medidas é funcional, e quando uma for, ela cai nas sobras com arquivo e linha, que é a resposta
|
|
716
|
+
* honesta.
|
|
717
|
+
*/
|
|
718
|
+
function asClasses(body) {
|
|
719
|
+
return body.replace(/@utility\s+([a-zA-Z][\w-]*)(\s*\{)/g, (_whole, name, brace) => `.${name}${brace}`);
|
|
720
|
+
}
|
|
703
721
|
export function readGlobalClasses(globals, declared) {
|
|
704
722
|
const resolve = (block) => {
|
|
705
723
|
const out = {};
|
|
706
724
|
for (const [prop, value] of Object.entries(block)) {
|
|
707
|
-
out[prop] = value.replace(/var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]*)?\)/g, (whole, name) => declared.has(name) ? tokenRefFor(name) : whole);
|
|
725
|
+
out[prop] = value.replace(/var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]*)?\)/g, (whole, name) => declared.has(name) ? tokenRefFor(name, declared) : whole);
|
|
708
726
|
}
|
|
709
727
|
return out;
|
|
710
728
|
};
|
|
711
729
|
const out = new Map();
|
|
712
730
|
for (const sheet of globals) {
|
|
713
|
-
const read = readModuleCss(sheet.body);
|
|
731
|
+
const read = readModuleCss(asClasses(sheet.body));
|
|
714
732
|
const dependent = new Set(Object.values(read.classes).flatMap((c) => c.children));
|
|
715
733
|
for (const [name, cls] of Object.entries(read.classes)) {
|
|
716
|
-
/**
|
|
717
|
-
|
|
734
|
+
/**
|
|
735
|
+
* O filho de `.a .b` só existe naquele contexto - vesti-lo solto pintaria errado. MAS uma
|
|
736
|
+
* classe que também tem REGRA PRÓPRIA não é um filho: é uma classe com variação contextual, e
|
|
737
|
+
* descartá-la joga fora a regra base junto.
|
|
738
|
+
*
|
|
739
|
+
* O QUE ISSO CUSTAVA: cinco das 24 classes da folha dele, entre elas `stroke-text` e
|
|
740
|
+
* `holo-card` - o contorno de texto e a borda holográfica. A causa era uma linha de tema,
|
|
741
|
+
* `html.dark .stroke-text { … }`, que fazia a classe aparecer como filha em algum lugar.
|
|
742
|
+
*
|
|
743
|
+
* O PADRÃO É UNIVERSAL, e é isso que torna o conserto necessário e não específico: `html.dark
|
|
744
|
+
* .x` é como se escreve tema em CSS e `.parent:hover .child` é como se escreve estado. Medido
|
|
745
|
+
* em duas folhas independentes - 5 de 24 na dele, 2 de 22 na nossa.
|
|
746
|
+
*/
|
|
747
|
+
if (dependent.has(name) && !cls.own)
|
|
718
748
|
continue;
|
|
719
749
|
const states = {};
|
|
720
750
|
for (const [state, block] of Object.entries(cls.states))
|
|
@@ -51,7 +51,50 @@ export function tierOf(file) {
|
|
|
51
51
|
return undefined;
|
|
52
52
|
}
|
|
53
53
|
/** `export function X`, `export default function X`, `export const X =`. */
|
|
54
|
-
const EXPORTED = /export\s+(?:default\s+)?(?:async\s+)?(?:function\s+([A-Z][A-Za-z0-9_]*)|const\s+([A-Z][A-Za-z0-9_]*)\s*[:=])/g;
|
|
54
|
+
const EXPORTED = /export\s+(?:default\s+)?(?:async\s+)?(?:function\s+([A-Z][A-Za-z0-9_]*)|const\s+([A-Z][A-Za-z0-9_]*)\s*[:=]|class\s+([A-Z][A-Za-z0-9_]*))/g;
|
|
55
|
+
/**
|
|
56
|
+
* O `export default` QUE NOMEIA A PEÇA EM OUTRA LINHA - e é assim que metade da indústria escreve.
|
|
57
|
+
*
|
|
58
|
+
* O QUE O CLIENTE PERDIA: 108 componentes do dashboard dele, o kit de mídia inteiro entre eles
|
|
59
|
+
* (`Media`, `MediaHeader`, `MediaTitle`, `MediaFooter`, `MediaDescription`), mais o `SelectItem` e o
|
|
60
|
+
* `CurateContentSection`. Nenhum no censo, nenhum aviso: o leitor exigia a palavra `export` na MESMA
|
|
61
|
+
* linha da declaração. `export default function Media()` era lido, e
|
|
62
|
+
* `function Media() {…}` + `export default Media;` no fim do arquivo, não - a mesma peça, com a
|
|
63
|
+
* exportação numa linha própria.
|
|
64
|
+
*
|
|
65
|
+
* MEDIDO EM QUATRO POPULAÇÕES, e é o que decide o que cobrir:
|
|
66
|
+
*
|
|
67
|
+
* forma codelevel fh/ui fh/dashboard synthesisui
|
|
68
|
+
* export default function Name 0 0 519 73
|
|
69
|
+
* export default Name; 0 0 110 0
|
|
70
|
+
* export default memo/forwardRef(…) 0 0 1 0
|
|
71
|
+
* export { Name as default } 0 0 0 0
|
|
72
|
+
* export default class Name 0 0 0 0
|
|
73
|
+
* export default () => / function() 0 0 0 0
|
|
74
|
+
*
|
|
75
|
+
* As formas com zero entram de propósito: são o que a LINGUAGEM oferece, e uma régua que só cobre a
|
|
76
|
+
* forma do repositório que eu tenho na mão é uma régua que quebra no próximo cliente.
|
|
77
|
+
*
|
|
78
|
+
* O ANÔNIMO FICA DE FORA, declarado: em `export default () => …` não existe nome para ler. O único
|
|
79
|
+
* candidato seria o nome do arquivo, e batizar peça alheia é supor - o que este censo não faz.
|
|
80
|
+
*/
|
|
81
|
+
const DEFAULT_NAMED = [
|
|
82
|
+
/** `export default Media;` e `export default memo(forwardRef(Media))`, com ou sem `React.` */
|
|
83
|
+
/export\s+default\s+(?:(?:React\.)?(?:memo|forwardRef)\s*\(\s*)*([A-Z][A-Za-z0-9_]*)/g,
|
|
84
|
+
/** `export { Media as default }` - a forma canônica do ES, e ela aceita vizinhos na mesma chave */
|
|
85
|
+
/export\s*\{[^}]*?\b([A-Z][A-Za-z0-9_]*)\s+as\s+default\b/g,
|
|
86
|
+
];
|
|
87
|
+
/**
|
|
88
|
+
* O NOME PRECISA SER DECLARADO AQUI, e é isto que separa uma definição de um repasse.
|
|
89
|
+
*
|
|
90
|
+
* `import Button from "@acme/ui"; export default Button;` é um re-export: a peça é de outra pessoa e
|
|
91
|
+
* reivindicá-la faria o censo listar como dele um componente que ele só encaminha. A pergunta é se
|
|
92
|
+
* ESTE arquivo constrói o nome - `function X`, `const X =`, `class X`.
|
|
93
|
+
*/
|
|
94
|
+
function declaresName(source, name) {
|
|
95
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
96
|
+
return new RegExp(`(?:^|\\n)\\s*(?:export\\s+)?(?:default\\s+)?(?:async\\s+)?(?:function|class|const|let|var)\\s+${escaped}\\b`).test(source);
|
|
97
|
+
}
|
|
55
98
|
/** `type ButtonProps = { … }` or `interface ButtonProps { … }`, to its closing
|
|
56
99
|
* brace, wherever it sits.
|
|
57
100
|
*
|
|
@@ -102,8 +145,21 @@ export function scanDefinitions(file, source) {
|
|
|
102
145
|
const out = [];
|
|
103
146
|
const seen = new Set();
|
|
104
147
|
EXPORTED.lastIndex = 0;
|
|
105
|
-
|
|
106
|
-
|
|
148
|
+
/**
|
|
149
|
+
* A EXPORTAÇÃO EM LINHA PRÓPRIA ENTRA NA MESMA FILA - ver `DEFAULT_NAMED`.
|
|
150
|
+
*
|
|
151
|
+
* Reunidas antes do laço para que a deduplicação por `seen` valha para as duas origens: um
|
|
152
|
+
* arquivo que escreve `export default function Card()` casa nos dois padrões e a peça é uma só.
|
|
153
|
+
*/
|
|
154
|
+
const names = [...source.matchAll(EXPORTED)].map((m) => m[1] ?? m[2] ?? m[3] ?? "");
|
|
155
|
+
for (const rx of DEFAULT_NAMED) {
|
|
156
|
+
rx.lastIndex = 0;
|
|
157
|
+
for (const m of source.matchAll(rx)) {
|
|
158
|
+
if (m[1] && declaresName(source, m[1]))
|
|
159
|
+
names.push(m[1]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const name of names) {
|
|
107
163
|
if (!name || seen.has(name))
|
|
108
164
|
continue;
|
|
109
165
|
seen.add(name);
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* hover:shadow-md → states.hover
|
|
43
43
|
* data-[checked]:bg-ocean-50 → states.checked
|
|
44
44
|
*/
|
|
45
|
+
import { tokenRefFor } from "../token-ref.js";
|
|
45
46
|
/**
|
|
46
47
|
* Utility prefix → the property name THE DOCUMENT USES, which is camelCase.
|
|
47
48
|
*
|
|
@@ -1218,7 +1219,11 @@ function readUtilityCore(utility, declared) {
|
|
|
1218
1219
|
if (themeProperty && rest) {
|
|
1219
1220
|
const own = `--${prefix}-${rest}`;
|
|
1220
1221
|
if (declared.has(own)) {
|
|
1221
|
-
return {
|
|
1222
|
+
return {
|
|
1223
|
+
property: themeProperty,
|
|
1224
|
+
value: tokenRefFor(own, declared),
|
|
1225
|
+
token: own,
|
|
1226
|
+
};
|
|
1222
1227
|
}
|
|
1223
1228
|
}
|
|
1224
1229
|
return null;
|
|
@@ -1310,7 +1315,7 @@ function resolveArbitrary(inner, declared) {
|
|
|
1310
1315
|
if (raw.startsWith("--")) {
|
|
1311
1316
|
const name = raw.split(",")[0].trim();
|
|
1312
1317
|
return declared.has(name)
|
|
1313
|
-
? { value: tokenRefFor(name), token: name }
|
|
1318
|
+
? { value: tokenRefFor(name, declared), token: name }
|
|
1314
1319
|
: null;
|
|
1315
1320
|
}
|
|
1316
1321
|
// A literal in brackets is still a real value somebody typed.
|
|
@@ -1318,7 +1323,7 @@ function resolveArbitrary(inner, declared) {
|
|
|
1318
1323
|
}
|
|
1319
1324
|
const name = v[1];
|
|
1320
1325
|
if (declared.has(name))
|
|
1321
|
-
return { value: tokenRefFor(name), token: name };
|
|
1326
|
+
return { value: tokenRefFor(name, declared), token: name };
|
|
1322
1327
|
/**
|
|
1323
1328
|
* A `var()` THEY NEVER DECLARED, WHEN THEY DECLARED IT UNDER ITS NAMESPACE.
|
|
1324
1329
|
*
|
|
@@ -1338,7 +1343,7 @@ function resolveArbitrary(inner, declared) {
|
|
|
1338
1343
|
*/
|
|
1339
1344
|
const meant = MEANT_NAMESPACES.map((ns) => `--${ns}-${name.slice(2)}`).find((candidate) => declared.has(candidate));
|
|
1340
1345
|
if (meant)
|
|
1341
|
-
return { value: tokenRefFor(meant), token: meant };
|
|
1346
|
+
return { value: tokenRefFor(meant, declared), token: meant };
|
|
1342
1347
|
return { value: raw };
|
|
1343
1348
|
}
|
|
1344
1349
|
/**
|
|
@@ -1362,53 +1367,6 @@ const MEANT_NAMESPACES = [
|
|
|
1362
1367
|
* `--text-body-s` → `{typography.scale.body-s.fontSize}`. Their name is the path,
|
|
1363
1368
|
* which is the whole "your names travel unchanged" promise applied one level deeper.
|
|
1364
1369
|
*/
|
|
1365
|
-
function tokenRefFor(name) {
|
|
1366
|
-
const bare = name.replace(/^--/, "");
|
|
1367
|
-
if (bare.startsWith("color-"))
|
|
1368
|
-
return refFor(bare.slice("color-".length));
|
|
1369
|
-
if (bare.startsWith("radius-"))
|
|
1370
|
-
return `{radius.${bare.slice(7)}}`;
|
|
1371
|
-
if (bare.startsWith("spacing-"))
|
|
1372
|
-
return `{spacing.${bare.slice(8)}}`;
|
|
1373
|
-
if (bare.startsWith("shadow-"))
|
|
1374
|
-
return `{shadow.${bare.slice(7)}}`;
|
|
1375
|
-
// `--gradient-ui` and `--background-image-gradient-ui` are one token in two
|
|
1376
|
-
// spellings - Tailwind v4's utility namespace wraps the first. Both reach
|
|
1377
|
-
// `{gradients.ui}`, which is where the census files them.
|
|
1378
|
-
if (bare.startsWith("background-image-gradient-")) {
|
|
1379
|
-
return `{gradients.${bare.slice("background-image-gradient-".length)}}`;
|
|
1380
|
-
}
|
|
1381
|
-
if (bare.startsWith("gradient-"))
|
|
1382
|
-
return `{gradients.${bare.slice(9)}}`;
|
|
1383
|
-
if (bare.startsWith("text-")) {
|
|
1384
|
-
/**
|
|
1385
|
-
* THE COMPANION TOKEN. Tailwind v4 spells "the line-height OF text-body-s" as
|
|
1386
|
-
* `--text-body-s--line-height` - a double dash inside one name. Read as a step name
|
|
1387
|
-
* it produced `{typography.scale.body-s--line-height.fontSize}`, whose double dash
|
|
1388
|
-
* no ref grammar accepts, and the whole recipe was REFUSED at validation (test13,
|
|
1389
|
-
* 01/08). The suffix names the property; the middle names the step.
|
|
1390
|
-
*/
|
|
1391
|
-
const companion = /^text-(.+?)--(line-height|letter-spacing|font-weight)$/.exec(bare);
|
|
1392
|
-
if (companion) {
|
|
1393
|
-
const prop = {
|
|
1394
|
-
"line-height": "lineHeight",
|
|
1395
|
-
"letter-spacing": "letterSpacing",
|
|
1396
|
-
"font-weight": "weight",
|
|
1397
|
-
}[companion[2]];
|
|
1398
|
-
return `{typography.scale.${companion[1]}.${prop}}`;
|
|
1399
|
-
}
|
|
1400
|
-
// Any other double dash is a name this grammar cannot hold - the literal var()
|
|
1401
|
-
// still resolves against their own stylesheet, and an invalid ref helps nobody.
|
|
1402
|
-
if (bare.slice(5).includes("--"))
|
|
1403
|
-
return `var(${name})`;
|
|
1404
|
-
return `{typography.scale.${bare.slice(5)}.fontSize}`;
|
|
1405
|
-
}
|
|
1406
|
-
if (bare.startsWith("font-"))
|
|
1407
|
-
return `{typography.families.${bare.slice(5)}}`;
|
|
1408
|
-
// A namespace we do not model. The literal `var()` is still correct CSS against
|
|
1409
|
-
// their own stylesheet, and inventing a ref would point at nothing.
|
|
1410
|
-
return `var(${name})`;
|
|
1411
|
-
}
|
|
1412
1370
|
function refFor(name) {
|
|
1413
1371
|
const m = /^(.*)-(\d{2,4})$/.exec(name);
|
|
1414
1372
|
if (!m)
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* property resolve in their app by position in the string; a recipe resolves by
|
|
31
31
|
* position in `layers`. They agree only if the reader inserts in the order it read.
|
|
32
32
|
*/
|
|
33
|
+
import { wearGlobal, withGlobal } from "../global-wear.js";
|
|
33
34
|
import { parseClass, transcribe } from "./transcribe.js";
|
|
34
35
|
/**
|
|
35
36
|
* A Tailwind modifier, mapped to the condition it IS.
|
|
@@ -136,8 +137,7 @@ export function conditionOf(modifiers) {
|
|
|
136
137
|
* condição do DOM dele, escrita por extenso - descartá-la porque a nossa tabela não a lista
|
|
137
138
|
* seria a tabela decidindo o que o código dele pode dizer. `lost` fica para o que não dá para
|
|
138
139
|
* ler, não para o que a gente não previu.
|
|
139
|
-
*/
|
|
140
|
-
else if (nome)
|
|
140
|
+
*/ else if (nome)
|
|
141
141
|
out.state = camel(nome);
|
|
142
142
|
else
|
|
143
143
|
out.lost = true;
|
|
@@ -954,7 +954,19 @@ export function readClassTernaries(source) {
|
|
|
954
954
|
* `declared` is their own custom properties, so `bg-ocean-950` comes out as a ref to
|
|
955
955
|
* the token they declared rather than as a hex we looked up.
|
|
956
956
|
*/
|
|
957
|
-
export function transcribeVariants(source, declared
|
|
957
|
+
export function transcribeVariants(source, declared,
|
|
958
|
+
/**
|
|
959
|
+
* AS CLASSES QUE A FOLHA GLOBAL DELE DECLARA - obrigatório, e por um motivo medido.
|
|
960
|
+
*
|
|
961
|
+
* O import vestia a regra global na RAIZ do componente e só ali. Uma utility escrita dentro da
|
|
962
|
+
* TABELA DE VARIANTES - que é onde o `Typography` do `codelevel-ui` põe `text-grad-fire` - vinha
|
|
963
|
+
* por aqui, e este caminho não conhecia folha nenhuma: o título dele saía em cor lisa, e nada
|
|
964
|
+
* dizia que o gradiente tinha sido perdido.
|
|
965
|
+
*
|
|
966
|
+
* Sem default de propósito. Um `Map` vazio é a resposta de quem não tem folha global, e ela se
|
|
967
|
+
* escreve em voz alta; um parâmetro que se pode esquecer é o defeito que isto está desfazendo.
|
|
968
|
+
*/
|
|
969
|
+
globals) {
|
|
958
970
|
const reads = readVariants(source);
|
|
959
971
|
const fromRecords = {};
|
|
960
972
|
const unslotted = [];
|
|
@@ -1048,7 +1060,21 @@ export function transcribeVariants(source, declared) {
|
|
|
1048
1060
|
*/
|
|
1049
1061
|
const bare = layer.classes.map((cls) => parseClass(cls).utility);
|
|
1050
1062
|
const t = transcribe(bare, declared);
|
|
1051
|
-
|
|
1063
|
+
/**
|
|
1064
|
+
* A REGRA GLOBAL QUE ESTA CAMADA VESTE - ver `wearGlobal`.
|
|
1065
|
+
*
|
|
1066
|
+
* Antes da transcrição no objeto final, e não depois: uma utility da folha dele e uma utility
|
|
1067
|
+
* da Tailwind pintando a mesma propriedade resolvem pelo cascade, e o cascade diz que a
|
|
1068
|
+
* última classe escrita ganha. `transcribe` já respeita essa ordem entre as dela; aqui a
|
|
1069
|
+
* folha entra como a camada de baixo, que é onde o CSS a coloca.
|
|
1070
|
+
*/
|
|
1071
|
+
const wornHere = wearGlobal(bare, globals);
|
|
1072
|
+
const style = {
|
|
1073
|
+
...wornHere.base,
|
|
1074
|
+
...wornHere.dark,
|
|
1075
|
+
...t.base,
|
|
1076
|
+
...t.dark,
|
|
1077
|
+
};
|
|
1052
1078
|
return {
|
|
1053
1079
|
...(layer.when ? { when: layer.when } : {}),
|
|
1054
1080
|
...(layer.at ? { at: layer.at } : {}),
|
|
@@ -1063,7 +1089,7 @@ export function transcribeVariants(source, declared) {
|
|
|
1063
1089
|
defaults,
|
|
1064
1090
|
layers,
|
|
1065
1091
|
raw: allLayers,
|
|
1066
|
-
base: transcribe(baseClasses, declared),
|
|
1092
|
+
base: withGlobal(transcribe(baseClasses, declared), baseClasses, globals),
|
|
1067
1093
|
unslotted,
|
|
1068
1094
|
notes,
|
|
1069
1095
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { parseClass } from "./doctor/transcribe.js";
|
|
2
|
+
export function wearGlobal(classes, globals,
|
|
3
|
+
/** Registra quais classes a folha global reivindicou, para o juiz de duplicidade. */
|
|
4
|
+
claimed) {
|
|
5
|
+
const worn = { base: {}, dark: {}, states: {} };
|
|
6
|
+
for (const cls of classes) {
|
|
7
|
+
const rule = globals.get(cls);
|
|
8
|
+
if (!rule)
|
|
9
|
+
continue;
|
|
10
|
+
claimed?.add(cls);
|
|
11
|
+
Object.assign(worn.base, rule.base);
|
|
12
|
+
Object.assign(worn.dark, rule.dark);
|
|
13
|
+
for (const [state, block] of Object.entries(rule.states))
|
|
14
|
+
worn.states[state] = { ...worn.states[state], ...block };
|
|
15
|
+
}
|
|
16
|
+
return worn;
|
|
17
|
+
}
|
|
18
|
+
/** Tem alguma declaração? Uma camada vazia não vira camada. */
|
|
19
|
+
export function wornAnything(worn) {
|
|
20
|
+
return (Object.keys(worn.base).length > 0 ||
|
|
21
|
+
Object.keys(worn.dark).length > 0 ||
|
|
22
|
+
Object.keys(worn.states).length > 0);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A TRANSCRIÇÃO COM A FOLHA GLOBAL DELE POR BAIXO.
|
|
26
|
+
*
|
|
27
|
+
* A folha é a camada de baixo porque é onde o CSS a coloca: uma utility da Tailwind escrita no mesmo
|
|
28
|
+
* elemento ganha da classe da folha quando as duas pintam a mesma propriedade.
|
|
29
|
+
*/
|
|
30
|
+
export function withGlobal(t, classes,
|
|
31
|
+
/** Ausente é a resposta de quem não tem folha global: a transcrição volta intacta. */
|
|
32
|
+
globals) {
|
|
33
|
+
if (!globals || globals.size === 0)
|
|
34
|
+
return t;
|
|
35
|
+
const worn = wearGlobal(classes.map((cls) => parseClass(cls).utility), globals);
|
|
36
|
+
if (!wornAnything(worn))
|
|
37
|
+
return t;
|
|
38
|
+
return {
|
|
39
|
+
...t,
|
|
40
|
+
base: { ...worn.base, ...t.base },
|
|
41
|
+
dark: { ...worn.dark, ...t.dark },
|
|
42
|
+
states: Object.fromEntries([...new Set([...Object.keys(worn.states), ...Object.keys(t.states)])].map((k) => [k, { ...worn.states[k], ...t.states[k] }])),
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/install-marks.js
CHANGED
|
@@ -253,7 +253,7 @@ export const CHECKER_SINCE = "0.16.250";
|
|
|
253
253
|
* publicado antes deste código existir, e uma marca nele calaria o aviso para quem o instalou. Mesma
|
|
254
254
|
* lição de algumas horas antes, na mesma sessão.
|
|
255
255
|
*/
|
|
256
|
-
export const READER_SINCE = "0.16.
|
|
256
|
+
export const READER_SINCE = "0.16.288";
|
|
257
257
|
/**
|
|
258
258
|
* O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
|
|
259
259
|
*
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Modal.Header` E `ModalHeader` SÃO UMA PEÇA - e a vitrine dele mostrava duas.
|
|
3
|
+
*
|
|
4
|
+
* O QUE O CLIENTE VIA: o design system dele contando 75 componentes quando ele escreveu 68. Sete
|
|
5
|
+
* pares apareciam duas vezes - `Modal.Header` + `ModalHeader`, `Modal.Body` + `ModalBody`,
|
|
6
|
+
* `Modal.Footer` + `ModalFooter`, `Toast.Icon` + `ToastIcon`, `Toast.Body` + `ToastBody`,
|
|
7
|
+
* `Tooltip.Content` + `TooltipContent`, `PillNav.Link` + `PillNavLink` - porque as duas grafias são
|
|
8
|
+
* a mesma coisa dita de dois jeitos: `export const ModalHeader` é a DECLARAÇÃO, e
|
|
9
|
+
* `Modal.Header = ModalHeader` é o açúcar de namespace que o React deixa escrever.
|
|
10
|
+
*
|
|
11
|
+
* A DECLARAÇÃO GANHA, e é a mesma regra que o resto do censo já segue: uso é o que alguém escolheu,
|
|
12
|
+
* declaração é o que o autor desenhou. O nome que sobrevive é o que ele exportou.
|
|
13
|
+
*
|
|
14
|
+
* MEDIDO NAS DUAS POPULAÇÕES, e a segunda é a que prova que a régua não é sobre um repositório:
|
|
15
|
+
*
|
|
16
|
+
* codelevel-ui 75 no inventário · 11 nomes com ponto · 7 pares
|
|
17
|
+
* frontend-hub 302 no inventário · 23 nomes com ponto · 0 pares
|
|
18
|
+
*
|
|
19
|
+
* No `frontend-hub` `Card.Root` e `Icon.ChevronUp` não têm um `CardRoot` nem um `IconChevronUp`
|
|
20
|
+
* declarado ao lado - são namespaces de verdade, e continuam como estão. É a EXISTÊNCIA da
|
|
21
|
+
* declaração plana que faz o par, nunca a grafia com ponto por si.
|
|
22
|
+
*/
|
|
23
|
+
export function mergeNamespacePairs(components,
|
|
24
|
+
/** O que este escopo DECLARA. Sem isso, um par não pode ser provado - ver `Census.defined`. */
|
|
25
|
+
declared) {
|
|
26
|
+
const byName = new Map(components.map((c) => [c.name, c]));
|
|
27
|
+
const absorbed = new Set();
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const c of components) {
|
|
30
|
+
if (absorbed.has(c.name))
|
|
31
|
+
continue;
|
|
32
|
+
if (!c.name.includes(".")) {
|
|
33
|
+
out.push(c);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const flat = c.name.split(".").join("");
|
|
37
|
+
const twin = byName.get(flat);
|
|
38
|
+
/** Só quando o nome plano EXISTE no inventário e é declarado por este escopo. */
|
|
39
|
+
if (!twin || !declared.has(flat)) {
|
|
40
|
+
out.push(c);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
absorbed.add(c.name);
|
|
44
|
+
absorbed.add(flat);
|
|
45
|
+
out.push(fuse(twin, c));
|
|
46
|
+
}
|
|
47
|
+
/** Os planos que foram absorvidos já entraram fundidos; os outros seguem na ordem. */
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* As duas linhas somadas sob o nome declarado.
|
|
52
|
+
*
|
|
53
|
+
* `count` soma - são usos distintos no código dele. `files` fica no MAIOR dos dois em vez de somar:
|
|
54
|
+
* as duas grafias costumam aparecer nos mesmos arquivos, e somar diria que o componente vive em mais
|
|
55
|
+
* lugares do que vive. Um número inflado é pior que um número conservador, porque ele decide
|
|
56
|
+
* prioridade.
|
|
57
|
+
*/
|
|
58
|
+
function fuse(flat, dotted) {
|
|
59
|
+
const props = { ...flat.props };
|
|
60
|
+
for (const [p, values] of Object.entries(dotted.props ?? {})) {
|
|
61
|
+
props[p] = [...new Set([...(props[p] ?? []), ...values])];
|
|
62
|
+
}
|
|
63
|
+
const propFiles = { ...(flat.propFiles ?? {}) };
|
|
64
|
+
for (const [p, n] of Object.entries(dotted.propFiles ?? {})) {
|
|
65
|
+
propFiles[p] = Math.max(propFiles[p] ?? 0, n);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...flat,
|
|
69
|
+
count: flat.count + dotted.count,
|
|
70
|
+
files: Math.max(flat.files, dotted.files),
|
|
71
|
+
props,
|
|
72
|
+
...(Object.keys(propFiles).length > 0 ? { propFiles } : {}),
|
|
73
|
+
/** Os eixos que qualquer uma das duas grafias declarou - é a mesma declaração. */
|
|
74
|
+
...(flat.declaredAxes || dotted.declaredAxes
|
|
75
|
+
? {
|
|
76
|
+
declaredAxes: { ...dotted.declaredAxes, ...flat.declaredAxes },
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O QUE ESTA LEITURA NÃO CONSEGUIU SEGURAR, escrito pela esteira e não pedido a ninguém.
|
|
3
|
+
*
|
|
4
|
+
* O QUE O CLIENTE GANHA: ele sabe, no minuto do import, o que não chegou - com o número do
|
|
5
|
+
* repositório dele e o arquivo e a linha de cada caso. É a lei 8 do método: o cliente perdoa o que
|
|
6
|
+
* a gente diz que não faz, e não perdoa descobrir sozinho.
|
|
7
|
+
*
|
|
8
|
+
* POR QUE ISTO EXISTE. O arquivo era escrito pelo AGENTE, instruído por três frases dentro das
|
|
9
|
+
* descrições das tools do MCP. Em 23/08 o mesmo repositório foi importado duas vezes: a primeira
|
|
10
|
+
* rodada escreveu 37 linhas de lacunas declaradas, a segunda escreveu nenhuma. As lacunas
|
|
11
|
+
* continuaram todas lá - 198 declarações de CSS sem leitor, 5 `var()` que não pintam nada -, e só o
|
|
12
|
+
* aviso desapareceu. Um pedido em texto a um modelo é uma sugestão, e nada percebia quando ela não
|
|
13
|
+
* era seguida.
|
|
14
|
+
*
|
|
15
|
+
* O PISO É DETERMINÍSTICO, e todo dado que ele usa já estava medido no censo. O agente continua
|
|
16
|
+
* livre para ACRESCENTAR o que só ele viu; o que ele não pode mais é ser a única testemunha.
|
|
17
|
+
*
|
|
18
|
+
* `null` quando não há lacuna nenhuma: um arquivo vazio dizendo "nada a declarar" é ruído, e a
|
|
19
|
+
* ausência do arquivo já é a resposta.
|
|
20
|
+
*/
|
|
21
|
+
export function notExpressed(census) {
|
|
22
|
+
const ledger = census.ledger;
|
|
23
|
+
const unread = ledger?.unread ?? [];
|
|
24
|
+
const broken = census.brokenRefs ?? [];
|
|
25
|
+
const skipped = census.skipped ?? [];
|
|
26
|
+
if (unread.length === 0 && broken.length === 0 && skipped.length === 0)
|
|
27
|
+
return null;
|
|
28
|
+
const out = [];
|
|
29
|
+
const scope = census.scope ? ` of \`${census.scope}\`` : "";
|
|
30
|
+
out.push(`# What this reading could not hold`);
|
|
31
|
+
out.push("");
|
|
32
|
+
out.push(`Measured by synthesisui \`${ledger?.cli ?? "unknown"}\`${scope}. Every number here is from` +
|
|
33
|
+
` your files. This file is written by the pipeline on every import - what it lists is what` +
|
|
34
|
+
` did NOT reach a recipe.`);
|
|
35
|
+
if (unread.length > 0) {
|
|
36
|
+
out.push("");
|
|
37
|
+
out.push("## Style your files hold and no recipe received");
|
|
38
|
+
for (const group of unread) {
|
|
39
|
+
out.push("");
|
|
40
|
+
out.push(`### ${group.uses} ${group.shape} declaration${group.uses === 1 ? "" : "s"}` +
|
|
41
|
+
` across ${group.files} file${group.files === 1 ? "" : "s"} - ${group.reason}`);
|
|
42
|
+
out.push("");
|
|
43
|
+
out.push(group.because);
|
|
44
|
+
out.push("");
|
|
45
|
+
for (const ex of group.examples ?? [])
|
|
46
|
+
out.push(` ${ex.file}:${ex.line} ${ex.text}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (broken.length > 0) {
|
|
50
|
+
out.push("");
|
|
51
|
+
out.push("## Variables referenced and never declared");
|
|
52
|
+
out.push("");
|
|
53
|
+
out.push("These do not paint a different colour - they paint nothing. They are yours to fix, in your" +
|
|
54
|
+
" own words. Up to three places are listed for each; the count says how many there are in" +
|
|
55
|
+
" total.");
|
|
56
|
+
out.push("");
|
|
57
|
+
for (const ref of broken) {
|
|
58
|
+
out.push(` ${ref.name} ${ref.count} reference${ref.count === 1 ? "" : "s"}` +
|
|
59
|
+
` in ${ref.files} file${ref.files === 1 ? "" : "s"}`);
|
|
60
|
+
for (const place of ref.at ?? [])
|
|
61
|
+
out.push(` ${place.file}:${place.line}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (skipped.length > 0) {
|
|
65
|
+
out.push("");
|
|
66
|
+
out.push("## Exports the gate left out, and why");
|
|
67
|
+
out.push("");
|
|
68
|
+
out.push("Nothing here was dropped in silence. If one of these IS a component of your system, that is" +
|
|
69
|
+
" a reading to correct - and the reason below is the one to argue with.");
|
|
70
|
+
out.push("");
|
|
71
|
+
for (const s of skipped)
|
|
72
|
+
out.push(` ${s.name}${s.file ? ` (${s.file})` : ""}\n ${s.because}`);
|
|
73
|
+
}
|
|
74
|
+
out.push("");
|
|
75
|
+
return `${out.join("\n")}\n`;
|
|
76
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O NOME QUE UM TOKEN DELE TEM DENTRO DO DOCUMENTO - uma regra, num lugar só.
|
|
3
|
+
*
|
|
4
|
+
* O QUE O CLIENTE GANHA: mudar um valor no Studio repinta o componente que o usa. Um ref liga os
|
|
5
|
+
* dois; um `var()` literal não liga nada - ele renderiza certo hoje e fica órfão da edição.
|
|
6
|
+
*
|
|
7
|
+
* ESTA FUNÇÃO EXISTIA DUAS VEZES, em `transcribe.ts` e em `css-modules.ts`, e as duas divergiam: a
|
|
8
|
+
* segunda não conhecia `--font-*`, então a família de fonte dele virava referência quando lida de uma
|
|
9
|
+
* classe e `var()` quando lida de uma folha - a mesma decisão dele com dois destinos, dependendo de
|
|
10
|
+
* onde a esteira olhou. Gêmeo não declarado sempre acaba assim.
|
|
11
|
+
*
|
|
12
|
+
* E O GRADIENTE ERA DECIDIDO PELO NOME. A regra reconhecia `--gradient-*`; o cliente escreveu
|
|
13
|
+
* `--grad-cool`. O escritor da plataforma reconhece pelo VALOR - `linear-gradient(…)` - e guarda em
|
|
14
|
+
* `foundations.gradients.grad-cool`, então o token TINHA casa no documento e a classe que o usa
|
|
15
|
+
* apontava para o vazio. É a lei 13 aplicada a token: a origem decide, e aqui a origem é o valor.
|
|
16
|
+
*
|
|
17
|
+
* Medido: `--grad-*` são 5 no `codelevel-ui`, `--gradient-*` são 3 no `frontend-hub`, e as duas
|
|
18
|
+
* grafias passam a chegar onde o documento as guarda.
|
|
19
|
+
*/
|
|
20
|
+
export function tokenRefFor(name,
|
|
21
|
+
/**
|
|
22
|
+
* O que cada token DELE vale. O mapa é o que permite reconhecer um gradiente pela forma em vez do
|
|
23
|
+
* nome; vazio, a decisão volta a ser só pelo nome, que é o comportamento de quem não tem o valor
|
|
24
|
+
* em mãos.
|
|
25
|
+
*/
|
|
26
|
+
declared) {
|
|
27
|
+
const bare = name.replace(/^--/, "");
|
|
28
|
+
if (bare.startsWith("color-"))
|
|
29
|
+
return refFor(bare.slice("color-".length));
|
|
30
|
+
if (bare.startsWith("radius-"))
|
|
31
|
+
return `{radius.${bare.slice(7)}}`;
|
|
32
|
+
if (bare.startsWith("spacing-"))
|
|
33
|
+
return `{spacing.${bare.slice(8)}}`;
|
|
34
|
+
if (bare.startsWith("shadow-"))
|
|
35
|
+
return `{shadow.${bare.slice(7)}}`;
|
|
36
|
+
// `--gradient-ui` e `--background-image-gradient-ui` são um token em duas grafias - o namespace de
|
|
37
|
+
// utility do Tailwind v4 embrulha o primeiro. Os dois chegam a `{gradients.ui}`.
|
|
38
|
+
if (bare.startsWith("background-image-gradient-")) {
|
|
39
|
+
return `{gradients.${bare.slice("background-image-gradient-".length)}}`;
|
|
40
|
+
}
|
|
41
|
+
if (bare.startsWith("gradient-"))
|
|
42
|
+
return `{gradients.${bare.slice(9)}}`;
|
|
43
|
+
if (bare.startsWith("text-")) {
|
|
44
|
+
/**
|
|
45
|
+
* O TOKEN COMPANHEIRO. O Tailwind v4 escreve "a altura de linha DE text-body-s" como
|
|
46
|
+
* `--text-body-s--line-height` - um duplo hífen dentro de um nome. Lido como nome de passo ele
|
|
47
|
+
* produzia `{typography.scale.body-s--line-height.fontSize}`, cujo duplo hífen nenhuma gramática
|
|
48
|
+
* de ref aceita, e a receita inteira era RECUSADA na validação (test13, 01/08). O sufixo nomeia a
|
|
49
|
+
* propriedade; o meio nomeia o passo.
|
|
50
|
+
*/
|
|
51
|
+
const companion = /^text-(.+?)--(line-height|letter-spacing|font-weight)$/.exec(bare);
|
|
52
|
+
if (companion) {
|
|
53
|
+
const prop = {
|
|
54
|
+
"line-height": "lineHeight",
|
|
55
|
+
"letter-spacing": "letterSpacing",
|
|
56
|
+
"font-weight": "weight",
|
|
57
|
+
}[companion[2]];
|
|
58
|
+
return `{typography.scale.${companion[1]}.${prop}}`;
|
|
59
|
+
}
|
|
60
|
+
// Qualquer outro duplo hífen é um nome que esta gramática não segura - o `var()` literal ainda
|
|
61
|
+
// resolve contra a folha dele, e um ref inválido não ajuda ninguém.
|
|
62
|
+
if (bare.slice(5).includes("--"))
|
|
63
|
+
return `var(${name})`;
|
|
64
|
+
return `{typography.scale.${bare.slice(5)}.fontSize}`;
|
|
65
|
+
}
|
|
66
|
+
if (bare.startsWith("font-"))
|
|
67
|
+
return `{typography.families.${bare.slice(5)}}`;
|
|
68
|
+
/**
|
|
69
|
+
* O GRADIENTE PELA FORMA DO VALOR, e é a última pergunta e não a primeira.
|
|
70
|
+
*
|
|
71
|
+
* Os namespaces acima são vocabulário conhecido e ganham deste teste - `--color-brand` que valha um
|
|
72
|
+
* `linear-gradient` continua sendo cor pelo nome que ele deu. Aqui embaixo estão os nomes que a
|
|
73
|
+
* gente NÃO modela, e é onde `--grad-cool` cai. A chave é o nome dele inteiro, que é exatamente o
|
|
74
|
+
* que `gradientsFromCensus` guarda no documento.
|
|
75
|
+
*/
|
|
76
|
+
const value = declared.get(name);
|
|
77
|
+
if (value && /\b(?:linear|radial|conic)-gradient\(/.test(value)) {
|
|
78
|
+
return `{gradients.${bare}}`;
|
|
79
|
+
}
|
|
80
|
+
// Um namespace que não modelamos. O `var()` literal continua sendo CSS correto contra a folha
|
|
81
|
+
// dele, e inventar um ref apontaria para nada.
|
|
82
|
+
return `var(${name})`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `ocean-500` → `{color.ocean.500}`. O passo é o número final; o que vem antes é a família, hífens
|
|
86
|
+
* intactos, porque `royal-blue-500` é uma família chamada `royal-blue`.
|
|
87
|
+
*/
|
|
88
|
+
function refFor(name) {
|
|
89
|
+
const m = /^(.*)-(\d{2,4})$/.exec(name);
|
|
90
|
+
if (!m)
|
|
91
|
+
return `{color.${name}}`;
|
|
92
|
+
return `{color.${m[1]}.${m[2]}}`;
|
|
93
|
+
}
|
package/package.json
CHANGED