@softize/opus 15.2.2 → 16.1.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.
- package/CHANGELOG.md +60 -18
- package/bin/lib/check.mjs +98 -15
- package/bin/lib/copy.mjs +811 -314
- package/bin/lib/gen-manifest.mjs +24 -23
- package/bin/lib/gen-runner.mjs +188 -148
- package/docs/adr/0010-page-header-owns-page-chrome.md +3 -4
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -3
- package/docs/adr/0012-modal-header-only-names-the-surface.md +45 -0
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +92 -0
- package/docs/code-style.md +24 -19
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +43 -26
- package/src/core/presentation.ts +512 -0
- package/src/presentation/index.ts +1 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +26 -9
- package/src/ui/components/patterns/confirm.tsx +34 -29
- package/src/ui/components/patterns/form-dialog.tsx +20 -8
- package/src/ui/components/patterns/list.tsx +26 -9
- package/src/ui/components/patterns/page.tsx +99 -139
- package/src/ui/components/patterns/presentation.tsx +539 -0
- package/src/ui/components/patterns/sidebar.tsx +3 -3
- package/src/ui/components/patterns/trigger.tsx +38 -17
- package/src/ui/components/primitives/button-group.tsx +53 -43
- package/src/ui/components/primitives/command.tsx +30 -72
- package/src/ui/components/primitives/dialog.tsx +23 -89
- package/src/ui/components/primitives/drawer.tsx +8 -34
- package/src/ui/docs/content/action-form-dialog.md +12 -10
- package/src/ui/docs/content/action-list-dialog.md +22 -17
- package/src/ui/docs/content/button.md +52 -35
- package/src/ui/docs/content/communication.md +26 -26
- package/src/ui/docs/content/dialog.md +173 -154
- package/src/ui/docs/content/drawer.md +12 -11
- package/src/ui/docs/content/page.md +72 -91
- package/src/ui/docs/content/presentation.md +188 -0
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/meta.ts +8 -2
- package/src/ui/react.tsx +16 -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',
|
|
58
|
-
'
|
|
59
|
-
'
|
|
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 =
|
|
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',
|
|
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',
|
|
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
|
-
[
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
[
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
[
|
|
215
|
+
[
|
|
216
|
+
'Alert',
|
|
217
|
+
new Map([
|
|
218
|
+
['title', 'title'],
|
|
219
|
+
['description', 'message'],
|
|
220
|
+
]),
|
|
221
|
+
],
|
|
151
222
|
['CommandInput', new Map([['placeholder', 'placeholder']])],
|
|
152
|
-
[
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
[
|
|
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
|
-
[
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
[
|
|
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
|
-
[
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
[
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
[
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
[
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
[
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
[
|
|
353
|
+
[
|
|
354
|
+
'Alert',
|
|
355
|
+
new Map([
|
|
356
|
+
['title', 'className'],
|
|
357
|
+
['description', 'className'],
|
|
358
|
+
]),
|
|
359
|
+
],
|
|
213
360
|
['CommandInput', new Map([['placeholder', 'className']])],
|
|
214
|
-
[
|
|
215
|
-
|
|
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
|
-
[
|
|
220
|
-
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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',
|
|
240
|
-
'
|
|
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
|
-
[
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
]
|
|
255
|
-
[
|
|
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',
|
|
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,
|
|
268
|
-
|
|
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: {
|
|
538
|
+
options: {
|
|
539
|
+
allowJs: true,
|
|
540
|
+
jsx: ts.JsxEmit.Preserve,
|
|
541
|
+
noLib: true,
|
|
542
|
+
noResolve: true,
|
|
543
|
+
},
|
|
310
544
|
host,
|
|
311
545
|
})
|
|
312
|
-
return {
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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) => ({
|
|
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) ||
|
|
434
|
-
ts.
|
|
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
|
-
)
|
|
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
|
|
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
|
-
)
|
|
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
|
-
|
|
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
|
|
519
|
-
|
|
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
|
|
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) ||
|
|
562
|
-
ts.
|
|
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) &&
|
|
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) ||
|
|
609
|
-
|
|
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' &&
|
|
619
|
-
|
|
620
|
-
|
|
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 (
|
|
623
|
-
|
|
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
|
-
|
|
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) &&
|
|
912
|
+
ts.isIdentifier(node) &&
|
|
913
|
+
sameBinding(node, binding.symbol, checker) &&
|
|
646
914
|
!declarationIdentifier(node, binding.symbol)
|
|
647
|
-
)
|
|
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 &&
|
|
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
|
|
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 (
|
|
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)
|
|
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)
|
|
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) &&
|
|
1049
|
+
ts.isCallExpression(source) &&
|
|
1050
|
+
ts.isPropertyAccessExpression(unwrap(source.expression)) &&
|
|
771
1051
|
ELEMENT_PRESERVING_ARRAY_METHODS.has(unwrap(source.expression).name.text)
|
|
772
|
-
)
|
|
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
|
|
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
|
-
)
|
|
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
|
|
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
|
-
)
|
|
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 = {
|
|
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)
|
|
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
|
-
)
|
|
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
|
|
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(
|
|
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
|
-
)
|
|
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 (
|
|
1574
|
-
|
|
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 (
|
|
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)
|
|
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(
|
|
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 {
|
|
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) => ({
|
|
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(
|
|
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 {
|
|
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)
|
|
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 {
|
|
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(
|
|
1717
|
-
|
|
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(
|
|
1723
|
-
|
|
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 (
|
|
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 = {
|
|
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)
|
|
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
|
-
)
|
|
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)
|
|
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(
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
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)
|
|
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 =
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
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
|
-
|
|
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 (
|
|
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')
|
|
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 = {
|
|
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(
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
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 = {
|
|
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(
|
|
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)
|
|
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'))
|
|
2363
|
+
if (object?.kind === ts.SyntaxKind.NullKeyword || (ts.isIdentifier(object) && object.text === 'undefined'))
|
|
2364
|
+
continue
|
|
1969
2365
|
if (!ts.isObjectLiteralExpression(object)) {
|
|
1970
|
-
diagnostics.push(
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
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)
|
|
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(
|
|
1990
|
-
file,
|
|
1991
|
-
|
|
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 =
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
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(
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
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({
|
|
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
|
-
|
|
2049
|
-
|
|
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(
|
|
2054
|
-
|
|
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 =
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
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 =
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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 = {
|
|
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))
|
|
2529
|
+
for (const [name, role] of DIALOG_METHOD_ROLES.get(method))
|
|
2530
|
+
addVisualObjectProperty(options, name, role, `${field}.`)
|
|
2107
2531
|
if (method === 'choose') {
|
|
2108
|
-
nestedObject(
|
|
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 {
|
|
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) =>
|
|
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:
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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(
|
|
2314
|
-
entry
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
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',
|
|
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
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
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(
|
|
2382
|
-
|
|
2383
|
-
|
|
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(
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
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(
|
|
2398
|
-
|
|
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: {
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
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({
|
|
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({
|
|
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(
|
|
2566
|
-
|
|
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(
|
|
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
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|