@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/README.md CHANGED
@@ -1 +1,234 @@
1
- # @tamagui/codemod-flat-values
1
+ # Flat-values codemod
2
+
3
+ Converts existing Tamagui style syntax to V3 flat property values. Dry-run mode
4
+ reports every conversion; `--write` applies every flat-eligible rewrite in one
5
+ transaction and records host or target assessments for manual review.
6
+
7
+ Run it from the root of the project you are migrating. Paths, the report and the
8
+ `tsconfig.json` it reads the type information from all resolve from there.
9
+
10
+ ```sh
11
+ npx @tamagui/codemod-flat-values ./src # dry run
12
+ npx @tamagui/codemod-flat-values --json report.json ./src # machine readable
13
+ npx @tamagui/codemod-flat-values --write ./src # apply safe conversions
14
+ npx @tamagui/codemod-flat-values --help
15
+ ```
16
+
17
+ Inside this repository the same two runs over the pinned corpus are
18
+ `bun run dry-run` and `bun run write` from `code/core/codemod-flat-values`. That
19
+ corpus is `code/kitchen-sink/src/usecases` plus the canonical `Button.tsx` skin,
20
+ and its acceptance test asserts the migrated corpus has no V1 conversion sites.
21
+ Focused fixtures still parse every emitted program back through
22
+ `@tamagui/style-grammar`.
23
+
24
+ A run with no positional argument exits 2, and so does a positional argument that
25
+ matches no file, so a typo in a migration path can never read as a clean corpus.
26
+
27
+ A directory containing `.tamagui-flat-values-ignore` is excluded recursively from
28
+ both dry-run and write mode. Use the marker only for intentionally pinned legacy
29
+ fixtures, such as the V2 side of a version comparison; application source should be
30
+ migrated rather than excluded.
31
+
32
+ ## What it converts
33
+
34
+ A conversion site is one style object: a JSX attribute list, a `styled()` config, or
35
+ a single variant branch. Everything comes from `@tamagui/style-grammar` — the value
36
+ parser, the legacy condition converter, the clause merge, and the property/unit
37
+ tables — so a converted program is spelled exactly the way the runtime and the
38
+ compiler read it back.
39
+
40
+ Only Tamagui bindings convert. A JSX tag or a `styled` callee has to resolve, through
41
+ module specifiers, to an import from `tamagui`/`@tamagui/*`, a re-export chain ending
42
+ in one, or a value built by a Tamagui factory. Emotion's `styled`, a local `styled`
43
+ helper, a `react-native` component, and an intrinsic tag are all left alone —
44
+ `hoverStyle` and `$token` mean nothing to them.
45
+
46
+ - Condition objects become clauses on the longhand's program:
47
+ `bg="$surface" hoverStyle={{ bg: '$surfaceHover' }}` becomes
48
+ `bg="surface hover:surfaceHover"`.
49
+ - v1 platform conditions (`$web`, `$ios`, …) become platform modifiers, and v1
50
+ camelCase media keys resolve to their V6 kebab-case names. The V6 built-in theme
51
+ names use one explicit rename table, so `$backgroundHover` becomes
52
+ `background-hover`. User-defined names keep their authored spelling.
53
+ `$backgroundActive` is the one non-mechanical migration: v3 removed it, so it
54
+ becomes `background-press`, matching the corrected active-state component defaults.
55
+ - Half-step size tokens are renamed, not dropped. V3 spells a numeric dot-path with a
56
+ dash, so `$0.5` becomes `0-5`, `$1.5` becomes `1-5`, and so on. The values are
57
+ unchanged across the two packs (`0.5`/`0-5` is 1, `1.5`/`1-5` is 4, `2.5`/`2-5` is
58
+ 10, `3.5`/`3-5` is 16), so this is a spelling migration with no visual effect. It is
59
+ worth converting rather than flagging because it is the single largest flag category
60
+ on real corpora: on a mid-size app it moved 1007 clean sites to 1212 and dropped
61
+ flagged sites from 279 to 72. A dot-path that is not numeric is a rename nobody can
62
+ derive, so it stays flagged.
63
+ - Group conditions split the way V3 splits them: `$group-card-hover` becomes
64
+ `group-hover/card:`, and a container size (`$group-card-maxMd`) becomes
65
+ `@max-md/card:` plus `container="card"` on the element that declares
66
+ the group. That element has to be proven, never inferred: a JSX ancestor declaring
67
+ the group takes the container silently, a single declaration elsewhere in the file
68
+ takes it under `unproven-container-group`, and anything else stays authored
69
+ (`ambiguous-container-group`, `container-group-not-declared`). Declaring a
70
+ container changes containment, so it never lands on a `group` this pass cannot tie
71
+ to the condition.
72
+ - A base value only folds into a program when a clause would otherwise need a second
73
+ attribute of the same name. `opacity={0.5}` plus `enterStyle={{ opacity: 0 }}`
74
+ becomes `opacity="0.5 enter:0"`, while a numeric or dynamic value nothing conditions
75
+ on is left exactly as authored.
76
+ - A dynamic value converts when its units and token spellings are provable: a tree of
77
+ literals is rewritten in place (`active ? '$red10' : '$blue10'`), and an expression
78
+ whose type the checker can prove interpolates into the program
79
+ (`` backgroundColor={`${GREEN} disabled:${GREY}`} ``).
80
+ - Variant branches convert as their own style objects, so a
81
+ branch contributes at its own position in the forward pass. No base is invented for
82
+ a condition-only branch: with clause-level merge (decision 21)
83
+ `exitStyle={{ x: 10 }}` is exactly `x="exit:10px"`, which keeps whatever base the
84
+ styled component, variant, or call site defined — the same thing the v1 prop did.
85
+ - V2 spread variant keys become typed `styled.dynamic` callbacks for the size,
86
+ space, color, radius, font-size, and z-index token categories. Type keys become
87
+ one `number | string | boolean` dynamic, with `typeof` branches when each body
88
+ is one object-literal return. The report leaves catch-all keys, mixed exact and
89
+ function branches, and sibling-prop reads authored.
90
+
91
+ Every program the report suggests is parsed back with the real value parser and merged
92
+ with the real clause merge before it is printed. A program that does not read back
93
+ identically is reported (`emitted-program-mismatch`, `emitted-value-invalid`) instead
94
+ of suggested.
95
+
96
+ The codemod preserves palette-step names such as `blue10` and `red10`. It does
97
+ not evaluate the application's runtime config, so it cannot know whether a
98
+ custom config still defines them or which absolute shade preserves the design.
99
+ Every preserved v5 palette name appears as a non-blocking
100
+ `legacy-palette-token` configuration warning in the Markdown and JSON reports.
101
+ Write mode still applies the safe syntax conversion, so resolve every warning
102
+ before running the converted application. When moving to `@tamagui/config/v6`,
103
+ migrate each name manually to an absolute token such as `blue-500`, or enter a
104
+ color theme and use its adaptive `colorN` ramp. The v6 defaults do not define
105
+ the old palette-step names, and a missing color is dropped without a runtime
106
+ warning.
107
+
108
+ ## Conversion assessment
109
+
110
+ Syntax alone cannot make a conversion safe. Before suggesting a program, the codemod
111
+ asks the shared conversion contract whether the property can carry clauses, whether
112
+ every clause evaluates on every target implied by the file name, and whether the
113
+ component's TypeScript prop type accepts the style.
114
+
115
+ - **clean** means all three checks passed. A shared `.tsx` file must work on web and
116
+ native; `.web.tsx` and `.native.tsx` require only their named target.
117
+ - **needs-relocation** means the syntax is valid but the authored target or host cannot
118
+ evaluate it. Write mode still removes the V1 syntax because V3 has no legacy runtime;
119
+ the report prints the required hand edit. For example, a component-tier web state in
120
+ a shared file moves to `.web.tsx`, while text-only styles on a View move to Text or
121
+ `html.*`.
122
+ - **unknown-host** means the component is provably Tamagui but its TypeScript type does
123
+ not expose enough component identity and style-prop information to verify the host.
124
+ Write mode converts the site and leaves the host check in the report for review.
125
+ - **ineligible** means the property has no flat clause spelling. Transform and React
126
+ Native shadow part props stay authored, with a remedy that points to the `transform`,
127
+ `boxShadow`, or `textShadow` composite.
128
+
129
+ The type-aware host lookup is shared with `@tamagui/language-service`; the codemod does
130
+ not import or evaluate an app's runtime config. Host validity is a question about the
131
+ property rather than its spelling, so a host that types `rounded` accepts `borderRadius`
132
+ too: `onlyAllowShorthands: true` omits every longhand a shorthand covers from the
133
+ component's prop type, and the conversion resolves an authored shorthand to its longhand
134
+ before it asks.
135
+
136
+ Ordering is never traded for a bigger diff. A program merges only when every
137
+ contribution still beats and loses to the same things it did, so an opaque spread or
138
+ an unconverted condition object between two contributions leaves the later ones
139
+ authored (`condition-order-not-preservable`, `base-order-not-preservable`). Spreading
140
+ an object literal writes its members out, and a nested spread or an unreadable key
141
+ inside it stays exactly where it was authored and orders the merge like any other
142
+ spread. An unconvertible nested condition counts every longhand under it, at any
143
+ depth, as something its member can still set.
144
+
145
+ ## Apply mode
146
+
147
+ Clause-free tokens now become base-only programs: `p="$4"` becomes `p="4"` and
148
+ resolves config-first through the same flat engine as a value with modifiers.
149
+ For `x` and `y`, this also applies the v3 category cutover: the legacy `$4`
150
+ resolved through the size scale, while flat `4` resolves through the space scale.
151
+ If a custom config gives those scales different values, the rendered offset changes
152
+ by design and those rows need review during migration.
153
+
154
+ All legacy runtime gates are closed. Apply mode is the migration path; there is no
155
+ runtime compatibility setting or fallback parser.
156
+
157
+ ## Structured native values
158
+
159
+ Unconditional React Native objects and arrays keep their authored representation and
160
+ do not appear as migration work. When a condition needs the same property in one
161
+ string program, the codemod converts only the structures with an explicit,
162
+ round-trippable CSS spelling:
163
+
164
+ - a static `transform` array becomes an ordered CSS transform function list;
165
+ - a static `fontVariant` string array becomes a space-separated token list;
166
+ - one static React Native `linear-gradient` object becomes `linear-gradient(...)`.
167
+
168
+ Dynamic and Animated transform entries stay authored under
169
+ `structured-transform-dynamic`. Matrix arrays stay authored under
170
+ `structured-transform-matrix`, because React Native's matrix array and portable CSS
171
+ matrix forms do not have the same shape. A dynamic gradient gets
172
+ `structured-background-image-dynamic`; unsupported structured properties use a
173
+ property-specific `structured-<property>` code.
174
+
175
+ Plain `shadowOffset` and `textShadowOffset` objects also stay natural and are not
176
+ inventory. If a condition targets one, the shared eligibility contract reports it as
177
+ ineligible and points to the complete `boxShadow` or `textShadow` composite. The
178
+ codemod never guesses the missing color, radius, opacity, or sibling offset.
179
+
180
+ Transition arrays, per-property objects, and dynamic references remain authored
181
+ because they are supported animation-driver configuration, not flat-value
182
+ migration work. An open string type that could still hold a `$token` at runtime
183
+ is listed separately as `dynamic-string-value`, which only a human can confirm.
184
+
185
+ ## Flag codes
186
+
187
+ A flag means a human decides. Every code the tool can emit:
188
+
189
+ | code | meaning |
190
+ | --- | --- |
191
+ | `legacy-token-dot-path` | `$1.5` and friends need one flat token name |
192
+ | `legacy-numeric-composite-token` | a numeric token embedded in a composite needs its resolved CSS value |
193
+ | `legacy-token-constant` | the token lives in a module constant (`const RADIUS = '$6'`), so the constant is what migrates |
194
+ | `unproven-container-group`, `ambiguous-container-group`, `container-group-not-declared` | the element that has to declare the query container is not provable from this file |
195
+ | `value-reparses-as-program` | the converted string would read back as something other than one base value |
196
+ | `legacy-group-presence` | `$group-card` with no state or size styles every descendant unconditionally and has no flat spelling |
197
+ | `unknown-legacy-condition` | the condition name is not a registered spelling (a media key missing from the V6 defaults, for one) |
198
+ | `unregistered-legacy-condition` | a registered shape whose parameter is not registered |
199
+ | `non-style-condition-entry` | a conditional non-style prop (`numberOfLines`) has no flat target |
200
+ | `dynamic-legacy-condition` | the condition object is built at runtime (spread, computed key, non-literal) |
201
+ | `legacy-condition-in-spread` | a spread the conversion cannot open (`{...(wide && { $sm: … })}`) holds v1 condition keys, named in the flag |
202
+ | `unprovable-dynamic-value` | a condition needs the base value, whose type cannot be proven |
203
+ | `dynamic-condition-value` | a value inside the condition object is not statically known |
204
+ | `non-css-style-value` | a condition needs a base that is `true`, `false`, or `null` |
205
+ | `empty-style-value` | a condition needs a base that is always nullish |
206
+ | `structured-transform-*` | a transform array is dynamic or has no faithful static function-list spelling; matrix arrays are intentionally included |
207
+ | `structured-font-variant-*`, `structured-background-image-*` | a font-variant or gradient structure is dynamic, empty, layered, or unsupported |
208
+ | `structured-<property>` | a condition needs an object or array with no verified CSS-shaped migration rule |
209
+ | `condition-order-not-preservable`, `base-order-not-preservable` | merging would move a value past something that can also set it |
210
+ | `computed-property` | a computed key hides the affected style property |
211
+ | `functional-variant-needs-resolve` | the callback reads `extras.props`; use the generated `.resolve` draft |
212
+ | `functional-variant-mixed` | exact branches and a function key share one variant |
213
+ | `functional-variant-catch-all` | `'...'` needs an explicit value type chosen by the app |
214
+ | `functional-variant-type-bodies` | different type-key bodies cannot be combined into safe `typeof` branches |
215
+ | `functional-variant-unsupported`, `functional-variant-unsupported-extras`, `functional-variant-styled-import` | the callback, env access, or `styled` import does not have a provable automatic rewrite |
216
+ | `emitted-program-mismatch`, `emitted-value-invalid` | the printer failed its own re-parse; this is a codemod bug |
217
+ | plus any code from the shared converter | `unsupported-legacy-value`, `legacy-condition-object`, `ambiguous-legacy-group`, `legacy-composite-shorthand` |
218
+
219
+ ## Configuration warning codes
220
+
221
+ | code | meaning |
222
+ | --- | --- |
223
+ | `legacy-palette-token` | the conversion preserves a v5 palette-step name that the v6 defaults do not define; choose an absolute palette token or an adaptive `colorN` value |
224
+
225
+ `unsupported-legacy-value` covers the token-context refusals: a `$` mixed with quoted
226
+ or unquoted `url()` content is literal CSS the resolver never reads as a token
227
+ candidate, so the value is reported rather than rewritten. Base values and clause
228
+ payloads go through the same shared converter, so they always agree on that.
229
+
230
+ ## Remaining manual migration
231
+
232
+ The report counts the files whose sites all converted: those have no legacy condition
233
+ object or flagged functional variant left. V3 has no compatibility setting, so the
234
+ report names every file that still needs a hand edit after `--write`.
@@ -0,0 +1,14 @@
1
+ import { v6ThemeNameReplacements } from "@tamagui/style-grammar/tooling";
2
+
3
+ const v6CodemodBuiltInNameReplacements = {
4
+ ...v6ThemeNameReplacements,
5
+ backgroundActive: "background-press",
6
+ true: "4"
7
+ };
8
+ const builtInTokenPattern = new RegExp(`\\$(${Object.keys(v6CodemodBuiltInNameReplacements).join("|")})(?![\\w-])`, "g");
9
+ function replaceV6BuiltInTokens(value) {
10
+ return value.replace(builtInTokenPattern, (_, name) => `$${v6CodemodBuiltInNameReplacements[name]}`);
11
+ }
12
+
13
+ export { replaceV6BuiltInTokens, v6CodemodBuiltInNameReplacements };
14
+ //# sourceMappingURL=builtInNames.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtInNames.js","names":[],"sources":["builtInNames.js"],"sourcesContent":["import { v6ThemeNameReplacements } from \"@tamagui/style-grammar/tooling\";\nconst v6CodemodBuiltInNameReplacements = {\n ...v6ThemeNameReplacements,\n // v3 removed backgroundActive after its component defaults had already\n // stopped resolving; press is the corrected active-state default\n backgroundActive: \"background-press\",\n // v3 configs no longer alias `true`; the default config pointed it at `4`\n true: \"4\"\n};\nconst builtInTokenPattern = new RegExp(\n `\\\\$(${Object.keys(v6CodemodBuiltInNameReplacements).join(\"|\")})(?![\\\\w-])`,\n \"g\"\n);\nfunction replaceV6BuiltInTokens(value) {\n return value.replace(\n builtInTokenPattern,\n (_, name) => `$${v6CodemodBuiltInNameReplacements[name]}`\n );\n}\nexport {\n replaceV6BuiltInTokens,\n v6CodemodBuiltInNameReplacements\n};\n//# sourceMappingURL=builtInNames.js.map\n"],"mappings":";;;AACA,MAAM,mCAAmC;CACvC,GAAG;CAGH,kBAAkB;CAElB,MAAM;AACR;AACA,MAAM,sBAAsB,IAAI,OAC9B,OAAO,OAAO,KAAK,gCAAgC,CAAC,CAAC,KAAK,GAAG,EAAE,cAC/D,GACF;AACA,SAAS,uBAAuB,OAAO;CACrC,OAAO,MAAM,QACX,sBACC,GAAG,SAAS,IAAI,iCAAiC,OACpD;AACF"}
@@ -0,0 +1,141 @@
1
+ import { Node, SyntaxKind } from "ts-morph";
2
+ import { isLegacyConditionName, resolveLegacyName } from "./legacyNames.mjs";
3
+
4
+ const emptyPlan = {
5
+ targets: /* @__PURE__ */ new Map(),
6
+ unresolved: /* @__PURE__ */ new Map()
7
+ };
8
+ function declaredGroup(declaration) {
9
+ if (Node.isJsxAttribute(declaration)) {
10
+ const initializer2 = declaration.getInitializer();
11
+ if (initializer2 === void 0) return "";
12
+ if (Node.isStringLiteral(initializer2)) return initializer2.getLiteralValue();
13
+ if (!Node.isJsxExpression(initializer2)) return null;
14
+ const expression = initializer2.getExpression();
15
+ if (expression === void 0) return null;
16
+ if (Node.isStringLiteral(expression) || Node.isNoSubstitutionTemplateLiteral(expression)) {
17
+ return expression.getLiteralValue();
18
+ }
19
+ return expression.getKind() === SyntaxKind.TrueKeyword ? "" : null;
20
+ }
21
+ const initializer = declaration.getInitializer();
22
+ if (initializer === void 0) return null;
23
+ if (Node.isStringLiteral(initializer) || Node.isNoSubstitutionTemplateLiteral(initializer)) {
24
+ return initializer.getLiteralValue();
25
+ }
26
+ return initializer.getKind() === SyntaxKind.TrueKeyword ? "" : null;
27
+ }
28
+ function groupAttribute(opening) {
29
+ for (const attribute of opening.getAttributes()) {
30
+ if (!Node.isJsxAttribute(attribute)) continue;
31
+ const name = attribute.getNameNode();
32
+ if (Node.isIdentifier(name) && name.getText() === "group") return attribute;
33
+ }
34
+ return null;
35
+ }
36
+ function matches(consumerGroup, declaration) {
37
+ if (declaration === null) return true;
38
+ return consumerGroup === "" || consumerGroup === declaration;
39
+ }
40
+ function containerConsumers(sourceFile, registry) {
41
+ const consumers = [];
42
+ const add = (node, name) => {
43
+ if (!isLegacyConditionName(name)) return;
44
+ const resolution = resolveLegacyName(name, registry);
45
+ if (!resolution.ok || resolution.resolved.container === null) return;
46
+ consumers.push({
47
+ node,
48
+ group: resolution.resolved.container.group ?? ""
49
+ });
50
+ };
51
+ for (const attribute of sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute)) {
52
+ const name = attribute.getNameNode();
53
+ if (Node.isIdentifier(name)) add(attribute, name.getText());
54
+ }
55
+ for (const property of sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAssignment)) {
56
+ const name = property.getNameNode();
57
+ if (Node.isComputedPropertyName(name)) continue;
58
+ add(property, name.getText().replace(/^['"]|['"]$/g, ""));
59
+ }
60
+ return consumers;
61
+ }
62
+ function declarations(sourceFile) {
63
+ const found = [];
64
+ for (const attribute of sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute)) {
65
+ const name = attribute.getNameNode();
66
+ if (Node.isIdentifier(name) && name.getText() === "group") found.push(attribute);
67
+ }
68
+ for (const property of sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAssignment)) {
69
+ const name = property.getNameNode();
70
+ if (Node.isComputedPropertyName(name)) continue;
71
+ if (name.getText().replace(/^['"]|['"]$/g, "") === "group") found.push(property);
72
+ }
73
+ return found;
74
+ }
75
+ function provenAncestor(consumer) {
76
+ const owner = consumer.node.getFirstAncestor((node) => Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node));
77
+ if (owner === void 0) return null;
78
+ for (const ancestor of owner.getAncestors()) {
79
+ if (!Node.isJsxElement(ancestor)) continue;
80
+ const declaration = groupAttribute(ancestor.getOpeningElement());
81
+ if (declaration === null) continue;
82
+ const group = declaredGroup(declaration);
83
+ if (group === null) return "ambiguous";
84
+ if (matches(consumer.group, group)) return { declaration };
85
+ }
86
+ return null;
87
+ }
88
+ function planContainers(sourceFile, registry) {
89
+ const consumers = containerConsumers(sourceFile, registry);
90
+ if (!consumers.length) return emptyPlan;
91
+ const targets = /* @__PURE__ */ new Map();
92
+ const unresolved = /* @__PURE__ */ new Map();
93
+ const all = declarations(sourceFile);
94
+ const target = (declaration, consumer, flag) => {
95
+ const existing = targets.get(declaration);
96
+ if (existing) {
97
+ existing.named ||= consumer.group !== "";
98
+ existing.flag ??= flag;
99
+ return;
100
+ }
101
+ targets.set(declaration, {
102
+ group: declaredGroup(declaration) ?? consumer.group,
103
+ named: consumer.group !== "",
104
+ flag
105
+ });
106
+ };
107
+ for (const consumer of consumers) {
108
+ const label = consumer.group === "" ? "the nearest group" : `group "${consumer.group}"`;
109
+ const proven = provenAncestor(consumer);
110
+ if (proven === "ambiguous") {
111
+ unresolved.set(consumer.node, {
112
+ code: "ambiguous-container-group",
113
+ 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`
114
+ });
115
+ continue;
116
+ }
117
+ if (proven !== null) {
118
+ target(proven.declaration, consumer, null);
119
+ continue;
120
+ }
121
+ const candidates = all.filter((declaration) => matches(consumer.group, declaredGroup(declaration)));
122
+ if (candidates.length === 1) {
123
+ target(candidates[0], consumer, {
124
+ code: "unproven-container-group",
125
+ 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`
126
+ });
127
+ continue;
128
+ }
129
+ unresolved.set(consumer.node, {
130
+ code: candidates.length ? "ambiguous-container-group" : "container-group-not-declared",
131
+ detail: candidates.length ? `${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` : `${label} is not declared in this file, so the "@\u2026" query this condition becomes has no container to match; add "container" to the element declaring the group`
132
+ });
133
+ }
134
+ return {
135
+ targets,
136
+ unresolved
137
+ };
138
+ }
139
+
140
+ export { planContainers };
141
+ //# sourceMappingURL=containers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containers.js","names":[],"sources":["containers.js"],"sourcesContent":["import {\n Node,\n SyntaxKind\n} from \"ts-morph\";\nimport { isLegacyConditionName, resolveLegacyName } from \"./legacyNames\";\nconst emptyPlan = {\n targets: /* @__PURE__ */ new Map(),\n unresolved: /* @__PURE__ */ new Map()\n};\nfunction declaredGroup(declaration) {\n if (Node.isJsxAttribute(declaration)) {\n const initializer2 = declaration.getInitializer();\n if (initializer2 === void 0) return \"\";\n if (Node.isStringLiteral(initializer2)) return initializer2.getLiteralValue();\n if (!Node.isJsxExpression(initializer2)) return null;\n const expression = initializer2.getExpression();\n if (expression === void 0) return null;\n if (Node.isStringLiteral(expression) || Node.isNoSubstitutionTemplateLiteral(expression)) {\n return expression.getLiteralValue();\n }\n return expression.getKind() === SyntaxKind.TrueKeyword ? \"\" : null;\n }\n const initializer = declaration.getInitializer();\n if (initializer === void 0) return null;\n if (Node.isStringLiteral(initializer) || Node.isNoSubstitutionTemplateLiteral(initializer)) {\n return initializer.getLiteralValue();\n }\n return initializer.getKind() === SyntaxKind.TrueKeyword ? \"\" : null;\n}\nfunction groupAttribute(opening) {\n for (const attribute of opening.getAttributes()) {\n if (!Node.isJsxAttribute(attribute)) continue;\n const name = attribute.getNameNode();\n if (Node.isIdentifier(name) && name.getText() === \"group\") return attribute;\n }\n return null;\n}\nfunction matches(consumerGroup, declaration) {\n if (declaration === null) return true;\n return consumerGroup === \"\" || consumerGroup === declaration;\n}\nfunction containerConsumers(sourceFile, registry) {\n const consumers = [];\n const add = (node, name) => {\n if (!isLegacyConditionName(name)) return;\n const resolution = resolveLegacyName(name, registry);\n if (!resolution.ok || resolution.resolved.container === null) return;\n consumers.push({ node, group: resolution.resolved.container.group ?? \"\" });\n };\n for (const attribute of sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute)) {\n const name = attribute.getNameNode();\n if (Node.isIdentifier(name)) add(attribute, name.getText());\n }\n for (const property of sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAssignment)) {\n const name = property.getNameNode();\n if (Node.isComputedPropertyName(name)) continue;\n add(property, name.getText().replace(/^['\"]|['\"]$/g, \"\"));\n }\n return consumers;\n}\nfunction declarations(sourceFile) {\n const found = [];\n for (const attribute of sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute)) {\n const name = attribute.getNameNode();\n if (Node.isIdentifier(name) && name.getText() === \"group\") found.push(attribute);\n }\n for (const property of sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAssignment)) {\n const name = property.getNameNode();\n if (Node.isComputedPropertyName(name)) continue;\n if (name.getText().replace(/^['\"]|['\"]$/g, \"\") === \"group\") found.push(property);\n }\n return found;\n}\nfunction provenAncestor(consumer) {\n const owner = consumer.node.getFirstAncestor(\n (node) => Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node)\n );\n if (owner === void 0) return null;\n for (const ancestor of owner.getAncestors()) {\n if (!Node.isJsxElement(ancestor)) continue;\n const declaration = groupAttribute(ancestor.getOpeningElement());\n if (declaration === null) continue;\n const group = declaredGroup(declaration);\n if (group === null) return \"ambiguous\";\n if (matches(consumer.group, group)) return { declaration };\n }\n return null;\n}\nfunction planContainers(sourceFile, registry) {\n const consumers = containerConsumers(sourceFile, registry);\n if (!consumers.length) return emptyPlan;\n const targets = /* @__PURE__ */ new Map();\n const unresolved = /* @__PURE__ */ new Map();\n const all = declarations(sourceFile);\n const target = (declaration, consumer, flag) => {\n const existing = targets.get(declaration);\n if (existing) {\n existing.named ||= consumer.group !== \"\";\n existing.flag ??= flag;\n return;\n }\n targets.set(declaration, {\n group: declaredGroup(declaration) ?? consumer.group,\n named: consumer.group !== \"\",\n flag\n });\n };\n for (const consumer of consumers) {\n const label = consumer.group === \"\" ? \"the nearest group\" : `group \"${consumer.group}\"`;\n const proven = provenAncestor(consumer);\n if (proven === \"ambiguous\") {\n unresolved.set(consumer.node, {\n code: \"ambiguous-container-group\",\n 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`\n });\n continue;\n }\n if (proven !== null) {\n target(proven.declaration, consumer, null);\n continue;\n }\n const candidates = all.filter(\n (declaration) => matches(consumer.group, declaredGroup(declaration))\n );\n if (candidates.length === 1) {\n target(candidates[0], consumer, {\n code: \"unproven-container-group\",\n 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`\n });\n continue;\n }\n unresolved.set(consumer.node, {\n code: candidates.length ? \"ambiguous-container-group\" : \"container-group-not-declared\",\n detail: candidates.length ? `${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` : `${label} is not declared in this file, so the \"@\\u2026\" query this condition becomes has no container to match; add \"container\" to the element declaring the group`\n });\n }\n return { targets, unresolved };\n}\nexport {\n planContainers\n};\n//# sourceMappingURL=containers.js.map\n"],"mappings":"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"}