synthesisui 0.16.135 → 0.16.137
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 +44 -2
- package/dist/commands/import.js +127 -12
- package/dist/doctor/component-gate.js +14 -0
- package/dist/doctor/css-modules.js +44 -0
- package/dist/doctor/fragments.js +107 -10
- package/dist/doctor/gap-triage.js +8 -1
- package/dist/doctor/sketch.js +27 -0
- package/dist/doctor/style-ledger.js +1 -0
- package/dist/doctor/style-props.js +0 -0
- package/package.json +1 -1
package/dist/anatomy-read.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* validate everywhere and render as a hole, and "valid and unread" is the worst
|
|
30
30
|
* of the three outcomes this pipeline has paid for twice.
|
|
31
31
|
*/
|
|
32
|
+
import { readSxProps } from "./doctor/style-props.js";
|
|
32
33
|
import { transcribe, } from "./doctor/transcribe.js";
|
|
33
34
|
import { frontierKind } from "./frontier-kind.js";
|
|
34
35
|
/** The ten forms, closed. A renderer that accepts any tag draws anything. */
|
|
@@ -141,6 +142,23 @@ function frontierClasses(node, sketch) {
|
|
|
141
142
|
const trimmed = raw?.trim();
|
|
142
143
|
return trimmed ? { classes: trimmed } : {};
|
|
143
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* A PROP DE ESTILO DE UM NÓ, verbatim, para o snippet devolvê-la.
|
|
147
|
+
*
|
|
148
|
+
* Numa biblioteca OPACA a gente nomeia e para - ela se desenha sozinha, e as declarações do `sx`
|
|
149
|
+
* não viram estilo nosso. Mas sem a prop de volta, o snippet entrega um `<Box>` que perdeu a
|
|
150
|
+
* margem que eles escreveram: 57 de 57 `sx` do app dele estão exatamente aí (05/08). É a mesma
|
|
151
|
+
* decisão que `motionProps` já carrega.
|
|
152
|
+
*/
|
|
153
|
+
function frontierSx(node, sketch) {
|
|
154
|
+
const raw = typeof node.sx === "string"
|
|
155
|
+
? node.sx
|
|
156
|
+
: typeof node.at === "number"
|
|
157
|
+
? sketch?.[node.at]?.sx
|
|
158
|
+
: undefined;
|
|
159
|
+
const trimmed = raw?.trim();
|
|
160
|
+
return trimmed ? { sx: trimmed.slice(0, 400) } : {};
|
|
161
|
+
}
|
|
144
162
|
export function resolveAnatomy(read, declared, deps,
|
|
145
163
|
/** Their name → the slug it reaches in this system. See `RefResolver`. */
|
|
146
164
|
resolve,
|
|
@@ -155,7 +173,12 @@ resolve,
|
|
|
155
173
|
root,
|
|
156
174
|
/** This component's sketch, so a node can name its classes by INDEX instead
|
|
157
175
|
* of retyping them - see `AnatomyRead.at`. */
|
|
158
|
-
sketch
|
|
176
|
+
sketch,
|
|
177
|
+
/**
|
|
178
|
+
* A escala de espaçamento que o projeto DELES declara para o `sx`. Ausente = o default
|
|
179
|
+
* documentado do MUI, que é fato da biblioteca que eles escolheram - ver `spacingOf`.
|
|
180
|
+
*/
|
|
181
|
+
sxSpacing) {
|
|
159
182
|
/**
|
|
160
183
|
* THE PACKAGE THE RETURNED ELEMENT CAME FROM, measured rather than spelled.
|
|
161
184
|
* Node 0 of the sketch IS what the component returns, and the reader stamped its
|
|
@@ -261,6 +284,7 @@ sketch) {
|
|
|
261
284
|
from,
|
|
262
285
|
...(version ? { version } : {}),
|
|
263
286
|
...frontierClasses(node, sketch),
|
|
287
|
+
...frontierSx(node, sketch),
|
|
264
288
|
...holds,
|
|
265
289
|
});
|
|
266
290
|
continue;
|
|
@@ -386,7 +410,25 @@ sketch) {
|
|
|
386
410
|
: "";
|
|
387
411
|
const classes = raw.split(/\s+/).filter(Boolean);
|
|
388
412
|
const t = transcribe(classes, declared);
|
|
389
|
-
|
|
413
|
+
/**
|
|
414
|
+
* A PROP DE ESTILO DESTE NÓ, sob a classe dele.
|
|
415
|
+
*
|
|
416
|
+
* `sx={{ width: "100%" }}` pertence ao elemento que a carrega - a mesma atribuição que a
|
|
417
|
+
* classe tem, e é por isso que ela viaja no sketch. Sob a classe porque uma prop é a
|
|
418
|
+
* camada mais fraca: se a classe declara a mesma propriedade, a classe ganha (é o que o
|
|
419
|
+
* `style={{ }}` já faz na raiz).
|
|
420
|
+
*/
|
|
421
|
+
const sxText = typeof node.at === "number"
|
|
422
|
+
? sketch?.[node.at]?.sx
|
|
423
|
+
: undefined;
|
|
424
|
+
const fromSx = sxText
|
|
425
|
+
? readSxProps(sxText, sxSpacing).declarations
|
|
426
|
+
: {};
|
|
427
|
+
parts[key] = {
|
|
428
|
+
base: { ...fromSx, ...t.base },
|
|
429
|
+
dark: t.dark,
|
|
430
|
+
states: t.states,
|
|
431
|
+
};
|
|
390
432
|
/**
|
|
391
433
|
* A drop on a PART is still a drop. The root path already says these out loud;
|
|
392
434
|
* this one silently swallowed `p-spacing-sm` on a real menu popup, so the part
|
package/dist/commands/import.js
CHANGED
|
@@ -13,7 +13,7 @@ import { describeGate, gateComponent, groupSkips, SCREENS_FOR_SYSTEM, } from "..
|
|
|
13
13
|
import { emptyTally, internalSpecifiers, scanComponentsInto, tallyToInventory, } from "../doctor/components-scan.js";
|
|
14
14
|
import { countShape, describeCoverage, summarizeCoverage, } from "../doctor/coverage.js";
|
|
15
15
|
import { crosswalk, floorSize, isLibrary, observedRules, useLiveCatalogue, } from "../doctor/crosswalk.js";
|
|
16
|
-
import { keyframeOffsets, moduleImports, readModuleCss, readModuleUsage, transcribeModule, } from "../doctor/css-modules.js";
|
|
16
|
+
import { keyframeOffsets, moduleImports, partialCandidates, readModuleCss, readModuleUsage, sheetImports, transcribeModule, } from "../doctor/css-modules.js";
|
|
17
17
|
import { dataContract } from "../doctor/data-contract.js";
|
|
18
18
|
import { reconcile, scanDefinitions, } from "../doctor/definitions-scan.js";
|
|
19
19
|
import { fragmentsOfSource, fragmentsOfStylesheet, judgeFragments, } from "../doctor/fragments.js";
|
|
@@ -27,6 +27,7 @@ import { describeSignals, emptySignals, finishSignals, readSignalsInto, } from "
|
|
|
27
27
|
import { sketchOf } from "../doctor/sketch.js";
|
|
28
28
|
import { describeForeign, readStyleIslands, } from "../doctor/style-island.js";
|
|
29
29
|
import { buildLedger, describeLedger, } from "../doctor/style-ledger.js";
|
|
30
|
+
import { MUI_DEFAULT_SPACING, readStyledComponents, spacingOf, } from "../doctor/style-props.js";
|
|
30
31
|
import { buildTable } from "../doctor/tokens.js";
|
|
31
32
|
import { definitionSpan, readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
|
|
32
33
|
import { transcribeVariants } from "../doctor/variant-read.js";
|
|
@@ -128,6 +129,48 @@ async function siblingProjects(root) {
|
|
|
128
129
|
* this" - could not be said, and we ended up rediscovering by colour distance
|
|
129
130
|
* what their stylesheet had spelled out all along.
|
|
130
131
|
*/
|
|
132
|
+
/**
|
|
133
|
+
* UMA FOLHA E TUDO O QUE ELA TRAZ PARA DENTRO DE SI, na ordem em que o sass compila.
|
|
134
|
+
*
|
|
135
|
+
* Uma partial (`_darkMode.scss`) nunca é compilada sozinha: ela entra em quem a importa, e portanto
|
|
136
|
+
* herda o escopo de quem a importa. Ler o `.module.scss` sem seguir os `@import` dele deixava de
|
|
137
|
+
* fora 1 400 declarações (91% de tudo o que a esteira chamava de "CSS global sem leitor") - e elas
|
|
138
|
+
* são, no build dele, as faces light e dark de classes que o componente JÁ veste (medido, 05/08).
|
|
139
|
+
*
|
|
140
|
+
* Devolve o CSS concatenado e os arquivos que entraram, porque o ledger precisa saber que a partial
|
|
141
|
+
* pertence a um componente admitido - senão ela continua contabilizada como forma sem leitor, e o
|
|
142
|
+
* relatório passa a discordar da receita.
|
|
143
|
+
*/
|
|
144
|
+
async function sheetWithImports(path, seen = new Set(), depth = 0) {
|
|
145
|
+
if (depth > 6 || seen.has(path))
|
|
146
|
+
return { css: "", files: [] };
|
|
147
|
+
seen.add(path);
|
|
148
|
+
const body = await readFile(path, "utf8").catch(() => "");
|
|
149
|
+
if (!body)
|
|
150
|
+
return { css: "", files: [] };
|
|
151
|
+
let css = "";
|
|
152
|
+
const files = [path];
|
|
153
|
+
/**
|
|
154
|
+
* O IMPORTADO VEM ANTES, que é a ordem do sass: quem importa sobrescreve o que importou. Inverter
|
|
155
|
+
* isto faria a face dark de uma partial vencer a base do próprio module.
|
|
156
|
+
*/
|
|
157
|
+
for (const spec of sheetImports(body)) {
|
|
158
|
+
for (const candidate of partialCandidates(spec)) {
|
|
159
|
+
const child = join(dirname(path), candidate);
|
|
160
|
+
const nested = await sheetWithImports(child, seen, depth + 1);
|
|
161
|
+
if (nested.css || nested.files.length > 1 || (await exists(child))) {
|
|
162
|
+
css += nested.css;
|
|
163
|
+
files.push(...nested.files);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { css: `${css}\n${body}`, files };
|
|
169
|
+
}
|
|
170
|
+
/** Existe no disco - o candidato de partial que o sass acharia primeiro. */
|
|
171
|
+
async function exists(path) {
|
|
172
|
+
return await readFile(path, "utf8").then(() => true, () => false);
|
|
173
|
+
}
|
|
131
174
|
async function harvestOwnCss(root,
|
|
132
175
|
/**
|
|
133
176
|
* O LEDGER TAMBÉM COME AQUI, e por um motivo de promessa: este é o único laço que vê TODO
|
|
@@ -139,17 +182,40 @@ async function harvestOwnCss(root,
|
|
|
139
182
|
* não admitido é um motivo, não um desaparecimento.
|
|
140
183
|
*/
|
|
141
184
|
into) {
|
|
142
|
-
|
|
185
|
+
/**
|
|
186
|
+
* DOIS PASSES, porque o escopo de uma folha está no GRAFO e não no nome dela.
|
|
187
|
+
*
|
|
188
|
+
* O primeiro junta tudo; o segundo julga cada folha sabendo quem importa quem. Um pass só
|
|
189
|
+
* decidia por `/\.module\./` e chamava de "CSS global sem leitor" 1 400 declarações que o build
|
|
190
|
+
* deles compila dentro de um module (medido, 05/08).
|
|
191
|
+
*/
|
|
192
|
+
const sheets = [];
|
|
143
193
|
for await (const file of walkAll([root])) {
|
|
144
194
|
if (!/\.(css|scss|sass|less)$/i.test(file))
|
|
145
195
|
continue;
|
|
146
196
|
const body = await readFile(file, "utf8").catch(() => "");
|
|
197
|
+
sheets.push({ file, body });
|
|
198
|
+
}
|
|
199
|
+
/** Todo caminho que alguma folha importa, resolvido como o sass resolveria. */
|
|
200
|
+
const imported = new Set();
|
|
201
|
+
for (const sheet of sheets)
|
|
202
|
+
for (const spec of sheetImports(sheet.body))
|
|
203
|
+
for (const candidate of partialCandidates(spec))
|
|
204
|
+
imported.add(join(dirname(sheet.file), candidate));
|
|
205
|
+
let css = "";
|
|
206
|
+
for (const { file, body } of sheets) {
|
|
147
207
|
css += `\n${body}`;
|
|
148
|
-
if (into
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
208
|
+
if (!into || !body)
|
|
209
|
+
continue;
|
|
210
|
+
const partial = basename(file).startsWith("_");
|
|
211
|
+
into.push({
|
|
212
|
+
file: relative(root, file),
|
|
213
|
+
fragments: fragmentsOfStylesheet(relative(root, file), body, /\.module\./.test(file) || (partial && imported.has(file))
|
|
214
|
+
? "scoped"
|
|
215
|
+
: partial
|
|
216
|
+
? "dead"
|
|
217
|
+
: "global"),
|
|
218
|
+
});
|
|
153
219
|
}
|
|
154
220
|
return css;
|
|
155
221
|
}
|
|
@@ -353,6 +419,8 @@ export async function takeCensus(root, opts) {
|
|
|
353
419
|
const islands = new Map();
|
|
354
420
|
/** How each component expresses style, so the census can state its own coverage. */
|
|
355
421
|
const shapes = new Map();
|
|
422
|
+
/** A escala de espaçamento que o projeto declara para o `sx` - ver o uso abaixo. */
|
|
423
|
+
let sxSpacing;
|
|
356
424
|
/** Component FILES the gate accepted - the honest denominator for coverage. A first
|
|
357
425
|
* version used the whole inventory, which includes everything they compose from a
|
|
358
426
|
* package, and reported 128 for a library of 36 (probe, 01/08). */
|
|
@@ -481,6 +549,20 @@ export async function takeCensus(root, opts) {
|
|
|
481
549
|
* permite o relatório dizer o que existe e não foi lido, em vez de só o que foi.
|
|
482
550
|
*/
|
|
483
551
|
fragments.push(fragmentsOfSource(rel, src));
|
|
552
|
+
/**
|
|
553
|
+
* A ESCALA DE ESPAÇAMENTO DELES, se eles declaram uma.
|
|
554
|
+
*
|
|
555
|
+
* `mt: 2` num `sx` significa `margin-top: 2 * spacing`, e o `spacing` é decisão do projeto.
|
|
556
|
+
* 8px é o default DOCUMENTADO do MUI - fato da biblioteca que eles escolheram, não palpite
|
|
557
|
+
* nosso - mas um projeto que escreve `createTheme({ spacing: 4 })` está declarando outra
|
|
558
|
+
* coisa, e a convenção do usuário ganha da nossa sempre. Medido uma vez, aqui, onde os
|
|
559
|
+
* arquivos já estão abertos: reabri-los depois seria a esteira medindo duas vezes.
|
|
560
|
+
*/
|
|
561
|
+
if (sxSpacing == null && /createTheme\s*\(/.test(src)) {
|
|
562
|
+
const declaredSpacing = spacingOf(src);
|
|
563
|
+
if (declaredSpacing !== MUI_DEFAULT_SPACING)
|
|
564
|
+
sxSpacing = declaredSpacing;
|
|
565
|
+
}
|
|
484
566
|
for (const d of found) {
|
|
485
567
|
sourceOf.set(d.name, { file: rel, source: src });
|
|
486
568
|
/** O que a peça É, do portão - ver `GateVerdict.kind` e `CensusLook.kind`. */
|
|
@@ -564,7 +646,13 @@ export async function takeCensus(root, opts) {
|
|
|
564
646
|
let fromModule = null;
|
|
565
647
|
for (const mod of modules) {
|
|
566
648
|
const cssPath = join(dirname(file), mod.specifier);
|
|
567
|
-
|
|
649
|
+
/**
|
|
650
|
+
* O MODULE E AS PARTIALS QUE ELE IMPORTA, que no build dele são um arquivo só. Ler apenas
|
|
651
|
+
* o `.module.scss` deixava 1 400 declarações fora - as faces light e dark de classes que
|
|
652
|
+
* o componente já veste, escritas em `_lightMode.scss`/`_darkMode.scss` ao lado.
|
|
653
|
+
*/
|
|
654
|
+
const sheet = await sheetWithImports(cssPath);
|
|
655
|
+
const moduleCss = sheet.css.trim();
|
|
568
656
|
if (!moduleCss)
|
|
569
657
|
continue;
|
|
570
658
|
const usage = readModuleUsage(src, mod.local);
|
|
@@ -574,8 +662,14 @@ export async function takeCensus(root, opts) {
|
|
|
574
662
|
* ESTE MODULE PERTENCE A UM COMPONENTE ADMITIDO - o único fato que este laço tem e o
|
|
575
663
|
* ledger precisa. Os fragmentos dele vêm do `harvestOwnCss`, que vê todos os arquivos
|
|
576
664
|
* de estilo e não só os de componente aceito.
|
|
665
|
+
*
|
|
666
|
+
* A PARTIAL ENTRA JUNTO: ela é parte deste module, então o veredito dela é o mesmo. Sem
|
|
667
|
+
* isto o relatório continuaria chamando de "forma sem leitor" exatamente o que a receita
|
|
668
|
+
* acabou de ler - relatório e receita discordando, que é o defeito que o ledger existe
|
|
669
|
+
* para não ter.
|
|
577
670
|
*/
|
|
578
|
-
|
|
671
|
+
for (const part of sheet.files)
|
|
672
|
+
admittedStyles.add(relative(root, part));
|
|
579
673
|
const readModule = transcribeModule(readModuleCss(moduleCss), usage, declaredValues);
|
|
580
674
|
if (readModule.read === 0)
|
|
581
675
|
continue;
|
|
@@ -597,18 +691,34 @@ export async function takeCensus(root, opts) {
|
|
|
597
691
|
* not more honest than literal - v1 mirrors, v2 normalises.
|
|
598
692
|
*/
|
|
599
693
|
const inlineStyle = readInlineStyle(src);
|
|
694
|
+
/**
|
|
695
|
+
* O QUE UM COMPONENTE `styled` DECLARA - e ele não tem JSX, então nada até aqui o leu.
|
|
696
|
+
*
|
|
697
|
+
* `const Title = styled.h1\`font-size: 1.5rem\`` é a aparência de um `<h1>` inteiro, com o
|
|
698
|
+
* nome do componente na variável. O corpo é lido pelo MESMO leitor de regra que lê um
|
|
699
|
+
* module, então `&:hover` e `@media` vêm de graça; o que depende de `${}` é contado e não
|
|
700
|
+
* lido, porque não está na fonte (medido no app dele: 9 componentes, 100 declarações, 5
|
|
701
|
+
* com interpolação).
|
|
702
|
+
*/
|
|
703
|
+
const styledHere = readStyledComponents(src).find((c) => c.name === found[0].name);
|
|
600
704
|
const base = {
|
|
601
705
|
...inlineStyle,
|
|
706
|
+
...styledHere?.base,
|
|
602
707
|
...fromModule?.base,
|
|
603
708
|
...v.base.base,
|
|
604
709
|
...t.base,
|
|
605
710
|
};
|
|
606
711
|
const states = {
|
|
712
|
+
...styledHere?.states,
|
|
607
713
|
...fromModule?.states,
|
|
608
714
|
...v.base.states,
|
|
609
715
|
...t.states,
|
|
610
716
|
};
|
|
611
|
-
const dark = {
|
|
717
|
+
const dark = {
|
|
718
|
+
...styledHere?.dark,
|
|
719
|
+
...fromModule?.dark,
|
|
720
|
+
...t.dark,
|
|
721
|
+
};
|
|
612
722
|
const layers = [...(fromModule?.layers ?? []), ...v.layers];
|
|
613
723
|
const parts = { ...fromModule?.parts, ...t.parts };
|
|
614
724
|
const tree = fromModule?.tree ?? [];
|
|
@@ -1468,6 +1578,11 @@ export async function takeCensus(root, opts) {
|
|
|
1468
1578
|
* ninguém ter que reportar (dono, 04/08).
|
|
1469
1579
|
*/
|
|
1470
1580
|
ledger,
|
|
1581
|
+
/**
|
|
1582
|
+
* A ESCALA QUE O `sx` DELES USA, só quando eles declaram uma diferente do default do MUI.
|
|
1583
|
+
* Medida no censo e consumida pela derivação - ver `readSxProps`.
|
|
1584
|
+
*/
|
|
1585
|
+
...(sxSpacing ? { sxSpacing } : {}),
|
|
1471
1586
|
/**
|
|
1472
1587
|
* THE SHAPE, AND THE RULE IT BECOMES.
|
|
1473
1588
|
*
|
|
@@ -2228,9 +2343,9 @@ async function resolveReadParts(census, root) {
|
|
|
2228
2343
|
? resolveAnatomy(framed, declared, deps, resolveRef, entry?.root,
|
|
2229
2344
|
// The component's own sketch, so a node naming itself by index reads
|
|
2230
2345
|
// the exact class string the census measured (dono, 01/08).
|
|
2231
|
-
looks[component]?.sketch)
|
|
2346
|
+
looks[component]?.sketch, census.sxSpacing)
|
|
2232
2347
|
: derived && derived.read.length > 0
|
|
2233
|
-
? resolveAnatomy(derived.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch)
|
|
2348
|
+
? resolveAnatomy(derived.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing)
|
|
2234
2349
|
: Array.isArray(entry?.parts) && entry.parts.length > 0
|
|
2235
2350
|
? resolveFlatParts(entry.parts, declared)
|
|
2236
2351
|
: null;
|
|
@@ -220,6 +220,18 @@ export function gateComponent(input) {
|
|
|
220
220
|
because: `\`${name}\` renders \`${adapter}\`'s components and no element of its own - its markup and every pixel are that library's, so there is nothing here for this system to re-theme`,
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* UM COMPONENTE `styled` NÃO TEM MARKUP E É UM COMPONENTE.
|
|
225
|
+
*
|
|
226
|
+
* `const Title = styled.h1\`font-size: 1.5rem\`` não escreve JSX nenhum, então caía em "config":
|
|
227
|
+
* uma constante. Mas ele decide a aparência de um `<h1>` inteiro e o nome dele é o nome do
|
|
228
|
+
* componente - recusá-lo é a mesma falha de classificar por FORMA DO ARQUIVO em vez de pelo que
|
|
229
|
+
* a coisa é (medido no app dele: 9 desses, 100 declarações).
|
|
230
|
+
*
|
|
231
|
+
* Antes do teste de JSX, e não depois, porque o teste de JSX é exatamente o que erra aqui.
|
|
232
|
+
*/
|
|
233
|
+
if (STYLED_FACTORY.test(source))
|
|
234
|
+
return { ok: true };
|
|
223
235
|
if (!HAS_JSX.test(source)) {
|
|
224
236
|
return {
|
|
225
237
|
ok: false,
|
|
@@ -252,6 +264,8 @@ export function gateComponent(input) {
|
|
|
252
264
|
* components", which is the one thing a census must never imply. Every name is
|
|
253
265
|
* carried; the printer shows the first few per group and says how many more.
|
|
254
266
|
*/
|
|
267
|
+
/** `styled.h1\`…\`` / `styled(Botao)\`…\`` - uma fábrica de componente, não uma constante. */
|
|
268
|
+
const STYLED_FACTORY = /\bstyled(?:\.[a-zA-Z][\w]*|\([^)]{1,60}\))\s*(?:<[^>]{0,120}>)?\s*`/;
|
|
255
269
|
export const SKIP_LABEL = {
|
|
256
270
|
route: "routes and framework files",
|
|
257
271
|
screen: "whole screens, and the frames around them",
|
|
@@ -335,6 +335,50 @@ export function moduleImports(source) {
|
|
|
335
335
|
specifier: m[2],
|
|
336
336
|
}));
|
|
337
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* `@import "./_darkMode"`, `@use "./_lightMode.scss"` - o que uma folha traz para dentro de si.
|
|
340
|
+
*
|
|
341
|
+
* UMA PARTIAL NÃO É UMA FOLHA. Um arquivo `_nome.scss` nunca é compilado sozinho - o sass o
|
|
342
|
+
* compila DENTRO de quem o importa, e por isso o escopo dele é o de quem importa. No app do dono,
|
|
343
|
+
* 122 das 138 folhas "globais" são partials e 120 delas são importadas por um `.module.scss` do
|
|
344
|
+
* mesmo diretório: 1 400 declarações (91%) que a esteira classificava como CSS global sem leitor
|
|
345
|
+
* são, no build dele, regras de module - hasheadas e escopadas ao componente (medido, 05/08).
|
|
346
|
+
*
|
|
347
|
+
* Decidir escopo por NOME DE ARQUIVO (`/\.module\./`) é a lei 14 um nível abaixo: a resposta está
|
|
348
|
+
* no grafo de imports, não na string do caminho.
|
|
349
|
+
*/
|
|
350
|
+
const SHEET_IMPORT = /@(?:import|use|forward)\s+["']([^"']+)["']/g;
|
|
351
|
+
export function sheetImports(css) {
|
|
352
|
+
SHEET_IMPORT.lastIndex = 0;
|
|
353
|
+
return ([...stripCssComments(css).matchAll(SHEET_IMPORT)]
|
|
354
|
+
.map((m) => m[1].trim())
|
|
355
|
+
/**
|
|
356
|
+
* Só o que é relativo. `@use "sass:math"` e um pacote do node_modules não carregam decisão de
|
|
357
|
+
* aparência deles, e seguir esses caminhos é como um leitor de CSS vira um resolvedor de
|
|
358
|
+
* módulos pela metade.
|
|
359
|
+
*/
|
|
360
|
+
.filter((spec) => spec.startsWith(".") || !spec.includes(":"))
|
|
361
|
+
.filter((spec) => !spec.startsWith("~") && !spec.startsWith("url(")));
|
|
362
|
+
}
|
|
363
|
+
/** `./_darkMode` → os nomes de arquivo que o sass tentaria, na ordem em que ele tenta. */
|
|
364
|
+
export function partialCandidates(specifier) {
|
|
365
|
+
const clean = specifier.replace(/^\.\//, "");
|
|
366
|
+
const slash = clean.lastIndexOf("/");
|
|
367
|
+
const dir = slash === -1 ? "" : `${clean.slice(0, slash)}/`;
|
|
368
|
+
const base = slash === -1 ? clean : clean.slice(slash + 1);
|
|
369
|
+
if (/\.(css|scss|sass|less)$/.test(base))
|
|
370
|
+
return base.startsWith("_")
|
|
371
|
+
? [`${dir}${base}`]
|
|
372
|
+
: [`${dir}${base}`, `${dir}_${base}`];
|
|
373
|
+
const stem = base.startsWith("_") ? base : `_${base}`;
|
|
374
|
+
return [
|
|
375
|
+
`${dir}${base}.scss`,
|
|
376
|
+
`${dir}${stem}.scss`,
|
|
377
|
+
`${dir}${base}.sass`,
|
|
378
|
+
`${dir}${stem}.sass`,
|
|
379
|
+
`${dir}${base}.css`,
|
|
380
|
+
];
|
|
381
|
+
}
|
|
338
382
|
/**
|
|
339
383
|
* A generic in type position, not an element.
|
|
340
384
|
*
|
package/dist/doctor/fragments.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* se ele foi lido é a interpretação, que roda depois. Juntar as duas coisas numa função seria
|
|
14
14
|
* repetir a mistura que a tabela de cobertura já paga: forma e veredito são fatos diferentes.
|
|
15
15
|
*/
|
|
16
|
+
import { readStyledComponents, readSxProps } from "./style-props.js";
|
|
16
17
|
import { readInlineStyle, transcribe } from "./transcribe.js";
|
|
17
18
|
/** `className="…"` e `className={"…"}` - o literal puro. */
|
|
18
19
|
const CLASS_LITERAL = /className=(?:"([^"]{0,400})"|\{\s*"([^"]{0,400})"\s*\})/g;
|
|
@@ -25,10 +26,16 @@ const CALL_LITERAL = /\b(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(([^)]
|
|
|
25
26
|
const STRING_IN_CALL = /"([^"]{0,300})"|'([^']{0,300})'/g;
|
|
26
27
|
/** `style={{ … }}`. */
|
|
27
28
|
const INLINE = /style=\{\{([^}]{0,400})\}\}/g;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/**
|
|
30
|
+
* `sx={{ … }}` e `styled.x\`…\``, achados por CHAVE BALANCEADA e sem teto.
|
|
31
|
+
*
|
|
32
|
+
* As duas versões anteriores eram um regex com `[^}]{0,400}` e `[^`]{0,400}`, e as duas mentiam:
|
|
33
|
+
* `sx={{ "&:hover": { … } }}` para no primeiro `}` e não casa, e um template de 24 declarações
|
|
34
|
+
* passa de 400 chars e desaparece. Medido no app dele: 5 dos 58 `sx` e 3 dos 9 `styled` eram
|
|
35
|
+
* invisíveis - num ledger cuja promessa é que nada é descartado em silêncio (05/08).
|
|
36
|
+
*/
|
|
37
|
+
const SX_OPEN = /\bsx=\{\{/g;
|
|
38
|
+
const STYLED_OPEN = /\bstyled(?:\.[a-zA-Z][\w]*|\([^)]{1,60}\))\s*(?:<[^>]{0,120}>)?\s*`/g;
|
|
32
39
|
/**
|
|
33
40
|
* Um pedaço de linha de estilo e o que o termina: `{` faz do pedaço um seletor, `;` e `}` fazem
|
|
34
41
|
* dele uma declaração. Ler a linha inteira de uma vez perde `.card { padding: 8px }` numa linha só,
|
|
@@ -97,12 +104,55 @@ export function fragmentsOfSource(file, source) {
|
|
|
97
104
|
}
|
|
98
105
|
for (const m of source.matchAll(INLINE))
|
|
99
106
|
push("inline", m.index ?? 0, m[1] ?? "");
|
|
100
|
-
for (const m of source.matchAll(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
for (const m of source.matchAll(SX_OPEN)) {
|
|
108
|
+
const from = (m.index ?? 0) + m[0].length;
|
|
109
|
+
push("sx", m.index ?? 0, source.slice(from, balanced(source, from, "}")));
|
|
110
|
+
}
|
|
111
|
+
for (const m of source.matchAll(STYLED_OPEN)) {
|
|
112
|
+
const from = (m.index ?? 0) + m[0].length;
|
|
113
|
+
push("styled", m.index ?? 0, source.slice(from, backtick(source, from)));
|
|
114
|
+
}
|
|
104
115
|
return out;
|
|
105
116
|
}
|
|
117
|
+
/** O fim de um objeto aberto com duas chaves - `sx={{ … }}` fecha em duas. */
|
|
118
|
+
function balanced(source, from, close) {
|
|
119
|
+
let depth = 2;
|
|
120
|
+
let i = from;
|
|
121
|
+
while (i < source.length && depth > 0) {
|
|
122
|
+
if (source[i] === "{")
|
|
123
|
+
depth += 1;
|
|
124
|
+
else if (source[i] === close)
|
|
125
|
+
depth -= 1;
|
|
126
|
+
i += 1;
|
|
127
|
+
}
|
|
128
|
+
return i - 2;
|
|
129
|
+
}
|
|
130
|
+
/** O backtick que fecha um template, pulando `${…}` e escape. */
|
|
131
|
+
function backtick(source, from) {
|
|
132
|
+
let i = from;
|
|
133
|
+
while (i < source.length) {
|
|
134
|
+
if (source[i] === "\\") {
|
|
135
|
+
i += 2;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (source[i] === "`")
|
|
139
|
+
return i;
|
|
140
|
+
if (source[i] === "$" && source[i + 1] === "{") {
|
|
141
|
+
let depth = 1;
|
|
142
|
+
i += 2;
|
|
143
|
+
while (i < source.length && depth > 0) {
|
|
144
|
+
if (source[i] === "{")
|
|
145
|
+
depth += 1;
|
|
146
|
+
else if (source[i] === "}")
|
|
147
|
+
depth -= 1;
|
|
148
|
+
i += 1;
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
i += 1;
|
|
153
|
+
}
|
|
154
|
+
return source.length;
|
|
155
|
+
}
|
|
106
156
|
/**
|
|
107
157
|
* Os fragmentos de um arquivo de estilo: uma declaração é um fragmento.
|
|
108
158
|
*
|
|
@@ -110,7 +160,20 @@ export function fragmentsOfSource(file, source) {
|
|
|
110
160
|
* contagem: um global com regra de CLASSE é forma que nenhum leitor cobre hoje - 399 declarações
|
|
111
161
|
* no repo dele, e a única lacuna que estava calada em vez de declarada (04/08).
|
|
112
162
|
*/
|
|
113
|
-
export function fragmentsOfStylesheet(file, css,
|
|
163
|
+
export function fragmentsOfStylesheet(file, css,
|
|
164
|
+
/**
|
|
165
|
+
* COMO ESTA FOLHA CHEGA NO BUILD DELES, e isso vem do GRAFO DE IMPORTS e nunca do nome do
|
|
166
|
+
* arquivo. Uma partial (`_darkMode.scss`) é compilada dentro de quem a importa e herda o escopo
|
|
167
|
+
* dele: 122 das 138 folhas "globais" do app dele são partials, e 120 entram num `.module.scss`
|
|
168
|
+
* ao lado (medido, 05/08).
|
|
169
|
+
*
|
|
170
|
+
* scoped module, ou partial de um module: o veredito é do componente dono, e
|
|
171
|
+
* `judgeFragments` o dá olhando se o escopo o admitiu
|
|
172
|
+
* global uma folha que o código importa: forma que nenhum leitor cobre hoje
|
|
173
|
+
* dead partial que ninguém importa: o build deles não a compila, e ela não
|
|
174
|
+
* pinta nada - aqui, nem lá
|
|
175
|
+
*/
|
|
176
|
+
kind) {
|
|
114
177
|
const out = [];
|
|
115
178
|
let selector = "";
|
|
116
179
|
for (const [i, raw] of css.split("\n").entries()) {
|
|
@@ -143,7 +206,11 @@ export function fragmentsOfStylesheet(file, css, scoped) {
|
|
|
143
206
|
line: i + 1,
|
|
144
207
|
text: `${selector ? `${selector} ` : ""}{ ${m[1]}: ${m[2].trim()} }`,
|
|
145
208
|
read: false,
|
|
146
|
-
...(
|
|
209
|
+
...(kind === "global"
|
|
210
|
+
? { reason: "shape-not-read" }
|
|
211
|
+
: kind === "dead"
|
|
212
|
+
? { reason: "sheet-not-imported" }
|
|
213
|
+
: {}),
|
|
147
214
|
});
|
|
148
215
|
}
|
|
149
216
|
}
|
|
@@ -212,6 +279,36 @@ export function judgeFragments(seen, declared, admitted) {
|
|
|
212
279
|
read: false,
|
|
213
280
|
reason: "component-not-admitted",
|
|
214
281
|
};
|
|
282
|
+
case "sx": {
|
|
283
|
+
/**
|
|
284
|
+
* O MESMO LEITOR QUE A RECEITA USA - `readSxProps`. Um segundo julgamento escrito à parte
|
|
285
|
+
* é a maneira mais rápida de o relatório e a receita começarem a discordar.
|
|
286
|
+
*/
|
|
287
|
+
const read = readSxProps(f.text);
|
|
288
|
+
return Object.keys(read.declarations).length > 0
|
|
289
|
+
? { ...f, read: true }
|
|
290
|
+
: {
|
|
291
|
+
...f,
|
|
292
|
+
read: false,
|
|
293
|
+
/**
|
|
294
|
+
* Nada lido de um `sx` que EXISTE só acontece por valor: um ternário, um objeto
|
|
295
|
+
* responsivo, ou um seletor que aponta para o interior de uma biblioteca. A forma é
|
|
296
|
+
* lida - dizer `shape-not-read` aqui mandaria alguém reescrever o leitor que
|
|
297
|
+
* acabou de rodar.
|
|
298
|
+
*/
|
|
299
|
+
reason: "value-not-read",
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
case "styled": {
|
|
303
|
+
const read = readStyledComponents(`const X = styled.div\`${f.text}\`;`);
|
|
304
|
+
const first = read[0];
|
|
305
|
+
const got = Object.keys(first?.base ?? {}).length +
|
|
306
|
+
Object.keys(first?.dark ?? {}).length +
|
|
307
|
+
Object.keys(first?.states ?? {}).length;
|
|
308
|
+
return got > 0
|
|
309
|
+
? { ...f, read: true }
|
|
310
|
+
: { ...f, read: false, reason: "value-not-read" };
|
|
311
|
+
}
|
|
215
312
|
default:
|
|
216
313
|
return { ...f, read: false, reason: "shape-not-read" };
|
|
217
314
|
}
|
|
@@ -84,7 +84,14 @@ function whereOf(shape, reason) {
|
|
|
84
84
|
module: null,
|
|
85
85
|
work: reason === "component-not-admitted"
|
|
86
86
|
? "the gate refused the component: these styles exist and no recipe claims them. Changing that means moving the GATE - a decision about the vitrine, not a reader to write"
|
|
87
|
-
:
|
|
87
|
+
: reason === "sheet-not-imported"
|
|
88
|
+
? /**
|
|
89
|
+
* NÃO É LACUNA NOSSA NEM DELES-QUE-A-GENTE-CONSERTA: é um arquivo que o build deles
|
|
90
|
+
* não compila. Cair na frase de runtime dizia que o valor é montado em tempo de
|
|
91
|
+
* execução, o que é falso e mandaria alguém procurar o que não existe.
|
|
92
|
+
*/
|
|
93
|
+
"a sass partial no stylesheet imports: their own build never compiles it, so it paints nothing today - here or in their app. Deleting it is their call, not a reader to write"
|
|
94
|
+
: "the value is assembled at render time and is not in the source at any level - there is nothing to read",
|
|
88
95
|
};
|
|
89
96
|
}
|
|
90
97
|
}
|
package/dist/doctor/sketch.js
CHANGED
|
@@ -431,6 +431,29 @@ function closeOf(source, open) {
|
|
|
431
431
|
* `className={styles.content}` e `className={s.content}` - a forma que um app de CSS Modules
|
|
432
432
|
* usa em todo elemento. O identificador é o import do module, e a propriedade é a classe.
|
|
433
433
|
*/
|
|
434
|
+
/**
|
|
435
|
+
* O CORPO DE `sx={{ … }}`, por chave balanceada.
|
|
436
|
+
*
|
|
437
|
+
* Um regex `[^}]` para no primeiro `}` e perde `sx={{ "&:hover": { … } }}` inteiro, que é
|
|
438
|
+
* justamente a forma em que a declaração aninhada mora.
|
|
439
|
+
*/
|
|
440
|
+
function sxBody(body) {
|
|
441
|
+
const at = body.search(/\bsx=\{\{/);
|
|
442
|
+
if (at === -1)
|
|
443
|
+
return undefined;
|
|
444
|
+
const from = at + (/\bsx=\{\{/.exec(body)?.[0].length ?? 0);
|
|
445
|
+
let depth = 2;
|
|
446
|
+
let i = from;
|
|
447
|
+
while (i < body.length && depth > 0) {
|
|
448
|
+
if (body[i] === "{")
|
|
449
|
+
depth += 1;
|
|
450
|
+
else if (body[i] === "}")
|
|
451
|
+
depth -= 1;
|
|
452
|
+
i += 1;
|
|
453
|
+
}
|
|
454
|
+
const text = body.slice(from, i - 2).trim();
|
|
455
|
+
return text || undefined;
|
|
456
|
+
}
|
|
434
457
|
const MODULE_CLASS = /className=\{\s*([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)\s*\}/;
|
|
435
458
|
export function sketchOf(source, name) {
|
|
436
459
|
/**
|
|
@@ -528,6 +551,10 @@ export function sketchOf(source, name) {
|
|
|
528
551
|
const mod = MODULE_CLASS.exec(event.body);
|
|
529
552
|
if (mod)
|
|
530
553
|
node.moduleClass = mod[2];
|
|
554
|
+
/** A prop de estilo que ele carrega - ver `sx`. */
|
|
555
|
+
const sx = sxBody(event.body);
|
|
556
|
+
if (sx)
|
|
557
|
+
node.sx = sx;
|
|
531
558
|
// What has to be true for this element to exist at all - see `guardAt`.
|
|
532
559
|
const guard = guardAt(source, event.at);
|
|
533
560
|
if (guard) {
|
|
@@ -54,6 +54,7 @@ const BECAUSE = {
|
|
|
54
54
|
"value-not-read": "the shape is read and this value is not - a utility or property outside the scales this project declares",
|
|
55
55
|
computed: "the value is assembled at render time, so it is not in the source at any level and there is nothing to read",
|
|
56
56
|
"component-not-admitted": "read, and it belongs to a component this scope does not admit into the system - the styles exist and no recipe claims them",
|
|
57
|
+
"sheet-not-imported": "this is a sass partial and no stylesheet imports it, so your own build never compiles it - it paints nothing today, here or anywhere",
|
|
57
58
|
};
|
|
58
59
|
/**
|
|
59
60
|
* O ledger de uma varredura, a partir dos fragmentos vistos.
|
|
Binary file
|
package/package.json
CHANGED