synthesisui 0.16.380 → 0.16.381

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.
@@ -22,8 +22,28 @@ const CLASS_LITERAL = /className=(?:"([^"]{0,400})"|\{\s*"([^"]{0,400})"\s*\})/g
22
22
  const CLASS_TEMPLATE = /className=\{\s*`([^`]{0,400})`\s*\}/g;
23
23
  /** `className={algo}` sem literal nenhum: não existe na fonte. */
24
24
  const CLASS_RUNTIME = /className=\{\s*([A-Za-z_$][\w$.[\]]{0,60})\s*\}/g;
25
- /** Um literal dentro de `cn("…", …)` e amigos. */
26
- const CALL_LITERAL = /\b(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(([^)]{0,600})\)/g;
25
+ /**
26
+ * A ABERTURA de `cn("…", …)` e amigos - o FIM sai por parêntese balanceado, e não por regex.
27
+ *
28
+ * O QUE ACONTECIA: o corpo era `([^)]{0,600})`, que para no PRIMEIRO `)`. Basta uma chamada aninhada
29
+ * - `cn("flex", buttonVariants({ size }), "p-4")` - para tudo o que vem depois dela desaparecer da
30
+ * conta. Não é o teto de 600: é o parêntese.
31
+ *
32
+ * MEDIDO EM TRÊS POPULAÇÕES (05/09, classes que o coletor não via por causa do corte):
33
+ *
34
+ * codelevel/packages/ui 21 de 33 chamadas truncadas de 83
35
+ * frontend-hub/packages/ui 41 de 17 chamadas truncadas de 78
36
+ * frontend-hub/apps/web-dashboard 1 de 1 chamada truncada de 47
37
+ *
38
+ * E o que sumia não era resto: `shadow-[0_10px_30px_-10px_rgba(…)]`, `data-[starting-style]:…`,
39
+ * `bg-[var(--color-lightgray-300)]` - as classes mais ricas do repositório, que são exatamente as
40
+ * que o cliente quer ver contadas. Num ledger cuja promessa é que nada é descartado em silêncio,
41
+ * elas não eram lidas E não eram "não lidas": não existiam.
42
+ *
43
+ * É O MESMO PRECEDENTE do `sx` e do `styled` três blocos abaixo, e pela mesma razão: um regex com
44
+ * teto escolhe a formatação do arquivo de outra pessoa.
45
+ */
46
+ const CALL_OPEN = /\b(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(/g;
27
47
  const STRING_IN_CALL = /"([^"]{0,300})"|'([^']{0,300})'/g;
28
48
  /** `style={{ … }}`. */
29
49
  const INLINE = /style=\{\{([^}]{0,400})\}\}/g;
@@ -72,6 +92,7 @@ const MODULE_REF = /\$\{\s*[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*\s*\}/g;
72
92
  *
73
93
  * status === "success" ? "bg-success-500" : ... "success" é OPERANDO da comparação
74
94
  * // ... to prevent the "short width" issue "short width" está num COMENTÁRIO
95
+ * cn(iconVariants({ animation: … : "none" }), …) "none" é ARGUMENTO de outra chamada
75
96
  *
76
97
  * MEDIDO EM 11/08 no repo real, sobre as 26 declarações que o censo listava como `value-not-read`:
77
98
  *
@@ -93,6 +114,38 @@ function blankComments(body) {
93
114
  function isComparedTo(body, index) {
94
115
  return /(?:===|!==|==|!=)\s*$/.test(body.slice(0, index));
95
116
  }
117
+ /** Um utilitário de classe abrindo parêntese, para saber QUEM abriu a chamada mais interna. */
118
+ const OPENS_A_CLASS_CALL = /\b(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\($/;
119
+ /**
120
+ * O LITERAL É ARGUMENTO DE OUTRA CHAMADA, e argumento de outra chamada não é classe.
121
+ *
122
+ * `cn(iconVariants({ animation: shouldAnimate ? animation : "none" }), className)` põe `"none"`
123
+ * dentro do `cn`, mas ele não veste nada: é o valor que a função de variantes recebe para escolher a
124
+ * classe. A esteira listava `none` como uma classe que não conseguiu ler, e o cliente ia procurar no
125
+ * código dele um estilo que ele nunca escreveu - o mesmo custo do operando de `===` logo acima.
126
+ *
127
+ * MEDIDO EM TRÊS POPULAÇÕES (05/09, sobre os literais que já passam pelos dois guards anteriores):
128
+ *
129
+ * codelevel/packages/ui 3 de 83 `"none"` em Card, Icon, Typography
130
+ * frontend-hub/packages/ui 0 de 123
131
+ * frontend-hub/apps/web-dashboard 0 de 68
132
+ *
133
+ * QUEM ABRIU A CHAMADA DECIDE, e não o simples fato de haver uma. Um utilitário de classe aninhado
134
+ * (`cn(clsx("flex"))`) continua vestindo, e o que está dentro dele É classe - sem esta metade, o
135
+ * conserto de um falso positivo criaria um falso negativo. Nas três populações essa forma dá N=0
136
+ * hoje; ela está aqui porque aninhar utilitário é forma de qualquer projeto, e o `CALL_LITERAL` só
137
+ * casa a chamada de fora.
138
+ */
139
+ function isArgumentOfAnotherCall(body, index) {
140
+ const open = [];
141
+ for (let i = 0; i < index; i++) {
142
+ if (body[i] === "(")
143
+ open.push(!OPENS_A_CLASS_CALL.test(body.slice(0, i + 1)));
144
+ else if (body[i] === ")")
145
+ open.pop();
146
+ }
147
+ return open[open.length - 1] === true;
148
+ }
96
149
  export function fragmentsOfSource(file, source) {
97
150
  const out = [];
98
151
  const at = (index) => source.slice(0, index).split("\n").length;
@@ -127,15 +180,28 @@ export function fragmentsOfSource(file, source) {
127
180
  * OS LITERAIS DENTRO DE UMA CHAMADA. `cn("flex p-4", cond && "gap-2")` tem dois, e os dois são
128
181
  * classe escrita à mão - a forma mais comum do repo dele (113 arquivos).
129
182
  */
130
- for (const call of source.matchAll(CALL_LITERAL)) {
131
- const body = blankComments(call[1] ?? "");
132
- const base = (call.index ?? 0) + (call[0].length - body.length - 1);
183
+ /**
184
+ * SÓ A CHAMADA MAIS EXTERNA - senão um utilitário aninhado conta os literais dele duas vezes.
185
+ *
186
+ * Enquanto o corpo parava no primeiro `)`, um `clsx(` dentro de um `cn(` era uma segunda match e
187
+ * ninguém percebia. Com o corpo inteiro, o de fora já cobre o de dentro.
188
+ */
189
+ let until = 0;
190
+ for (const call of source.matchAll(CALL_OPEN)) {
191
+ const from = (call.index ?? 0) + call[0].length;
192
+ if (from < until)
193
+ continue;
194
+ until = closingParen(source, from);
195
+ const body = blankComments(source.slice(from, until));
196
+ const base = from;
133
197
  for (const s of body.matchAll(STRING_IN_CALL)) {
134
198
  const text = s[1] ?? s[2] ?? "";
135
199
  if (!LOOKS_LIKE_CLASS.test(text))
136
200
  continue;
137
201
  if (isComparedTo(body, s.index ?? 0))
138
202
  continue;
203
+ if (isArgumentOfAnotherCall(body, s.index ?? 0))
204
+ continue;
139
205
  push("class", base + (s.index ?? 0), text);
140
206
  }
141
207
  }
@@ -151,6 +217,19 @@ export function fragmentsOfSource(file, source) {
151
217
  }
152
218
  return out;
153
219
  }
220
+ /** O fim da chamada aberta em `from`, contando parênteses. Sem fechamento, o fim do arquivo. */
221
+ function closingParen(source, from) {
222
+ let depth = 1;
223
+ let i = from;
224
+ while (i < source.length && depth > 0) {
225
+ if (source[i] === "(")
226
+ depth += 1;
227
+ else if (source[i] === ")")
228
+ depth -= 1;
229
+ i += 1;
230
+ }
231
+ return depth === 0 ? i - 1 : source.length;
232
+ }
154
233
  /** O fim de um objeto aberto com duas chaves - `sx={{ … }}` fecha em duas. */
155
234
  function balanced(source, from, close) {
156
235
  let depth = 2;
@@ -680,7 +680,7 @@ export const CHECKER_SINCE = "0.16.378";
680
680
  * posição de conteúdo. Um censo medido antes desta versão conta como deriva o que a folha do app
681
681
  * nem alcança - e o número do cliente é a promessa.
682
682
  */
683
- export const READER_SINCE = "0.16.378";
683
+ export const READER_SINCE = "0.16.381";
684
684
  /**
685
685
  * O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
686
686
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.380",
3
+ "version": "0.16.381",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {