@tamagui/codemod-flat-values 3.0.0-beta.889.1 → 3.0.0-beta.901.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/dist/builtInNames.mjs +2 -1
- package/dist/builtInNames.mjs.map +1 -1
- package/dist/convert.mjs +98 -23
- package/dist/convert.mjs.map +1 -1
- package/dist/grammar.mjs +6 -1
- package/dist/grammar.mjs.map +1 -1
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/report.mjs +22 -6
- package/dist/report.mjs.map +1 -1
- package/dist/sheetAnatomy.mjs +200 -0
- package/dist/sheetAnatomy.mjs.map +1 -0
- package/package.json +6 -6
- package/src/builtInNames.ts +2 -0
- package/src/convert.ts +149 -45
- package/src/grammar.ts +10 -0
- package/src/index.ts +32 -1
- package/src/report.ts +46 -18
- package/src/sheetAnatomy.ts +277 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// v3 splits `Sheet.Frame` into `Sheet.Container` (layout) and `Sheet.Background`
|
|
2
|
+
// (surface). Every Frame becomes a Container whose first child is a Background
|
|
3
|
+
// carrying the surface props, so a migrated sheet keeps the surface v2 gave it.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Node,
|
|
7
|
+
SyntaxKind,
|
|
8
|
+
type JsxAttribute,
|
|
9
|
+
type JsxElement,
|
|
10
|
+
type JsxOpeningElement,
|
|
11
|
+
type JsxSelfClosingElement,
|
|
12
|
+
type SourceFile,
|
|
13
|
+
} from 'ts-morph'
|
|
14
|
+
import type { Flag } from './convert'
|
|
15
|
+
import type { createProvenance } from './provenance'
|
|
16
|
+
|
|
17
|
+
export interface SheetFrameReport {
|
|
18
|
+
label: string
|
|
19
|
+
line: number
|
|
20
|
+
before: string
|
|
21
|
+
after: string
|
|
22
|
+
flags: Flag[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const surfaceProps = new Set([
|
|
26
|
+
'bg',
|
|
27
|
+
'background',
|
|
28
|
+
'backgroundColor',
|
|
29
|
+
'backgroundImage',
|
|
30
|
+
'borderColor',
|
|
31
|
+
'borderRadius',
|
|
32
|
+
'borderStyle',
|
|
33
|
+
'borderWidth',
|
|
34
|
+
'boxShadow',
|
|
35
|
+
'disableHideBottomOverflow',
|
|
36
|
+
'elevate',
|
|
37
|
+
'elevation',
|
|
38
|
+
'elevationAndroid',
|
|
39
|
+
'outlineColor',
|
|
40
|
+
'outlineOffset',
|
|
41
|
+
'outlineStyle',
|
|
42
|
+
'outlineWidth',
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
function isSurfaceProp(name: string): boolean {
|
|
46
|
+
return (
|
|
47
|
+
surfaceProps.has(name) ||
|
|
48
|
+
/^border[A-Z].*(Color|Radius|Style|Width)$/.test(name) ||
|
|
49
|
+
/^shadow[A-Z]/.test(name)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface Edit {
|
|
54
|
+
start: number
|
|
55
|
+
end: number
|
|
56
|
+
text: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyEdits(source: string, base: number, edits: readonly Edit[]): string {
|
|
60
|
+
let text = source
|
|
61
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
62
|
+
text = `${text.slice(0, edit.start - base)}${edit.text}${text.slice(edit.end - base)}`
|
|
63
|
+
}
|
|
64
|
+
return text
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `Sheet.Frame`, `Dialog.Sheet.Frame`: the sheet expression and the part name */
|
|
68
|
+
function sheetPart(node: Node): { sheet: string; part: Node; partName: string } | null {
|
|
69
|
+
if (!Node.isPropertyAccessExpression(node)) return null
|
|
70
|
+
const owner = node.getExpression()
|
|
71
|
+
const ownerName = Node.isPropertyAccessExpression(owner)
|
|
72
|
+
? owner.getName()
|
|
73
|
+
: Node.isIdentifier(owner)
|
|
74
|
+
? owner.getText()
|
|
75
|
+
: null
|
|
76
|
+
if (ownerName !== 'Sheet') return null
|
|
77
|
+
return { sheet: owner.getText(), part: node.getNameNode(), partName: node.getName() }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function frameOpening(
|
|
81
|
+
opening: JsxOpeningElement | JsxSelfClosingElement,
|
|
82
|
+
provenance: ReturnType<typeof createProvenance>
|
|
83
|
+
): { sheet: string; part: Node } | null {
|
|
84
|
+
const found = sheetPart(opening.getTagNameNode())
|
|
85
|
+
if (!found || found.partName !== 'Frame') return null
|
|
86
|
+
if (!provenance.isTamaguiElement(opening)) return null
|
|
87
|
+
return found
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function attributeName(attribute: JsxAttribute): string | null {
|
|
91
|
+
const name = attribute.getNameNode()
|
|
92
|
+
return Node.isIdentifier(name) ? name.getText() : null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function rewriteFrame(
|
|
96
|
+
opening: JsxOpeningElement | JsxSelfClosingElement,
|
|
97
|
+
sheet: string,
|
|
98
|
+
part: Node
|
|
99
|
+
): { after: string; start: number; end: number; flags: Flag[] } {
|
|
100
|
+
const element = Node.isJsxOpeningElement(opening)
|
|
101
|
+
? opening.getParentIfKindOrThrow(SyntaxKind.JsxElement)
|
|
102
|
+
: opening
|
|
103
|
+
const start = element.getStart()
|
|
104
|
+
const end = element.getEnd()
|
|
105
|
+
const source = element.getText()
|
|
106
|
+
const edits: Edit[] = [
|
|
107
|
+
{ start: part.getStart(), end: part.getEnd(), text: 'Container' },
|
|
108
|
+
]
|
|
109
|
+
const flags: Flag[] = []
|
|
110
|
+
|
|
111
|
+
const moved: string[] = []
|
|
112
|
+
let previousEnd = opening.getTagNameNode().getEnd()
|
|
113
|
+
for (const attribute of opening.getAttributes()) {
|
|
114
|
+
if (Node.isJsxSpreadAttribute(attribute)) {
|
|
115
|
+
flags.push({
|
|
116
|
+
code: 'sheet-frame-spread',
|
|
117
|
+
detail: `${attribute.getText()} stays on ${sheet}.Container; move any surface props it carries onto ${sheet}.Background by hand`,
|
|
118
|
+
})
|
|
119
|
+
} else {
|
|
120
|
+
const name = attributeName(attribute)
|
|
121
|
+
if (name !== null && isSurfaceProp(name)) {
|
|
122
|
+
moved.push(attribute.getText())
|
|
123
|
+
edits.push({ start: previousEnd, end: attribute.getEnd(), text: '' })
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
previousEnd = attribute.getEnd()
|
|
127
|
+
}
|
|
128
|
+
const background = `<${sheet}.Background${moved.length ? ` ${moved.join(' ')}` : ''} />`
|
|
129
|
+
|
|
130
|
+
if (Node.isJsxSelfClosingElement(element)) {
|
|
131
|
+
// `<Sheet.Frame />` closes over nothing, so the Background is its only child
|
|
132
|
+
edits.push({
|
|
133
|
+
start: element.getEnd() - 2,
|
|
134
|
+
end: element.getEnd(),
|
|
135
|
+
text: `>${background}</${sheet}.Container>`,
|
|
136
|
+
})
|
|
137
|
+
// a trailing space before `/>` would separate the last attribute from `>`
|
|
138
|
+
const beforeClose = source.slice(0, -2)
|
|
139
|
+
if (/\s$/.test(beforeClose)) {
|
|
140
|
+
edits.push({
|
|
141
|
+
start: element.getEnd() - 2 - (beforeClose.length - beforeClose.trimEnd().length),
|
|
142
|
+
end: element.getEnd() - 2,
|
|
143
|
+
text: '',
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
return { after: applyEdits(source, start, edits), start, end, flags }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const closingPart = sheetPart(element.getClosingElement().getTagNameNode())
|
|
150
|
+
if (closingPart) {
|
|
151
|
+
edits.push({
|
|
152
|
+
start: closingPart.part.getStart(),
|
|
153
|
+
end: closingPart.part.getEnd(),
|
|
154
|
+
text: 'Container',
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const firstChild = element
|
|
159
|
+
.getJsxChildren()
|
|
160
|
+
.find((child) => !Node.isJsxText(child) || child.getText().trim() !== '')
|
|
161
|
+
const existing =
|
|
162
|
+
firstChild &&
|
|
163
|
+
(Node.isJsxElement(firstChild) || Node.isJsxSelfClosingElement(firstChild))
|
|
164
|
+
? sheetPart(
|
|
165
|
+
(Node.isJsxElement(firstChild)
|
|
166
|
+
? firstChild.getOpeningElement()
|
|
167
|
+
: firstChild
|
|
168
|
+
).getTagNameNode()
|
|
169
|
+
)
|
|
170
|
+
: null
|
|
171
|
+
if (existing?.partName === 'Background') {
|
|
172
|
+
if (moved.length) {
|
|
173
|
+
const existingOpening = Node.isJsxElement(firstChild)
|
|
174
|
+
? firstChild.getOpeningElement()
|
|
175
|
+
: (firstChild as JsxSelfClosingElement)
|
|
176
|
+
const attributes = existingOpening.getAttributes()
|
|
177
|
+
const anchor = attributes.length
|
|
178
|
+
? attributes[attributes.length - 1]!.getEnd()
|
|
179
|
+
: existingOpening.getTagNameNode().getEnd()
|
|
180
|
+
edits.push({ start: anchor, end: anchor, text: ` ${moved.join(' ')}` })
|
|
181
|
+
}
|
|
182
|
+
return { after: applyEdits(source, start, edits), start, end, flags }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// a Frame written one child per line gets the Background on its own line at
|
|
186
|
+
// the children's indentation; an inline one stays inline
|
|
187
|
+
const afterOpening = source.slice(opening.getEnd() - start)
|
|
188
|
+
const childLine = /^(\r?\n[ \t]*)/.exec(afterOpening)
|
|
189
|
+
edits.push({
|
|
190
|
+
start: opening.getEnd(),
|
|
191
|
+
end: opening.getEnd(),
|
|
192
|
+
text: childLine ? `${childLine[1]}${background}` : background,
|
|
193
|
+
})
|
|
194
|
+
return { after: applyEdits(source, start, edits), start, end, flags }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Rewrites every provable `Sheet.Frame` element in the file. In write mode the
|
|
199
|
+
* source is edited in place, one element at a time: an edit forgets every node
|
|
200
|
+
* in the file, so each round re-reads the file for the next Frame.
|
|
201
|
+
*/
|
|
202
|
+
export function convertSheetFrames(
|
|
203
|
+
sourceFile: SourceFile,
|
|
204
|
+
provenance: ReturnType<typeof createProvenance>,
|
|
205
|
+
write: boolean
|
|
206
|
+
): SheetFrameReport[] {
|
|
207
|
+
const reports: SheetFrameReport[] = []
|
|
208
|
+
|
|
209
|
+
const openings = (): Array<{
|
|
210
|
+
opening: JsxOpeningElement | JsxSelfClosingElement
|
|
211
|
+
sheet: string
|
|
212
|
+
part: Node
|
|
213
|
+
}> =>
|
|
214
|
+
[
|
|
215
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),
|
|
216
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),
|
|
217
|
+
]
|
|
218
|
+
.sort((left, right) => left.getStart() - right.getStart())
|
|
219
|
+
.flatMap((opening) => {
|
|
220
|
+
const found = frameOpening(opening, provenance)
|
|
221
|
+
return found ? [{ opening, ...found }] : []
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
const record = (
|
|
225
|
+
opening: JsxOpeningElement | JsxSelfClosingElement,
|
|
226
|
+
sheet: string,
|
|
227
|
+
part: Node
|
|
228
|
+
) => {
|
|
229
|
+
const element = Node.isJsxOpeningElement(opening)
|
|
230
|
+
? opening.getParentIfKindOrThrow(SyntaxKind.JsxElement)
|
|
231
|
+
: opening
|
|
232
|
+
const before = element.getText()
|
|
233
|
+
const rewritten = rewriteFrame(opening, sheet, part)
|
|
234
|
+
reports.push({
|
|
235
|
+
label: `<${sheet}.Frame>`,
|
|
236
|
+
line: opening.getStartLineNumber(),
|
|
237
|
+
before,
|
|
238
|
+
after: rewritten.after,
|
|
239
|
+
flags: rewritten.flags,
|
|
240
|
+
})
|
|
241
|
+
return rewritten
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (write) {
|
|
245
|
+
for (let next = openings()[0]; next; next = openings()[0]) {
|
|
246
|
+
const rewritten = record(next.opening, next.sheet, next.part)
|
|
247
|
+
sourceFile.replaceText([rewritten.start, rewritten.end], rewritten.after)
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
for (const next of openings()) record(next.opening, next.sheet, next.part)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const call of sourceFile
|
|
254
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
255
|
+
.filter((call) => provenance.isTamaguiStyledCall(call))
|
|
256
|
+
.sort((left, right) => right.getStart() - left.getStart())) {
|
|
257
|
+
const target = call.getArguments()[0]
|
|
258
|
+
const found = target ? sheetPart(target) : null
|
|
259
|
+
if (!found || found.partName !== 'Frame') continue
|
|
260
|
+
const before = target!.getText()
|
|
261
|
+
reports.push({
|
|
262
|
+
label: `styled(${before}, …)`,
|
|
263
|
+
line: call.getStartLineNumber(),
|
|
264
|
+
before,
|
|
265
|
+
after: `${found.sheet}.Container`,
|
|
266
|
+
flags: [
|
|
267
|
+
{
|
|
268
|
+
code: 'sheet-frame-styled',
|
|
269
|
+
detail: `${before} is split into ${found.sheet}.Container and ${found.sheet}.Background; the styled target is now the Container, so move any surface styles in this config to a styled ${found.sheet}.Background`,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
})
|
|
273
|
+
if (write) found.part.replaceWithText('Container')
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return reports.sort((left, right) => left.line - right.line)
|
|
277
|
+
}
|