@tamagui/codemod-flat-values 0.0.0-bootstrap.0 → 3.0.0-beta.643.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 +211 -1
- package/dist/builtInNames.mjs +13 -0
- package/dist/builtInNames.mjs.map +1 -0
- package/dist/containers.mjs +141 -0
- package/dist/containers.mjs.map +1 -0
- package/dist/convert.mjs +1139 -0
- package/dist/convert.mjs.map +1 -0
- package/dist/expressions.mjs +188 -0
- package/dist/expressions.mjs.map +1 -0
- package/dist/grammar.mjs +68 -0
- package/dist/grammar.mjs.map +1 -0
- package/dist/index.mjs +342 -0
- package/dist/index.mjs.map +1 -0
- package/dist/legacyConditions.mjs +293 -0
- package/dist/legacyConditions.mjs.map +1 -0
- package/dist/legacyNames.mjs +130 -0
- package/dist/legacyNames.mjs.map +1 -0
- package/dist/provenance.mjs +137 -0
- package/dist/provenance.mjs.map +1 -0
- package/dist/report.mjs +129 -0
- package/dist/report.mjs.map +1 -0
- package/dist/structuredNative.mjs +275 -0
- package/dist/structuredNative.mjs.map +1 -0
- package/package.json +35 -7
- package/src/builtInNames.ts +21 -0
- package/src/containers.ts +227 -0
- package/src/convert.ts +1784 -0
- package/src/expressions.ts +220 -0
- package/src/grammar.ts +180 -0
- package/src/index.ts +517 -0
- package/src/legacyConditions.ts +346 -0
- package/src/legacyNames.ts +160 -0
- package/src/provenance.ts +210 -0
- package/src/report.ts +235 -0
- package/src/structuredNative.ts +459 -0
package/README.md
CHANGED
|
@@ -1 +1,211 @@
|
|
|
1
|
-
#
|
|
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
|
+
- Group conditions split the way V3 splits them: `$group-card-hover` becomes
|
|
56
|
+
`group-hover/card:`, and a container size (`$group-card-maxMd`) becomes
|
|
57
|
+
`@max-md/card:` plus `container containerName="card"` on the element that declares
|
|
58
|
+
the group. That element has to be proven, never inferred: a JSX ancestor declaring
|
|
59
|
+
the group takes the container silently, a single declaration elsewhere in the file
|
|
60
|
+
takes it under `unproven-container-group`, and anything else stays authored
|
|
61
|
+
(`ambiguous-container-group`, `container-group-not-declared`). Declaring a
|
|
62
|
+
container changes containment, so it never lands on a `group` this pass cannot tie
|
|
63
|
+
to the condition.
|
|
64
|
+
- A base value only folds into a program when a clause would otherwise need a second
|
|
65
|
+
attribute of the same name. `opacity={0.5}` plus `enterStyle={{ opacity: 0 }}`
|
|
66
|
+
becomes `opacity="0.5 enter:0"`, while a numeric or dynamic value nothing conditions
|
|
67
|
+
on is left exactly as authored.
|
|
68
|
+
- A dynamic value converts when its units and token spellings are provable: a tree of
|
|
69
|
+
literals is rewritten in place (`active ? '$red10' : '$blue10'`), and an expression
|
|
70
|
+
whose type the checker can prove interpolates into the program
|
|
71
|
+
(`` backgroundColor={`${GREEN} disabled:${GREY}`} ``).
|
|
72
|
+
- Variant and compound-variant branches convert as their own style objects, so a
|
|
73
|
+
branch contributes at its own position in the forward pass. No base is invented for
|
|
74
|
+
a condition-only branch: with clause-level merge (decision 21)
|
|
75
|
+
`exitStyle={{ x: 10 }}` is exactly `x="exit:10px"`, which keeps whatever base the
|
|
76
|
+
styled component, variant, or call site defined — the same thing the v1 prop did.
|
|
77
|
+
|
|
78
|
+
Every program the report suggests is parsed back with the real value parser and merged
|
|
79
|
+
with the real clause merge before it is printed. A program that does not read back
|
|
80
|
+
identically is reported (`emitted-program-mismatch`, `emitted-value-invalid`) instead
|
|
81
|
+
of suggested.
|
|
82
|
+
|
|
83
|
+
The codemod preserves palette-step names such as `blue10` and `red10`. It does
|
|
84
|
+
not evaluate the application's runtime config, so it cannot know whether a
|
|
85
|
+
custom config still defines them or which absolute shade preserves the design.
|
|
86
|
+
Every preserved v5 palette name appears as a non-blocking
|
|
87
|
+
`legacy-palette-token` configuration warning in the Markdown and JSON reports.
|
|
88
|
+
Write mode still applies the safe syntax conversion, so resolve every warning
|
|
89
|
+
before running the converted application. When moving to `@tamagui/config/v6`,
|
|
90
|
+
migrate each name manually to an absolute token such as `blue-500`, or enter a
|
|
91
|
+
color theme and use its adaptive `colorN` ramp. The v6 defaults do not define
|
|
92
|
+
the old palette-step names, and a missing color is dropped without a runtime
|
|
93
|
+
warning.
|
|
94
|
+
|
|
95
|
+
## Conversion assessment
|
|
96
|
+
|
|
97
|
+
Syntax alone cannot make a conversion safe. Before suggesting a program, the codemod
|
|
98
|
+
asks the shared conversion contract whether the property can carry clauses, whether
|
|
99
|
+
every clause evaluates on every target implied by the file name, and whether the
|
|
100
|
+
component's TypeScript prop type accepts the style.
|
|
101
|
+
|
|
102
|
+
- **clean** means all three checks passed. A shared `.tsx` file must work on web and
|
|
103
|
+
native; `.web.tsx` and `.native.tsx` require only their named target.
|
|
104
|
+
- **needs-relocation** means the syntax is valid but the authored target or host cannot
|
|
105
|
+
evaluate it. Write mode still removes the V1 syntax because V3 has no legacy runtime;
|
|
106
|
+
the report prints the required hand edit. For example, a component-tier web state in
|
|
107
|
+
a shared file moves to `.web.tsx`, while text-only styles on a View move to Text or
|
|
108
|
+
`html.*`.
|
|
109
|
+
- **unknown-host** means the component is provably Tamagui but its TypeScript type does
|
|
110
|
+
not expose enough component identity and style-prop information to verify the host.
|
|
111
|
+
Write mode converts the site and leaves the host check in the report for review.
|
|
112
|
+
- **ineligible** means the property has no flat clause spelling. Transform and React
|
|
113
|
+
Native shadow part props stay authored, with a remedy that points to the `transform`,
|
|
114
|
+
`boxShadow`, or `textShadow` composite.
|
|
115
|
+
|
|
116
|
+
The type-aware host lookup is shared with `@tamagui/language-service`; the codemod does
|
|
117
|
+
not import or evaluate an app's runtime config.
|
|
118
|
+
|
|
119
|
+
Ordering is never traded for a bigger diff. A program merges only when every
|
|
120
|
+
contribution still beats and loses to the same things it did, so an opaque spread or
|
|
121
|
+
an unconverted condition object between two contributions leaves the later ones
|
|
122
|
+
authored (`condition-order-not-preservable`, `base-order-not-preservable`). Spreading
|
|
123
|
+
an object literal writes its members out, and a nested spread or an unreadable key
|
|
124
|
+
inside it stays exactly where it was authored and orders the merge like any other
|
|
125
|
+
spread. An unconvertible nested condition counts every longhand under it, at any
|
|
126
|
+
depth, as something its member can still set.
|
|
127
|
+
|
|
128
|
+
## Apply mode
|
|
129
|
+
|
|
130
|
+
Clause-free tokens now become base-only programs: `p="$4"` becomes `p="4"` and
|
|
131
|
+
resolves config-first through the same flat engine as a value with modifiers.
|
|
132
|
+
For `x` and `y`, this also applies the v3 category cutover: the legacy `$4`
|
|
133
|
+
resolved through the size scale, while flat `4` resolves through the space scale.
|
|
134
|
+
If a custom config gives those scales different values, the rendered offset changes
|
|
135
|
+
by design and those rows need review during migration.
|
|
136
|
+
|
|
137
|
+
All legacy runtime gates are closed. Apply mode is the migration path; there is no
|
|
138
|
+
runtime compatibility setting or fallback parser.
|
|
139
|
+
|
|
140
|
+
## Structured native values
|
|
141
|
+
|
|
142
|
+
Unconditional React Native objects and arrays keep their authored representation and
|
|
143
|
+
do not appear as migration work. When a condition needs the same property in one
|
|
144
|
+
string program, the codemod converts only the structures with an explicit,
|
|
145
|
+
round-trippable CSS spelling:
|
|
146
|
+
|
|
147
|
+
- a static `transform` array becomes an ordered CSS transform function list;
|
|
148
|
+
- a static `fontVariant` string array becomes a space-separated token list;
|
|
149
|
+
- one static React Native `linear-gradient` object becomes `linear-gradient(...)`.
|
|
150
|
+
|
|
151
|
+
Dynamic and Animated transform entries stay authored under
|
|
152
|
+
`structured-transform-dynamic`. Matrix arrays stay authored under
|
|
153
|
+
`structured-transform-matrix`, because React Native's matrix array and portable CSS
|
|
154
|
+
matrix forms do not have the same shape. A dynamic gradient gets
|
|
155
|
+
`structured-background-image-dynamic`; unsupported structured properties use a
|
|
156
|
+
property-specific `structured-<property>` code.
|
|
157
|
+
|
|
158
|
+
Plain `shadowOffset` and `textShadowOffset` objects also stay natural and are not
|
|
159
|
+
inventory. If a condition targets one, the shared eligibility contract reports it as
|
|
160
|
+
ineligible and points to the complete `boxShadow` or `textShadow` composite. The
|
|
161
|
+
codemod never guesses the missing color, radius, opacity, or sibling offset.
|
|
162
|
+
|
|
163
|
+
Transition arrays, per-property objects, and dynamic references remain authored
|
|
164
|
+
because they are supported animation-driver configuration, not flat-value
|
|
165
|
+
migration work. An open string type that could still hold a `$token` at runtime
|
|
166
|
+
is listed separately as `dynamic-string-value`, which only a human can confirm.
|
|
167
|
+
|
|
168
|
+
## Flag codes
|
|
169
|
+
|
|
170
|
+
A flag means a human decides. Every code the tool can emit:
|
|
171
|
+
|
|
172
|
+
| code | meaning |
|
|
173
|
+
| --- | --- |
|
|
174
|
+
| `legacy-token-dot-path` | `$1.5` and friends need one flat token name |
|
|
175
|
+
| `legacy-numeric-composite-token` | a numeric token embedded in a composite needs its resolved CSS value |
|
|
176
|
+
| `legacy-token-constant` | the token lives in a module constant (`const RADIUS = '$6'`), so the constant is what migrates |
|
|
177
|
+
| `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 |
|
|
178
|
+
| `value-reparses-as-program` | the converted string would read back as something other than one base value |
|
|
179
|
+
| `legacy-group-presence` | `$group-card` with no state or size styles every descendant unconditionally and has no flat spelling |
|
|
180
|
+
| `unknown-legacy-condition` | the condition name is not a registered spelling (a media key missing from the V6 defaults, for one) |
|
|
181
|
+
| `unregistered-legacy-condition` | a registered shape whose parameter is not registered |
|
|
182
|
+
| `non-style-condition-entry` | a conditional non-style prop (`numberOfLines`) has no flat target |
|
|
183
|
+
| `dynamic-legacy-condition` | the condition object is built at runtime (spread, computed key, non-literal) |
|
|
184
|
+
| `unprovable-dynamic-value` | a condition needs the base value, whose type cannot be proven |
|
|
185
|
+
| `dynamic-condition-value` | a value inside the condition object is not statically known |
|
|
186
|
+
| `non-css-style-value` | a condition needs a base that is `true`, `false`, or `null` |
|
|
187
|
+
| `empty-style-value` | a condition needs a base that is always nullish |
|
|
188
|
+
| `structured-transform-*` | a transform array is dynamic or has no faithful static function-list spelling; matrix arrays are intentionally included |
|
|
189
|
+
| `structured-font-variant-*`, `structured-background-image-*` | a font-variant or gradient structure is dynamic, empty, layered, or unsupported |
|
|
190
|
+
| `structured-<property>` | a condition needs an object or array with no verified CSS-shaped migration rule |
|
|
191
|
+
| `condition-order-not-preservable`, `base-order-not-preservable` | merging would move a value past something that can also set it |
|
|
192
|
+
| `computed-property` | a computed key hides the affected style property |
|
|
193
|
+
| `emitted-program-mismatch`, `emitted-value-invalid` | the printer failed its own re-parse; this is a codemod bug |
|
|
194
|
+
| plus any code from the shared converter | `unsupported-legacy-value`, `legacy-condition-object`, `ambiguous-legacy-group`, `legacy-composite-shorthand` |
|
|
195
|
+
|
|
196
|
+
## Configuration warning codes
|
|
197
|
+
|
|
198
|
+
| code | meaning |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `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 |
|
|
201
|
+
|
|
202
|
+
`unsupported-legacy-value` covers the token-context refusals: a `$` mixed with quoted
|
|
203
|
+
or unquoted `url()` content is literal CSS the resolver never reads as a token
|
|
204
|
+
candidate, so the value is reported rather than rewritten. Base values and clause
|
|
205
|
+
payloads go through the same shared converter, so they always agree on that.
|
|
206
|
+
|
|
207
|
+
## Remaining manual migration
|
|
208
|
+
|
|
209
|
+
The report counts the files whose sites all converted: those have no legacy condition
|
|
210
|
+
object left. V3 has no compatibility setting, so the report names every file that
|
|
211
|
+
still needs a hand edit after `--write`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { v6ThemeNameReplacements } from "@tamagui/style-grammar/tooling";
|
|
2
|
+
|
|
3
|
+
const v6CodemodBuiltInNameReplacements = {
|
|
4
|
+
...v6ThemeNameReplacements,
|
|
5
|
+
backgroundActive: "background-press"
|
|
6
|
+
};
|
|
7
|
+
const builtInTokenPattern = new RegExp(`\\$(${Object.keys(v6CodemodBuiltInNameReplacements).join("|")})(?![\\w-])`, "g");
|
|
8
|
+
function replaceV6BuiltInTokens(value) {
|
|
9
|
+
return value.replace(builtInTokenPattern, (_, name) => `$${v6CodemodBuiltInNameReplacements[name]}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { replaceV6BuiltInTokens, v6CodemodBuiltInNameReplacements };
|
|
13
|
+
//# 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};\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;AACpB;AACA,MAAM,sBAAsB,IAAI,OAC9B,OAAO,OAAO,KAAK,gCAAgC,EAAE,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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"}
|