@tamagui/codemod-flat-values 0.0.0-bootstrap.0 → 3.0.0-beta.1093.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.
Files changed (44) hide show
  1. package/README.md +234 -1
  2. package/dist/builtInNames.mjs +14 -0
  3. package/dist/builtInNames.mjs.map +1 -0
  4. package/dist/containers.mjs +141 -0
  5. package/dist/containers.mjs.map +1 -0
  6. package/dist/convert.mjs +1233 -0
  7. package/dist/convert.mjs.map +1 -0
  8. package/dist/expressions.mjs +188 -0
  9. package/dist/expressions.mjs.map +1 -0
  10. package/dist/functionalVariants.mjs +469 -0
  11. package/dist/functionalVariants.mjs.map +1 -0
  12. package/dist/grammar.mjs +73 -0
  13. package/dist/grammar.mjs.map +1 -0
  14. package/dist/index.mjs +374 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/legacyConditions.mjs +293 -0
  17. package/dist/legacyConditions.mjs.map +1 -0
  18. package/dist/legacyNames.mjs +130 -0
  19. package/dist/legacyNames.mjs.map +1 -0
  20. package/dist/provenance.mjs +137 -0
  21. package/dist/provenance.mjs.map +1 -0
  22. package/dist/report.mjs +188 -0
  23. package/dist/report.mjs.map +1 -0
  24. package/dist/sheetAnatomy.mjs +200 -0
  25. package/dist/sheetAnatomy.mjs.map +1 -0
  26. package/dist/structuredNative.mjs +275 -0
  27. package/dist/structuredNative.mjs.map +1 -0
  28. package/dist/transition.mjs +257 -0
  29. package/dist/transition.mjs.map +1 -0
  30. package/package.json +35 -7
  31. package/src/builtInNames.ts +23 -0
  32. package/src/containers.ts +227 -0
  33. package/src/convert.ts +1977 -0
  34. package/src/expressions.ts +220 -0
  35. package/src/functionalVariants.ts +642 -0
  36. package/src/grammar.ts +190 -0
  37. package/src/index.ts +589 -0
  38. package/src/legacyConditions.ts +357 -0
  39. package/src/legacyNames.ts +160 -0
  40. package/src/provenance.ts +210 -0
  41. package/src/report.ts +362 -0
  42. package/src/sheetAnatomy.ts +277 -0
  43. package/src/structuredNative.ts +459 -0
  44. package/src/transition.ts +415 -0
@@ -0,0 +1,220 @@
1
+ // Two different questions get asked about a dynamic style value, each answered
2
+ // exactly one way.
3
+ //
4
+ // 1. Can the expression be rewritten? Only a tree of literals can: its `$token`
5
+ // spellings are right there in the source, so `active ? '$red10' : '$blue10'`
6
+ // becomes `active ? 'red10' : 'blue10'`. `literalTree` answers this from the
7
+ // AST.
8
+ // 2. Can the expression be left alone? Only if its runtime type cannot carry a
9
+ // legacy `$token` string. `runtimeType` answers this from the type checker, so
10
+ // `const GREY = 'rgb(217, 215, 210)'` is provably safe while an untyped
11
+ // identifier is not.
12
+
13
+ import { Node, SyntaxKind, type Expression, type TemplateExpression } from 'ts-morph'
14
+ import { flatStringValue, type ModifierRegistryView } from './grammar'
15
+
16
+ export function unwrapExpression(expression: Expression): Expression {
17
+ let current = expression
18
+ while (
19
+ Node.isParenthesizedExpression(current) ||
20
+ Node.isAsExpression(current) ||
21
+ Node.isTypeAssertion(current) ||
22
+ Node.isNonNullExpression(current)
23
+ ) {
24
+ current = current.getExpression()
25
+ }
26
+ return current
27
+ }
28
+
29
+ export function numericValue(expression: Expression): number | null {
30
+ const current = unwrapExpression(expression)
31
+ if (Node.isNumericLiteral(current)) return Number(current.getText())
32
+ if (Node.isPrefixUnaryExpression(current)) {
33
+ const operand = current.getOperand()
34
+ if (!Node.isNumericLiteral(operand)) return null
35
+ const value = Number(operand.getText())
36
+ const operator = current.getOperatorToken()
37
+ if (operator === SyntaxKind.MinusToken) return -value
38
+ if (operator === SyntaxKind.PlusToken) return value
39
+ }
40
+ return null
41
+ }
42
+
43
+ /** the statically known value of a legacy condition object leaf */
44
+ export function staticLeafValue(
45
+ expression: Expression
46
+ ): { found: true; value: unknown } | null {
47
+ const current = unwrapExpression(expression)
48
+ if (Node.isStringLiteral(current) || Node.isNoSubstitutionTemplateLiteral(current)) {
49
+ return { found: true, value: current.getLiteralValue() }
50
+ }
51
+ const number = numericValue(current)
52
+ if (number !== null) return { found: true, value: number }
53
+ if (current.getKind() === SyntaxKind.TrueKeyword) return { found: true, value: true }
54
+ if (current.getKind() === SyntaxKind.FalseKeyword) return { found: true, value: false }
55
+ if (current.getKind() === SyntaxKind.NullKeyword) return { found: true, value: null }
56
+ return null
57
+ }
58
+
59
+ export type LiteralKind = 'number' | 'string' | 'nullish'
60
+
61
+ export interface TreeError {
62
+ code: string
63
+ message: string
64
+ }
65
+
66
+ export interface LiteralTree {
67
+ kind: LiteralKind
68
+ /** the expression source with legacy token spellings rewritten */
69
+ text: string
70
+ /** original string literal chunks, for migrations that need authored names */
71
+ strings: readonly string[]
72
+ /** set when a token spelling in the tree has no flat name */
73
+ error: TreeError | null
74
+ }
75
+
76
+ /**
77
+ * One literal chunk's flat spelling. A chunk with no `$` is already flat, and one
78
+ * that has a `$` goes through the shared converter, so the same value refused as
79
+ * a clause payload (`url($asset)`) is refused here instead of being rewritten.
80
+ */
81
+ function literalText(
82
+ value: string,
83
+ registry: ModifierRegistryView
84
+ ): { text: string; error: TreeError | null } {
85
+ if (!value.includes('$')) return { text: value, error: null }
86
+ const flat = flatStringValue(value, registry)
87
+ if (flat.text === null) {
88
+ return {
89
+ text: value,
90
+ error: {
91
+ code: flat.error?.code ?? 'unsupported-legacy-value',
92
+ message: flat.error?.message ?? `"${value}" has no flat spelling`,
93
+ },
94
+ }
95
+ }
96
+ return { text: flat.text, error: null }
97
+ }
98
+
99
+ function templateTree(
100
+ template: TemplateExpression,
101
+ registry: ModifierRegistryView
102
+ ): LiteralTree {
103
+ // every literal chunk converts on its own: `\`$accent${i}\`` names one token
104
+ // whose tail is computed, and a chunk holding quoted or url() content is refused
105
+ const head = literalText(template.getHead().getLiteralText(), registry)
106
+ let error = head.error
107
+ let text = `\`${head.text}`
108
+ const strings = [template.getHead().getLiteralText()]
109
+ for (const span of template.getTemplateSpans()) {
110
+ const literal = span.getLiteral().getLiteralText()
111
+ const tail = literalText(literal, registry)
112
+ error ??= tail.error
113
+ strings.push(literal)
114
+ text += `\${${span.getExpression().getText().trim()}}${tail.text}`
115
+ }
116
+ return { kind: 'string', text: `${text}\``, strings, error }
117
+ }
118
+
119
+ /**
120
+ * The expression as a tree of literals, or null when any leaf is computed. Every
121
+ * literal is reprinted, which is what strips `$` from token spellings.
122
+ */
123
+ export function literalTree(
124
+ expression: Expression,
125
+ registry: ModifierRegistryView
126
+ ): LiteralTree | null {
127
+ const current = unwrapExpression(expression)
128
+
129
+ if (Node.isStringLiteral(current) || Node.isNoSubstitutionTemplateLiteral(current)) {
130
+ const value = current.getLiteralValue()
131
+ const literal = literalText(value, registry)
132
+ return {
133
+ kind: 'string',
134
+ text: JSON.stringify(literal.text),
135
+ strings: [value],
136
+ error: literal.error,
137
+ }
138
+ }
139
+ if (Node.isTemplateExpression(current)) return templateTree(current, registry)
140
+
141
+ const number = numericValue(current)
142
+ if (number !== null) {
143
+ return { kind: 'number', text: String(number), strings: [], error: null }
144
+ }
145
+
146
+ if (
147
+ current.getKind() === SyntaxKind.UndefinedKeyword ||
148
+ current.getKind() === SyntaxKind.NullKeyword ||
149
+ (Node.isIdentifier(current) && current.getText() === 'undefined')
150
+ ) {
151
+ return { kind: 'nullish', text: current.getText(), strings: [], error: null }
152
+ }
153
+
154
+ if (Node.isConditionalExpression(current)) {
155
+ const whenTrue = literalTree(current.getWhenTrue(), registry)
156
+ const whenFalse = literalTree(current.getWhenFalse(), registry)
157
+ if (!whenTrue || !whenFalse) return null
158
+ if (
159
+ whenTrue.kind !== whenFalse.kind &&
160
+ whenTrue.kind !== 'nullish' &&
161
+ whenFalse.kind !== 'nullish'
162
+ ) {
163
+ return null
164
+ }
165
+ return {
166
+ kind: whenTrue.kind === 'nullish' ? whenFalse.kind : whenTrue.kind,
167
+ text: `${current.getCondition().getText().trim()} ? ${whenTrue.text} : ${whenFalse.text}`,
168
+ strings: [...whenTrue.strings, ...whenFalse.strings],
169
+ error: whenTrue.error ?? whenFalse.error,
170
+ }
171
+ }
172
+
173
+ return null
174
+ }
175
+
176
+ export interface RuntimeType {
177
+ kind: 'number' | 'string' | 'unknown'
178
+ /** every string literal the type can be, or null when the set is open */
179
+ literals: readonly string[] | null
180
+ }
181
+
182
+ /** what the type checker can prove the expression evaluates to */
183
+ export function runtimeType(expression: Expression): RuntimeType {
184
+ const type = unwrapExpression(expression).getType()
185
+ const parts = type.isUnion() ? type.getUnionTypes() : [type]
186
+ const literals: string[] = []
187
+ let numbers = 0
188
+ let strings = 0
189
+ let known = 0
190
+
191
+ for (const part of parts) {
192
+ if (part.isUndefined() || part.isNull()) continue
193
+ known++
194
+ if (part.isNumber() || part.isNumberLiteral()) {
195
+ numbers++
196
+ continue
197
+ }
198
+ if (part.isStringLiteral()) {
199
+ strings++
200
+ literals.push(String(part.getLiteralValue()))
201
+ continue
202
+ }
203
+ if (part.isString()) {
204
+ strings++
205
+ continue
206
+ }
207
+ return { kind: 'unknown', literals: null }
208
+ }
209
+
210
+ if (!known) return { kind: 'unknown', literals: null }
211
+ if (numbers === known) return { kind: 'number', literals: null }
212
+ if (strings === known) {
213
+ return { kind: 'string', literals: literals.length === known ? literals : null }
214
+ }
215
+ return { kind: 'unknown', literals: null }
216
+ }
217
+
218
+ export function compact(text: string): string {
219
+ return text.replace(/\s+/g, ' ').trim()
220
+ }