@softize/opus 15.2.2 → 16.0.0

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +51 -18
  2. package/bin/lib/check.mjs +98 -15
  3. package/bin/lib/copy.mjs +811 -314
  4. package/bin/lib/gen-manifest.mjs +24 -23
  5. package/bin/lib/gen-runner.mjs +188 -148
  6. package/docs/adr/0010-page-header-owns-page-chrome.md +3 -4
  7. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -3
  8. package/docs/adr/0012-modal-header-only-names-the-surface.md +45 -0
  9. package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +92 -0
  10. package/docs/code-style.md +24 -19
  11. package/package.json +5 -1
  12. package/registry/skills/build-opus-ui/references/ui-patterns.md +43 -26
  13. package/src/core/presentation.ts +512 -0
  14. package/src/presentation/index.ts +1 -0
  15. package/src/ui/components/patterns/action-list-dialog.tsx +26 -9
  16. package/src/ui/components/patterns/confirm.tsx +34 -29
  17. package/src/ui/components/patterns/form-dialog.tsx +20 -8
  18. package/src/ui/components/patterns/list.tsx +26 -9
  19. package/src/ui/components/patterns/page.tsx +99 -139
  20. package/src/ui/components/patterns/presentation.tsx +316 -0
  21. package/src/ui/components/patterns/sidebar.tsx +3 -3
  22. package/src/ui/components/patterns/trigger.tsx +38 -17
  23. package/src/ui/components/primitives/button-group.tsx +53 -43
  24. package/src/ui/components/primitives/command.tsx +30 -72
  25. package/src/ui/components/primitives/dialog.tsx +23 -89
  26. package/src/ui/components/primitives/drawer.tsx +8 -34
  27. package/src/ui/docs/content/action-form-dialog.md +12 -10
  28. package/src/ui/docs/content/action-list-dialog.md +22 -17
  29. package/src/ui/docs/content/button.md +52 -35
  30. package/src/ui/docs/content/communication.md +26 -26
  31. package/src/ui/docs/content/dialog.md +173 -154
  32. package/src/ui/docs/content/drawer.md +12 -11
  33. package/src/ui/docs/content/page.md +72 -91
  34. package/src/ui/docs/content/presentation.md +158 -0
  35. package/src/ui/docs/registry.tsx +6 -0
  36. package/src/ui/meta.ts +8 -2
  37. package/src/ui/react.tsx +10 -3
package/bin/lib/copy.mjs CHANGED
@@ -54,11 +54,28 @@ const IGNORED_FILE = /(?:\.d|\.spec|\.test|\.stories)\.[cm]?[jt]sx?$/u
54
54
  const COPY_RULE_ID = /^COPY\d{3}$/u
55
55
  const COPY_DYNAMIC_KINDS = new Set(['message-template', 'runtime-data'])
56
56
  const COPY_ROLES = new Set([
57
- 'badge', 'breadcrumb', 'button', 'description', 'dialog-body', 'empty-state', 'error',
58
- 'heading', 'helper-text', 'kicker', 'label', 'menu-item', 'message', 'notification',
59
- 'placeholder', 'success', 'tab', 'title', 'warning',
57
+ 'badge',
58
+ 'breadcrumb',
59
+ 'button',
60
+ 'description',
61
+ 'dialog-body',
62
+ 'empty-state',
63
+ 'error',
64
+ 'heading',
65
+ 'helper-text',
66
+ 'kicker',
67
+ 'label',
68
+ 'menu-item',
69
+ 'message',
70
+ 'notification',
71
+ 'placeholder',
72
+ 'success',
73
+ 'tab',
74
+ 'title',
75
+ 'warning',
60
76
  ])
61
- const AUDIT_REFERENCE = /^(?:https?:\/\/\S+|[a-z][a-z0-9-]*:\S+|(?:[A-Za-z0-9._-]+\/)+[A-Za-z0-9._#/-]+|[A-Za-z0-9._-]+\.(?:md|json|ya?ml)(?:#[^\s]+)?)$/u
77
+ const AUDIT_REFERENCE =
78
+ /^(?:https?:\/\/\S+|[a-z][a-z0-9-]*:\S+|(?:[A-Za-z0-9._-]+\/)+[A-Za-z0-9._#/-]+|[A-Za-z0-9._-]+\.(?:md|json|ya?ml)(?:#[^\s]+)?)$/u
62
79
  const CONTRACT_MODULES = new Set(['@softize/opus', '@softize/opus/core'])
63
80
  const CONTRACT_FACTORIES = new Set(['defineAction', 'defineContract'])
64
81
  const SCHEMA_ZOD_MODULES = new Set(['@softize/opus/schema/zod'])
@@ -122,10 +139,21 @@ const SELF_CONTAINED_CHILD_COMPONENTS = new Set(['LabelHelp'])
122
139
 
123
140
  const OPTIONAL_CHILD_COMPONENTS = new Set(['ActionForm', 'ActionFormCard', 'ActionFormDialog'])
124
141
  const JSX_ACTION_CONSUMERS = new Set([
125
- 'ActionForm', 'ActionFormCard', 'ActionFormDialog', 'ActionList', 'ActionListDialog', 'ActionTrigger', 'ActionView',
142
+ 'ActionForm',
143
+ 'ActionFormCard',
144
+ 'ActionFormDialog',
145
+ 'ActionList',
146
+ 'ActionListDialog',
147
+ 'ActionTrigger',
148
+ 'ActionView',
126
149
  ])
127
150
  const ACTION_HOOK_CONSUMERS = new Set([
128
- 'useAction', 'useFormAction', 'useListAction', 'useLookupAction', 'useTriggerAction', 'useViewAction',
151
+ 'useAction',
152
+ 'useFormAction',
153
+ 'useListAction',
154
+ 'useLookupAction',
155
+ 'useTriggerAction',
156
+ 'useViewAction',
129
157
  ])
130
158
  const OPAQUE_SLOT_PROPS = new Map([
131
159
  ['ActionForm', ['body', 'footer']],
@@ -133,69 +161,171 @@ const OPAQUE_SLOT_PROPS = new Map([
133
161
  ])
134
162
 
135
163
  const JSX_PROP_ROLES = new Map([
136
- ['ActionForm', new Map([['submitLabel', 'button'], ['cancelLabel', 'button']])],
137
- ['ActionFormCard', new Map([['title', 'title'], ['description', 'description'], ['submitLabel', 'button'], ['cancelLabel', 'button']])],
138
- ['ActionFormDialog', new Map([['title', 'title'], ['description', 'dialog-body'], ['submitLabel', 'button'], ['cancelLabel', 'button']])],
164
+ [
165
+ 'ActionForm',
166
+ new Map([
167
+ ['submitLabel', 'button'],
168
+ ['cancelLabel', 'button'],
169
+ ]),
170
+ ],
171
+ [
172
+ 'ActionFormCard',
173
+ new Map([
174
+ ['title', 'title'],
175
+ ['description', 'description'],
176
+ ['submitLabel', 'button'],
177
+ ['cancelLabel', 'button'],
178
+ ]),
179
+ ],
180
+ [
181
+ 'ActionFormDialog',
182
+ new Map([
183
+ ['title', 'title'],
184
+ ['description', 'dialog-body'],
185
+ ['intro', 'dialog-body'],
186
+ ['submitLabel', 'button'],
187
+ ['cancelLabel', 'button'],
188
+ ]),
189
+ ],
139
190
  // Estados unificados (14.0): a mesma tríade de mensagens em ActionList, ActionListDialog,
140
191
  // DataState e PageState; ActionView só tem o vazio.
141
- ['ActionList', new Map([['emptyMessage', 'empty-state'], ['errorMessage', 'error'], ['retryLabel', 'button']])],
142
- ['ActionListDialog', new Map([
143
- ['title', 'title'], ['description', 'dialog-body'],
144
- ['emptyMessage', 'empty-state'], ['errorMessage', 'error'], ['retryLabel', 'button'],
145
- ])],
192
+ [
193
+ 'ActionList',
194
+ new Map([
195
+ ['emptyMessage', 'empty-state'],
196
+ ['errorMessage', 'error'],
197
+ ['retryLabel', 'button'],
198
+ ]),
199
+ ],
200
+ [
201
+ 'ActionListDialog',
202
+ new Map([
203
+ ['title', 'title'],
204
+ ['description', 'dialog-body'],
205
+ ['intro', 'dialog-body'],
206
+ ['emptyMessage', 'empty-state'],
207
+ ['errorMessage', 'error'],
208
+ ['retryLabel', 'button'],
209
+ ]),
210
+ ],
146
211
  ['ActionView', new Map([['emptyMessage', 'empty-state']])],
147
212
  // `itemLabel` identifica o dado alvo (por exemplo, `customer.name`); não é
148
213
  // microcopy estável e, por isso, não pertence ao inventário editorial.
149
214
  ['ActionTrigger', new Map([['label', 'button']])],
150
- ['Alert', new Map([['title', 'title'], ['description', 'message']])],
215
+ [
216
+ 'Alert',
217
+ new Map([
218
+ ['title', 'title'],
219
+ ['description', 'message'],
220
+ ]),
221
+ ],
151
222
  ['CommandInput', new Map([['placeholder', 'placeholder']])],
152
- ['Content', new Map([['title', 'title'], ['description', 'description']])],
153
- ['ContentHeader', new Map([['title', 'title'], ['description', 'description']])],
154
- ['DataState', new Map([['emptyMessage', 'empty-state'], ['errorMessage', 'error'], ['retryLabel', 'button']])],
223
+ [
224
+ 'Content',
225
+ new Map([
226
+ ['title', 'title'],
227
+ ['description', 'description'],
228
+ ]),
229
+ ],
230
+ [
231
+ 'ContentHeader',
232
+ new Map([
233
+ ['title', 'title'],
234
+ ['description', 'description'],
235
+ ]),
236
+ ],
237
+ [
238
+ 'DataState',
239
+ new Map([
240
+ ['emptyMessage', 'empty-state'],
241
+ ['errorMessage', 'error'],
242
+ ['retryLabel', 'button'],
243
+ ]),
244
+ ],
155
245
  // A Dock nomeia a barra e cada ação por prop. Sem estas linhas, a copy sairia do inventário
156
246
  // exatamente quando uma superfície migra de <Button aria-label> para <DockAction label>.
157
247
  ['Dock', new Map([['label', 'label']])],
158
- ['DockAction', new Map([['label', 'label'], ['hint', 'description']])],
248
+ [
249
+ 'DockAction',
250
+ new Map([
251
+ ['label', 'label'],
252
+ ['hint', 'description'],
253
+ ]),
254
+ ],
159
255
  ['Input', new Map([['placeholder', 'placeholder']])],
160
256
  ['InputGroupInput', new Map([['placeholder', 'placeholder']])],
161
257
  ['InputGroupTextarea', new Map([['placeholder', 'placeholder']])],
162
258
  // A ajuda vive num tooltip (portal): não herda transformação nem classe do label.
163
259
  ['LabelHelp', new Map([['help', 'helper-text']])],
164
- ['MetricCard', new Map([['label', 'label'], ['description', 'description']])],
165
- ['Page', new Map([['title', 'title'], ['description', 'description']])],
166
- ['PageState', new Map([
167
- ['title', 'title'], ['description', 'description'],
168
- ['emptyMessage', 'empty-state'], ['errorMessage', 'error'], ['retryLabel', 'button'],
169
- ])],
170
- ['Select', new Map([
171
- ['placeholder', 'placeholder'],
172
- ['searchPlaceholder', 'placeholder'],
173
- ['emptyText', 'empty-state'],
174
- ])],
260
+ [
261
+ 'MetricCard',
262
+ new Map([
263
+ ['label', 'label'],
264
+ ['description', 'description'],
265
+ ]),
266
+ ],
267
+ [
268
+ 'Page',
269
+ new Map([
270
+ ['title', 'title'],
271
+ ['description', 'description'],
272
+ ]),
273
+ ],
274
+ [
275
+ 'PageState',
276
+ new Map([
277
+ ['title', 'title'],
278
+ ['description', 'description'],
279
+ ['emptyMessage', 'empty-state'],
280
+ ['errorMessage', 'error'],
281
+ ['retryLabel', 'button'],
282
+ ]),
283
+ ],
284
+ [
285
+ 'Select',
286
+ new Map([
287
+ ['placeholder', 'placeholder'],
288
+ ['searchPlaceholder', 'placeholder'],
289
+ ['emptyText', 'empty-state'],
290
+ ]),
291
+ ],
175
292
  ['Textarea', new Map([['placeholder', 'placeholder']])],
176
293
  ])
177
294
 
178
295
  const JSX_OBJECT_PROP_ROLES = new Map([
179
- ['ActionTrigger', new Map([
180
- ['confirm', new Map([
181
- ['title', 'title'],
182
- ['description', 'dialog-body'],
183
- ['actionLabel', 'button'],
184
- ['cancelLabel', 'button'],
185
- ])],
186
- ])],
296
+ [
297
+ 'ActionTrigger',
298
+ new Map([
299
+ [
300
+ 'confirm',
301
+ new Map([
302
+ ['title', 'title'],
303
+ ['description', 'dialog-body'],
304
+ ['body', 'dialog-body'],
305
+ ['actionLabel', 'button'],
306
+ ['cancelLabel', 'button'],
307
+ ]),
308
+ ],
309
+ ]),
310
+ ],
187
311
  ])
188
312
 
189
313
  const JSX_ARRAY_PROP_ROLES = new Map([
190
- ['Select', new Map([
191
- ['options', new Map([
192
- ['label', 'menu-item'],
193
- ['hint', 'label'],
194
- ['triggerLabel', 'label'],
195
- ['group', 'heading'],
196
- ['content', 'menu-item'],
197
- ])],
198
- ])],
314
+ [
315
+ 'Select',
316
+ new Map([
317
+ [
318
+ 'options',
319
+ new Map([
320
+ ['label', 'menu-item'],
321
+ ['hint', 'label'],
322
+ ['triggerLabel', 'label'],
323
+ ['group', 'heading'],
324
+ ['content', 'menu-item'],
325
+ ]),
326
+ ],
327
+ ]),
328
+ ],
199
329
  ])
200
330
 
201
331
  // Prop de classe que alcança cada superfície textual. A lista acompanha a implementação
@@ -203,21 +333,62 @@ const JSX_ARRAY_PROP_ROLES = new Map([
203
333
  // `cardClassName` envolve o título; o menu do Select custom vive em portal e não herda a
204
334
  // classe do gatilho.
205
335
  const JSX_PROP_CLASS = new Map([
206
- ['ActionForm', new Map([['submitLabel', 'className'], ['cancelLabel', 'className']])],
207
- ['ActionFormCard', new Map([
208
- ['title', 'cardClassName'], ['description', 'cardClassName'],
209
- ['submitLabel', 'className'], ['cancelLabel', 'className'],
210
- ])],
336
+ [
337
+ 'ActionForm',
338
+ new Map([
339
+ ['submitLabel', 'className'],
340
+ ['cancelLabel', 'className'],
341
+ ]),
342
+ ],
343
+ [
344
+ 'ActionFormCard',
345
+ new Map([
346
+ ['title', 'cardClassName'],
347
+ ['description', 'cardClassName'],
348
+ ['submitLabel', 'className'],
349
+ ['cancelLabel', 'className'],
350
+ ]),
351
+ ],
211
352
  ['ActionTrigger', new Map([['label', 'className']])],
212
- ['Alert', new Map([['title', 'className'], ['description', 'className']])],
353
+ [
354
+ 'Alert',
355
+ new Map([
356
+ ['title', 'className'],
357
+ ['description', 'className'],
358
+ ]),
359
+ ],
213
360
  ['CommandInput', new Map([['placeholder', 'className']])],
214
- ['Content', new Map([['title', 'className'], ['description', 'className']])],
215
- ['ContentHeader', new Map([['title', 'className'], ['description', 'className']])],
361
+ [
362
+ 'Content',
363
+ new Map([
364
+ ['title', 'className'],
365
+ ['description', 'className'],
366
+ ]),
367
+ ],
368
+ [
369
+ 'ContentHeader',
370
+ new Map([
371
+ ['title', 'className'],
372
+ ['description', 'className'],
373
+ ]),
374
+ ],
216
375
  ['Input', new Map([['placeholder', 'className']])],
217
376
  ['InputGroupInput', new Map([['placeholder', 'className']])],
218
377
  ['InputGroupTextarea', new Map([['placeholder', 'className']])],
219
- ['MetricCard', new Map([['label', 'className'], ['description', 'className']])],
220
- ['Page', new Map([['title', 'className'], ['description', 'className']])],
378
+ [
379
+ 'MetricCard',
380
+ new Map([
381
+ ['label', 'className'],
382
+ ['description', 'className'],
383
+ ]),
384
+ ],
385
+ [
386
+ 'Page',
387
+ new Map([
388
+ ['title', 'className'],
389
+ ['description', 'className'],
390
+ ]),
391
+ ],
221
392
  ['Select', new Map([['placeholder', 'className']])],
222
393
  ['Textarea', new Map([['placeholder', 'className']])],
223
394
  ])
@@ -226,33 +397,77 @@ const JSX_PROP_CLASS = new Map([
226
397
  // Select e os wrappers de form não expõem style; inferir ali criaria uma garantia falsa.
227
398
  const JSX_PROP_STYLE = new Map([
228
399
  ['ActionTrigger', new Map([['label', 'style']])],
229
- ['Alert', new Map([['title', 'style'], ['description', 'style']])],
400
+ [
401
+ 'Alert',
402
+ new Map([
403
+ ['title', 'style'],
404
+ ['description', 'style'],
405
+ ]),
406
+ ],
230
407
  ['CommandInput', new Map([['placeholder', 'style']])],
231
408
  ['Input', new Map([['placeholder', 'style']])],
232
409
  ['InputGroupInput', new Map([['placeholder', 'style']])],
233
410
  ['InputGroupTextarea', new Map([['placeholder', 'style']])],
234
- ['MetricCard', new Map([['label', 'style'], ['description', 'style']])],
411
+ [
412
+ 'MetricCard',
413
+ new Map([
414
+ ['label', 'style'],
415
+ ['description', 'style'],
416
+ ]),
417
+ ],
235
418
  ['Textarea', new Map([['placeholder', 'style']])],
236
419
  ])
237
420
 
238
421
  const JSX_PORTAL_BOUNDARIES = new Set([
239
- 'DialogContent', 'DrawerContent', 'MenuContent', 'MenuSubContent',
240
- 'PopoverContent', 'TooltipContent',
422
+ 'DialogContent',
423
+ 'DrawerContent',
424
+ 'MenuContent',
425
+ 'MenuSubContent',
426
+ 'PopoverContent',
427
+ 'TooltipContent',
241
428
  ])
242
429
 
243
430
  // Respostas imperativas `dialog.*` da UI Opus. O DialogHost renderiza em portal com classes
244
431
  // próprias; nenhuma transformação do chamador alcança o texto.
245
432
  const DIALOG_METHOD_ROLES = new Map([
246
- ['alert', new Map([['title', 'title'], ['description', 'dialog-body'], ['body', 'dialog-body'], ['action', 'button']])],
247
- ['confirm', new Map([
248
- ['title', 'title'], ['description', 'dialog-body'], ['body', 'dialog-body'],
249
- ['action', 'button'], ['cancel', 'button'],
250
- ])],
251
- ['prompt', new Map([
252
- ['title', 'title'], ['description', 'dialog-body'], ['body', 'dialog-body'],
253
- ['action', 'button'], ['cancel', 'button'], ['placeholder', 'placeholder'],
254
- ])],
255
- ['choose', new Map([['title', 'title'], ['description', 'dialog-body'], ['body', 'dialog-body']])],
433
+ [
434
+ 'alert',
435
+ new Map([
436
+ ['title', 'title'],
437
+ ['description', 'dialog-body'],
438
+ ['body', 'dialog-body'],
439
+ ['action', 'button'],
440
+ ]),
441
+ ],
442
+ [
443
+ 'confirm',
444
+ new Map([
445
+ ['title', 'title'],
446
+ ['description', 'dialog-body'],
447
+ ['body', 'dialog-body'],
448
+ ['action', 'button'],
449
+ ['cancel', 'button'],
450
+ ]),
451
+ ],
452
+ [
453
+ 'prompt',
454
+ new Map([
455
+ ['title', 'title'],
456
+ ['description', 'dialog-body'],
457
+ ['body', 'dialog-body'],
458
+ ['action', 'button'],
459
+ ['cancel', 'button'],
460
+ ['placeholder', 'placeholder'],
461
+ ]),
462
+ ],
463
+ [
464
+ 'choose',
465
+ new Map([
466
+ ['title', 'title'],
467
+ ['description', 'dialog-body'],
468
+ ['body', 'dialog-body'],
469
+ ]),
470
+ ],
256
471
  ])
257
472
 
258
473
  // Métodos de array que não mutam o receptor. Os que devolvem os próprios elementos (num
@@ -261,11 +476,25 @@ const DIALOG_METHOD_ROLES = new Map([
261
476
  const ARRAY_RETURNING_ARRAY_METHODS = new Set(['concat', 'filter', 'flat', 'slice', 'toReversed', 'toSorted'])
262
477
  const ELEMENT_PICKING_ARRAY_METHODS = new Set(['at', 'find', 'findLast'])
263
478
  const ELEMENT_ITERATING_ARRAY_METHODS = new Set([
264
- 'every', 'filter', 'find', 'findIndex', 'findLast', 'findLastIndex', 'flatMap', 'forEach', 'map', 'some',
479
+ 'every',
480
+ 'filter',
481
+ 'find',
482
+ 'findIndex',
483
+ 'findLast',
484
+ 'findLastIndex',
485
+ 'flatMap',
486
+ 'forEach',
487
+ 'map',
488
+ 'some',
265
489
  ])
266
490
  const READ_ONLY_ARRAY_METHODS = new Set([
267
- ...ARRAY_RETURNING_ARRAY_METHODS, ...ELEMENT_PICKING_ARRAY_METHODS, ...ELEMENT_ITERATING_ARRAY_METHODS,
268
- 'includes', 'indexOf', 'join', 'lastIndexOf',
491
+ ...ARRAY_RETURNING_ARRAY_METHODS,
492
+ ...ELEMENT_PICKING_ARRAY_METHODS,
493
+ ...ELEMENT_ITERATING_ARRAY_METHODS,
494
+ 'includes',
495
+ 'indexOf',
496
+ 'join',
497
+ 'lastIndexOf',
269
498
  ])
270
499
  // Cadeia que preserva os elementos entre o array literal e a iteração que os lê.
271
500
  const ELEMENT_PRESERVING_ARRAY_METHODS = new Set(['filter', 'slice', 'toReversed', 'toSorted'])
@@ -306,10 +535,18 @@ function parseSource(file, sourceText) {
306
535
  }
307
536
  const program = ts.createProgram({
308
537
  rootNames: [virtual],
309
- options: { allowJs: true, jsx: ts.JsxEmit.Preserve, noLib: true, noResolve: true },
538
+ options: {
539
+ allowJs: true,
540
+ jsx: ts.JsxEmit.Preserve,
541
+ noLib: true,
542
+ noResolve: true,
543
+ },
310
544
  host,
311
545
  })
312
- return { sourceFile: program.getSourceFile(virtual) ?? parsed, checker: program.getTypeChecker() }
546
+ return {
547
+ sourceFile: program.getSourceFile(virtual) ?? parsed,
548
+ checker: program.getTypeChecker(),
549
+ }
313
550
  }
314
551
 
315
552
  function importDeclaration(node) {
@@ -329,17 +566,23 @@ function importBinding(identifier, checker) {
329
566
  if (declaration === undefined) return null
330
567
  const statement = importDeclaration(declaration)
331
568
  if (statement === null || !ts.isStringLiteral(statement.moduleSpecifier)) return null
332
- if (
333
- (ts.isImportSpecifier(declaration) && declaration.isTypeOnly) ||
334
- (statement.importClause?.isTypeOnly ?? false)
335
- ) return null
569
+ if ((ts.isImportSpecifier(declaration) && declaration.isTypeOnly) || (statement.importClause?.isTypeOnly ?? false))
570
+ return null
336
571
  if (ts.isImportSpecifier(declaration)) {
337
- return { kind: 'named', imported: declaration.propertyName?.text ?? declaration.name.text, module: statement.moduleSpecifier.text }
572
+ return {
573
+ kind: 'named',
574
+ imported: declaration.propertyName?.text ?? declaration.name.text,
575
+ module: statement.moduleSpecifier.text,
576
+ }
338
577
  }
339
578
  if (ts.isNamespaceImport(declaration)) {
340
579
  return { kind: 'namespace', module: statement.moduleSpecifier.text }
341
580
  }
342
- return { kind: 'default', imported: 'default', module: statement.moduleSpecifier.text }
581
+ return {
582
+ kind: 'default',
583
+ imported: 'default',
584
+ module: statement.moduleSpecifier.text,
585
+ }
343
586
  }
344
587
 
345
588
  function propertyValue(candidate) {
@@ -423,17 +666,23 @@ function bindingNames(name, checker) {
423
666
  }
424
667
  collect(name)
425
668
  return names
426
- .map((identifier) => ({ name: identifier, symbol: bindingSymbol(identifier, checker) }))
669
+ .map((identifier) => ({
670
+ name: identifier,
671
+ symbol: bindingSymbol(identifier, checker),
672
+ }))
427
673
  .filter((item) => item.symbol !== undefined)
428
674
  }
429
675
 
430
676
  function transparentCarrier(node) {
431
677
  let current = node
432
678
  while (
433
- ts.isParenthesizedExpression(current.parent) || ts.isAsExpression(current.parent) ||
434
- ts.isSatisfiesExpression(current.parent) || ts.isNonNullExpression(current.parent) ||
679
+ ts.isParenthesizedExpression(current.parent) ||
680
+ ts.isAsExpression(current.parent) ||
681
+ ts.isSatisfiesExpression(current.parent) ||
682
+ ts.isNonNullExpression(current.parent) ||
435
683
  ts.isTypeAssertionExpression(current.parent)
436
- ) current = current.parent
684
+ )
685
+ current = current.parent
437
686
  return current
438
687
  }
439
688
 
@@ -450,7 +699,7 @@ function importedMemberName(node, checker, modulePredicate) {
450
699
  const binding = importBinding(owner, checker)
451
700
  if (binding?.kind !== 'namespace' || !modulePredicate(binding.module)) return null
452
701
  if (ts.isPropertyAccessExpression(value)) return value.name.text
453
- return value.argumentExpression === undefined ? null : staticText(value.argumentExpression, checker)?.text ?? null
702
+ return value.argumentExpression === undefined ? null : (staticText(value.argumentExpression, checker)?.text ?? null)
454
703
  }
455
704
  return null
456
705
  }
@@ -467,9 +716,11 @@ function knownDictionaryFactory(call, checker) {
467
716
  ? expression.name.text
468
717
  : expression.argumentExpression === undefined
469
718
  ? null
470
- : staticText(expression.argumentExpression, checker)?.text ?? null
471
- return member === 'dict' &&
719
+ : (staticText(expression.argumentExpression, checker)?.text ?? null)
720
+ return (
721
+ member === 'dict' &&
472
722
  importedMemberName(expression.expression, checker, (module) => SCHEMA_ZOD_MODULES.has(module)) === 't'
723
+ )
473
724
  }
474
725
 
475
726
  function knownBindAction(call, checker) {
@@ -483,7 +734,8 @@ function knownDialogCall(call, checker) {
483
734
  ts.isPropertyAccessExpression(callee) &&
484
735
  DIALOG_METHOD_ROLES.has(callee.name.text) &&
485
736
  importedMemberName(callee.expression, checker, uiModule) === 'dialog'
486
- ) return { method: callee.name.text, field: `dialog.${callee.name.text}()` }
737
+ )
738
+ return { method: callee.name.text, field: `dialog.${callee.name.text}()` }
487
739
  return null
488
740
  }
489
741
 
@@ -492,10 +744,8 @@ function safeBindActionBinding(call, checker) {
492
744
  const binding = unwrap(call.arguments[1])
493
745
  if (!ts.isObjectLiteralExpression(binding)) return false
494
746
  return binding.properties.every((item) => {
495
- if (
496
- !ts.isPropertyAssignment(item) && !ts.isShorthandPropertyAssignment(item) &&
497
- !ts.isMethodDeclaration(item)
498
- ) return false
747
+ if (!ts.isPropertyAssignment(item) && !ts.isShorthandPropertyAssignment(item) && !ts.isMethodDeclaration(item))
748
+ return false
499
749
  const name = item.name === undefined ? null : propertyName(item.name, item.getSourceFile(), checker)
500
750
  return name !== null && ACTION_BINDING_KEYS.has(name)
501
751
  })
@@ -515,8 +765,12 @@ function knownJsxAggregateConsumer(expression, checker, mode) {
515
765
  const prop = jsx.parent.name.getText(expression.getSourceFile())
516
766
  const component = importedMemberName(opening.tagName, checker, uiModule)
517
767
  if (component === null) {
518
- return ts.isIdentifier(opening.tagName) && /^[a-z]/u.test(opening.tagName.text) &&
519
- mode === 'aggregate' && ['style', 'dangerouslySetInnerHTML'].includes(prop)
768
+ return (
769
+ ts.isIdentifier(opening.tagName) &&
770
+ /^[a-z]/u.test(opening.tagName.text) &&
771
+ mode === 'aggregate' &&
772
+ ['style', 'dangerouslySetInnerHTML'].includes(prop)
773
+ )
520
774
  }
521
775
  if (mode === 'factory-result' || mode === 'bound-result') {
522
776
  return prop === 'action' && JSX_ACTION_CONSUMERS.has(component)
@@ -534,7 +788,11 @@ function knownJsxAggregateConsumer(expression, checker, mode) {
534
788
  if (!ts.isJsxElement(element)) return false
535
789
  const component = importedMemberName(element.openingElement.tagName, checker, uiModule)
536
790
  if (component === null) {
537
- return mode === 'aggregate' && ts.isIdentifier(element.openingElement.tagName) && /^[a-z]/u.test(element.openingElement.tagName.text)
791
+ return (
792
+ mode === 'aggregate' &&
793
+ ts.isIdentifier(element.openingElement.tagName) &&
794
+ /^[a-z]/u.test(element.openingElement.tagName.text)
795
+ )
538
796
  }
539
797
  return mode === 'aggregate' && JSX_CHILD_ROLES.has(component)
540
798
  }
@@ -558,8 +816,10 @@ function stableComposition(node, checker, stack) {
558
816
  const parent = current.parent
559
817
  if (
560
818
  (ts.isPropertyAssignment(parent) && parent.initializer === current) ||
561
- ts.isShorthandPropertyAssignment(parent) || ts.isSpreadAssignment(parent) ||
562
- ts.isSpreadElement(parent) || ts.isArrayLiteralExpression(parent) ||
819
+ ts.isShorthandPropertyAssignment(parent) ||
820
+ ts.isSpreadAssignment(parent) ||
821
+ ts.isSpreadElement(parent) ||
822
+ ts.isArrayLiteralExpression(parent) ||
563
823
  ts.isObjectLiteralExpression(parent)
564
824
  ) {
565
825
  current = parent
@@ -581,16 +841,15 @@ function stableExpressionUse(node, checker, mode, stack) {
581
841
  }
582
842
  // defineContract retorna a própria entrada e bindAction faz clone raso. Qualquer
583
843
  // subobjeto alcançado pelo resultado continua compartilhado com a copy declarada.
584
- const consumerMode = sharedProperty && (mode === 'factory-result' || mode === 'bound-result')
585
- ? 'aggregate'
586
- : mode
844
+ const consumerMode = sharedProperty && (mode === 'factory-result' || mode === 'bound-result') ? 'aggregate' : mode
587
845
  const parent = carrier.parent
588
846
 
589
847
  if (assignmentTarget(carrier)) return false
590
848
  if (consumerMode === 'aggregate' && ts.isCallExpression(parent) && parent.expression === carrier) {
591
849
  const access = unwrap(carrier)
592
850
  return (
593
- ts.isPropertyAccessExpression(access) && arrayValued(access.expression, checker) &&
851
+ ts.isPropertyAccessExpression(access) &&
852
+ arrayValued(access.expression, checker) &&
594
853
  readOnlyArrayMethodUse(access.name.text, parent, checker, stack)
595
854
  )
596
855
  }
@@ -605,8 +864,11 @@ function stableExpressionUse(node, checker, mode, stack) {
605
864
  if (
606
865
  ts.isShorthandPropertyAssignment(parent) ||
607
866
  (ts.isPropertyAssignment(parent) && parent.initializer === carrier) ||
608
- ts.isSpreadAssignment(parent) || ts.isSpreadElement(parent) || ts.isArrayLiteralExpression(parent)
609
- ) return consumerMode === 'aggregate' && stableComposition(carrier, checker, stack)
867
+ ts.isSpreadAssignment(parent) ||
868
+ ts.isSpreadElement(parent) ||
869
+ ts.isArrayLiteralExpression(parent)
870
+ )
871
+ return consumerMode === 'aggregate' && stableComposition(carrier, checker, stack)
610
872
  if (ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === carrier)) {
611
873
  if (consumerMode === 'aggregate') {
612
874
  if (knownDictionaryFactory(parent, checker)) return true
@@ -615,12 +877,19 @@ function stableExpressionUse(node, checker, mode, stack) {
615
877
  return knownContractFactory(parent, checker) && stableFactoryResult(parent, checker, stack)
616
878
  }
617
879
  if (
618
- consumerMode === 'factory-result' && parent.arguments[0] === carrier &&
619
- knownBindAction(parent, checker) && safeBindActionBinding(parent, checker)
620
- ) return stableBoundResult(parent, checker, stack)
880
+ consumerMode === 'factory-result' &&
881
+ parent.arguments[0] === carrier &&
882
+ knownBindAction(parent, checker) &&
883
+ safeBindActionBinding(parent, checker)
884
+ )
885
+ return stableBoundResult(parent, checker, stack)
621
886
  const consumer = importedMemberName(parent.expression, checker, uiModule)
622
- return (consumerMode === 'factory-result' || consumerMode === 'bound-result') &&
623
- parent.arguments[0] === carrier && consumer !== null && ACTION_HOOK_CONSUMERS.has(consumer)
887
+ return (
888
+ (consumerMode === 'factory-result' || consumerMode === 'bound-result') &&
889
+ parent.arguments[0] === carrier &&
890
+ consumer !== null &&
891
+ ACTION_HOOK_CONSUMERS.has(consumer)
892
+ )
624
893
  }
625
894
  if (ts.isJsxExpression(parent)) return knownJsxAggregateConsumer(carrier, checker, consumerMode)
626
895
  if (ts.isExpressionStatement(parent)) return true
@@ -633,18 +902,18 @@ function stableReferences(binding, checker, mode, stack) {
633
902
  if (stack.has(binding.symbol)) return false
634
903
  const nextStack = new Set([...stack, binding.symbol])
635
904
  const statement = containingVariableStatement(binding.name)
636
- if (
637
- mode === 'aggregate' &&
638
- statement?.modifiers?.some((item) => item.kind === ts.SyntaxKind.ExportKeyword)
639
- ) return false
905
+ if (mode === 'aggregate' && statement?.modifiers?.some((item) => item.kind === ts.SyntaxKind.ExportKeyword))
906
+ return false
640
907
 
641
908
  let stable = true
642
909
  const visit = (node) => {
643
910
  if (!stable) return
644
911
  if (
645
- ts.isIdentifier(node) && sameBinding(node, binding.symbol, checker) &&
912
+ ts.isIdentifier(node) &&
913
+ sameBinding(node, binding.symbol, checker) &&
646
914
  !declarationIdentifier(node, binding.symbol)
647
- ) stable = stableExpressionUse(node, checker, mode, nextStack)
915
+ )
916
+ stable = stableExpressionUse(node, checker, mode, nextStack)
648
917
  ts.forEachChild(node, visit)
649
918
  }
650
919
  visit(binding.name.getSourceFile())
@@ -671,7 +940,8 @@ function readOnlyElementBinding(name, checker) {
671
940
  const access = carrier.parent
672
941
  readOnly =
673
942
  (ts.isPropertyAccessExpression(access) || ts.isElementAccessExpression(access)) &&
674
- access.expression === carrier && !assignmentTarget(access)
943
+ access.expression === carrier &&
944
+ !assignmentTarget(access)
675
945
  }
676
946
  ts.forEachChild(node, visit)
677
947
  }
@@ -712,8 +982,11 @@ function arrayValued(node, checker) {
712
982
  }
713
983
  if (ts.isCallExpression(value)) {
714
984
  const callee = unwrap(value.expression)
715
- return ts.isPropertyAccessExpression(callee) && ARRAY_RETURNING_ARRAY_METHODS.has(callee.name.text) &&
985
+ return (
986
+ ts.isPropertyAccessExpression(callee) &&
987
+ ARRAY_RETURNING_ARRAY_METHODS.has(callee.name.text) &&
716
988
  arrayValued(callee.expression, checker)
989
+ )
717
990
  }
718
991
  return ts.isArrayLiteralExpression(value)
719
992
  }
@@ -742,7 +1015,11 @@ function elementSource(identifier, checker) {
742
1015
  if (ts.isBindingElement(declaration)) {
743
1016
  if (declaration.dotDotDotToken !== undefined || declaration.initializer !== undefined) return null
744
1017
  const pattern = declaration.parent
745
- if (!ts.isObjectBindingPattern(pattern) || (!ts.isParameter(pattern.parent) && !ts.isVariableDeclaration(pattern.parent))) return null
1018
+ if (
1019
+ !ts.isObjectBindingPattern(pattern) ||
1020
+ (!ts.isParameter(pattern.parent) && !ts.isVariableDeclaration(pattern.parent))
1021
+ )
1022
+ return null
746
1023
  const keyNode = declaration.propertyName ?? declaration.name
747
1024
  if (!ts.isIdentifier(keyNode) && !ts.isStringLiteral(keyNode)) return null
748
1025
  key = keyNode.text
@@ -753,7 +1030,8 @@ function elementSource(identifier, checker) {
753
1030
  if (ts.isParameter(binding)) {
754
1031
  if (binding.dotDotDotToken !== undefined || binding.initializer !== undefined) return null
755
1032
  const callback = binding.parent
756
- if ((!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) || callback.parameters[0] !== binding) return null
1033
+ if ((!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) || callback.parameters[0] !== binding)
1034
+ return null
757
1035
  const call = transparentCarrier(callback).parent
758
1036
  if (!ts.isCallExpression(call) || call.arguments[0] !== transparentCarrier(callback)) return null
759
1037
  const callee = unwrap(call.expression)
@@ -761,15 +1039,18 @@ function elementSource(identifier, checker) {
761
1039
  receiver = callee.expression
762
1040
  } else if (ts.isVariableDeclaration(binding)) {
763
1041
  const list = binding.parent
764
- if (!ts.isVariableDeclarationList(list) || !ts.isForOfStatement(list.parent) || list.parent.initializer !== list) return null
1042
+ if (!ts.isVariableDeclarationList(list) || !ts.isForOfStatement(list.parent) || list.parent.initializer !== list)
1043
+ return null
765
1044
  receiver = list.parent.expression
766
1045
  } else return null
767
1046
 
768
1047
  let source = unwrap(receiver)
769
1048
  while (
770
- ts.isCallExpression(source) && ts.isPropertyAccessExpression(unwrap(source.expression)) &&
1049
+ ts.isCallExpression(source) &&
1050
+ ts.isPropertyAccessExpression(unwrap(source.expression)) &&
771
1051
  ELEMENT_PRESERVING_ARRAY_METHODS.has(unwrap(source.expression).name.text)
772
- ) source = unwrap(unwrap(source.expression).expression)
1052
+ )
1053
+ source = unwrap(unwrap(source.expression).expression)
773
1054
  const array = resolveBinding(source, checker)
774
1055
  return ts.isArrayLiteralExpression(array) ? { array, key } : null
775
1056
  }
@@ -788,7 +1069,7 @@ function elementTexts(node, checker) {
788
1069
  key = value.name.text
789
1070
  } else if (ts.isElementAccessExpression(value) && ts.isIdentifier(unwrap(value.expression))) {
790
1071
  identifier = unwrap(value.expression)
791
- key = value.argumentExpression === undefined ? null : staticText(value.argumentExpression, checker)?.text ?? null
1072
+ key = value.argumentExpression === undefined ? null : (staticText(value.argumentExpression, checker)?.text ?? null)
792
1073
  } else if (ts.isIdentifier(value)) {
793
1074
  identifier = value
794
1075
  } else return null
@@ -834,7 +1115,9 @@ function resolveBinding(node, checker, seen = new Set()) {
834
1115
  const owner = resolveBinding(value.expression, checker, seen)
835
1116
  const key = ts.isPropertyAccessExpression(value)
836
1117
  ? value.name.text
837
- : value.argumentExpression === undefined ? null : staticText(value.argumentExpression, checker)?.text ?? null
1118
+ : value.argumentExpression === undefined
1119
+ ? null
1120
+ : (staticText(value.argumentExpression, checker)?.text ?? null)
838
1121
  if (key === null) return value
839
1122
  if (ts.isObjectLiteralExpression(owner)) {
840
1123
  const resolved = property(owner, key, value.getSourceFile(), checker)
@@ -866,10 +1149,7 @@ function staticText(node, checker) {
866
1149
  if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value)) {
867
1150
  return { text: value.text, node: value }
868
1151
  }
869
- if (
870
- ts.isBinaryExpression(value) &&
871
- value.operatorToken.kind === ts.SyntaxKind.PlusToken
872
- ) {
1152
+ if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.PlusToken) {
873
1153
  const left = staticText(value.left, checker)
874
1154
  const right = staticText(value.right, checker)
875
1155
  if (left !== null && right !== null) return { text: left.text + right.text, node: value }
@@ -897,7 +1177,8 @@ function namedJsxEntity(entity) {
897
1177
  ts.isCallExpression(node) &&
898
1178
  node.arguments.length >= 3 &&
899
1179
  ts.isStringLiteral(node.arguments[2])
900
- ) decoded = node.arguments[2].text
1180
+ )
1181
+ decoded = node.arguments[2].text
901
1182
  ts.forEachChild(node, visit)
902
1183
  }
903
1184
  visit(emitted)
@@ -919,7 +1200,9 @@ function decodeJsxEntities(text) {
919
1200
  const decimal = /^\d+$/u.test(numeric)
920
1201
  const point = hexadecimal
921
1202
  ? Number.parseInt(numeric.slice(1), 16)
922
- : decimal ? Number.parseInt(numeric, 10) : Number.NaN
1203
+ : decimal
1204
+ ? Number.parseInt(numeric, 10)
1205
+ : Number.NaN
923
1206
  if (Number.isInteger(point) && point > 0 && point <= 0x10ffff && !(point >= 0xd800 && point <= 0xdfff)) {
924
1207
  return String.fromCodePoint(point)
925
1208
  }
@@ -953,7 +1236,8 @@ function destructuredNamespaceMember(identifier, checker, modulePredicate) {
953
1236
  declaration.dotDotDotToken !== undefined ||
954
1237
  !ts.isObjectBindingPattern(declaration.parent) ||
955
1238
  !constVariableDeclaration(declaration.parent.parent)
956
- ) return null
1239
+ )
1240
+ return null
957
1241
 
958
1242
  const memberNode = declaration.propertyName ?? declaration.name
959
1243
  const member = propertyName(memberNode, identifier.getSourceFile(), checker)
@@ -1148,7 +1432,12 @@ export function extractCopyFromSource(file, sourceText) {
1148
1432
  const extracted = i18nText(node, sourceFile, checker)
1149
1433
  if (extracted === null) diagnostics.push(diagnostic(file, sourceFile, node, field))
1150
1434
  else {
1151
- const entry = { source: file, line: lineOf(sourceFile, extracted.node), role, text: extracted.text }
1435
+ const entry = {
1436
+ source: file,
1437
+ line: lineOf(sourceFile, extracted.node),
1438
+ role,
1439
+ text: extracted.text,
1440
+ }
1152
1441
  entries.push(entry)
1153
1442
  return entry
1154
1443
  }
@@ -1192,7 +1481,8 @@ export function extractCopyFromSource(file, sourceText) {
1192
1481
  for (const item of listed.opaque) diagnostics.push(diagnostic(file, sourceFile, item, prefix.slice(0, -1)))
1193
1482
  for (const [itemName, item] of listed.items) {
1194
1483
  const listedValues = objects(propertyValue(item), checker)
1195
- for (const opaque of listedValues.opaque) diagnostics.push(diagnostic(file, sourceFile, opaque, `${prefix}${itemName}`))
1484
+ for (const opaque of listedValues.opaque)
1485
+ diagnostics.push(diagnostic(file, sourceFile, opaque, `${prefix}${itemName}`))
1196
1486
  if (listedValues.values.length === 0 && listedValues.opaque.length === 0) {
1197
1487
  diagnostics.push(diagnostic(file, sourceFile, propertyValue(item), `${prefix}${itemName}`))
1198
1488
  }
@@ -1419,7 +1709,8 @@ export function extractCopyFromSource(file, sourceText) {
1419
1709
  utility === 'lowercase' ||
1420
1710
  utility === 'capitalize' ||
1421
1711
  /^\[text-transform:(?:none|lowercase|capitalize)\]$/u.test(utility)
1422
- ) return { kind: 'other', conditional: separator !== -1, important }
1712
+ )
1713
+ return { kind: 'other', conditional: separator !== -1, important }
1423
1714
  return null
1424
1715
  }
1425
1716
 
@@ -1438,7 +1729,11 @@ export function extractCopyFromSource(file, sourceText) {
1438
1729
  }
1439
1730
  return {
1440
1731
  opaque: false,
1441
- transforms: text.text.split(/\s+/u).filter(Boolean).map(tailwindTransform).filter((item) => item !== null),
1732
+ transforms: text.text
1733
+ .split(/\s+/u)
1734
+ .filter(Boolean)
1735
+ .map(tailwindTransform)
1736
+ .filter((item) => item !== null),
1442
1737
  }
1443
1738
  }
1444
1739
 
@@ -1533,7 +1828,13 @@ export function extractCopyFromSource(file, sourceText) {
1533
1828
  result = visualTransform(opening.attributes, 'className', null, result.uppercase, '<ActionFormCard>')
1534
1829
  } else if (transformableElement(opening, component)) {
1535
1830
  const acceptsStyle = component === null || !['ActionForm', 'Page'].includes(component)
1536
- result = visualTransform(opening.attributes, 'className', acceptsStyle ? 'style' : null, result.uppercase, `<${component ?? opening.tagName.getText(sourceFile)}>`)
1831
+ result = visualTransform(
1832
+ opening.attributes,
1833
+ 'className',
1834
+ acceptsStyle ? 'style' : null,
1835
+ result.uppercase,
1836
+ `<${component ?? opening.tagName.getText(sourceFile)}>`,
1837
+ )
1537
1838
  }
1538
1839
  if (result.opaque) return result
1539
1840
  }
@@ -1547,7 +1848,8 @@ export function extractCopyFromSource(file, sourceText) {
1547
1848
  value.kind === ts.SyntaxKind.TrueKeyword ||
1548
1849
  value.kind === ts.SyntaxKind.FalseKeyword ||
1549
1850
  ts.isVoidExpression(value)
1550
- ) return true
1851
+ )
1852
+ return true
1551
1853
  if (ts.isIdentifier(value) && value.text === 'undefined') {
1552
1854
  return checker.getSymbolAtLocation(value) === undefined
1553
1855
  }
@@ -1570,13 +1872,25 @@ export function extractCopyFromSource(file, sourceText) {
1570
1872
  function staticTruthiness(node) {
1571
1873
  const value = resolveBinding(node, checker)
1572
1874
  if (value.kind === ts.SyntaxKind.TrueKeyword) return true
1573
- if (value.kind === ts.SyntaxKind.FalseKeyword || value.kind === ts.SyntaxKind.NullKeyword || ts.isVoidExpression(value)) return false
1574
- if (ts.isIdentifier(value) && value.text === 'undefined' && checker.getSymbolAtLocation(value) === undefined) return false
1875
+ if (
1876
+ value.kind === ts.SyntaxKind.FalseKeyword ||
1877
+ value.kind === ts.SyntaxKind.NullKeyword ||
1878
+ ts.isVoidExpression(value)
1879
+ )
1880
+ return false
1881
+ if (ts.isIdentifier(value) && value.text === 'undefined' && checker.getSymbolAtLocation(value) === undefined)
1882
+ return false
1575
1883
  const numeric = numericLiteral(value)
1576
1884
  if (numeric !== null) return numeric.value !== 0 && !Number.isNaN(numeric.value)
1577
1885
  const text = staticText(value, checker)
1578
1886
  if (text !== null) return text.text !== ''
1579
- if (ts.isObjectLiteralExpression(value) || ts.isArrayLiteralExpression(value) || ts.isArrowFunction(value) || ts.isFunctionExpression(value)) return true
1887
+ if (
1888
+ ts.isObjectLiteralExpression(value) ||
1889
+ ts.isArrayLiteralExpression(value) ||
1890
+ ts.isArrowFunction(value) ||
1891
+ ts.isFunctionExpression(value)
1892
+ )
1893
+ return true
1580
1894
  return null
1581
1895
  }
1582
1896
 
@@ -1591,7 +1905,11 @@ export function extractCopyFromSource(file, sourceText) {
1591
1905
  // `Excluir <strong>agora</strong>!`. Sem modelar o layout/nome acessível completo,
1592
1906
  // múltiplos fragmentos permanecem opacos; uma única expressão (inclusive uma
1593
1907
  // união finita de branches) é inequívoca.
1594
- if (rendered.length > 1) return { kind: 'opaque', node: rendered[1].node ?? rendered[1].values[0].node }
1908
+ if (rendered.length > 1)
1909
+ return {
1910
+ kind: 'opaque',
1911
+ node: rendered[1].node ?? rendered[1].values[0].node,
1912
+ }
1595
1913
  return rendered[0]
1596
1914
  }
1597
1915
 
@@ -1605,11 +1923,18 @@ export function extractCopyFromSource(file, sourceText) {
1605
1923
  const unique = new Map()
1606
1924
  for (const value of values) {
1607
1925
  const previous = unique.get(value.text)
1608
- unique.set(value.text, previous === undefined ? value : { ...previous, uppercase: previous.uppercase || value.uppercase })
1926
+ unique.set(
1927
+ value.text,
1928
+ previous === undefined ? value : { ...previous, uppercase: previous.uppercase || value.uppercase },
1929
+ )
1609
1930
  }
1610
1931
  const alternatives = [...unique.values()]
1611
1932
  if (alternatives.length === 1) return alternatives[0]
1612
- return { kind: 'alternatives', values: alternatives, node: alternatives[0].node }
1933
+ return {
1934
+ kind: 'alternatives',
1935
+ values: alternatives,
1936
+ node: alternatives[0].node,
1937
+ }
1613
1938
  }
1614
1939
 
1615
1940
  function childExpression(expression, inheritedUppercase = false, hiddenAncestor = false) {
@@ -1617,20 +1942,24 @@ export function extractCopyFromSource(file, sourceText) {
1617
1942
  if (noRenderedValue(value)) return { kind: 'decorative' }
1618
1943
  const text = staticText(value, checker)
1619
1944
  if (text !== null) {
1620
- return text.text.trim() === ''
1621
- ? { kind: 'decorative' }
1622
- : { kind: 'text', ...text, uppercase: inheritedUppercase }
1945
+ return text.text.trim() === '' ? { kind: 'decorative' } : { kind: 'text', ...text, uppercase: inheritedUppercase }
1623
1946
  }
1624
1947
  const listed = elementTexts(value, checker)
1625
1948
  if (listed !== null) {
1626
- const values = listed.map((item) => ({ kind: 'text', ...item, uppercase: inheritedUppercase }))
1949
+ const values = listed.map((item) => ({
1950
+ kind: 'text',
1951
+ ...item,
1952
+ uppercase: inheritedUppercase,
1953
+ }))
1627
1954
  return values.length === 1 ? values[0] : { kind: 'alternatives', values, node: values[0].node }
1628
1955
  }
1629
1956
  if (ts.isJsxElement(value) || ts.isJsxSelfClosingElement(value) || ts.isJsxFragment(value)) {
1630
1957
  return childNode(value, inheritedUppercase, hiddenAncestor)
1631
1958
  }
1632
1959
  if (ts.isArrayLiteralExpression(value)) {
1633
- return mergeChildResults(value.elements.map((element) => childExpression(element, inheritedUppercase, hiddenAncestor)))
1960
+ return mergeChildResults(
1961
+ value.elements.map((element) => childExpression(element, inheritedUppercase, hiddenAncestor)),
1962
+ )
1634
1963
  }
1635
1964
  if (ts.isConditionalExpression(value)) {
1636
1965
  const condition = resolveBinding(value.condition, checker)
@@ -1656,7 +1985,12 @@ export function extractCopyFromSource(file, sourceText) {
1656
1985
  const numeric = numericLiteral(condition)
1657
1986
  if (numeric !== null) {
1658
1987
  if (numeric.value === 0 || Number.isNaN(numeric.value)) {
1659
- return { kind: 'text', text: numeric.text, node: condition, uppercase: inheritedUppercase }
1988
+ return {
1989
+ kind: 'text',
1990
+ text: numeric.text,
1991
+ node: condition,
1992
+ uppercase: inheritedUppercase,
1993
+ }
1660
1994
  }
1661
1995
  }
1662
1996
  if (truthy === false) return { kind: 'decorative' }
@@ -1688,14 +2022,18 @@ export function extractCopyFromSource(file, sourceText) {
1688
2022
  const hidden = attribute(opening.attributes, 'aria-hidden')
1689
2023
  if (hidden.opaque !== undefined) return { kind: 'opaque', node: hidden.opaque }
1690
2024
  const hiddenValue = attributeBoolean(hidden)
1691
- if (hidden.candidate !== undefined && hiddenValue === null) return { kind: 'opaque', node: attributeValue(hidden) ?? opening }
2025
+ if (hidden.candidate !== undefined && hiddenValue === null)
2026
+ return { kind: 'opaque', node: attributeValue(hidden) ?? opening }
1692
2027
  // `aria-hidden` removes a subtree from the accessibility tree, not from the
1693
2028
  // rendered UI. It is therefore enough to classify a leaf icon as decorative,
1694
2029
  // but it must never hide visual text from the copy inventory.
1695
2030
  const subtreeHidden = hiddenAncestor || hiddenValue === true
1696
2031
  const dangerous = attribute(opening.attributes, 'dangerouslySetInnerHTML')
1697
2032
  if (dangerous.opaque !== undefined || dangerous.candidate !== undefined) {
1698
- return { kind: 'opaque', node: dangerous.opaque ?? attributeValue(dangerous) ?? opening }
2033
+ return {
2034
+ kind: 'opaque',
2035
+ node: dangerous.opaque ?? attributeValue(dangerous) ?? opening,
2036
+ }
1699
2037
  }
1700
2038
  // Props de um componente local/terceiro não provam nada sobre o DOM que ele
1701
2039
  // renderiza. Preserve a herança conhecida e recurse seus children, mas não use
@@ -1713,14 +2051,34 @@ export function extractCopyFromSource(file, sourceText) {
1713
2051
  if (subtreeHidden) {
1714
2052
  if (ts.isJsxSelfClosingElement(child)) return { kind: 'decorative' }
1715
2053
  if (!child.children.some(hasRuntimeChild)) return { kind: 'decorative' }
1716
- const transformed = visualTransform(opening.attributes, 'className', 'style', inheritedUppercase, opening.tagName.getText(sourceFile))
1717
- if (transformed.opaque) return { kind: 'opaque', node: attributeValue(attribute(opening.attributes, 'className')) ?? opening }
2054
+ const transformed = visualTransform(
2055
+ opening.attributes,
2056
+ 'className',
2057
+ 'style',
2058
+ inheritedUppercase,
2059
+ opening.tagName.getText(sourceFile),
2060
+ )
2061
+ if (transformed.opaque)
2062
+ return {
2063
+ kind: 'opaque',
2064
+ node: attributeValue(attribute(opening.attributes, 'className')) ?? opening,
2065
+ }
1718
2066
  return childList(child.children, transformed.uppercase, true)
1719
2067
  }
1720
2068
  if (ts.isJsxSelfClosingElement(child)) return { kind: 'opaque', node: child }
1721
2069
  if (!child.children.some(hasRuntimeChild)) return { kind: 'opaque', node: child }
1722
- const transformed = visualTransform(opening.attributes, 'className', 'style', inheritedUppercase, opening.tagName.getText(sourceFile))
1723
- if (transformed.opaque) return { kind: 'opaque', node: attributeValue(attribute(opening.attributes, 'className')) ?? opening }
2070
+ const transformed = visualTransform(
2071
+ opening.attributes,
2072
+ 'className',
2073
+ 'style',
2074
+ inheritedUppercase,
2075
+ opening.tagName.getText(sourceFile),
2076
+ )
2077
+ if (transformed.opaque)
2078
+ return {
2079
+ kind: 'opaque',
2080
+ node: attributeValue(attribute(opening.attributes, 'className')) ?? opening,
2081
+ }
1724
2082
  return childList(child.children, transformed.uppercase, false)
1725
2083
  }
1726
2084
  return { kind: 'opaque', node: child }
@@ -1745,12 +2103,22 @@ export function extractCopyFromSource(file, sourceText) {
1745
2103
  if (resolved.candidate === undefined) return null
1746
2104
  const raw = propertyValue(resolved.candidate)
1747
2105
  const value = resolveBinding(raw, checker)
1748
- if (ts.isJsxElement(value) || ts.isJsxSelfClosingElement(value) || ts.isJsxFragment(value) || ts.isArrayLiteralExpression(value)) {
2106
+ if (
2107
+ ts.isJsxElement(value) ||
2108
+ ts.isJsxSelfClosingElement(value) ||
2109
+ ts.isJsxFragment(value) ||
2110
+ ts.isArrayLiteralExpression(value)
2111
+ ) {
1749
2112
  const child = childExpression(value, inheritedUppercase)
1750
2113
  if (child.kind === 'opaque') diagnostics.push(diagnostic(file, sourceFile, child.node, `${prefix}${name}`))
1751
2114
  const texts = child.kind === 'text' ? [child] : child.kind === 'alternatives' ? child.values : []
1752
2115
  for (const text of texts) {
1753
- const entry = { source: file, line: lineOf(sourceFile, text.node), role, text: text.text }
2116
+ const entry = {
2117
+ source: file,
2118
+ line: lineOf(sourceFile, text.node),
2119
+ role,
2120
+ text: text.text,
2121
+ }
1754
2122
  if (text.uppercase) entry.transform = 'uppercase'
1755
2123
  entries.push(entry)
1756
2124
  }
@@ -1786,7 +2154,8 @@ export function extractCopyFromSource(file, sourceText) {
1786
2154
  function actionTriggerIconValueState(node) {
1787
2155
  const value = resolveBinding(node, checker)
1788
2156
  if (ts.isVoidExpression(value)) return 'absent'
1789
- if (ts.isIdentifier(value) && value.text === 'undefined' && checker.getSymbolAtLocation(value) === undefined) return 'absent'
2157
+ if (ts.isIdentifier(value) && value.text === 'undefined' && checker.getSymbolAtLocation(value) === undefined)
2158
+ return 'absent'
1790
2159
  if (
1791
2160
  value.kind === ts.SyntaxKind.NullKeyword ||
1792
2161
  value.kind === ts.SyntaxKind.TrueKeyword ||
@@ -1801,7 +2170,8 @@ export function extractCopyFromSource(file, sourceText) {
1801
2170
  ts.isJsxFragment(value) ||
1802
2171
  ts.isArrowFunction(value) ||
1803
2172
  ts.isFunctionExpression(value)
1804
- ) return 'present'
2173
+ )
2174
+ return 'present'
1805
2175
  return 'unknown'
1806
2176
  }
1807
2177
 
@@ -1865,17 +2235,20 @@ export function extractCopyFromSource(file, sourceText) {
1865
2235
 
1866
2236
  for (const name of OPAQUE_SLOT_PROPS.get(component) ?? []) {
1867
2237
  const slot = attribute(opening.attributes, name)
1868
- if (slot.opaque !== undefined) diagnostics.push(diagnostic(file, sourceFile, slot.opaque, `<${component}>.${name}`))
2238
+ if (slot.opaque !== undefined)
2239
+ diagnostics.push(diagnostic(file, sourceFile, slot.opaque, `<${component}>.${name}`))
1869
2240
  else if (slot.candidate !== undefined) {
1870
2241
  const raw = attributeValue(slot)
1871
2242
  if (raw === null || !noRenderedValue(raw)) {
1872
- diagnostics.push(diagnostic(
1873
- file,
1874
- sourceFile,
1875
- raw ?? opening,
1876
- `<${component}>.${name}`,
1877
- raw === null ? 'missing-content' : 'content',
1878
- ))
2243
+ diagnostics.push(
2244
+ diagnostic(
2245
+ file,
2246
+ sourceFile,
2247
+ raw ?? opening,
2248
+ `<${component}>.${name}`,
2249
+ raw === null ? 'missing-content' : 'content',
2250
+ ),
2251
+ )
1879
2252
  }
1880
2253
  }
1881
2254
  }
@@ -1884,40 +2257,49 @@ export function extractCopyFromSource(file, sourceText) {
1884
2257
  for (const [name, role] of props ?? []) {
1885
2258
  if (component === 'Select' && mode === 'native' && name !== 'placeholder') continue
1886
2259
  const item = attribute(opening.attributes, name)
1887
- if (item.opaque !== undefined) diagnostics.push(diagnostic(file, sourceFile, item.opaque, `<${component}>.${name}`))
2260
+ if (item.opaque !== undefined)
2261
+ diagnostics.push(diagnostic(file, sourceFile, item.opaque, `<${component}>.${name}`))
1888
2262
  else if (item.candidate === undefined) continue
1889
2263
  else {
1890
2264
  const classProp = JSX_PROP_CLASS.get(component)?.get(name)
1891
2265
  const styleProp = JSX_PROP_STYLE.get(component)?.get(name) ?? null
1892
- const inheritedForSurface = component === 'ActionFormCard' && classProp === 'className'
1893
- ? false
1894
- : inheritedFor(
1895
- classProp ?? null,
1896
- styleProp,
1897
- component === 'ActionFormDialog' || (component === 'Select' && name !== 'placeholder'),
1898
- )
1899
- const transformed = component === 'ActionTrigger' && name === 'label'
1900
- ? actionTriggerLabelTransform(opening, inheritedForSurface)
1901
- : component === 'ActionFormCard' && classProp === 'className'
1902
- ? actionFormCardBodyTransform()
1903
- : classProp === undefined
1904
- ? { opaque: false, uppercase: inheritedForSurface }
1905
- : visualTransform(
1906
- opening.attributes,
1907
- classProp,
2266
+ const inheritedForSurface =
2267
+ component === 'ActionFormCard' && classProp === 'className'
2268
+ ? false
2269
+ : inheritedFor(
2270
+ classProp ?? null,
1908
2271
  styleProp,
1909
- inheritedForSurface,
1910
- `<${component}>`,
2272
+ component === 'ActionFormDialog' || (component === 'Select' && name !== 'placeholder'),
1911
2273
  )
2274
+ const transformed =
2275
+ component === 'ActionTrigger' && name === 'label'
2276
+ ? actionTriggerLabelTransform(opening, inheritedForSurface)
2277
+ : component === 'ActionFormCard' && classProp === 'className'
2278
+ ? actionFormCardBodyTransform()
2279
+ : classProp === undefined
2280
+ ? { opaque: false, uppercase: inheritedForSurface }
2281
+ : visualTransform(opening.attributes, classProp, styleProp, inheritedForSurface, `<${component}>`)
1912
2282
  const raw = attributeValue(item)
1913
2283
  const resolved = raw === null ? null : resolveBinding(raw, checker)
1914
- if (resolved !== null && (ts.isJsxElement(resolved) || ts.isJsxSelfClosingElement(resolved) || ts.isJsxFragment(resolved) || ts.isArrayLiteralExpression(resolved))) {
2284
+ if (
2285
+ resolved !== null &&
2286
+ (ts.isJsxElement(resolved) ||
2287
+ ts.isJsxSelfClosingElement(resolved) ||
2288
+ ts.isJsxFragment(resolved) ||
2289
+ ts.isArrayLiteralExpression(resolved))
2290
+ ) {
1915
2291
  const child = childExpression(resolved, transformed.uppercase)
1916
- if (child.kind === 'opaque') diagnostics.push(diagnostic(file, sourceFile, child.node, `<${component}>.${name}`))
2292
+ if (child.kind === 'opaque')
2293
+ diagnostics.push(diagnostic(file, sourceFile, child.node, `<${component}>.${name}`))
1917
2294
  else {
1918
2295
  const texts = child.kind === 'text' ? [child] : child.kind === 'alternatives' ? child.values : []
1919
2296
  for (const text of texts) {
1920
- const entry = { source: file, line: lineOf(sourceFile, text.node), role, text: text.text }
2297
+ const entry = {
2298
+ source: file,
2299
+ line: lineOf(sourceFile, text.node),
2300
+ role,
2301
+ text: text.text,
2302
+ }
1921
2303
  if (text.uppercase) entry.transform = 'uppercase'
1922
2304
  entries.push(entry)
1923
2305
  }
@@ -1927,18 +2309,25 @@ export function extractCopyFromSource(file, sourceText) {
1927
2309
  // `title={group.label}` dentro de `GROUPS.map(...)`: cada literal do array local
1928
2310
  // vira um texto na própria linha, em vez de um diagnóstico sobre a prop.
1929
2311
  const listed = value === null && raw !== null ? elementTexts(raw, checker) : null
1930
- const texts = value !== null ? [value] : listed ?? []
2312
+ const texts = value !== null ? [value] : (listed ?? [])
1931
2313
  if (texts.length === 0) {
1932
- diagnostics.push(diagnostic(
1933
- file,
1934
- sourceFile,
1935
- raw ?? opening,
1936
- `<${component}>.${name}`,
1937
- raw === null ? 'missing-content' : 'content',
1938
- ))
2314
+ diagnostics.push(
2315
+ diagnostic(
2316
+ file,
2317
+ sourceFile,
2318
+ raw ?? opening,
2319
+ `<${component}>.${name}`,
2320
+ raw === null ? 'missing-content' : 'content',
2321
+ ),
2322
+ )
1939
2323
  }
1940
2324
  for (const text of texts) {
1941
- const entry = { source: file, line: lineOf(sourceFile, text.node), role, text: text.text }
2325
+ const entry = {
2326
+ source: file,
2327
+ line: lineOf(sourceFile, text.node),
2328
+ role,
2329
+ text: text.text,
2330
+ }
1942
2331
  if (transformed.uppercase) entry.transform = 'uppercase'
1943
2332
  entries.push(entry)
1944
2333
  }
@@ -1949,65 +2338,72 @@ export function extractCopyFromSource(file, sourceText) {
1949
2338
  if (component === 'ActionTrigger') {
1950
2339
  const label = attribute(opening.attributes, 'label')
1951
2340
  if (label.candidate === undefined && label.opaque === undefined) {
1952
- const transformed = actionTriggerLabelTransform(
1953
- opening,
1954
- inheritedFor('className', 'style'),
1955
- )
2341
+ const transformed = actionTriggerLabelTransform(opening, inheritedFor('className', 'style'))
1956
2342
  if (transformed.uppercase) {
1957
- diagnostics.push(diagnostic(file, sourceFile, opening, '<ActionTrigger>.label herdado do contrato sob uppercase', 'transform'))
2343
+ diagnostics.push(
2344
+ diagnostic(
2345
+ file,
2346
+ sourceFile,
2347
+ opening,
2348
+ '<ActionTrigger>.label herdado do contrato sob uppercase',
2349
+ 'transform',
2350
+ ),
2351
+ )
1958
2352
  }
1959
2353
  }
1960
2354
  }
1961
2355
 
1962
2356
  for (const [name, nestedRoles] of JSX_OBJECT_PROP_ROLES.get(component) ?? []) {
1963
2357
  const item = attribute(opening.attributes, name)
1964
- if (item.opaque !== undefined) diagnostics.push(diagnostic(file, sourceFile, item.opaque, `<${component}>.${name}`))
2358
+ if (item.opaque !== undefined)
2359
+ diagnostics.push(diagnostic(file, sourceFile, item.opaque, `<${component}>.${name}`))
1965
2360
  else if (item.candidate !== undefined) {
1966
2361
  const raw = attributeValue(item)
1967
2362
  const object = raw === null ? null : resolveBinding(raw, checker)
1968
- if (object?.kind === ts.SyntaxKind.NullKeyword || (ts.isIdentifier(object) && object.text === 'undefined')) continue
2363
+ if (object?.kind === ts.SyntaxKind.NullKeyword || (ts.isIdentifier(object) && object.text === 'undefined'))
2364
+ continue
1969
2365
  if (!ts.isObjectLiteralExpression(object)) {
1970
- diagnostics.push(diagnostic(
1971
- file,
1972
- sourceFile,
1973
- raw ?? opening,
1974
- `<${component}>.${name}`,
1975
- raw === null ? 'structure' : 'content',
1976
- ))
1977
- }
1978
- else for (const [nestedName, role] of nestedRoles) addProperty(object, nestedName, role, `<${component}>.${name}.`)
2366
+ diagnostics.push(
2367
+ diagnostic(
2368
+ file,
2369
+ sourceFile,
2370
+ raw ?? opening,
2371
+ `<${component}>.${name}`,
2372
+ raw === null ? 'structure' : 'content',
2373
+ ),
2374
+ )
2375
+ } else
2376
+ for (const [nestedName, role] of nestedRoles) addProperty(object, nestedName, role, `<${component}>.${name}.`)
1979
2377
  }
1980
2378
  }
1981
2379
 
1982
2380
  for (const [name, optionRoles] of JSX_ARRAY_PROP_ROLES.get(component) ?? []) {
1983
2381
  const item = attribute(opening.attributes, name)
1984
- if (item.opaque !== undefined) diagnostics.push(diagnostic(file, sourceFile, item.opaque, `<${component}>.${name}`))
2382
+ if (item.opaque !== undefined)
2383
+ diagnostics.push(diagnostic(file, sourceFile, item.opaque, `<${component}>.${name}`))
1985
2384
  else if (item.candidate !== undefined) {
1986
2385
  const raw = attributeValue(item)
1987
2386
  const listed = raw === null ? { values: [], opaque: [opening] } : objects(raw, checker)
1988
2387
  for (const opaque of listed.opaque) {
1989
- diagnostics.push(diagnostic(
1990
- file,
1991
- sourceFile,
1992
- opaque,
1993
- `<${component}>.${name}`,
1994
- raw === null ? 'structure' : 'content',
1995
- ))
2388
+ diagnostics.push(
2389
+ diagnostic(file, sourceFile, opaque, `<${component}>.${name}`, raw === null ? 'structure' : 'content'),
2390
+ )
1996
2391
  }
1997
2392
  for (const [index, option] of listed.values.entries()) {
1998
2393
  for (const [optionName, role] of optionRoles) {
1999
2394
  if (mode === 'native' && !['label', 'group'].includes(optionName)) continue
2000
2395
  const affectsTrigger = optionName === 'label' || (mode === 'custom' && optionName === 'triggerLabel')
2001
2396
  const affectsNative = mode === 'native' && (optionName === 'label' || optionName === 'group')
2002
- const transformed = affectsTrigger || affectsNative
2003
- ? visualTransform(
2004
- opening.attributes,
2005
- 'className',
2006
- null,
2007
- inheritedFor('className', null),
2008
- `<${component}>`,
2009
- )
2010
- : { opaque: false, uppercase: false }
2397
+ const transformed =
2398
+ affectsTrigger || affectsNative
2399
+ ? visualTransform(
2400
+ opening.attributes,
2401
+ 'className',
2402
+ null,
2403
+ inheritedFor('className', null),
2404
+ `<${component}>`,
2405
+ )
2406
+ : { opaque: false, uppercase: false }
2011
2407
  addVisualObjectProperty(
2012
2408
  option,
2013
2409
  optionName,
@@ -2028,30 +2424,45 @@ export function extractCopyFromSource(file, sourceText) {
2028
2424
  const raw = attributeValue(ariaLabelAttribute)
2029
2425
  const ariaLabel = raw === null ? null : attributeText(ariaLabelAttribute)
2030
2426
  if (ariaLabel === null) {
2031
- diagnostics.push(diagnostic(
2032
- file,
2033
- sourceFile,
2034
- raw ?? opening,
2035
- `<${component}>.aria-label`,
2036
- raw === null ? 'missing-content' : 'content',
2037
- ))
2427
+ diagnostics.push(
2428
+ diagnostic(
2429
+ file,
2430
+ sourceFile,
2431
+ raw ?? opening,
2432
+ `<${component}>.aria-label`,
2433
+ raw === null ? 'missing-content' : 'content',
2434
+ ),
2435
+ )
2038
2436
  }
2039
2437
  if (ariaLabel !== null) {
2040
2438
  hasStaticAriaLabel = true
2041
- entries.push({ source: file, line: lineOf(sourceFile, ariaLabel.node), role: 'label', text: ariaLabel.text })
2439
+ entries.push({
2440
+ source: file,
2441
+ line: lineOf(sourceFile, ariaLabel.node),
2442
+ role: 'label',
2443
+ text: ariaLabel.text,
2444
+ })
2042
2445
  }
2043
2446
  }
2044
2447
 
2045
2448
  const childRole = JSX_CHILD_ROLES.get(component)
2046
2449
  if (childRole !== undefined) {
2047
2450
  const childValue = (inheritedUppercase) => {
2048
- // Quebras/indentação puras não viram `children` no JSX emitido e, portanto,
2049
- // não sobrescrevem uma prop `children="..."` explícita.
2451
+ // Quebras/indentação puras não viram `children` no JSX emitido e, portanto,
2452
+ // não sobrescrevem uma prop `children="..."` explícita.
2050
2453
  if (ts.isJsxElement(element) && element.children.some(hasRuntimeChild)) {
2051
2454
  if (
2052
2455
  OPTIONAL_CHILD_COMPONENTS.has(component) &&
2053
- element.children.some((item) => ts.isJsxElement(item) || ts.isJsxSelfClosingElement(item) || ts.isJsxFragment(item))
2054
- ) return { kind: 'opaque', node: element.children.find((item) => ts.isJsxElement(item) || ts.isJsxSelfClosingElement(item) || ts.isJsxFragment(item)) }
2456
+ element.children.some(
2457
+ (item) => ts.isJsxElement(item) || ts.isJsxSelfClosingElement(item) || ts.isJsxFragment(item),
2458
+ )
2459
+ )
2460
+ return {
2461
+ kind: 'opaque',
2462
+ node: element.children.find(
2463
+ (item) => ts.isJsxElement(item) || ts.isJsxSelfClosingElement(item) || ts.isJsxFragment(item),
2464
+ ),
2465
+ }
2055
2466
  return childList(element.children, inheritedUppercase)
2056
2467
  }
2057
2468
  const children = attribute(opening.attributes, 'children')
@@ -2060,19 +2471,26 @@ export function extractCopyFromSource(file, sourceText) {
2060
2471
  const raw = attributeValue(children)
2061
2472
  return raw === null ? { kind: 'decorative' } : childExpression(raw, inheritedUppercase)
2062
2473
  }
2063
- const inheritedUppercase = component === 'ActionFormDialog'
2064
- ? false
2065
- : component === 'ActionFormCard'
2066
- ? actionFormCardBodyTransform().uppercase
2067
- : inheritedFor('className', 'style')
2474
+ const inheritedUppercase =
2475
+ component === 'ActionFormDialog'
2476
+ ? false
2477
+ : component === 'ActionFormCard'
2478
+ ? actionFormCardBodyTransform().uppercase
2479
+ : inheritedFor('className', 'style')
2068
2480
  let value = childValue(inheritedUppercase)
2069
2481
  if (value.kind === 'text' || value.kind === 'alternatives') {
2070
- const transformed = component === 'ActionFormDialog'
2071
- ? { opaque: false, uppercase: false }
2072
- : component === 'ActionFormCard'
2073
- ? actionFormCardBodyTransform()
2074
- : visualTransform(opening.attributes, 'className', 'style', inheritedUppercase, `<${component}>`)
2075
- value = transformed.opaque ? { kind: 'opaque', node: attributeValue(attribute(opening.attributes, 'className')) ?? opening } : childValue(transformed.uppercase)
2482
+ const transformed =
2483
+ component === 'ActionFormDialog'
2484
+ ? { opaque: false, uppercase: false }
2485
+ : component === 'ActionFormCard'
2486
+ ? actionFormCardBodyTransform()
2487
+ : visualTransform(opening.attributes, 'className', 'style', inheritedUppercase, `<${component}>`)
2488
+ value = transformed.opaque
2489
+ ? {
2490
+ kind: 'opaque',
2491
+ node: attributeValue(attribute(opening.attributes, 'className')) ?? opening,
2492
+ }
2493
+ : childValue(transformed.uppercase)
2076
2494
  }
2077
2495
  if (value.kind === 'opaque') diagnostics.push(diagnostic(file, sourceFile, value.node, `<${component}> children`))
2078
2496
  else if (value.kind === 'decorative') {
@@ -2082,7 +2500,12 @@ export function extractCopyFromSource(file, sourceText) {
2082
2500
  } else {
2083
2501
  const texts = value.kind === 'text' ? [value] : value.values
2084
2502
  for (const text of texts) {
2085
- const entry = { source: file, line: lineOf(sourceFile, text.node), role: childRole, text: text.text }
2503
+ const entry = {
2504
+ source: file,
2505
+ line: lineOf(sourceFile, text.node),
2506
+ role: childRole,
2507
+ text: text.text,
2508
+ }
2086
2509
  if (text.uppercase) entry.transform = 'uppercase'
2087
2510
  entries.push(entry)
2088
2511
  }
@@ -2103,9 +2526,15 @@ export function extractCopyFromSource(file, sourceText) {
2103
2526
  diagnostics.push(diagnostic(file, sourceFile, raw ?? call, `${field} options`, 'structure'))
2104
2527
  return
2105
2528
  }
2106
- for (const [name, role] of DIALOG_METHOD_ROLES.get(method)) addVisualObjectProperty(options, name, role, `${field}.`)
2529
+ for (const [name, role] of DIALOG_METHOD_ROLES.get(method))
2530
+ addVisualObjectProperty(options, name, role, `${field}.`)
2107
2531
  if (method === 'choose') {
2108
- nestedObject(options, 'actions', (action, prefix) => addVisualObjectProperty(action, 'label', 'button', prefix), `${field}.`)
2532
+ nestedObject(
2533
+ options,
2534
+ 'actions',
2535
+ (action, prefix) => addVisualObjectProperty(action, 'label', 'button', prefix),
2536
+ `${field}.`,
2537
+ )
2109
2538
  }
2110
2539
  }
2111
2540
 
@@ -2132,13 +2561,23 @@ export function extractCopyFromSource(file, sourceText) {
2132
2561
  }
2133
2562
  visit(sourceFile)
2134
2563
 
2135
- return { hasContract, hasCopySurface: hasContract || hasDictionary || hasUiImport, entries, diagnostics }
2564
+ return {
2565
+ hasContract,
2566
+ hasCopySurface: hasContract || hasDictionary || hasUiImport,
2567
+ entries,
2568
+ diagnostics,
2569
+ }
2136
2570
  }
2137
2571
 
2138
2572
  function globRegex(pattern) {
2139
2573
  const escaped = portable(pattern)
2140
2574
  .split('**')
2141
- .map((part) => part.split('*').map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('[^/]*'))
2575
+ .map((part) =>
2576
+ part
2577
+ .split('*')
2578
+ .map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
2579
+ .join('[^/]*'),
2580
+ )
2142
2581
  .join('.*')
2143
2582
  return new RegExp(`^${escaped}$`)
2144
2583
  }
@@ -2176,7 +2615,8 @@ function sourceFiles(root, excluded = []) {
2176
2615
  diagnostics.push({
2177
2616
  source,
2178
2617
  line: 1,
2179
- message: 'link simbólico dentro do escopo percorrido pode ocultar um diretório de sources; remova-o ou exclua o caminho explicitamente em copy.exclude.',
2618
+ message:
2619
+ 'link simbólico dentro do escopo percorrido pode ocultar um diretório de sources; remova-o ou exclua o caminho explicitamente em copy.exclude.',
2180
2620
  })
2181
2621
  }
2182
2622
  continue
@@ -2186,11 +2626,19 @@ function sourceFiles(root, excluded = []) {
2186
2626
  try {
2187
2627
  inspected = safeProjectPath(root, target, { mustExist: true })
2188
2628
  } catch (error) {
2189
- diagnostics.push({ source, line: 1, message: `diretório de source inseguro: ${projectPathErrorMessage(error)}` })
2629
+ diagnostics.push({
2630
+ source,
2631
+ line: 1,
2632
+ message: `diretório de source inseguro: ${projectPathErrorMessage(error)}`,
2633
+ })
2190
2634
  continue
2191
2635
  }
2192
2636
  if (inspected.kind !== 'directory') {
2193
- diagnostics.push({ source, line: 1, message: 'entrada de source deixou de ser diretório durante a descoberta.' })
2637
+ diagnostics.push({
2638
+ source,
2639
+ line: 1,
2640
+ message: 'entrada de source deixou de ser diretório durante a descoberta.',
2641
+ })
2194
2642
  continue
2195
2643
  }
2196
2644
  walk(target)
@@ -2203,12 +2651,20 @@ function sourceFiles(root, excluded = []) {
2203
2651
  try {
2204
2652
  const inspected = safeProjectPath(root, target, { mustExist: true })
2205
2653
  if (inspected.kind !== 'file') {
2206
- diagnostics.push({ source, line: 1, message: 'source deixou de ser arquivo regular durante a descoberta.' })
2654
+ diagnostics.push({
2655
+ source,
2656
+ line: 1,
2657
+ message: 'source deixou de ser arquivo regular durante a descoberta.',
2658
+ })
2207
2659
  } else {
2208
2660
  output.push(source)
2209
2661
  }
2210
2662
  } catch (error) {
2211
- diagnostics.push({ source, line: 1, message: `source inseguro: ${projectPathErrorMessage(error)}` })
2663
+ diagnostics.push({
2664
+ source,
2665
+ line: 1,
2666
+ message: `source inseguro: ${projectPathErrorMessage(error)}`,
2667
+ })
2212
2668
  }
2213
2669
  }
2214
2670
  }
@@ -2310,11 +2766,12 @@ function verifyCopyInputs(root, consistency) {
2310
2766
  function findEntry(entries, selector) {
2311
2767
  return entries
2312
2768
  .map((entry, index) => ({ entry, index }))
2313
- .filter(({ entry }) =>
2314
- entry.source === selector.source &&
2315
- entry.line === selector.line &&
2316
- entry.role === selector.role &&
2317
- (selector.text === undefined || entry.text === selector.text),
2769
+ .filter(
2770
+ ({ entry }) =>
2771
+ entry.source === selector.source &&
2772
+ entry.line === selector.line &&
2773
+ entry.role === selector.role &&
2774
+ (selector.text === undefined || entry.text === selector.text),
2318
2775
  )
2319
2776
  }
2320
2777
 
@@ -2345,7 +2802,12 @@ function applyDynamicMetadata(config, entries, diagnostics) {
2345
2802
  for (const [index, declaration] of config.dynamic.entries()) {
2346
2803
  const template = declaration?.kind === 'message-template'
2347
2804
  const allowedFields = new Set([
2348
- 'source', 'line', 'field', 'kind', 'reason', 'reference',
2805
+ 'source',
2806
+ 'line',
2807
+ 'field',
2808
+ 'kind',
2809
+ 'reason',
2810
+ 'reference',
2349
2811
  ...(template ? ['role', 'text'] : []),
2350
2812
  ])
2351
2813
  const normalizedReason = typeof declaration?.reason === 'string' ? declaration.reason.trim() : ''
@@ -2369,34 +2831,38 @@ function applyDynamicMetadata(config, entries, diagnostics) {
2369
2831
  normalizedReference.length < 8 ||
2370
2832
  normalizedReference.length > 500 ||
2371
2833
  !AUDIT_REFERENCE.test(normalizedReference) ||
2372
- (template && (
2373
- typeof declaration.text !== 'string' ||
2374
- declaration.text.trim() === '' ||
2375
- declaration.text !== declaration.text.trim() ||
2376
- /[\r\n]/u.test(declaration.text) ||
2377
- !COPY_ROLES.has(declaration.role)
2378
- )) ||
2834
+ (template &&
2835
+ (typeof declaration.text !== 'string' ||
2836
+ declaration.text.trim() === '' ||
2837
+ declaration.text !== declaration.text.trim() ||
2838
+ /[\r\n]/u.test(declaration.text) ||
2839
+ !COPY_ROLES.has(declaration.role))) ||
2379
2840
  (!template && (declaration.text !== undefined || declaration.role !== undefined))
2380
2841
  ) {
2381
- diagnostics.push(selectorDiagnostic(
2382
- `copy.dynamic[${index}] exige seletor, kind, reason e reference válidos` +
2383
- (template ? ', além de text e role para message-template.' : '.'),
2384
- ))
2842
+ diagnostics.push(
2843
+ selectorDiagnostic(
2844
+ `copy.dynamic[${index}] exige seletor, kind, reason e reference válidos` +
2845
+ (template ? ', além de text e role para message-template.' : '.'),
2846
+ ),
2847
+ )
2385
2848
  continue
2386
2849
  }
2387
2850
  const matches = diagnostics
2388
2851
  .map((item, diagnosticIndex) => ({ item, diagnosticIndex }))
2389
- .filter(({ item, diagnosticIndex }) =>
2390
- !consumed.has(diagnosticIndex) &&
2391
- item.category === 'content' &&
2392
- item.source === declaration.source &&
2393
- item.line === declaration.line &&
2394
- item.field === declaration.field,
2852
+ .filter(
2853
+ ({ item, diagnosticIndex }) =>
2854
+ !consumed.has(diagnosticIndex) &&
2855
+ item.category === 'content' &&
2856
+ item.source === declaration.source &&
2857
+ item.line === declaration.line &&
2858
+ item.field === declaration.field,
2395
2859
  )
2396
2860
  if (matches.length !== 1) {
2397
- diagnostics.push(selectorDiagnostic(
2398
- `copy.dynamic[${index}] deve selecionar exatamente um diagnóstico de extração; encontrou ${matches.length}.`,
2399
- ))
2861
+ diagnostics.push(
2862
+ selectorDiagnostic(
2863
+ `copy.dynamic[${index}] deve selecionar exatamente um diagnóstico de extração; encontrou ${matches.length}.`,
2864
+ ),
2865
+ )
2400
2866
  continue
2401
2867
  }
2402
2868
  consumed.add(matches[0].diagnosticIndex)
@@ -2416,7 +2882,11 @@ function applyDynamicMetadata(config, entries, diagnostics) {
2416
2882
  line: declaration.line,
2417
2883
  role: declaration.role,
2418
2884
  text: declaration.text,
2419
- dynamic: { kind: declaration.kind, field: declaration.field, reference: normalizedReference },
2885
+ dynamic: {
2886
+ kind: declaration.kind,
2887
+ field: declaration.field,
2888
+ reference: normalizedReference,
2889
+ },
2420
2890
  })
2421
2891
  }
2422
2892
  surfaces.push(surface)
@@ -2449,12 +2919,18 @@ function applyProjectMetadata(config, entries, diagnostics) {
2449
2919
  typeof exemption.reference !== 'string' ||
2450
2920
  exemption.reference.trim() === ''
2451
2921
  ) {
2452
- diagnostics.push(selectorDiagnostic(`copy.exemptions[${index}] exige seletor, ruleId, kind, reason e reference válidos.`))
2922
+ diagnostics.push(
2923
+ selectorDiagnostic(`copy.exemptions[${index}] exige seletor, ruleId, kind, reason e reference válidos.`),
2924
+ )
2453
2925
  continue
2454
2926
  }
2455
2927
  const matches = findEntry(entries, exemption)
2456
2928
  if (matches.length !== 1) {
2457
- diagnostics.push(selectorDiagnostic(`copy.exemptions[${index}] deve selecionar exatamente um texto; encontrou ${matches.length}.`))
2929
+ diagnostics.push(
2930
+ selectorDiagnostic(
2931
+ `copy.exemptions[${index}] deve selecionar exatamente um texto; encontrou ${matches.length}.`,
2932
+ ),
2933
+ )
2458
2934
  continue
2459
2935
  }
2460
2936
  const target = entries[matches[0].index]
@@ -2478,9 +2954,7 @@ function applyProjectMetadata(config, entries, diagnostics) {
2478
2954
  // Metadata declarativa complementa o que o extrator não consegue observar; ela
2479
2955
  // nunca pode enfraquecer um `uppercase` comprovado no JSX.
2480
2956
  const transformed = new Set(
2481
- entries
2482
- .map((entry, index) => (entry.transform === undefined ? null : index))
2483
- .filter((index) => index !== null),
2957
+ entries.map((entry, index) => (entry.transform === undefined ? null : index)).filter((index) => index !== null),
2484
2958
  )
2485
2959
  for (const [index, transform] of config.transforms.entries()) {
2486
2960
  if (
@@ -2496,7 +2970,11 @@ function applyProjectMetadata(config, entries, diagnostics) {
2496
2970
  }
2497
2971
  const matches = findEntry(entries, transform)
2498
2972
  if (matches.length !== 1) {
2499
- diagnostics.push(selectorDiagnostic(`copy.transforms[${index}] deve selecionar exatamente um texto; encontrou ${matches.length}.`))
2973
+ diagnostics.push(
2974
+ selectorDiagnostic(
2975
+ `copy.transforms[${index}] deve selecionar exatamente um texto; encontrou ${matches.length}.`,
2976
+ ),
2977
+ )
2500
2978
  continue
2501
2979
  }
2502
2980
  const entryIndex = matches[0].index
@@ -2519,7 +2997,10 @@ export function extractCopyInventory(root) {
2519
2997
  const entries = []
2520
2998
  const diagnostics = []
2521
2999
 
2522
- if (!Array.isArray(config.exclude) || config.exclude.some((pattern) => typeof pattern !== 'string' || pattern.trim() === '')) {
3000
+ if (
3001
+ !Array.isArray(config.exclude) ||
3002
+ config.exclude.some((pattern) => typeof pattern !== 'string' || pattern.trim() === '')
3003
+ ) {
2523
3004
  diagnostics.push(selectorDiagnostic('copy.exclude deve ser uma lista de globs não vazios.'))
2524
3005
  }
2525
3006
  const excluded = Array.isArray(config.exclude) ? config.exclude.filter((pattern) => typeof pattern === 'string') : []
@@ -2530,7 +3011,11 @@ export function extractCopyInventory(root) {
2530
3011
  try {
2531
3012
  file = readProjectFile(target, source)
2532
3013
  } catch (error) {
2533
- diagnostics.push({ source, line: 1, message: `source inseguro ou instável: ${projectPathErrorMessage(error)}` })
3014
+ diagnostics.push({
3015
+ source,
3016
+ line: 1,
3017
+ message: `source inseguro ou instável: ${projectPathErrorMessage(error)}`,
3018
+ })
2534
3019
  continue
2535
3020
  }
2536
3021
  const content = file.content
@@ -2546,7 +3031,11 @@ export function extractCopyInventory(root) {
2546
3031
  try {
2547
3032
  const current = readProjectFile(target, snapshot.source)
2548
3033
  if (!sameProjectFile(snapshot.file, current)) {
2549
- diagnostics.push({ source: snapshot.source, line: 1, message: 'source mudou durante a extração de copy.' })
3034
+ diagnostics.push({
3035
+ source: snapshot.source,
3036
+ line: 1,
3037
+ message: 'source mudou durante a extração de copy.',
3038
+ })
2550
3039
  }
2551
3040
  } catch (error) {
2552
3041
  diagnostics.push({
@@ -2562,9 +3051,11 @@ export function extractCopyInventory(root) {
2562
3051
  diagnostics.push(selectorDiagnostic(error.message))
2563
3052
  }
2564
3053
  if (sources.length === 0) {
2565
- diagnostics.push(selectorDiagnostic(
2566
- 'nenhum arquivo JavaScript/TypeScript elegível foi analisado; o inventário v2 exige ao menos uma fonte rastreável.',
2567
- ))
3054
+ diagnostics.push(
3055
+ selectorDiagnostic(
3056
+ 'nenhum arquivo JavaScript/TypeScript elegível foi analisado; o inventário v2 exige ao menos uma fonte rastreável.',
3057
+ ),
3058
+ )
2568
3059
  }
2569
3060
  const dynamicSurfaces = applyProjectMetadata(config, entries, diagnostics)
2570
3061
 
@@ -2630,7 +3121,9 @@ export function writeCopyInventory(root) {
2630
3121
  current = readProjectFile(targetRoot, destination, { allowMissing: true })
2631
3122
  verifyCopyInputs(targetRoot, result.consistency)
2632
3123
  } catch (error) {
2633
- throw new Error(`copy.inventory: ${projectPathErrorMessage(error) === 'operação de arquivo recusada por segurança.' ? error.message : projectPathErrorMessage(error)}`)
3124
+ throw new Error(
3125
+ `copy.inventory: ${projectPathErrorMessage(error) === 'operação de arquivo recusada por segurança.' ? error.message : projectPathErrorMessage(error)}`,
3126
+ )
2634
3127
  }
2635
3128
  if (current.exists && current.content === rendered) {
2636
3129
  return { ok: true, changed: false, path: target, ...result }
@@ -2639,12 +3132,10 @@ export function writeCopyInventory(root) {
2639
3132
  let written = null
2640
3133
  try {
2641
3134
  ensureCopyParents(targetRoot, destination)
2642
- writeProjectFileAtomically(
2643
- targetRoot,
2644
- destination,
2645
- rendered,
2646
- { exists: current.exists, content: current.content },
2647
- )
3135
+ writeProjectFileAtomically(targetRoot, destination, rendered, {
3136
+ exists: current.exists,
3137
+ content: current.content,
3138
+ })
2648
3139
  written = readProjectFile(targetRoot, destination)
2649
3140
  verifyCopyInputs(targetRoot, result.consistency)
2650
3141
  } catch (error) {
@@ -2671,7 +3162,9 @@ export function writeCopyInventory(root) {
2671
3162
  )
2672
3163
  }
2673
3164
  }
2674
- throw new Error(`copy.inventory: ${projectPathErrorMessage(error) === 'operação de arquivo recusada por segurança.' ? error.message : projectPathErrorMessage(error)}`)
3165
+ throw new Error(
3166
+ `copy.inventory: ${projectPathErrorMessage(error) === 'operação de arquivo recusada por segurança.' ? error.message : projectPathErrorMessage(error)}`,
3167
+ )
2675
3168
  }
2676
3169
  return { ok: true, changed: true, path: target, ...result }
2677
3170
  }
@@ -2693,7 +3186,9 @@ export function checkCopyInventory(root) {
2693
3186
  path: target,
2694
3187
  diagnostics: [
2695
3188
  ...result.diagnostics,
2696
- selectorDiagnostic(`copy.inventory inseguro ou instável: ${projectPathErrorMessage(error) === 'operação de arquivo recusada por segurança.' ? error.message : projectPathErrorMessage(error)}`),
3189
+ selectorDiagnostic(
3190
+ `copy.inventory inseguro ou instável: ${projectPathErrorMessage(error) === 'operação de arquivo recusada por segurança.' ? error.message : projectPathErrorMessage(error)}`,
3191
+ ),
2697
3192
  ],
2698
3193
  }
2699
3194
  }
@@ -2711,7 +3206,9 @@ export function checkCopyInventory(root) {
2711
3206
  path: target,
2712
3207
  diagnostics: [
2713
3208
  ...result.diagnostics,
2714
- selectorDiagnostic(`sources ou configuração mudaram antes da confirmação final: ${projectPathErrorMessage(error) === 'operação de arquivo recusada por segurança.' ? error.message : projectPathErrorMessage(error)}`),
3209
+ selectorDiagnostic(
3210
+ `sources ou configuração mudaram antes da confirmação final: ${projectPathErrorMessage(error) === 'operação de arquivo recusada por segurança.' ? error.message : projectPathErrorMessage(error)}`,
3211
+ ),
2715
3212
  ],
2716
3213
  }
2717
3214
  }