@tamagui/codemod-flat-values 0.0.0-bootstrap.0 → 3.0.0-beta.637.1
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/README.md +211 -1
- package/dist/builtInNames.mjs +13 -0
- package/dist/builtInNames.mjs.map +1 -0
- package/dist/containers.mjs +141 -0
- package/dist/containers.mjs.map +1 -0
- package/dist/convert.mjs +1139 -0
- package/dist/convert.mjs.map +1 -0
- package/dist/expressions.mjs +188 -0
- package/dist/expressions.mjs.map +1 -0
- package/dist/grammar.mjs +68 -0
- package/dist/grammar.mjs.map +1 -0
- package/dist/index.mjs +342 -0
- package/dist/index.mjs.map +1 -0
- package/dist/legacyConditions.mjs +293 -0
- package/dist/legacyConditions.mjs.map +1 -0
- package/dist/legacyNames.mjs +130 -0
- package/dist/legacyNames.mjs.map +1 -0
- package/dist/provenance.mjs +137 -0
- package/dist/provenance.mjs.map +1 -0
- package/dist/report.mjs +129 -0
- package/dist/report.mjs.map +1 -0
- package/dist/structuredNative.mjs +275 -0
- package/dist/structuredNative.mjs.map +1 -0
- package/package.json +35 -7
- package/src/builtInNames.ts +21 -0
- package/src/containers.ts +227 -0
- package/src/convert.ts +1784 -0
- package/src/expressions.ts +220 -0
- package/src/grammar.ts +180 -0
- package/src/index.ts +517 -0
- package/src/legacyConditions.ts +346 -0
- package/src/legacyNames.ts +160 -0
- package/src/provenance.ts +210 -0
- package/src/report.ts +235 -0
- package/src/structuredNative.ts +459 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { Node, SyntaxKind, type Expression } from 'ts-morph'
|
|
2
|
+
import { numericValue, unwrapExpression } from './expressions'
|
|
3
|
+
import { parseTransformString, sharedPayload, type ModifierRegistryView } from './grammar'
|
|
4
|
+
|
|
5
|
+
export interface StructuredNativeClassification {
|
|
6
|
+
payload: string | null
|
|
7
|
+
blocked: { code: string; detail: string } | null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const staticTransformOperations: ReadonlySet<string> = new Set([
|
|
11
|
+
'perspective',
|
|
12
|
+
'rotate',
|
|
13
|
+
'rotateX',
|
|
14
|
+
'rotateY',
|
|
15
|
+
'rotateZ',
|
|
16
|
+
'scale',
|
|
17
|
+
'scaleX',
|
|
18
|
+
'scaleY',
|
|
19
|
+
'skewX',
|
|
20
|
+
'skewY',
|
|
21
|
+
'translateX',
|
|
22
|
+
'translateY',
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
function blocked(code: string, detail: string): StructuredNativeClassification {
|
|
26
|
+
return { payload: null, blocked: { code, detail } }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function staticString(expression: Expression): string | null {
|
|
30
|
+
const current = unwrapExpression(expression)
|
|
31
|
+
return Node.isStringLiteral(current) || Node.isNoSubstitutionTemplateLiteral(current)
|
|
32
|
+
? current.getLiteralValue()
|
|
33
|
+
: null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hasOnlyStringValues(expression: Expression): boolean {
|
|
37
|
+
const current = unwrapExpression(expression)
|
|
38
|
+
if (
|
|
39
|
+
Node.isStringLiteral(current) ||
|
|
40
|
+
Node.isNoSubstitutionTemplateLiteral(current) ||
|
|
41
|
+
Node.isTemplateExpression(current)
|
|
42
|
+
) {
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const type = current.getType()
|
|
47
|
+
const parts = type.isUnion() ? type.getUnionTypes() : [type]
|
|
48
|
+
let strings = 0
|
|
49
|
+
for (const part of parts) {
|
|
50
|
+
if (part.isNull() || part.isUndefined()) continue
|
|
51
|
+
if (!part.isString() && !part.isStringLiteral()) return false
|
|
52
|
+
strings++
|
|
53
|
+
}
|
|
54
|
+
return strings > 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function staticTransformArray(
|
|
58
|
+
expression: Expression,
|
|
59
|
+
source: string
|
|
60
|
+
): StructuredNativeClassification {
|
|
61
|
+
const array = unwrapExpression(expression)
|
|
62
|
+
if (!Node.isArrayLiteralExpression(array)) {
|
|
63
|
+
return blocked(
|
|
64
|
+
'structured-transform-dynamic',
|
|
65
|
+
`transform value "${source}" is not an inline transform array; keep dynamic and Animated arrays authored`
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const functions: string[] = []
|
|
70
|
+
for (const element of array.getElements()) {
|
|
71
|
+
if (!Node.isObjectLiteralExpression(element)) {
|
|
72
|
+
return blocked(
|
|
73
|
+
'structured-transform-dynamic',
|
|
74
|
+
`transform value "${source}" contains a spread or computed entry; keep dynamic and Animated arrays authored`
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
const properties = element.getProperties()
|
|
78
|
+
if (properties.length !== 1 || !Node.isPropertyAssignment(properties[0])) {
|
|
79
|
+
return blocked(
|
|
80
|
+
'structured-transform-entry',
|
|
81
|
+
`transform value "${source}" must have exactly one static operation per array entry`
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const property = properties[0]
|
|
86
|
+
const nameNode = property.getNameNode()
|
|
87
|
+
if (!Node.isIdentifier(nameNode) && !Node.isStringLiteral(nameNode)) {
|
|
88
|
+
return blocked(
|
|
89
|
+
'structured-transform-entry',
|
|
90
|
+
`transform value "${source}" contains a computed operation name`
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
const operation = Node.isStringLiteral(nameNode)
|
|
94
|
+
? nameNode.getLiteralValue()
|
|
95
|
+
: nameNode.getText()
|
|
96
|
+
const initializer = unwrapExpression(property.getInitializerOrThrow())
|
|
97
|
+
|
|
98
|
+
if (operation === 'matrix' || operation === 'matrix3d') {
|
|
99
|
+
return blocked(
|
|
100
|
+
'structured-transform-matrix',
|
|
101
|
+
`transform value "${source}" contains "${operation}", whose React Native array and portable CSS function shapes do not match; keep it authored`
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
if (!staticTransformOperations.has(operation)) {
|
|
105
|
+
return blocked(
|
|
106
|
+
'structured-transform-operation',
|
|
107
|
+
`transform value "${source}" uses unsupported operation "${operation}"`
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const number = numericValue(initializer)
|
|
112
|
+
let value: string
|
|
113
|
+
if (number !== null) {
|
|
114
|
+
if (
|
|
115
|
+
operation === 'rotate' ||
|
|
116
|
+
operation === 'rotateX' ||
|
|
117
|
+
operation === 'rotateY' ||
|
|
118
|
+
operation === 'rotateZ' ||
|
|
119
|
+
operation === 'skewX' ||
|
|
120
|
+
operation === 'skewY'
|
|
121
|
+
) {
|
|
122
|
+
return blocked(
|
|
123
|
+
'structured-transform-unit',
|
|
124
|
+
`transform operation "${operation}" in "${source}" needs an explicit deg or rad string`
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
value =
|
|
128
|
+
operation === 'scale' || operation === 'scaleX' || operation === 'scaleY'
|
|
129
|
+
? String(number)
|
|
130
|
+
: `${number}px`
|
|
131
|
+
} else if (
|
|
132
|
+
Node.isStringLiteral(initializer) ||
|
|
133
|
+
Node.isNoSubstitutionTemplateLiteral(initializer)
|
|
134
|
+
) {
|
|
135
|
+
value = initializer.getLiteralValue()
|
|
136
|
+
} else {
|
|
137
|
+
return blocked(
|
|
138
|
+
'structured-transform-dynamic',
|
|
139
|
+
`transform operation "${operation}" in "${source}" is computed; keep dynamic and Animated arrays authored`
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
functions.push(`${operation}(${value})`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (functions.length === 0) {
|
|
146
|
+
return blocked(
|
|
147
|
+
'structured-transform-empty',
|
|
148
|
+
`transform value "${source}" is empty; no portable flat base can preserve its reset semantics`
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const payload = functions.join(' ')
|
|
153
|
+
const parsed = parseTransformString(payload)
|
|
154
|
+
if (parsed.errors.length) {
|
|
155
|
+
return blocked(
|
|
156
|
+
`structured-transform-${parsed.errors[0].code}`,
|
|
157
|
+
`${parsed.errors[0].message}; keep "${source}" authored`
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
return { payload, blocked: null }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function staticFontVariantArray(
|
|
164
|
+
expression: Expression,
|
|
165
|
+
source: string
|
|
166
|
+
): StructuredNativeClassification {
|
|
167
|
+
const array = unwrapExpression(expression)
|
|
168
|
+
if (!Node.isArrayLiteralExpression(array)) {
|
|
169
|
+
return blocked(
|
|
170
|
+
'structured-font-variant-dynamic',
|
|
171
|
+
`fontVariant value "${source}" is not an inline string array`
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const variants: string[] = []
|
|
176
|
+
for (const element of array.getElements()) {
|
|
177
|
+
const variant = staticString(element)
|
|
178
|
+
if (variant === null) {
|
|
179
|
+
return blocked(
|
|
180
|
+
'structured-font-variant-dynamic',
|
|
181
|
+
`fontVariant value "${source}" contains a computed entry; keep it authored`
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
variants.push(variant)
|
|
185
|
+
}
|
|
186
|
+
return variants.length
|
|
187
|
+
? { payload: variants.join(' '), blocked: null }
|
|
188
|
+
: blocked(
|
|
189
|
+
'structured-font-variant-empty',
|
|
190
|
+
`fontVariant value "${source}" is empty; no CSS token list preserves that reset`
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function staticBackgroundImageArray(
|
|
195
|
+
expression: Expression,
|
|
196
|
+
source: string
|
|
197
|
+
): StructuredNativeClassification {
|
|
198
|
+
const array = unwrapExpression(expression)
|
|
199
|
+
if (!Node.isArrayLiteralExpression(array)) {
|
|
200
|
+
return blocked(
|
|
201
|
+
'structured-background-image-dynamic',
|
|
202
|
+
`backgroundImage value "${source}" is not an inline gradient array`
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
if (array.getElements().length !== 1) {
|
|
206
|
+
return blocked(
|
|
207
|
+
'structured-background-image-layers',
|
|
208
|
+
`backgroundImage value "${source}" must contain exactly one gradient; native conditional evaluation does not accept multiple layers`
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const gradient = array.getElements()[0]
|
|
213
|
+
if (!Node.isObjectLiteralExpression(gradient)) {
|
|
214
|
+
return blocked(
|
|
215
|
+
'structured-background-image-dynamic',
|
|
216
|
+
`backgroundImage value "${source}" contains a spread or computed gradient`
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const fields = new Map<string, Expression>()
|
|
221
|
+
for (const member of gradient.getProperties()) {
|
|
222
|
+
if (!Node.isPropertyAssignment(member)) {
|
|
223
|
+
return blocked(
|
|
224
|
+
'structured-background-image-dynamic',
|
|
225
|
+
`backgroundImage value "${source}" contains a spread or computed gradient field`
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
const nameNode = member.getNameNode()
|
|
229
|
+
if (!Node.isIdentifier(nameNode) && !Node.isStringLiteral(nameNode)) {
|
|
230
|
+
return blocked(
|
|
231
|
+
'structured-background-image-dynamic',
|
|
232
|
+
`backgroundImage value "${source}" contains a computed gradient field`
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
const name = Node.isStringLiteral(nameNode)
|
|
236
|
+
? nameNode.getLiteralValue()
|
|
237
|
+
: nameNode.getText()
|
|
238
|
+
if (
|
|
239
|
+
fields.has(name) ||
|
|
240
|
+
(name !== 'type' && name !== 'direction' && name !== 'colorStops')
|
|
241
|
+
) {
|
|
242
|
+
return blocked(
|
|
243
|
+
'structured-background-image-shape',
|
|
244
|
+
`backgroundImage value "${source}" contains unsupported field "${name}"`
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
fields.set(name, member.getInitializerOrThrow())
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const type = fields.get('type')
|
|
251
|
+
const typeName = type ? staticString(type) : null
|
|
252
|
+
if (typeName !== 'linear-gradient') {
|
|
253
|
+
return blocked(
|
|
254
|
+
'structured-background-image-kind',
|
|
255
|
+
`backgroundImage value "${source}" is not a static linear-gradient object`
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const parts: string[] = []
|
|
260
|
+
const direction = fields.get('direction')
|
|
261
|
+
if (direction) {
|
|
262
|
+
const value = staticString(direction)
|
|
263
|
+
if (value === null) {
|
|
264
|
+
return blocked(
|
|
265
|
+
'structured-background-image-dynamic',
|
|
266
|
+
`backgroundImage value "${source}" has a computed direction`
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
parts.push(value)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const colorStopsExpression = fields.get('colorStops')
|
|
273
|
+
const colorStops = colorStopsExpression
|
|
274
|
+
? unwrapExpression(colorStopsExpression)
|
|
275
|
+
: undefined
|
|
276
|
+
if (!colorStops || !Node.isArrayLiteralExpression(colorStops)) {
|
|
277
|
+
return blocked(
|
|
278
|
+
'structured-background-image-dynamic',
|
|
279
|
+
`backgroundImage value "${source}" does not have an inline colorStops array`
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
if (colorStops.getElements().length < 2) {
|
|
283
|
+
return blocked(
|
|
284
|
+
'structured-background-image-stops',
|
|
285
|
+
`backgroundImage value "${source}" needs at least two color stops`
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const stopElements = colorStops.getElements()
|
|
290
|
+
for (const [stopIndex, stop] of stopElements.entries()) {
|
|
291
|
+
if (!Node.isObjectLiteralExpression(stop)) {
|
|
292
|
+
return blocked(
|
|
293
|
+
'structured-background-image-dynamic',
|
|
294
|
+
`backgroundImage value "${source}" contains a spread or computed color stop`
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let color: string | null | undefined
|
|
299
|
+
let hasColor = false
|
|
300
|
+
let positions: string[] = []
|
|
301
|
+
let hasPositions = false
|
|
302
|
+
for (const member of stop.getProperties()) {
|
|
303
|
+
if (!Node.isPropertyAssignment(member)) {
|
|
304
|
+
return blocked(
|
|
305
|
+
'structured-background-image-dynamic',
|
|
306
|
+
`backgroundImage value "${source}" contains a spread or computed color-stop field`
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
const nameNode = member.getNameNode()
|
|
310
|
+
if (!Node.isIdentifier(nameNode) && !Node.isStringLiteral(nameNode)) {
|
|
311
|
+
return blocked(
|
|
312
|
+
'structured-background-image-dynamic',
|
|
313
|
+
`backgroundImage value "${source}" contains a computed color-stop field`
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
const name = Node.isStringLiteral(nameNode)
|
|
317
|
+
? nameNode.getLiteralValue()
|
|
318
|
+
: nameNode.getText()
|
|
319
|
+
if (name === 'color' && !hasColor) {
|
|
320
|
+
hasColor = true
|
|
321
|
+
const value = unwrapExpression(member.getInitializerOrThrow())
|
|
322
|
+
if (value.getKind() === SyntaxKind.NullKeyword) {
|
|
323
|
+
color = null
|
|
324
|
+
continue
|
|
325
|
+
}
|
|
326
|
+
const staticColor = staticString(value)
|
|
327
|
+
if (staticColor === null) {
|
|
328
|
+
return blocked(
|
|
329
|
+
'structured-background-image-dynamic',
|
|
330
|
+
`backgroundImage value "${source}" contains a computed color`
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
color = staticColor
|
|
334
|
+
continue
|
|
335
|
+
}
|
|
336
|
+
if (name === 'positions' && !hasPositions) {
|
|
337
|
+
hasPositions = true
|
|
338
|
+
const value = unwrapExpression(member.getInitializerOrThrow())
|
|
339
|
+
if (!Node.isArrayLiteralExpression(value)) {
|
|
340
|
+
return blocked(
|
|
341
|
+
'structured-background-image-dynamic',
|
|
342
|
+
`backgroundImage value "${source}" contains computed color-stop positions`
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
positions = []
|
|
346
|
+
for (const positionExpression of value.getElements()) {
|
|
347
|
+
const number = numericValue(positionExpression)
|
|
348
|
+
if (number !== null) {
|
|
349
|
+
positions.push(`${number}px`)
|
|
350
|
+
continue
|
|
351
|
+
}
|
|
352
|
+
const position = staticString(positionExpression)
|
|
353
|
+
if (position === null) {
|
|
354
|
+
return blocked(
|
|
355
|
+
'structured-background-image-dynamic',
|
|
356
|
+
`backgroundImage value "${source}" contains a computed color-stop position`
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
if (!position.endsWith('%')) {
|
|
360
|
+
return blocked(
|
|
361
|
+
'structured-background-image-position',
|
|
362
|
+
`backgroundImage value "${source}" has position "${position}"; React Native gradient objects accept numeric points or percentage strings`
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
positions.push(position)
|
|
366
|
+
}
|
|
367
|
+
continue
|
|
368
|
+
}
|
|
369
|
+
return blocked(
|
|
370
|
+
'structured-background-image-shape',
|
|
371
|
+
`backgroundImage value "${source}" contains unsupported or repeated color-stop field "${name}"`
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
if (!hasColor || color === undefined) {
|
|
375
|
+
return blocked(
|
|
376
|
+
'structured-background-image-shape',
|
|
377
|
+
`backgroundImage value "${source}" contains a color stop without a color`
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
if (color === null) {
|
|
381
|
+
if (
|
|
382
|
+
positions.length !== 1 ||
|
|
383
|
+
stopIndex === 0 ||
|
|
384
|
+
stopIndex === stopElements.length - 1
|
|
385
|
+
) {
|
|
386
|
+
return blocked(
|
|
387
|
+
'structured-background-image-hint',
|
|
388
|
+
`backgroundImage value "${source}" has an invalid transition hint; it needs one position between two colored stops`
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
parts.push(positions[0])
|
|
392
|
+
} else if (positions.length > 2) {
|
|
393
|
+
for (const position of positions) parts.push(`${color} ${position}`)
|
|
394
|
+
} else {
|
|
395
|
+
parts.push([color, ...positions].join(' '))
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return { payload: `linear-gradient(${parts.join(', ')})`, blocked: null }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
type StructuredNativeSerializer = (
|
|
403
|
+
expression: Expression,
|
|
404
|
+
source: string
|
|
405
|
+
) => StructuredNativeClassification
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Each entry names one native structure with a verified CSS-shaped spelling.
|
|
409
|
+
* A new object or array shape stays authored until it has its own serializer.
|
|
410
|
+
*/
|
|
411
|
+
const structuredNativeSerializers: Readonly<
|
|
412
|
+
Partial<Record<string, StructuredNativeSerializer>>
|
|
413
|
+
> = Object.freeze({
|
|
414
|
+
backgroundImage: staticBackgroundImageArray,
|
|
415
|
+
fontVariant: staticFontVariantArray,
|
|
416
|
+
transform: staticTransformArray,
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* The migration table is intentionally property-shaped. A structured value
|
|
421
|
+
* stays in its natural React Native representation until a condition forces it
|
|
422
|
+
* into one flat string program.
|
|
423
|
+
*/
|
|
424
|
+
export function classifyStructuredNativeValue(
|
|
425
|
+
property: string,
|
|
426
|
+
expression: Expression,
|
|
427
|
+
source: string,
|
|
428
|
+
registry: ModifierRegistryView
|
|
429
|
+
): StructuredNativeClassification | null {
|
|
430
|
+
const serializer = structuredNativeSerializers[property]
|
|
431
|
+
if (serializer) {
|
|
432
|
+
if (hasOnlyStringValues(expression)) return null
|
|
433
|
+
const result = serializer(expression, source)
|
|
434
|
+
if (result.payload === null) return result
|
|
435
|
+
|
|
436
|
+
const flattened = sharedPayload(property, result.payload, registry)
|
|
437
|
+
const error = flattened.errors[0]
|
|
438
|
+
if (error || flattened.payload === null) {
|
|
439
|
+
return blocked(
|
|
440
|
+
error?.code ?? 'unsupported-structured-value',
|
|
441
|
+
`${property}: ${error?.message ?? `"${result.payload}" has no flat spelling`}`
|
|
442
|
+
)
|
|
443
|
+
}
|
|
444
|
+
return { payload: flattened.payload, blocked: null }
|
|
445
|
+
}
|
|
446
|
+
const current = unwrapExpression(expression)
|
|
447
|
+
if (
|
|
448
|
+
!Node.isObjectLiteralExpression(current) &&
|
|
449
|
+
!Node.isArrayLiteralExpression(current)
|
|
450
|
+
) {
|
|
451
|
+
return null
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const code = property.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
|
|
455
|
+
return blocked(
|
|
456
|
+
`structured-${code}`,
|
|
457
|
+
`${property} value "${source}" has no verified CSS-shaped migration rule; keep it authored`
|
|
458
|
+
)
|
|
459
|
+
}
|