synthesisui 0.16.285 → 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 +129 -13
- package/dist/doctor/broken-refs.js +15 -1
- package/dist/doctor/component-gate.js +59 -2
- package/dist/doctor/crosswalk.js +33 -4
- package/dist/doctor/css-modules.js +77 -47
- package/dist/doctor/definitions-scan.js +59 -3
- package/dist/doctor/transcribe.js +82 -50
- 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
|
*
|
|
@@ -732,9 +747,23 @@ export async function takeCensus(root, opts) {
|
|
|
732
747
|
if (esize > 0 || etag || esketch.length > 0) {
|
|
733
748
|
looks[extra.name] = {
|
|
734
749
|
...(esketch.length > 0 ? { sketch: esketch } : {}),
|
|
735
|
-
/**
|
|
736
|
-
|
|
737
|
-
|
|
750
|
+
/**
|
|
751
|
+
* Ver `CensusLook.aside` - o segundo componente do arquivo também.
|
|
752
|
+
*
|
|
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`.
|
|
759
|
+
*/
|
|
760
|
+
...(classifyAside({ name: extra.name, declaredAxes: extra.axes })
|
|
761
|
+
? {
|
|
762
|
+
aside: classifyAside({
|
|
763
|
+
name: extra.name,
|
|
764
|
+
declaredAxes: extra.axes,
|
|
765
|
+
}),
|
|
766
|
+
}
|
|
738
767
|
: {}),
|
|
739
768
|
...et,
|
|
740
769
|
...(etag ? { rootTag: etag } : {}),
|
|
@@ -770,7 +799,7 @@ export async function takeCensus(root, opts) {
|
|
|
770
799
|
* for the result, which is the "valid and unread" failure this pipeline has
|
|
771
800
|
* paid for twice already.
|
|
772
801
|
*/
|
|
773
|
-
const v = transcribeVariants(src, declaredValues);
|
|
802
|
+
const v = transcribeVariants(src, declaredValues, globalClasses);
|
|
774
803
|
/**
|
|
775
804
|
* THE RESTING OPTION, from the definition itself. `cva` says it in
|
|
776
805
|
* `defaultVariants`; a lookup-record component says it in the destructuring -
|
|
@@ -1025,9 +1054,25 @@ export async function takeCensus(root, opts) {
|
|
|
1025
1054
|
if (size > 0 || tag) {
|
|
1026
1055
|
looks[found[0].name] = {
|
|
1027
1056
|
...(sketch.length > 0 ? { sketch } : {}),
|
|
1028
|
-
/**
|
|
1029
|
-
|
|
1030
|
-
|
|
1057
|
+
/**
|
|
1058
|
+
* Ver `CensusLook.aside`: o fato viaja, a regra fica de um lado só.
|
|
1059
|
+
*
|
|
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`.
|
|
1065
|
+
*/
|
|
1066
|
+
...(classifyAside({
|
|
1067
|
+
name: found[0].name,
|
|
1068
|
+
declaredAxes: { ...found[0].axes, ...v.axes },
|
|
1069
|
+
})
|
|
1070
|
+
? {
|
|
1071
|
+
aside: classifyAside({
|
|
1072
|
+
name: found[0].name,
|
|
1073
|
+
declaredAxes: { ...found[0].axes, ...v.axes },
|
|
1074
|
+
}),
|
|
1075
|
+
}
|
|
1031
1076
|
: {}),
|
|
1032
1077
|
...withoutDark,
|
|
1033
1078
|
base,
|
|
@@ -1842,7 +1887,14 @@ export async function takeCensus(root, opts) {
|
|
|
1842
1887
|
list.push(kebab(pair.child));
|
|
1843
1888
|
companionsOf.set(pair.component, list);
|
|
1844
1889
|
}
|
|
1845
|
-
|
|
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) => ({
|
|
1846
1898
|
...c,
|
|
1847
1899
|
...(verdicts.get(idOf(c)) ?? {}),
|
|
1848
1900
|
...(laws.has(c.name) ? { laws: laws.get(c.name) } : {}),
|
|
@@ -2049,6 +2101,25 @@ export async function takeCensus(root, opts) {
|
|
|
2049
2101
|
}
|
|
2050
2102
|
: {}),
|
|
2051
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
|
+
: {}),
|
|
2052
2123
|
classStyle,
|
|
2053
2124
|
...(Object.keys(looks).length > 0 ? { looks } : {}),
|
|
2054
2125
|
...(naming.components > 0 ? { naming } : {}),
|
|
@@ -2848,6 +2919,23 @@ export function ladderReach(declared) {
|
|
|
2848
2919
|
}
|
|
2849
2920
|
return { light, dark };
|
|
2850
2921
|
}
|
|
2922
|
+
/**
|
|
2923
|
+
* QUEM DÁ NOME AO SISTEMA, e o cliente para de ganhar um sistema morto.
|
|
2924
|
+
*
|
|
2925
|
+
* O QUE ELE VIVEU EM 23/08: respondeu "CodeLevel" à pergunta do nome, o agente escreveu isso em
|
|
2926
|
+
* `reading.name` - o campo certo -, e o sistema nasceu como `repo-ui`, tirado do `@repo/ui` do
|
|
2927
|
+
* `package.json`. O slug não muda depois de criado, então corrigir custou um segundo import e ele
|
|
2928
|
+
* ficou com dois sistemas para uma biblioteca, um deles morto, e nenhum caminho de volta que não
|
|
2929
|
+
* seja apagar à mão.
|
|
2930
|
+
*
|
|
2931
|
+
* A ORDEM É A MESMA DO `scheme`, dez linhas abaixo de onde isto é chamado: a flag é uma decisão
|
|
2932
|
+
* digitada agora, a leitura é uma resposta que alguém já deu, e a pergunta é o último recurso.
|
|
2933
|
+
* `null` quer dizer "ninguém disse" - o ÚNICO caso em que se pergunta - e é o que fecha a porta:
|
|
2934
|
+
* não há como esta função devolver um palpite quando existe uma resposta.
|
|
2935
|
+
*/
|
|
2936
|
+
export function chosenName(input) {
|
|
2937
|
+
return input.flag?.trim() || input.read?.trim() || null;
|
|
2938
|
+
}
|
|
2851
2939
|
export function deriveName(pkgName, dirName) {
|
|
2852
2940
|
const raw = (pkgName ?? "").trim();
|
|
2853
2941
|
const scoped = /^@([^/]+)\/(.+)$/.exec(raw);
|
|
@@ -3127,6 +3215,16 @@ export function rootBehaviour(tag, sketch) {
|
|
|
3127
3215
|
export async function resolveReadParts(census, root,
|
|
3128
3216
|
/** `true` numa re-medição: o resumo desta etapa fica para o `sync --full` - ver `say`. */
|
|
3129
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 ?? {}));
|
|
3130
3228
|
const say = quiet
|
|
3131
3229
|
? () => { }
|
|
3132
3230
|
: (line) => {
|
|
@@ -3239,13 +3337,13 @@ quiet = false) {
|
|
|
3239
3337
|
? resolveAnatomy(framed, declared, deps, resolveRef, entry?.root,
|
|
3240
3338
|
// The component's own sketch, so a node naming itself by index reads
|
|
3241
3339
|
// the exact class string the census measured (dono, 01/08).
|
|
3242
|
-
looks[component]?.sketch, census.sxSpacing)
|
|
3340
|
+
looks[component]?.sketch, census.sxSpacing, globalClasses)
|
|
3243
3341
|
: patched
|
|
3244
|
-
? 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)
|
|
3245
3343
|
: derived && derived.read.length > 0
|
|
3246
|
-
? 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)
|
|
3247
3345
|
: Array.isArray(entry?.parts) && entry.parts.length > 0
|
|
3248
|
-
? resolveFlatParts(entry.parts, declared)
|
|
3346
|
+
? resolveFlatParts(entry.parts, declared, globalClasses)
|
|
3249
3347
|
: null;
|
|
3250
3348
|
if (!resolved)
|
|
3251
3349
|
continue;
|
|
@@ -3660,6 +3758,23 @@ export async function runImport(opts) {
|
|
|
3660
3758
|
const out = opts.census ?? join(root, "_synthesisui", "census.json");
|
|
3661
3759
|
await mkdir(dirname(out), { recursive: true });
|
|
3662
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
|
+
}
|
|
3663
3778
|
console.log("");
|
|
3664
3779
|
console.log(body(`Written to ${paint.strong(relative(root, out))}`));
|
|
3665
3780
|
if (opts.dry) {
|
|
@@ -3671,7 +3786,8 @@ export async function runImport(opts) {
|
|
|
3671
3786
|
return;
|
|
3672
3787
|
}
|
|
3673
3788
|
const suggested = deriveName(census.project.name, basename(root) || "system");
|
|
3674
|
-
const chosen = opts.name
|
|
3789
|
+
const chosen = chosenName({ flag: opts.name, read: census.reading?.name }) ??
|
|
3790
|
+
(await askName(suggested));
|
|
3675
3791
|
const reach = ladderReach(census.declared);
|
|
3676
3792
|
// An explicit flag is a decision already made. Otherwise the person answers,
|
|
3677
3793
|
// starting from the agent's reading if there is one and from the ladder's own
|
|
@@ -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,
|
package/dist/doctor/crosswalk.js
CHANGED
|
@@ -293,10 +293,36 @@ export function axisOverlap(a, b) {
|
|
|
293
293
|
*/
|
|
294
294
|
const IS_ICON = /(^Icon|Icon$)/;
|
|
295
295
|
const IS_PROVIDER = /(Provider|Context)$/;
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
/**
|
|
297
|
+
* O QUE O CLIENTE GANHA: o componente dele para de desaparecer por causa do nome.
|
|
298
|
+
*
|
|
299
|
+
* Um glifo é modelado como biblioteca de ícones e não como receita - decisão de produto, e ela
|
|
300
|
+
* continua valendo. O que mudou é QUEM decide. O nome decidia sozinho, e em 23/08 isso custou o
|
|
301
|
+
* `IconButton` do `codelevel-ui`: três eixos declarados (`glow`, `size`, `variant`), a forma
|
|
302
|
+
* aninhada já derivada, e nenhuma linha no design system dele. Quem abrisse o Studio pediria um
|
|
303
|
+
* botão de ícone e ouviria que o sistema não tem - com o componente pronto no código.
|
|
304
|
+
*
|
|
305
|
+
* A lei 13 diz que a origem decide e não o nome, e um regex sobre o nome é exatamente o caso
|
|
306
|
+
* especial que ela proíbe. O discriminante estava no dado, e foi medido nas DUAS populações:
|
|
307
|
+
*
|
|
308
|
+
* frontend-hub 43 nomes casam o regex · 0 declaram eixos
|
|
309
|
+
* codelevel-ui 3 nomes casam o regex · 2 declaram eixos
|
|
310
|
+
*
|
|
311
|
+
* UM GLIFO NÃO DECLARA VARIAÇÃO - é um desenho, e desenho não tem `variant`. Quem declara eixos
|
|
312
|
+
* escreveu um contrato, e contrato é receita. Nenhum dos 43 do repositório real muda de lado, então
|
|
313
|
+
* a regra não afrouxou: ela passou a perguntar a coisa certa.
|
|
314
|
+
*
|
|
315
|
+
* `props` NÃO serve para isto, e a diferença é o caso do `Icon` dele: recebe `size` e `color` no uso
|
|
316
|
+
* e não declara nem um nem outro. Uso é o que alguém escolheu; declaração é o que o autor desenhou.
|
|
317
|
+
*
|
|
318
|
+
* A FORMA VEM INTEIRA, e não como segundo parâmetro opcional (§6): um argumento que pode ser
|
|
319
|
+
* esquecido é o defeito que este conserto está desfazendo, e não há como chamar isto pela metade.
|
|
320
|
+
*/
|
|
321
|
+
export function classifyAside(shape) {
|
|
322
|
+
const declares = Object.keys(shape.declaredAxes ?? {}).length > 0;
|
|
323
|
+
if (IS_ICON.test(shape.name) && !declares)
|
|
298
324
|
return "icon";
|
|
299
|
-
if (IS_PROVIDER.test(name))
|
|
325
|
+
if (IS_PROVIDER.test(shape.name))
|
|
300
326
|
return "provider";
|
|
301
327
|
return null;
|
|
302
328
|
}
|
|
@@ -429,7 +455,10 @@ opts) {
|
|
|
429
455
|
}
|
|
430
456
|
return mine
|
|
431
457
|
.map((component) => {
|
|
432
|
-
const aside = classifyAside(
|
|
458
|
+
const aside = classifyAside({
|
|
459
|
+
name: component.name,
|
|
460
|
+
declaredAxes: component.declaredAxes,
|
|
461
|
+
});
|
|
433
462
|
if (aside) {
|
|
434
463
|
return {
|
|
435
464
|
component,
|