@tamagui/codemod-flat-values 3.0.0-beta.1313.1 → 3.0.0-beta.1341.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 +20 -0
- package/dist/expressions.mjs +1 -1
- package/dist/expressions.mjs.map +1 -1
- package/dist/grammar.mjs +7 -4
- package/dist/grammar.mjs.map +1 -1
- package/dist/index.mjs +320 -6
- package/dist/index.mjs.map +1 -1
- package/dist/provenance.mjs +10 -1
- package/dist/provenance.mjs.map +1 -1
- package/package.json +5 -5
- package/src/expressions.ts +1 -1
- package/src/grammar.ts +11 -4
- package/src/index.ts +454 -5
- package/src/provenance.ts +11 -0
package/src/index.ts
CHANGED
|
@@ -11,13 +11,17 @@ import {
|
|
|
11
11
|
ScriptTarget,
|
|
12
12
|
SyntaxKind,
|
|
13
13
|
ts,
|
|
14
|
+
type CallExpression,
|
|
14
15
|
type Expression,
|
|
16
|
+
type JsxOpeningElement,
|
|
17
|
+
type JsxSelfClosingElement,
|
|
15
18
|
type ObjectLiteralExpression,
|
|
19
|
+
type PropertyAssignment,
|
|
16
20
|
type SourceFile,
|
|
17
21
|
} from 'ts-morph'
|
|
18
22
|
import { planContainers, type ContainerPlan } from './containers'
|
|
19
23
|
import { convertJsxSite, convertStyleObject, type SiteReport } from './convert'
|
|
20
|
-
import { compact, unwrapExpression } from './expressions'
|
|
24
|
+
import { compact, numericValue, runtimeType, unwrapExpression } from './expressions'
|
|
21
25
|
import {
|
|
22
26
|
addFunctionalVariantTypeImports,
|
|
23
27
|
convertFunctionalVariants,
|
|
@@ -37,8 +41,408 @@ import { createProvenance } from './provenance'
|
|
|
37
41
|
import { renderReport, type FileReport } from './report'
|
|
38
42
|
import { convertSheetFrames } from './sheetAnatomy'
|
|
39
43
|
import { convertTransitions } from './transition'
|
|
44
|
+
import { isLegacyConditionName } from './legacyNames'
|
|
40
45
|
|
|
41
46
|
type Provenance = ReturnType<typeof createProvenance>
|
|
47
|
+
type SourceSemantics = 'v2-pixels' | 'v3-ratios'
|
|
48
|
+
|
|
49
|
+
interface SourceEdit {
|
|
50
|
+
start: number
|
|
51
|
+
end: number
|
|
52
|
+
text: string
|
|
53
|
+
dynamic: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function propertyName(node: Node): string | null {
|
|
57
|
+
if (
|
|
58
|
+
Node.isIdentifier(node) ||
|
|
59
|
+
Node.isStringLiteral(node) ||
|
|
60
|
+
Node.isNumericLiteral(node)
|
|
61
|
+
) {
|
|
62
|
+
return node.getText().replace(/^['"]|['"]$/g, '')
|
|
63
|
+
}
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isLineHeightName(name: string | null): boolean {
|
|
68
|
+
return name === 'lineHeight' || name === 'lh'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function extractedTokenValue(expression: Expression): boolean {
|
|
72
|
+
const nodes = [expression, ...expression.getDescendants()]
|
|
73
|
+
return nodes.some(
|
|
74
|
+
(node) =>
|
|
75
|
+
(Node.isPropertyAccessExpression(node) && node.getName() === 'val') ||
|
|
76
|
+
(Node.isCallExpression(node) &&
|
|
77
|
+
Node.isIdentifier(node.getExpression()) &&
|
|
78
|
+
node.getExpression().getText() === 'getVariableValue')
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function pixelReplacement(
|
|
83
|
+
expression: Expression
|
|
84
|
+
): { text: string; dynamic: boolean } | { flag: SiteReport['flags'][number] } | null {
|
|
85
|
+
const current = unwrapExpression(expression)
|
|
86
|
+
const number = numericValue(current)
|
|
87
|
+
if (number !== null) return { text: JSON.stringify(`${number}px`), dynamic: false }
|
|
88
|
+
const type = runtimeType(current)
|
|
89
|
+
if (type.kind === 'string') return null
|
|
90
|
+
|
|
91
|
+
if (extractedTokenValue(current)) {
|
|
92
|
+
return {
|
|
93
|
+
flag: {
|
|
94
|
+
code: 'ambiguous-line-height-token-value',
|
|
95
|
+
detail: `lineHeight value "${compact(current.getText())}" extracts a token value whose units are no longer visible; replace it with an explicit ratio or px length by hand`,
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (type.kind === 'number') {
|
|
101
|
+
return { text: `\`\${${current.getText()}}px\``, dynamic: true }
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
flag: {
|
|
105
|
+
code: 'ambiguous-line-height-expression',
|
|
106
|
+
detail: `lineHeight value "${compact(current.getText())}" has no provable numeric type; replace old pixel semantics with an explicit px length by hand`,
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function declarationContainsNumericLineHeight(expression: Expression): boolean {
|
|
112
|
+
const symbol = expression.getSymbol()
|
|
113
|
+
if (!symbol) return false
|
|
114
|
+
for (const declaration of symbol.getDeclarations()) {
|
|
115
|
+
for (const property of [
|
|
116
|
+
...(Node.isPropertyAssignment(declaration) ? [declaration] : []),
|
|
117
|
+
...declaration.getDescendantsOfKind(SyntaxKind.PropertyAssignment),
|
|
118
|
+
]) {
|
|
119
|
+
if (!isLineHeightName(propertyName(property.getNameNode()))) continue
|
|
120
|
+
const initializer = property.getInitializer()
|
|
121
|
+
if (!initializer) continue
|
|
122
|
+
const value = unwrapExpression(initializer)
|
|
123
|
+
if (numericValue(value) !== null || runtimeType(value).kind === 'number')
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function lineHeightValueIsUnmigrated(expression: Expression): boolean {
|
|
131
|
+
const current = unwrapExpression(expression)
|
|
132
|
+
if (Node.isObjectLiteralExpression(current)) {
|
|
133
|
+
return current.getProperties().some((property) => {
|
|
134
|
+
if (!Node.isPropertyAssignment(property)) return false
|
|
135
|
+
const initializer = property.getInitializer()
|
|
136
|
+
return initializer !== undefined && lineHeightValueIsUnmigrated(initializer)
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
if (Node.isConditionalExpression(current)) {
|
|
140
|
+
return (
|
|
141
|
+
lineHeightValueIsUnmigrated(current.getWhenTrue()) ||
|
|
142
|
+
lineHeightValueIsUnmigrated(current.getWhenFalse())
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
return pixelReplacement(current) !== null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function replaceWithin(
|
|
149
|
+
source: string,
|
|
150
|
+
sourceStart: number,
|
|
151
|
+
edits: readonly SourceEdit[]
|
|
152
|
+
): string {
|
|
153
|
+
let output = source
|
|
154
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
155
|
+
output =
|
|
156
|
+
output.slice(0, edit.start - sourceStart) +
|
|
157
|
+
edit.text +
|
|
158
|
+
output.slice(edit.end - sourceStart)
|
|
159
|
+
}
|
|
160
|
+
return output
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function migrateLegacyLineHeights(
|
|
164
|
+
sourceFile: SourceFile,
|
|
165
|
+
provenance: Provenance
|
|
166
|
+
): SiteReport[] {
|
|
167
|
+
const edits: SourceEdit[] = []
|
|
168
|
+
const claimed = new Set<number>()
|
|
169
|
+
const reports: SiteReport[] = []
|
|
170
|
+
|
|
171
|
+
const scanLineHeightExpression = (
|
|
172
|
+
expression: Expression,
|
|
173
|
+
contextEdits: SourceEdit[],
|
|
174
|
+
flags: SiteReport['flags']
|
|
175
|
+
) => {
|
|
176
|
+
const current = unwrapExpression(expression)
|
|
177
|
+
if (Node.isObjectLiteralExpression(current)) {
|
|
178
|
+
for (const property of current.getProperties()) {
|
|
179
|
+
if (!Node.isPropertyAssignment(property)) continue
|
|
180
|
+
const initializer = property.getInitializer()
|
|
181
|
+
if (initializer) scanLineHeightExpression(initializer, contextEdits, flags)
|
|
182
|
+
}
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
if (Node.isConditionalExpression(current)) {
|
|
186
|
+
scanLineHeightExpression(current.getWhenTrue(), contextEdits, flags)
|
|
187
|
+
scanLineHeightExpression(current.getWhenFalse(), contextEdits, flags)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if (claimed.has(current.getStart())) return
|
|
191
|
+
const replacement = pixelReplacement(current)
|
|
192
|
+
if (!replacement) return
|
|
193
|
+
claimed.add(current.getStart())
|
|
194
|
+
if ('flag' in replacement) {
|
|
195
|
+
flags.push(replacement.flag)
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
const edit = {
|
|
199
|
+
start: current.getStart(),
|
|
200
|
+
end: current.getEnd(),
|
|
201
|
+
text: replacement.text,
|
|
202
|
+
dynamic: replacement.dynamic,
|
|
203
|
+
}
|
|
204
|
+
edits.push(edit)
|
|
205
|
+
contextEdits.push(edit)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const scanProperty = (
|
|
209
|
+
property: PropertyAssignment,
|
|
210
|
+
contextEdits: SourceEdit[],
|
|
211
|
+
flags: SiteReport['flags']
|
|
212
|
+
) => {
|
|
213
|
+
if (!isLineHeightName(propertyName(property.getNameNode()))) return
|
|
214
|
+
const initializer = property.getInitializer()
|
|
215
|
+
if (initializer) scanLineHeightExpression(initializer, contextEdits, flags)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const scanObject = (
|
|
219
|
+
object: ObjectLiteralExpression,
|
|
220
|
+
contextEdits: SourceEdit[],
|
|
221
|
+
flags: SiteReport['flags']
|
|
222
|
+
) => {
|
|
223
|
+
for (const property of object.getDescendantsOfKind(SyntaxKind.PropertyAssignment)) {
|
|
224
|
+
scanProperty(property, contextEdits, flags)
|
|
225
|
+
}
|
|
226
|
+
for (const property of object.getProperties()) {
|
|
227
|
+
if (Node.isPropertyAssignment(property)) scanProperty(property, contextEdits, flags)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const addReport = (
|
|
232
|
+
node: Node,
|
|
233
|
+
kind: SiteReport['kind'],
|
|
234
|
+
label: string,
|
|
235
|
+
contextEdits: SourceEdit[],
|
|
236
|
+
flags: SiteReport['flags']
|
|
237
|
+
) => {
|
|
238
|
+
if (!contextEdits.length && !flags.length) return
|
|
239
|
+
const before = compact(node.getText())
|
|
240
|
+
reports.push({
|
|
241
|
+
kind,
|
|
242
|
+
label,
|
|
243
|
+
line: sourceFile.getLineAndColumnAtPos(node.getStart()).line,
|
|
244
|
+
before,
|
|
245
|
+
after: compact(replaceWithin(node.getText(), node.getStart(), contextEdits)),
|
|
246
|
+
programs: contextEdits.map((edit) => ({
|
|
247
|
+
name: 'lineHeight',
|
|
248
|
+
value: edit.text,
|
|
249
|
+
dynamic: edit.dynamic,
|
|
250
|
+
})),
|
|
251
|
+
assessments: [],
|
|
252
|
+
assessmentVerdict: 'clean',
|
|
253
|
+
warnings: [],
|
|
254
|
+
flags,
|
|
255
|
+
inventory: [],
|
|
256
|
+
pending: [],
|
|
257
|
+
notes: [
|
|
258
|
+
contextEdits.length
|
|
259
|
+
? 'numeric lineHeight used V2 pixel semantics and now carries an explicit px unit'
|
|
260
|
+
: 'lineHeight was left authored for a manual unit decision',
|
|
261
|
+
],
|
|
262
|
+
legacyLeft: flags.length,
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const scanJsx = (opening: JsxOpeningElement | JsxSelfClosingElement) => {
|
|
267
|
+
const contextEdits: SourceEdit[] = []
|
|
268
|
+
const flags: SiteReport['flags'] = []
|
|
269
|
+
for (const attribute of opening.getAttributes()) {
|
|
270
|
+
if (Node.isJsxSpreadAttribute(attribute)) {
|
|
271
|
+
const expression = unwrapExpression(attribute.getExpression())
|
|
272
|
+
if (Node.isObjectLiteralExpression(expression)) {
|
|
273
|
+
scanObject(expression, contextEdits, flags)
|
|
274
|
+
} else if (declarationContainsNumericLineHeight(expression)) {
|
|
275
|
+
flags.push({
|
|
276
|
+
code: 'ambiguous-shared-line-height-style',
|
|
277
|
+
detail: `spread "${compact(expression.getText())}" contains numeric lineHeight and may also be consumed outside Tamagui; migrate it at this Tamagui boundary by hand`,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
if (!Node.isJsxAttribute(attribute)) continue
|
|
283
|
+
const nameNode = attribute.getNameNode()
|
|
284
|
+
const name = Node.isIdentifier(nameNode) ? nameNode.getText() : null
|
|
285
|
+
const initializer = attribute.getInitializer()
|
|
286
|
+
if (isLineHeightName(name)) {
|
|
287
|
+
if (!Node.isJsxExpression(initializer)) continue
|
|
288
|
+
const expression = initializer.getExpression()
|
|
289
|
+
if (!expression) continue
|
|
290
|
+
const current = unwrapExpression(expression)
|
|
291
|
+
const number = numericValue(current)
|
|
292
|
+
if (number !== null && !claimed.has(current.getStart())) {
|
|
293
|
+
claimed.add(current.getStart())
|
|
294
|
+
const edit = {
|
|
295
|
+
start: initializer.getStart(),
|
|
296
|
+
end: initializer.getEnd(),
|
|
297
|
+
text: JSON.stringify(`${number}px`),
|
|
298
|
+
dynamic: false,
|
|
299
|
+
}
|
|
300
|
+
edits.push(edit)
|
|
301
|
+
contextEdits.push(edit)
|
|
302
|
+
} else {
|
|
303
|
+
scanLineHeightExpression(current, contextEdits, flags)
|
|
304
|
+
}
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
if (isLegacyConditionName(name || '') && Node.isJsxExpression(initializer)) {
|
|
308
|
+
const expression = initializer.getExpression()
|
|
309
|
+
if (expression) {
|
|
310
|
+
const current = unwrapExpression(expression)
|
|
311
|
+
for (const object of [
|
|
312
|
+
...(Node.isObjectLiteralExpression(current) ? [current] : []),
|
|
313
|
+
...current.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression),
|
|
314
|
+
]) {
|
|
315
|
+
scanObject(object, contextEdits, flags)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
if (name !== 'style' || !Node.isJsxExpression(initializer)) continue
|
|
321
|
+
const expression = initializer.getExpression()
|
|
322
|
+
if (!expression) continue
|
|
323
|
+
const current = unwrapExpression(expression)
|
|
324
|
+
const objects = [
|
|
325
|
+
...(Node.isObjectLiteralExpression(current) ? [current] : []),
|
|
326
|
+
...current.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression),
|
|
327
|
+
]
|
|
328
|
+
if (objects.length) {
|
|
329
|
+
for (const object of objects) scanObject(object, contextEdits, flags)
|
|
330
|
+
} else if (declarationContainsNumericLineHeight(current)) {
|
|
331
|
+
flags.push({
|
|
332
|
+
code: 'ambiguous-shared-line-height-style',
|
|
333
|
+
detail: `style "${compact(current.getText())}" contains numeric lineHeight and may also be consumed by React Native or Restyle; migrate it at this Tamagui boundary by hand`,
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
addReport(
|
|
338
|
+
opening,
|
|
339
|
+
'jsx',
|
|
340
|
+
`<${opening.getTagNameNode().getText()}> lineHeight`,
|
|
341
|
+
contextEdits,
|
|
342
|
+
flags
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const scanCall = (call: CallExpression, label: string) => {
|
|
347
|
+
const contextEdits: SourceEdit[] = []
|
|
348
|
+
const flags: SiteReport['flags'] = []
|
|
349
|
+
for (const argument of call.getArguments()) {
|
|
350
|
+
for (const object of [
|
|
351
|
+
...(Node.isObjectLiteralExpression(argument) ? [argument] : []),
|
|
352
|
+
...argument.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression),
|
|
353
|
+
]) {
|
|
354
|
+
scanObject(object, contextEdits, flags)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
addReport(call, 'styled', label, contextEdits, flags)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const wrapperCache = new Map<Node, boolean>()
|
|
361
|
+
for (const opening of [
|
|
362
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),
|
|
363
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),
|
|
364
|
+
]) {
|
|
365
|
+
if (provenance.isTamaguiElement(opening)) {
|
|
366
|
+
scanJsx(opening)
|
|
367
|
+
continue
|
|
368
|
+
}
|
|
369
|
+
const hasLegacyValue = opening.getAttributes().some((attribute) => {
|
|
370
|
+
if (!Node.isJsxAttribute(attribute)) return false
|
|
371
|
+
const name = propertyName(attribute.getNameNode())
|
|
372
|
+
const initializer = attribute.getInitializer()
|
|
373
|
+
if (!initializer || !Node.isJsxExpression(initializer)) return false
|
|
374
|
+
const expression = initializer.getExpression()
|
|
375
|
+
if (!expression) return false
|
|
376
|
+
if (isLineHeightName(name)) return lineHeightValueIsUnmigrated(expression)
|
|
377
|
+
if (name !== 'style') return false
|
|
378
|
+
const current = unwrapExpression(expression)
|
|
379
|
+
const properties = current.getDescendantsOfKind(SyntaxKind.PropertyAssignment)
|
|
380
|
+
return properties.length
|
|
381
|
+
? properties.some((property) => {
|
|
382
|
+
const value = property.getInitializer()
|
|
383
|
+
return (
|
|
384
|
+
isLineHeightName(propertyName(property.getNameNode())) &&
|
|
385
|
+
value !== undefined &&
|
|
386
|
+
lineHeightValueIsUnmigrated(value)
|
|
387
|
+
)
|
|
388
|
+
})
|
|
389
|
+
: declarationContainsNumericLineHeight(current)
|
|
390
|
+
})
|
|
391
|
+
if (!hasLegacyValue) continue
|
|
392
|
+
|
|
393
|
+
const tag = opening.getTagNameNode()
|
|
394
|
+
const symbol = tag.getSymbol()
|
|
395
|
+
if (!symbol) continue
|
|
396
|
+
const declarations = (symbol.getAliasedSymbol() ?? symbol).getDeclarations()
|
|
397
|
+
const isLocalWrapper = declarations.some((declaration) => {
|
|
398
|
+
const cached = wrapperCache.get(declaration)
|
|
399
|
+
if (cached !== undefined) return cached
|
|
400
|
+
const file = declaration.getSourceFile()
|
|
401
|
+
const filePath = file.getFilePath()
|
|
402
|
+
const local =
|
|
403
|
+
!file.isDeclarationFile() &&
|
|
404
|
+
!filePath.includes('/node_modules/') &&
|
|
405
|
+
!relative(projectRoot, filePath).startsWith('..')
|
|
406
|
+
const wrapsTamagui =
|
|
407
|
+
local &&
|
|
408
|
+
[
|
|
409
|
+
...declaration.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),
|
|
410
|
+
...declaration.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),
|
|
411
|
+
].some((child) => provenance.isTamaguiElement(child))
|
|
412
|
+
wrapperCache.set(declaration, wrapsTamagui)
|
|
413
|
+
return wrapsTamagui
|
|
414
|
+
})
|
|
415
|
+
if (!isLocalWrapper) continue
|
|
416
|
+
addReport(
|
|
417
|
+
opening,
|
|
418
|
+
'jsx',
|
|
419
|
+
`<${tag.getText()}> lineHeight`,
|
|
420
|
+
[],
|
|
421
|
+
[
|
|
422
|
+
{
|
|
423
|
+
code: 'ambiguous-wrapper-line-height',
|
|
424
|
+
detail: `local wrapper <${tag.getText()}> renders Tamagui JSX, so numeric lineHeight may still be V2 pixels, but the wrapper may transform units; migrate this call site by hand`,
|
|
425
|
+
},
|
|
426
|
+
]
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
431
|
+
if (provenance.isTamaguiStyledCall(call)) {
|
|
432
|
+
scanCall(
|
|
433
|
+
call,
|
|
434
|
+
`styled(${compact(call.getArguments()[0]?.getText() ?? 'unknown')}, …) lineHeight`
|
|
435
|
+
)
|
|
436
|
+
} else if (provenance.isTamaguiStyleCall(call)) {
|
|
437
|
+
scanCall(call, `${compact(call.getExpression().getText())}(…) lineHeight`)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (edits.length) {
|
|
442
|
+
sourceFile.replaceWithText(replaceWithin(sourceFile.getFullText(), 0, edits))
|
|
443
|
+
}
|
|
444
|
+
return reports
|
|
445
|
+
}
|
|
42
446
|
|
|
43
447
|
// every path is resolved against the directory the codemod is invoked from, so it
|
|
44
448
|
// migrates the project you are standing in whether that is an app or this repo
|
|
@@ -368,8 +772,23 @@ function inspectFile(
|
|
|
368
772
|
sourceFile: SourceFile,
|
|
369
773
|
registry: ModifierRegistryView,
|
|
370
774
|
provenance: Provenance,
|
|
775
|
+
sourceSemantics: SourceSemantics,
|
|
776
|
+
lineHeightOnly: boolean,
|
|
371
777
|
write: boolean
|
|
372
778
|
): FileReport {
|
|
779
|
+
const lineHeightSites =
|
|
780
|
+
sourceSemantics === 'v2-pixels'
|
|
781
|
+
? migrateLegacyLineHeights(sourceFile, provenance)
|
|
782
|
+
: []
|
|
783
|
+
if (lineHeightOnly) {
|
|
784
|
+
return {
|
|
785
|
+
file: relative(projectRoot, sourceFile.getFilePath()),
|
|
786
|
+
sites: lineHeightSites,
|
|
787
|
+
functionalVariants: [],
|
|
788
|
+
sheetFrames: [],
|
|
789
|
+
transitions: [],
|
|
790
|
+
}
|
|
791
|
+
}
|
|
373
792
|
// the anatomy rewrite runs first so the Background it adds, and the surface
|
|
374
793
|
// props it moves there, go through the flat-value conversion below
|
|
375
794
|
const sheetFrames = convertSheetFrames(sourceFile, provenance, write)
|
|
@@ -378,7 +797,7 @@ function inspectFile(
|
|
|
378
797
|
const transitions = convertTransitions(sourceFile, provenance, write)
|
|
379
798
|
const containers = planContainers(sourceFile, registry)
|
|
380
799
|
const targets = conversionTargets(sourceFile.getFilePath())
|
|
381
|
-
const sites: SiteReport[] = []
|
|
800
|
+
const sites: SiteReport[] = [...lineHeightSites]
|
|
382
801
|
const functionalVariants: FunctionalVariantReport[] = []
|
|
383
802
|
const requiredTypeImports: RequiredTypeImport[] = []
|
|
384
803
|
const styledCalls = sourceFile
|
|
@@ -455,6 +874,10 @@ const usage = `Converts Tamagui style syntax to V3 flat property values and repo
|
|
|
455
874
|
)})
|
|
456
875
|
--json <path> also write the machine-readable report
|
|
457
876
|
--write rewrite every statically safe conversion in place
|
|
877
|
+
--source-semantics <v2-pixels|v3-ratios>
|
|
878
|
+
interpret numeric Tamagui lineHeight from that source contract
|
|
879
|
+
(default: v3-ratios)
|
|
880
|
+
--line-height-only apply and report only the selected lineHeight source migration
|
|
458
881
|
--help print this
|
|
459
882
|
|
|
460
883
|
Run it from your project root, which is where paths and the tsconfig resolve from.
|
|
@@ -465,11 +888,15 @@ function parseArguments(argv: readonly string[]): {
|
|
|
465
888
|
jsonPath: string | null
|
|
466
889
|
inputs: string[]
|
|
467
890
|
write: boolean
|
|
891
|
+
sourceSemantics: SourceSemantics
|
|
892
|
+
lineHeightOnly: boolean
|
|
468
893
|
} {
|
|
469
894
|
const inputs: string[] = []
|
|
470
895
|
let reportPath = defaultReportPath
|
|
471
896
|
let jsonPath: string | null = null
|
|
472
897
|
let write = false
|
|
898
|
+
let sourceSemantics: SourceSemantics = 'v3-ratios'
|
|
899
|
+
let lineHeightOnly = false
|
|
473
900
|
|
|
474
901
|
for (let index = 0; index < argv.length; index++) {
|
|
475
902
|
const argument = argv[index]
|
|
@@ -481,6 +908,20 @@ function parseArguments(argv: readonly string[]): {
|
|
|
481
908
|
write = true
|
|
482
909
|
continue
|
|
483
910
|
}
|
|
911
|
+
if (argument === '--line-height-only') {
|
|
912
|
+
lineHeightOnly = true
|
|
913
|
+
continue
|
|
914
|
+
}
|
|
915
|
+
if (argument === '--source-semantics') {
|
|
916
|
+
const next = argv[index + 1]
|
|
917
|
+
if (next !== 'v2-pixels' && next !== 'v3-ratios') {
|
|
918
|
+
console.error(`${argument} requires v2-pixels or v3-ratios\n\n${usage}`)
|
|
919
|
+
process.exit(2)
|
|
920
|
+
}
|
|
921
|
+
sourceSemantics = next
|
|
922
|
+
index++
|
|
923
|
+
continue
|
|
924
|
+
}
|
|
484
925
|
if (argument === '--report' || argument === '--json') {
|
|
485
926
|
const next = argv[index + 1]
|
|
486
927
|
if (!next) {
|
|
@@ -508,10 +949,11 @@ function parseArguments(argv: readonly string[]): {
|
|
|
508
949
|
process.exit(2)
|
|
509
950
|
}
|
|
510
951
|
|
|
511
|
-
return { reportPath, jsonPath, inputs, write }
|
|
952
|
+
return { reportPath, jsonPath, inputs, write, sourceSemantics, lineHeightOnly }
|
|
512
953
|
}
|
|
513
954
|
|
|
514
|
-
const { reportPath, jsonPath, inputs, write } =
|
|
955
|
+
const { reportPath, jsonPath, inputs, write, sourceSemantics, lineHeightOnly } =
|
|
956
|
+
parseArguments(process.argv.slice(2))
|
|
515
957
|
const { sourceFiles, ignoredFiles } = collectFiles(inputs)
|
|
516
958
|
for (const sourceFile of sourceFiles) {
|
|
517
959
|
const diagnostics = (
|
|
@@ -535,7 +977,14 @@ const modifierRegistry = createModifierRegistry({
|
|
|
535
977
|
})
|
|
536
978
|
const provenance = createProvenance()
|
|
537
979
|
const files = sourceFiles.map((sourceFile) =>
|
|
538
|
-
inspectFile(
|
|
980
|
+
inspectFile(
|
|
981
|
+
sourceFile,
|
|
982
|
+
modifierRegistry.registry,
|
|
983
|
+
provenance,
|
|
984
|
+
sourceSemantics,
|
|
985
|
+
lineHeightOnly,
|
|
986
|
+
write
|
|
987
|
+
)
|
|
539
988
|
)
|
|
540
989
|
if (write) {
|
|
541
990
|
for (const sourceFile of sourceFiles) {
|
package/src/provenance.ts
CHANGED
|
@@ -28,6 +28,7 @@ function rootIdentifier(node: Node): Identifier | null {
|
|
|
28
28
|
export function createProvenance(): {
|
|
29
29
|
isTamaguiElement: (opening: JsxOpeningElement | JsxSelfClosingElement) => boolean
|
|
30
30
|
isTamaguiStyledCall: (call: CallExpression) => boolean
|
|
31
|
+
isTamaguiStyleCall: (call: CallExpression) => boolean
|
|
31
32
|
} {
|
|
32
33
|
const resolved = new Map<Node, boolean>()
|
|
33
34
|
const active = new Set<Node>()
|
|
@@ -177,6 +178,15 @@ export function createProvenance(): {
|
|
|
177
178
|
return fromIdentifier(callee)
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
const isTamaguiStyleCall = (call: CallExpression): boolean => {
|
|
182
|
+
const callee = call.getExpression()
|
|
183
|
+
if (!Node.isPropertyAccessExpression(callee)) return false
|
|
184
|
+
const method = callee.getName()
|
|
185
|
+
if (method !== 'style' && method !== 'resolve' && method !== 'dynamic') return false
|
|
186
|
+
const owner = rootIdentifier(callee.getExpression())
|
|
187
|
+
return owner !== null && fromIdentifier(owner)
|
|
188
|
+
}
|
|
189
|
+
|
|
180
190
|
/**
|
|
181
191
|
* A value re-bound from Tamagui: an alias (`const Sheet = SheetRaw as ...`), a
|
|
182
192
|
* member of one, or the result of a Tamagui factory (`styled`,
|
|
@@ -206,5 +216,6 @@ export function createProvenance(): {
|
|
|
206
216
|
return fromIdentifier(tag)
|
|
207
217
|
},
|
|
208
218
|
isTamaguiStyledCall,
|
|
219
|
+
isTamaguiStyleCall,
|
|
209
220
|
}
|
|
210
221
|
}
|