synthesisui 0.16.342 → 0.16.344
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/doctor.js +25 -3
- package/dist/commands/mcp.js +2 -2
- package/dist/doctor/scan.js +4 -1
- package/dist/doctor/their-names.js +14 -2
- package/dist/doctor/tokens.js +1 -1
- package/dist/doctor/transcribe.js +65 -0
- package/dist/their-vars.js +1 -1
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -8,7 +8,7 @@ import { emptyTally, internalSpecifiers, scanComponentsInto, tallyToInventory, }
|
|
|
8
8
|
import { checkContracts } from "../doctor/contract-check.js";
|
|
9
9
|
import { describeMissing, missingDependencies, summarizeMissing, } from "../doctor/dependencies.js";
|
|
10
10
|
import { findFrozenBindings } from "../doctor/frozen.js";
|
|
11
|
-
import { appendEvent, COVERAGE_RULE, readEvents, suggestionsFrom,
|
|
11
|
+
import { appendEvent, COVERAGE_RULE, readEvents, suggestionsFrom, summarize, unrepresentedFrom, } from "../doctor/ledger.js";
|
|
12
12
|
import { bindingsFromDocument, countComponents, findOverrides, } from "../doctor/overrides.js";
|
|
13
13
|
import { checkableName, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
|
|
14
14
|
import { DEFAULT_ROOT_PX, rootSizeOf, saidOfRoot, } from "../doctor/root-size.js";
|
|
@@ -156,6 +156,28 @@ export async function* walkAll(roots) {
|
|
|
156
156
|
* doctor como se fosse um componente pedido. Uma tela que rotula errado é pior que uma que não
|
|
157
157
|
* rotula: quem lê decide em cima do rótulo.
|
|
158
158
|
*/
|
|
159
|
+
/**
|
|
160
|
+
* O EMPATE DITO EM VOZ ALTA - a metade que faltava do nome dele.
|
|
161
|
+
*
|
|
162
|
+
* O QUE ELE PERDIA: quando mais de um token DELE segura o mesmo valor, a esteira desempata por
|
|
163
|
+
* critérios lidos do repositório dele e o relatório imprimia só o vencedor, `→ {color.tier-gold}`,
|
|
164
|
+
* como se fosse fato. No sistema do dono aquele `#f59e0b` é `--color-tier-gold` E
|
|
165
|
+
* `--color-feedback-warning` - gamificação e estado, dois conceitos dele com o mesmo hex -, e a
|
|
166
|
+
* escolha entre eles nunca foi dele. Medido em 31/08 nos 26 sistemas do banco: 7 (27%) têm ao
|
|
167
|
+
* menos um valor com mais de um nome.
|
|
168
|
+
*
|
|
169
|
+
* A ESCOLHA CONTINUA SENDO FEITA, porque o `--fix` precisa de UM nome para escrever. O que muda é
|
|
170
|
+
* que ela para de passar por fato: ele vê que houve empate e pode discordar com informação, que é
|
|
171
|
+
* a diferença entre uma lacuna declarada e um silêncio.
|
|
172
|
+
*
|
|
173
|
+
* UM SÓ É NOMEADO, o resto vira contagem: dois nomes cabem na linha, cinco viram uma parede que
|
|
174
|
+
* ninguém lê - e o número já diz que a decisão existe.
|
|
175
|
+
*/
|
|
176
|
+
const alsoNamed = (f) => !f.theirAlso?.length
|
|
177
|
+
? ""
|
|
178
|
+
: f.theirAlso.length === 1
|
|
179
|
+
? ` · you also call it ${f.theirAlso[0]}`
|
|
180
|
+
: ` · you also call it ${f.theirAlso[0]} and ${f.theirAlso.length - 1} other${f.theirAlso.length - 1 === 1 ? "" : "s"}`;
|
|
159
181
|
function requestLabel(r) {
|
|
160
182
|
if (r.kind === "token")
|
|
161
183
|
return `token ${r.value} as ${r.name}`;
|
|
@@ -1314,7 +1336,7 @@ export async function doctor(opts) {
|
|
|
1314
1336
|
const named = r.crossFamily && !r.theirToken
|
|
1315
1337
|
? ` → no ${r.kind} named for it · the value lives as ${r.token}`
|
|
1316
1338
|
: nameToWrite(r)
|
|
1317
|
-
? ` → ${nameToWrite(r)}`
|
|
1339
|
+
? ` → ${nameToWrite(r)}${alsoNamed(r)}`
|
|
1318
1340
|
: near
|
|
1319
1341
|
? ` → nearest is ${near.theirs ?? near.name} (${near.value})`
|
|
1320
1342
|
: "";
|
|
@@ -1338,7 +1360,7 @@ export async function doctor(opts) {
|
|
|
1338
1360
|
const named = x.crossFamily && !x.theirToken
|
|
1339
1361
|
? `→ no ${x.kind} named for it · the value lives as ${x.token}`
|
|
1340
1362
|
: nameToWrite(x)
|
|
1341
|
-
? `→ ${nameToWrite(x)}`
|
|
1363
|
+
? `→ ${nameToWrite(x)}${alsoNamed(x)}`
|
|
1342
1364
|
: near
|
|
1343
1365
|
? `→ nearest is ${near.theirs ?? near.name} (${near.value})`
|
|
1344
1366
|
: "→ no token holds this value yet";
|
package/dist/commands/mcp.js
CHANGED
|
@@ -404,9 +404,9 @@ async function findToken(root, value) {
|
|
|
404
404
|
*/
|
|
405
405
|
const theirNameFor = (v) => {
|
|
406
406
|
const norm = normalizeValue(v, table.rootPx);
|
|
407
|
-
for (const [key,
|
|
407
|
+
for (const [key, theirs] of table.aliases)
|
|
408
408
|
if (key.endsWith(`:${norm}`))
|
|
409
|
-
return name;
|
|
409
|
+
return theirs.name;
|
|
410
410
|
return null;
|
|
411
411
|
};
|
|
412
412
|
const exact = tokenFor(table, value);
|
package/dist/doctor/scan.js
CHANGED
|
@@ -420,7 +420,8 @@ function scanCore(file, source, table) {
|
|
|
420
420
|
...(/(?<!r)em$/i.test(literal.trim())
|
|
421
421
|
? { fontRelative: true }
|
|
422
422
|
: {}),
|
|
423
|
-
...(theirs ? { theirToken: theirs } : {}),
|
|
423
|
+
...(theirs ? { theirToken: theirs.name } : {}),
|
|
424
|
+
...(theirs?.also.length ? { theirAlso: theirs.also } : {}),
|
|
424
425
|
/**
|
|
425
426
|
* A COINCIDÊNCIA VIAJA COM O ACHADO - ver `tokenMatch`.
|
|
426
427
|
*
|
|
@@ -609,6 +610,7 @@ export function diagnose(files) {
|
|
|
609
610
|
kind: f.kind,
|
|
610
611
|
token: f.token,
|
|
611
612
|
...(f.theirToken ? { theirToken: f.theirToken } : {}),
|
|
613
|
+
...(f.theirAlso?.length ? { theirAlso: f.theirAlso } : {}),
|
|
612
614
|
...(f.fontRelative ? { fontRelative: true } : {}),
|
|
613
615
|
count: 1,
|
|
614
616
|
files: new Set([f.file]),
|
|
@@ -622,6 +624,7 @@ export function diagnose(files) {
|
|
|
622
624
|
kind: v.kind,
|
|
623
625
|
token: v.token,
|
|
624
626
|
...(v.theirToken ? { theirToken: v.theirToken } : {}),
|
|
627
|
+
...(v.theirAlso?.length ? { theirAlso: v.theirAlso } : {}),
|
|
625
628
|
...(v.fontRelative ? { fontRelative: true } : {}),
|
|
626
629
|
count: v.count,
|
|
627
630
|
files: v.files.size,
|
|
@@ -98,6 +98,18 @@ function pick(candidates, ours, convention) {
|
|
|
98
98
|
* `24px` pode ser raio, espaçamento ou tamanho de fonte, e sem um token nosso não há o que validasse.
|
|
99
99
|
* Esses continuam saindo como "no name for it", que é verdade e é o caminho do `absorb`.
|
|
100
100
|
*/
|
|
101
|
+
/**
|
|
102
|
+
* O VENCEDOR E OS PRETERIDOS, juntos - porque um empate silencioso é o defeito.
|
|
103
|
+
*
|
|
104
|
+
* `pick` escolhe entre nomes DELE por critérios lidos do repositório dele, e isso continua certo:
|
|
105
|
+
* o achado precisa de UM nome para escrever. O que estava errado era descartar os outros. No
|
|
106
|
+
* sistema do dono, `#f59e0b` é `--color-tier-gold` E `--color-feedback-warning` - dois conceitos
|
|
107
|
+
* dele, gamificação e estado -, e o relatório dizia `→ {color.tier-gold}` como se fosse fato.
|
|
108
|
+
*/
|
|
109
|
+
function escolha(candidates, ours, convention) {
|
|
110
|
+
const name = pick(candidates, ours, convention);
|
|
111
|
+
return { name, also: candidates.filter((c) => c !== name) };
|
|
112
|
+
}
|
|
101
113
|
export function theirNames(ours, theirs) {
|
|
102
114
|
const out = new Map();
|
|
103
115
|
if (theirs.byName.size === 0)
|
|
@@ -139,7 +151,7 @@ export function theirNames(ours, theirs) {
|
|
|
139
151
|
: candidates.filter((n) => familySays(kind, n));
|
|
140
152
|
if (usable.length === 0)
|
|
141
153
|
continue;
|
|
142
|
-
out.set(key,
|
|
154
|
+
out.set(key, escolha(usable, name, convention));
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
157
|
for (const [value, candidates] of theirs.byValue) {
|
|
@@ -149,7 +161,7 @@ export function theirNames(ours, theirs) {
|
|
|
149
161
|
const key = `${kind}:${value}`;
|
|
150
162
|
if (out.has(key))
|
|
151
163
|
continue;
|
|
152
|
-
out.set(key,
|
|
164
|
+
out.set(key, escolha(candidates, null, convention));
|
|
153
165
|
}
|
|
154
166
|
}
|
|
155
167
|
return out;
|
package/dist/doctor/tokens.js
CHANGED
|
@@ -418,7 +418,7 @@ kind) {
|
|
|
418
418
|
const theirs = kind
|
|
419
419
|
? table.aliases.get(`${kind}:${normalizeValue(best.value, table.rootPx)}`)
|
|
420
420
|
: undefined;
|
|
421
|
-
return theirs ? { ...best, theirs } : best;
|
|
421
|
+
return theirs ? { ...best, theirs: theirs.name } : best;
|
|
422
422
|
}
|
|
423
423
|
/**
|
|
424
424
|
* The family a token belongs to, from the drift it was found in.
|
|
@@ -2029,8 +2029,27 @@ export function definitionSpan(source, name) {
|
|
|
2029
2029
|
const decl = new RegExp(`(?:function|const|let|var)\\s+${name}\\b|${name}\\s*[:=]\\s*(?:function|\\()`).exec(source);
|
|
2030
2030
|
if (!decl)
|
|
2031
2031
|
return null;
|
|
2032
|
+
/**
|
|
2033
|
+
* O CORPO PODE NÃO TER CHAVE NENHUMA - `forwardRef((props, ref) => (<div/>))`.
|
|
2034
|
+
*
|
|
2035
|
+
* O QUE O CLIENTE PERDIA: esta é a forma canônica de uma biblioteca de componentes (o shadcn/ui
|
|
2036
|
+
* escreve assim), e para ela `bodyBrace` não acha `{` de corpo - o arrow retorna direto. O span
|
|
2037
|
+
* voltava `null`, e sem span `variantCallsIn` devolve lista vazia: a tabela `cva` do componente
|
|
2038
|
+
* NÃO era ligada a ele, e o censo gravava classe vazia num componente que declara seis linhas de
|
|
2039
|
+
* base. Ele abre o Studio e vê a caixa pelada.
|
|
2040
|
+
*
|
|
2041
|
+
* REPRODUZIDO EM 31/08, com a mesma tabela e o mesmo call site, variando só o corpo:
|
|
2042
|
+
*
|
|
2043
|
+
* forwardRef(({ s }, ref) => { return <div className={cn(v({s}))}/> }) a tabela e' ligada
|
|
2044
|
+
* forwardRef(({ s }, ref) => (<div className={cn(v({s}))}/>)) lista VAZIA
|
|
2045
|
+
*
|
|
2046
|
+
* O corpo aqui é delimitado pelo parêntese do PRÓPRIO wrapper, e é ele que fecha o span.
|
|
2047
|
+
*/
|
|
2032
2048
|
const brace = bodyBrace(source, decl.index);
|
|
2033
2049
|
if (brace === -1) {
|
|
2050
|
+
const implicito = implicitBodySpan(source, decl.index);
|
|
2051
|
+
if (implicito)
|
|
2052
|
+
return { from: decl.index, to: implicito };
|
|
2034
2053
|
// The export is an alias; the body belongs to what it wraps.
|
|
2035
2054
|
const target = aliasTarget(source, name);
|
|
2036
2055
|
if (target)
|
|
@@ -2040,6 +2059,52 @@ export function definitionSpan(source, name) {
|
|
|
2040
2059
|
const end = matchingBrace(source, brace);
|
|
2041
2060
|
return { from: decl.index, to: end === -1 ? source.length : end + 1 };
|
|
2042
2061
|
}
|
|
2062
|
+
/**
|
|
2063
|
+
* ONDE FECHA UM CORPO SEM CHAVE - `= forwardRef((props, ref) => (<div/>))`, e nada mais.
|
|
2064
|
+
*
|
|
2065
|
+
* Ela só responde para a forma que `bodyBrace` não sabe ler: um wrapper reconhecido, seguido de um
|
|
2066
|
+
* arrow cujo corpo abre em PARÊNTESE. Um arrow com chaves, uma função nomeada ou qualquer outra
|
|
2067
|
+
* coisa devolve `null` e o caminho antigo segue intacto - esta é uma porta a mais, nunca um desvio
|
|
2068
|
+
* do que já funcionava.
|
|
2069
|
+
*
|
|
2070
|
+
* O fecho é o parêntese do próprio wrapper, contado com atenção a aspas: um `)` dentro de
|
|
2071
|
+
* `color = "rgba(0,0,0,.2)"` não fecha nada, e foi essa mesma armadilha que já custou a raiz de
|
|
2072
|
+
* sete componentes de um cliente.
|
|
2073
|
+
*/
|
|
2074
|
+
function implicitBodySpan(source, declAt) {
|
|
2075
|
+
const head = source.slice(declAt, declAt + 400);
|
|
2076
|
+
const wrapper = /=\s*(?:React\.)?(?:forwardRef|memo|styled\([^)]*\))\s*(?:<[^(]*>)?\s*\(/.exec(head);
|
|
2077
|
+
if (!wrapper)
|
|
2078
|
+
return null;
|
|
2079
|
+
const open = declAt + wrapper.index + wrapper[0].length - 1;
|
|
2080
|
+
const rest = source.slice(open);
|
|
2081
|
+
const arrow = /=>\s*[({]/.exec(rest);
|
|
2082
|
+
/** Corpo em chave é do `bodyBrace`; aqui só entra o retorno implícito. */
|
|
2083
|
+
if (!arrow || arrow[0].trimEnd().endsWith("{"))
|
|
2084
|
+
return null;
|
|
2085
|
+
let depth = 0;
|
|
2086
|
+
let quote = null;
|
|
2087
|
+
for (let i = open; i < source.length; i++) {
|
|
2088
|
+
const ch = source[i];
|
|
2089
|
+
if (quote) {
|
|
2090
|
+
if (ch === "\\")
|
|
2091
|
+
i += 1;
|
|
2092
|
+
else if (ch === quote)
|
|
2093
|
+
quote = null;
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
if (ch === '"' || ch === "'" || ch === "`")
|
|
2097
|
+
quote = ch;
|
|
2098
|
+
else if (ch === "(")
|
|
2099
|
+
depth += 1;
|
|
2100
|
+
else if (ch === ")") {
|
|
2101
|
+
depth -= 1;
|
|
2102
|
+
if (depth === 0)
|
|
2103
|
+
return i + 1;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
return null;
|
|
2107
|
+
}
|
|
2043
2108
|
/** The `{` that opens a function body declared at `declAt`, or -1. */
|
|
2044
2109
|
function bodyBrace(source, declAt) {
|
|
2045
2110
|
/**
|
package/dist/their-vars.js
CHANGED
|
@@ -115,7 +115,7 @@ export function pointAtTheirNames(css, theirs, resolvable) {
|
|
|
115
115
|
const kind = Object.entries(FAMILY_KIND).find(([prefix]) => name.startsWith(prefix))?.[1];
|
|
116
116
|
if (!kind)
|
|
117
117
|
return line;
|
|
118
|
-
const theirName = alias.get(`${kind}:${normalizeValue(value.trim(), ours.rootPx)}`);
|
|
118
|
+
const theirName = alias.get(`${kind}:${normalizeValue(value.trim(), ours.rootPx)}`)?.name;
|
|
119
119
|
if (!theirName || theirName === name)
|
|
120
120
|
return line;
|
|
121
121
|
if (!resolvable.has(theirName)) {
|
package/package.json
CHANGED