synthesisui 0.16.379 → 0.16.380
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 +4 -1
- package/dist/doctor/apply-fix.js +145 -21
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -621,7 +621,10 @@ async function runFix(root, d, writing) {
|
|
|
621
621
|
await appendEvent(root, {
|
|
622
622
|
kind: "fix",
|
|
623
623
|
at: new Date().toISOString(),
|
|
624
|
-
|
|
624
|
+
/** ESCRITAS, e não achados - ver `Applied.occurrences`: um achado de folha minificada vale
|
|
625
|
+
* por sete trocas, e o ledger que contasse achados registraria um ato menor do que o que
|
|
626
|
+
* aconteceu no repositório dele. */
|
|
627
|
+
named: result.applied.reduce((n, a) => n + a.occurrences, 0),
|
|
625
628
|
});
|
|
626
629
|
console.log("");
|
|
627
630
|
console.log(body(paint.dim("Read the diff before committing - it is your code, and this replaced literals with the tokens your own system declares.")));
|
package/dist/doctor/apply-fix.js
CHANGED
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
* confiança:
|
|
19
19
|
*
|
|
20
20
|
* 1. tocar uma linha que mudou desde a medição - se o texto não bate, pula e conta
|
|
21
|
-
* 2.
|
|
22
|
-
*
|
|
21
|
+
* 2. sair da linha que o achado nomeia - a troca acontece naquela linha e em nenhuma outra.
|
|
22
|
+
* DENTRO dela, todas as ocorrências daquele valor são trocadas, e o recibo conta ESCRITAS,
|
|
23
|
+
* não achados: o scan emite um achado por (arquivo, linha, valor), e numa folha minificada
|
|
24
|
+
* esse um vale por sete
|
|
23
25
|
* 3. escrever em arquivo que o scanner marcou como fora (gerado, vendored) - esses nunca
|
|
24
26
|
* chegam aqui, porque o walk já os pula
|
|
25
27
|
*/
|
|
@@ -27,17 +29,117 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
27
29
|
import { join } from "node:path";
|
|
28
30
|
import { nameToWrite } from "./scan.js";
|
|
29
31
|
/**
|
|
30
|
-
*
|
|
32
|
+
* ONDE O VALOR DE UMA DECLARAÇÃO DESTE MESMO TOKEN COMEÇA E TERMINA, nesta linha.
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
34
|
+
* Só os intervalos do token que a troca ESCREVERIA: é dentro deles, e só ali, que trocar produz
|
|
35
|
+
* `--x: var(--x)`. Todo o resto da linha - inclusive a declaração de OUTRO token dele - é uso
|
|
36
|
+
* legítimo, e continua trocável.
|
|
35
37
|
*/
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
function selfRanges(line, name) {
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const m of line.matchAll(/(--[a-z0-9-]+)\s*:\s*([^;}]*)/gi)) {
|
|
41
|
+
if (m[1] !== name)
|
|
42
|
+
continue;
|
|
43
|
+
const at = (m.index ?? 0) + m[0].length - m[2].length;
|
|
44
|
+
out.push([at, at + m[2].length]);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A TROCA NA LINHA, EM TODAS AS VEZES QUE O VALOR APARECE NELA - e era aqui que o número mentia.
|
|
50
|
+
*
|
|
51
|
+
* O QUE ACONTECIA: o scan emite UM achado por (arquivo, linha, valor), e esta função trocava a
|
|
52
|
+
* PRIMEIRA ocorrência. Numa folha minificada a linha inteira é o arquivo: em
|
|
53
|
+
* `apps/landing/app/styles/pulse.css` a linha 7 escreve `#6b6b78` sete vezes, o comando trocava uma,
|
|
54
|
+
* contava uma, e deixava seis - sem dizer. A medição seguinte reencontrava o mesmo achado, e o número
|
|
55
|
+
* que o comando prometeu passava a não bater com o número que a próxima leitura mostra.
|
|
56
|
+
*
|
|
57
|
+
* MEDIDO EM DUAS POPULAÇÕES (05/09, pelo caminho real do `doctor --fix --write`, antes e depois):
|
|
58
|
+
*
|
|
59
|
+
* codelevel 114 achados aplicados · escrevia 114 · agora escreve **126** · discrepância 5 -> 0
|
|
60
|
+
* frontend-hub 1010 achados aplicados · escrevia 1010 · agora escreve **1012** · já era 0
|
|
61
|
+
*
|
|
62
|
+
* E A DIFERENÇA QUE ISTO FECHA: no codelevel o comando anunciava 114 trocas e a medição seguinte
|
|
63
|
+
* mostrava só 109 achados a menos - **5 sem explicação**, que são estes: cinco linhas onde o valor
|
|
64
|
+
* aparecia mais de uma vez, o achado continuava lá depois do conserto, e o cliente que rodasse os
|
|
65
|
+
* dois comandos em sequência via os dois números. Agora a queda é 114 de 114.
|
|
66
|
+
*
|
|
67
|
+
* O TETO É MAIOR QUE O EFEITO DE HOJE, e vale dizer: no codelevel, 39 dos 208 achados NOMEADOS
|
|
68
|
+
* escondem 235 ocorrências, 234 delas num único arquivo minificado - a maior parte em achados que o
|
|
69
|
+
* `--fix` ainda não aplica porque apontam para tokens nossos, que só resolvem depois da fiação. No
|
|
70
|
+
* dia em que ele fizer a fiação, são essas que passam a ser escritas.
|
|
71
|
+
*
|
|
72
|
+
* Não é forma de um repositório: é toda folha que alguém minifica, e todo arquivo que escreve o mesmo
|
|
73
|
+
* valor duas vezes na mesma linha.
|
|
74
|
+
*
|
|
75
|
+
* O GUARD DA AUTO-REFERÊNCIA PASSA A VALER POR OCORRÊNCIA, e não pela linha inteira. Numa linha que
|
|
76
|
+
* DECLARA o token e também o USA, recusar as duas coisas deixava o uso sem conserto por causa da
|
|
77
|
+
* vizinha; agora a declaração fica intacta e o uso é trocado. Quando toda ocorrência da linha é a
|
|
78
|
+
* própria declaração, o resultado é o de sempre: nada trocado, e o motivo dito.
|
|
79
|
+
*
|
|
80
|
+
* E O VALOR TEM QUE TERMINAR ONDE ELE TERMINA - ver `wholeValue`.
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* A OCORRÊNCIA É O VALOR INTEIRO, E NÃO O COMEÇO DE OUTRO - e sem isto a troca escreve cor inválida.
|
|
84
|
+
*
|
|
85
|
+
* `#4f46e5` é um prefixo de `#4f46e50b`, que é a MESMA cor com alpha. Trocando pelo prefixo, a linha
|
|
86
|
+
* fica `var(--color-brand-blue)0b`: o navegador descarta a declaração inteira e o elemento perde a
|
|
87
|
+
* cor - o mesmo estrago do ciclo, por outro caminho. Vale igual para comprimento: `1rem` é prefixo de
|
|
88
|
+
* `1remx` e sufixo de `11rem`.
|
|
89
|
+
*
|
|
90
|
+
* NÃO É HIPÓTESE: `apps/landing/app/styles/pulse.css` escreve 8 valores em `#rrggbbaa`, e o
|
|
91
|
+
* `--fix` só não os atingia porque trocava a PRIMEIRA ocorrência da linha e ela calhava de ser um hex
|
|
92
|
+
* puro. Sorte da população, não proteção - e trocando todas, a sorte acaba.
|
|
93
|
+
*/
|
|
94
|
+
function wholeValue(line, at, literal) {
|
|
95
|
+
const before = at > 0 ? line[at - 1] : "";
|
|
96
|
+
const after = line[at + literal.length] ?? "";
|
|
97
|
+
return !/[0-9a-z#]/i.test(before) && !/[0-9a-z]/i.test(after);
|
|
98
|
+
}
|
|
99
|
+
/** Onde este valor está escrito INTEIRO nesta linha - a mesma pergunta que a troca faz. */
|
|
100
|
+
function wholeValuesIn(line, literal) {
|
|
101
|
+
const out = [];
|
|
102
|
+
for (let at = line.indexOf(literal); at !== -1;) {
|
|
103
|
+
if (wholeValue(line, at, literal))
|
|
104
|
+
out.push(at);
|
|
105
|
+
at = line.indexOf(literal, at + literal.length);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function swapAll(line, literal, name) {
|
|
110
|
+
const guarded = selfRanges(line, name);
|
|
111
|
+
let out = "";
|
|
112
|
+
let from = 0;
|
|
113
|
+
let count = 0;
|
|
114
|
+
let blocked = 0;
|
|
115
|
+
for (;;) {
|
|
116
|
+
const at = line.indexOf(literal, from);
|
|
117
|
+
if (at === -1)
|
|
118
|
+
break;
|
|
119
|
+
const declares = guarded.some(([start, end]) => at >= start && at + literal.length <= end);
|
|
120
|
+
/**
|
|
121
|
+
* A BORDA NÃO CONTA COMO AUTO-REFERÊNCIA - senão o relatório dá o motivo de outro caso.
|
|
122
|
+
*
|
|
123
|
+
* `blocked` é só a declaração do próprio token, porque é ele que decide a frase impressa quando
|
|
124
|
+
* nada foi trocado. Um prefixo de valor maior não é uma recusa: é um valor que não está escrito
|
|
125
|
+
* naquela linha, e cai no motivo que já existe para isso.
|
|
126
|
+
*/
|
|
127
|
+
if (!wholeValue(line, at, literal)) {
|
|
128
|
+
out += line.slice(from, at + literal.length);
|
|
129
|
+
from = at + literal.length;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (declares) {
|
|
133
|
+
blocked++;
|
|
134
|
+
out += line.slice(from, at + literal.length);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
count++;
|
|
138
|
+
out += `${line.slice(from, at)}var(${name})`;
|
|
139
|
+
}
|
|
140
|
+
from = at + literal.length;
|
|
141
|
+
}
|
|
142
|
+
return { line: out + line.slice(from), count, blocked };
|
|
41
143
|
}
|
|
42
144
|
/**
|
|
43
145
|
* Aplica o que tem token, em memória, e devolve o conteúdo novo por arquivo.
|
|
@@ -53,6 +155,15 @@ export function planFix(d, read) {
|
|
|
53
155
|
const lines = new Map();
|
|
54
156
|
/** A mesma chave, congelada antes da primeira troca - ver o skip de `not-written`. */
|
|
55
157
|
const originals = new Map();
|
|
158
|
+
/**
|
|
159
|
+
* O MESMO VALOR, NA MESMA LINHA, APONTADO DUAS VEZES É UM CONSERTO SÓ - e não uma linha que mudou.
|
|
160
|
+
*
|
|
161
|
+
* A troca cobre TODAS as ocorrências daquela linha de uma vez, então um segundo achado idêntico não
|
|
162
|
+
* encontra mais o literal e cairia no skip de `moved`, que imprime *"linhas mudaram desde o scan -
|
|
163
|
+
* rode de novo"* num repositório onde ninguém mexeu em nada. Nada é perdido de vista: as duas
|
|
164
|
+
* escritas já estão contadas no `occurrences` do primeiro.
|
|
165
|
+
*/
|
|
166
|
+
const done = new Set();
|
|
56
167
|
/**
|
|
57
168
|
* DE BAIXO PARA CIMA no arquivo não é necessário - a troca não muda a contagem de linhas - mas a
|
|
58
169
|
* ORDEM por arquivo é, para dois achados na mesma linha não se atropelarem: o segundo procura o seu
|
|
@@ -172,10 +283,15 @@ export function planFix(d, read) {
|
|
|
172
283
|
* seguem trocáveis: numa linha minificada, um `background:#fbfaf6` ao lado de um `--violet:...`
|
|
173
284
|
* continua sendo um uso legítimo.
|
|
174
285
|
*/
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
286
|
+
const already = `${f.file}:${f.line}:${f.literal}`;
|
|
287
|
+
if (done.has(already))
|
|
288
|
+
continue;
|
|
289
|
+
const swapped = current === undefined ? null : swapAll(current, f.literal, name);
|
|
290
|
+
/**
|
|
291
|
+
* TODA OCORRÊNCIA DA LINHA ERA A PRÓPRIA DECLARAÇÃO - o caso que o guard sempre recusou, dito
|
|
292
|
+
* com a mesma frase de antes. O que mudou é que uma linha que declara E usa deixou de cair aqui.
|
|
293
|
+
*/
|
|
294
|
+
if (swapped !== null && swapped.count === 0 && swapped.blocked > 0) {
|
|
179
295
|
skipped.push({
|
|
180
296
|
file: f.file,
|
|
181
297
|
line: f.line,
|
|
@@ -184,8 +300,7 @@ export function planFix(d, read) {
|
|
|
184
300
|
});
|
|
185
301
|
continue;
|
|
186
302
|
}
|
|
187
|
-
|
|
188
|
-
if (swapped === null) {
|
|
303
|
+
if (swapped === null || swapped.count === 0) {
|
|
189
304
|
/**
|
|
190
305
|
* DOIS MOTIVOS DIFERENTES, E A TELA DIZIA UM SÓ - e o que ela dizia era falso quase sempre.
|
|
191
306
|
*
|
|
@@ -215,19 +330,21 @@ export function planFix(d, read) {
|
|
|
215
330
|
file: f.file,
|
|
216
331
|
line: f.line,
|
|
217
332
|
literal: f.literal,
|
|
218
|
-
because: before === undefined || before
|
|
333
|
+
because: before === undefined || wholeValuesIn(before, f.literal).length > 0
|
|
219
334
|
? "moved"
|
|
220
335
|
: "not-written",
|
|
221
336
|
});
|
|
222
337
|
continue;
|
|
223
338
|
}
|
|
224
|
-
body[idx] = swapped;
|
|
339
|
+
body[idx] = swapped.line;
|
|
225
340
|
applied.push({
|
|
226
341
|
file: f.file,
|
|
227
342
|
line: f.line,
|
|
228
343
|
literal: f.literal,
|
|
229
344
|
token: name,
|
|
345
|
+
occurrences: swapped.count,
|
|
230
346
|
});
|
|
347
|
+
done.add(already);
|
|
231
348
|
}
|
|
232
349
|
for (const [file, body] of lines) {
|
|
233
350
|
const touched = applied.some((a) => a.file === file);
|
|
@@ -292,10 +409,17 @@ export function describeFix(result, dry) {
|
|
|
292
409
|
: "Nothing to apply.");
|
|
293
410
|
return lines;
|
|
294
411
|
}
|
|
295
|
-
|
|
412
|
+
/**
|
|
413
|
+
* O NÚMERO É O DE ESCRITAS, e não o de achados - ver `Applied.occurrences`.
|
|
414
|
+
*
|
|
415
|
+
* Um achado numa linha minificada vale por sete trocas. Contando achados, o recibo dizia 114 onde o
|
|
416
|
+
* comando escreveu 349, e a medição seguinte não batia com nenhum dos dois números.
|
|
417
|
+
*/
|
|
418
|
+
const uses = applied.reduce((n, a) => n + a.occurrences, 0);
|
|
419
|
+
lines.push(`${dry ? "Would replace" : "Replaced"} ${uses} hand-written value${uses === 1 ? "" : "s"} with the token your system already has, across ${result.files} file${result.files === 1 ? "" : "s"}.`);
|
|
296
420
|
const byToken = new Map();
|
|
297
421
|
for (const a of applied)
|
|
298
|
-
byToken.set(a.token, (byToken.get(a.token) ?? 0) +
|
|
422
|
+
byToken.set(a.token, (byToken.get(a.token) ?? 0) + a.occurrences);
|
|
299
423
|
const ranked = [...byToken.entries()].sort((a, b) => b[1] - a[1]);
|
|
300
424
|
const SHOWN = 6;
|
|
301
425
|
for (const [token, n] of ranked.slice(0, SHOWN))
|
package/package.json
CHANGED