@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
package/package.json CHANGED
@@ -1,16 +1,44 @@
1
1
  {
2
2
  "name": "@tamagui/codemod-flat-values",
3
- "version": "0.0.0-bootstrap.0",
4
- "description": "Tamagui v3 package bootstrap",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/tamagui/tamagui.git"
8
- },
3
+ "version": "3.0.0-beta.1093.1",
9
4
  "license": "MIT",
5
+ "type": "module",
6
+ "bin": {
7
+ "tamagui-codemod-flat-values": "dist/index.mjs"
8
+ },
9
+ "source": "src/index.ts",
10
10
  "files": [
11
- "README.md"
11
+ "src",
12
+ "dist"
12
13
  ],
13
14
  "publishConfig": {
14
15
  "access": "public"
16
+ },
17
+ "scripts": {
18
+ "build": "tamagui-build --skip-native --skip-types",
19
+ "watch": "bun run build --watch",
20
+ "clean": "tamagui-build clean",
21
+ "clean:build": "tamagui-build clean:build",
22
+ "dry-run": "cd ../../.. && bun code/core/codemod-flat-values/src/index.ts --report code/core/codemod-flat-values/dry-run-report.md code/kitchen-sink/src/usecases code/ui/tamagui/src/components/Button.tsx",
23
+ "write": "cd ../../.. && bun code/core/codemod-flat-values/src/index.ts --write --report code/core/codemod-flat-values/dry-run-report.md code/kitchen-sink/src/usecases code/ui/tamagui/src/components/Button.tsx",
24
+ "test": "bun test --timeout 20000 test",
25
+ "test:web": "bun test --timeout 20000 test",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@tamagui/helpers": "3.0.0-beta.1093.1",
30
+ "@tamagui/language-service": "3.0.0-beta.1093.1",
31
+ "@tamagui/shorthands": "3.0.0-beta.1093.1",
32
+ "@tamagui/style-grammar": "3.0.0-beta.1093.1",
33
+ "ts-morph": "^28.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@tamagui/build": "3.0.0-beta.1093.1",
37
+ "typescript": "~6.0.3"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/tamagui/tamagui.git",
42
+ "directory": "code/core/codemod-flat-values"
15
43
  }
16
44
  }
@@ -0,0 +1,23 @@
1
+ import { v6ThemeNameReplacements } from '@tamagui/style-grammar/tooling'
2
+
3
+ export const v6CodemodBuiltInNameReplacements = {
4
+ ...v6ThemeNameReplacements,
5
+ // v3 removed backgroundActive after its component defaults had already
6
+ // stopped resolving; press is the corrected active-state default
7
+ backgroundActive: 'background-press',
8
+ // v3 configs no longer alias `true`; the default config pointed it at `4`
9
+ true: '4',
10
+ } as const
11
+
12
+ const builtInTokenPattern = new RegExp(
13
+ `\\$(${Object.keys(v6CodemodBuiltInNameReplacements).join('|')})(?![\\w-])`,
14
+ 'g'
15
+ )
16
+
17
+ export function replaceV6BuiltInTokens(value: string): string {
18
+ return value.replace(
19
+ builtInTokenPattern,
20
+ (_, name: keyof typeof v6CodemodBuiltInNameReplacements) =>
21
+ `$${v6CodemodBuiltInNameReplacements[name]}`
22
+ )
23
+ }
@@ -0,0 +1,227 @@
1
+ // V3 splits the group from the query container, so a legacy group condition that
2
+ // carries a container size (`$group-card-maxMd`) needs the element declaring the
3
+ // group to declare a query container too.
4
+ //
5
+ // Which element that is has to be proven, never inferred. Declaring a container
6
+ // changes containment and layout, so adding one to a `group="card"` in an
7
+ // unrelated tree is a behavior change with no v1 counterpart. Only two answers
8
+ // are allowed: the ancestor is visible in this file's JSX, or the site is flagged
9
+ // for a human.
10
+
11
+ import {
12
+ Node,
13
+ SyntaxKind,
14
+ type JsxAttribute,
15
+ type JsxOpeningElement,
16
+ type JsxSelfClosingElement,
17
+ type PropertyAssignment,
18
+ type SourceFile,
19
+ } from 'ts-morph'
20
+ import type { Flag } from './convert'
21
+ import type { ModifierRegistryView } from './grammar'
22
+ import { isLegacyConditionName, resolveLegacyName } from './legacyNames'
23
+
24
+ /** the element or styled config that declares `group` */
25
+ type Declaration = JsxAttribute | PropertyAssignment
26
+
27
+ export interface ContainerTarget {
28
+ /** the group name the declaration carries, or the empty string for the unnamed group */
29
+ group: string
30
+ /** a consumer named this group, so the container needs a name to match */
31
+ named: boolean
32
+ /** set when the render-tree relationship could not be proven from the source */
33
+ flag: Flag | null
34
+ }
35
+
36
+ export interface ContainerPlan {
37
+ /** keyed by the node declaring `group`, for the declarations that get a container */
38
+ targets: ReadonlyMap<Node, ContainerTarget>
39
+ /** keyed by the node holding the legacy container-size condition */
40
+ unresolved: ReadonlyMap<Node, Flag>
41
+ }
42
+
43
+ const emptyPlan: ContainerPlan = {
44
+ targets: new Map(),
45
+ unresolved: new Map(),
46
+ }
47
+
48
+ /** the group name a declaration carries, or null when it is not statically known */
49
+ function declaredGroup(declaration: Declaration): string | null {
50
+ if (Node.isJsxAttribute(declaration)) {
51
+ const initializer = declaration.getInitializer()
52
+ // a bare `group` prop declares the unnamed group
53
+ if (initializer === undefined) return ''
54
+ if (Node.isStringLiteral(initializer)) return initializer.getLiteralValue()
55
+ if (!Node.isJsxExpression(initializer)) return null
56
+ const expression = initializer.getExpression()
57
+ if (expression === undefined) return null
58
+ if (
59
+ Node.isStringLiteral(expression) ||
60
+ Node.isNoSubstitutionTemplateLiteral(expression)
61
+ ) {
62
+ return expression.getLiteralValue()
63
+ }
64
+ return expression.getKind() === SyntaxKind.TrueKeyword ? '' : null
65
+ }
66
+
67
+ const initializer = declaration.getInitializer()
68
+ if (initializer === undefined) return null
69
+ if (
70
+ Node.isStringLiteral(initializer) ||
71
+ Node.isNoSubstitutionTemplateLiteral(initializer)
72
+ ) {
73
+ return initializer.getLiteralValue()
74
+ }
75
+ return initializer.getKind() === SyntaxKind.TrueKeyword ? '' : null
76
+ }
77
+
78
+ function groupAttribute(
79
+ opening: JsxOpeningElement | JsxSelfClosingElement
80
+ ): JsxAttribute | null {
81
+ for (const attribute of opening.getAttributes()) {
82
+ if (!Node.isJsxAttribute(attribute)) continue
83
+ const name = attribute.getNameNode()
84
+ if (Node.isIdentifier(name) && name.getText() === 'group') return attribute
85
+ }
86
+ return null
87
+ }
88
+
89
+ /** an unnamed condition takes the nearest group; a named one takes its own */
90
+ function matches(consumerGroup: string, declaration: string | null): boolean {
91
+ if (declaration === null) return true
92
+ return consumerGroup === '' || consumerGroup === declaration
93
+ }
94
+
95
+ interface Consumer {
96
+ /** the JSX attribute or object property holding the legacy condition */
97
+ node: Declaration
98
+ group: string
99
+ }
100
+
101
+ function containerConsumers(
102
+ sourceFile: SourceFile,
103
+ registry: ModifierRegistryView
104
+ ): Consumer[] {
105
+ const consumers: Consumer[] = []
106
+ const add = (node: Declaration, name: string): void => {
107
+ if (!isLegacyConditionName(name)) return
108
+ const resolution = resolveLegacyName(name, registry)
109
+ if (!resolution.ok || resolution.resolved.container === null) return
110
+ consumers.push({ node, group: resolution.resolved.container.group ?? '' })
111
+ }
112
+
113
+ for (const attribute of sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute)) {
114
+ const name = attribute.getNameNode()
115
+ if (Node.isIdentifier(name)) add(attribute, name.getText())
116
+ }
117
+ for (const property of sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAssignment)) {
118
+ const name = property.getNameNode()
119
+ if (Node.isComputedPropertyName(name)) continue
120
+ add(property, name.getText().replace(/^['"]|['"]$/g, ''))
121
+ }
122
+ return consumers
123
+ }
124
+
125
+ function declarations(sourceFile: SourceFile): Declaration[] {
126
+ const found: Declaration[] = []
127
+ for (const attribute of sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute)) {
128
+ const name = attribute.getNameNode()
129
+ if (Node.isIdentifier(name) && name.getText() === 'group') found.push(attribute)
130
+ }
131
+ for (const property of sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAssignment)) {
132
+ const name = property.getNameNode()
133
+ if (Node.isComputedPropertyName(name)) continue
134
+ if (name.getText().replace(/^['"]|['"]$/g, '') === 'group') found.push(property)
135
+ }
136
+ return found
137
+ }
138
+
139
+ /**
140
+ * The nearest JSX ancestor declaring the group, `'ambiguous'` when an ancestor
141
+ * declares a group name this pass cannot read, or null when no ancestor declares
142
+ * one at all.
143
+ */
144
+ function provenAncestor(
145
+ consumer: Consumer
146
+ ): { declaration: JsxAttribute } | 'ambiguous' | null {
147
+ const owner = consumer.node.getFirstAncestor(
148
+ (node): node is JsxOpeningElement | JsxSelfClosingElement =>
149
+ Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node)
150
+ )
151
+ if (owner === undefined) return null
152
+
153
+ // an element's own group never applies to itself, so the walk starts above it
154
+ for (const ancestor of owner.getAncestors()) {
155
+ if (!Node.isJsxElement(ancestor)) continue
156
+ const declaration = groupAttribute(ancestor.getOpeningElement())
157
+ if (declaration === null) continue
158
+ const group = declaredGroup(declaration)
159
+ if (group === null) return 'ambiguous'
160
+ if (matches(consumer.group, group)) return { declaration }
161
+ }
162
+ return null
163
+ }
164
+
165
+ export function planContainers(
166
+ sourceFile: SourceFile,
167
+ registry: ModifierRegistryView
168
+ ): ContainerPlan {
169
+ const consumers = containerConsumers(sourceFile, registry)
170
+ if (!consumers.length) return emptyPlan
171
+
172
+ const targets = new Map<Node, ContainerTarget>()
173
+ const unresolved = new Map<Node, Flag>()
174
+ const all = declarations(sourceFile)
175
+
176
+ const target = (declaration: Declaration, consumer: Consumer, flag: Flag | null) => {
177
+ const existing = targets.get(declaration)
178
+ if (existing) {
179
+ existing.named ||= consumer.group !== ''
180
+ existing.flag ??= flag
181
+ return
182
+ }
183
+ targets.set(declaration, {
184
+ group: declaredGroup(declaration) ?? consumer.group,
185
+ named: consumer.group !== '',
186
+ flag,
187
+ })
188
+ }
189
+
190
+ for (const consumer of consumers) {
191
+ const label =
192
+ consumer.group === '' ? 'the nearest group' : `group "${consumer.group}"`
193
+ const proven = provenAncestor(consumer)
194
+ if (proven === 'ambiguous') {
195
+ unresolved.set(consumer.node, {
196
+ code: 'ambiguous-container-group',
197
+ detail: `a JSX ancestor declares a group name this pass cannot read, so the element that has to declare the container for ${label} is not provable; add "container" by hand`,
198
+ })
199
+ continue
200
+ }
201
+ if (proven !== null) {
202
+ target(proven.declaration, consumer, null)
203
+ continue
204
+ }
205
+
206
+ const candidates = all.filter((declaration) =>
207
+ matches(consumer.group, declaredGroup(declaration))
208
+ )
209
+ if (candidates.length === 1) {
210
+ target(candidates[0], consumer, {
211
+ code: 'unproven-container-group',
212
+ detail: `a legacy container-size condition targets ${label} and this is the only declaration of it in the file, but no JSX ancestry proves it wraps that element; confirm the container belongs here`,
213
+ })
214
+ continue
215
+ }
216
+ unresolved.set(consumer.node, {
217
+ code: candidates.length
218
+ ? 'ambiguous-container-group'
219
+ : 'container-group-not-declared',
220
+ detail: candidates.length
221
+ ? `${candidates.length} declarations of ${label} are in this file and no JSX ancestry picks one, so the element that has to declare the container is not provable; add "container" by hand`
222
+ : `${label} is not declared in this file, so the "@…" query this condition becomes has no container to match; add "container" to the element declaring the group`,
223
+ })
224
+ }
225
+
226
+ return { targets, unresolved }
227
+ }