@idealyst/theme 1.1.6 → 1.1.8

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.
@@ -0,0 +1,871 @@
1
+ /**
2
+ * Idealyst StyleBuilder Babel Plugin
3
+ *
4
+ * Transforms defineStyle/extendStyle calls to StyleSheet.create with $iterator expansion.
5
+ * All merging happens at BUILD TIME so Unistyles can properly trace theme dependencies.
6
+ *
7
+ * IMPORTANT: For extensions to work, the extension file must be imported BEFORE
8
+ * the components that use defineStyle. Example:
9
+ *
10
+ * // App.tsx
11
+ * import './style-extensions'; // FIRST - registers extensions
12
+ * import { Text } from '@idealyst/components'; // SECOND - uses extensions
13
+ *
14
+ * Transformation:
15
+ * defineStyle('Button', (theme) => ({
16
+ * button: { backgroundColor: theme.$intents.primary }
17
+ * }))
18
+ *
19
+ * → StyleSheet.create((theme) => ({
20
+ * button: {
21
+ * variants: {
22
+ * intent: {
23
+ * primary: { backgroundColor: theme.intents.primary.primary },
24
+ * success: { backgroundColor: theme.intents.success.primary },
25
+ * ...
26
+ * }
27
+ * }
28
+ * }
29
+ * }))
30
+ */
31
+
32
+ const { loadThemeKeys } = require('./theme-analyzer');
33
+
34
+ // ============================================================================
35
+ // Global Extension Registry - Persists across files during build
36
+ // ============================================================================
37
+
38
+ // Map of componentName -> { base: AST | null, extensions: AST[], overrides: AST | null }
39
+ const styleRegistry = {};
40
+
41
+ function getOrCreateEntry(componentName) {
42
+ if (!styleRegistry[componentName]) {
43
+ styleRegistry[componentName] = {
44
+ base: null,
45
+ extensions: [],
46
+ override: null,
47
+ themeParam: 'theme',
48
+ };
49
+ }
50
+ return styleRegistry[componentName];
51
+ }
52
+
53
+ // ============================================================================
54
+ // AST Deep Merge - Merges style object ASTs at build time
55
+ // ============================================================================
56
+
57
+ /**
58
+ * Deep merge two ObjectExpression ASTs.
59
+ * Source properties override target properties.
60
+ * Nested objects are recursively merged.
61
+ */
62
+ function mergeObjectExpressions(t, target, source) {
63
+ if (!t.isObjectExpression(target) || !t.isObjectExpression(source)) {
64
+ // If source is not an object, it replaces target entirely
65
+ return source;
66
+ }
67
+
68
+ const targetProps = new Map();
69
+ for (const prop of target.properties) {
70
+ if (t.isObjectProperty(prop)) {
71
+ const key = t.isIdentifier(prop.key) ? prop.key.name :
72
+ t.isStringLiteral(prop.key) ? prop.key.value : null;
73
+ if (key) {
74
+ targetProps.set(key, prop);
75
+ }
76
+ }
77
+ }
78
+
79
+ const resultProps = [...target.properties];
80
+
81
+ for (const prop of source.properties) {
82
+ if (!t.isObjectProperty(prop)) continue;
83
+
84
+ const key = t.isIdentifier(prop.key) ? prop.key.name :
85
+ t.isStringLiteral(prop.key) ? prop.key.value : null;
86
+ if (!key) continue;
87
+
88
+ const existingProp = targetProps.get(key);
89
+
90
+ if (existingProp) {
91
+ // Both have this property - need to merge or replace
92
+ const existingValue = existingProp.value;
93
+ const newValue = prop.value;
94
+
95
+ // If both are objects, deep merge
96
+ if (t.isObjectExpression(existingValue) && t.isObjectExpression(newValue)) {
97
+ const mergedValue = mergeObjectExpressions(t, existingValue, newValue);
98
+ // Replace the existing prop's value
99
+ const idx = resultProps.indexOf(existingProp);
100
+ resultProps[idx] = t.objectProperty(existingProp.key, mergedValue);
101
+ }
102
+ // If both are arrow functions (dynamic styles), merge their return values
103
+ else if (t.isArrowFunctionExpression(existingValue) && t.isArrowFunctionExpression(newValue)) {
104
+ const mergedFn = mergeDynamicStyleFunctions(t, existingValue, newValue);
105
+ const idx = resultProps.indexOf(existingProp);
106
+ resultProps[idx] = t.objectProperty(existingProp.key, mergedFn);
107
+ }
108
+ // Otherwise, source replaces target
109
+ else {
110
+ const idx = resultProps.indexOf(existingProp);
111
+ resultProps[idx] = prop;
112
+ }
113
+ } else {
114
+ // New property from source
115
+ resultProps.push(prop);
116
+ }
117
+ }
118
+
119
+ return t.objectExpression(resultProps);
120
+ }
121
+
122
+ /**
123
+ * Merge two dynamic style functions (arrow functions that return style objects).
124
+ * Creates a new function that merges both return values.
125
+ */
126
+ function mergeDynamicStyleFunctions(t, baseFn, extFn) {
127
+ // Get the bodies (assuming they return ObjectExpressions)
128
+ let baseBody = baseFn.body;
129
+ let extBody = extFn.body;
130
+
131
+ // Handle parenthesized expressions
132
+ if (t.isParenthesizedExpression(baseBody)) {
133
+ baseBody = baseBody.expression;
134
+ }
135
+ if (t.isParenthesizedExpression(extBody)) {
136
+ extBody = extBody.expression;
137
+ }
138
+
139
+ // If both return ObjectExpressions directly, merge them
140
+ if (t.isObjectExpression(baseBody) && t.isObjectExpression(extBody)) {
141
+ const mergedBody = mergeObjectExpressions(t, baseBody, extBody);
142
+ return t.arrowFunctionExpression(baseFn.params, mergedBody);
143
+ }
144
+
145
+ // For block statements, this is more complex - just use extension for now
146
+ return extFn;
147
+ }
148
+
149
+ /**
150
+ * Merge a callback body (the object returned by theme => ({ ... }))
151
+ */
152
+ function mergeCallbackBodies(t, baseCallback, extCallback) {
153
+ let baseBody = baseCallback.body;
154
+ let extBody = extCallback.body;
155
+
156
+ // Handle parenthesized expressions
157
+ if (t.isParenthesizedExpression(baseBody)) {
158
+ baseBody = baseBody.expression;
159
+ }
160
+ if (t.isParenthesizedExpression(extBody)) {
161
+ extBody = extBody.expression;
162
+ }
163
+
164
+ if (t.isObjectExpression(baseBody) && t.isObjectExpression(extBody)) {
165
+ const mergedBody = mergeObjectExpressions(t, baseBody, extBody);
166
+ return t.arrowFunctionExpression(baseCallback.params, mergedBody);
167
+ }
168
+
169
+ // If can't merge, use base
170
+ return baseCallback;
171
+ }
172
+
173
+ // ============================================================================
174
+ // $iterator Expansion Logic
175
+ // ============================================================================
176
+
177
+ function expandIterators(t, callback, themeParam, keys, verbose, expandedVariants) {
178
+ const cloned = t.cloneDeep(callback);
179
+
180
+ function processNode(node, depth = 0) {
181
+ if (!node || typeof node !== 'object') return node;
182
+
183
+ if (Array.isArray(node)) {
184
+ return node.map(n => processNode(n, depth));
185
+ }
186
+
187
+ // Look for variants: { intent: { ... }, size: { ... } }
188
+ if (t.isObjectProperty(node) && t.isIdentifier(node.key, { name: 'variants' })) {
189
+ if (t.isObjectExpression(node.value)) {
190
+ const expanded = expandVariantsObject(t, node.value, themeParam, keys, verbose, expandedVariants);
191
+ return t.objectProperty(node.key, expanded);
192
+ } else if (t.isTSAsExpression(node.value)) {
193
+ const innerValue = node.value.expression;
194
+ if (t.isObjectExpression(innerValue)) {
195
+ const expanded = expandVariantsObject(t, innerValue, themeParam, keys, verbose, expandedVariants);
196
+ return t.objectProperty(node.key, t.tsAsExpression(expanded, node.value.typeAnnotation));
197
+ }
198
+ }
199
+ }
200
+
201
+ if (t.isObjectExpression(node)) {
202
+ return t.objectExpression(
203
+ node.properties.map(prop => processNode(prop, depth + 1))
204
+ );
205
+ }
206
+
207
+ if (t.isObjectProperty(node)) {
208
+ return t.objectProperty(
209
+ node.key,
210
+ processNode(node.value, depth + 1),
211
+ node.computed,
212
+ node.shorthand
213
+ );
214
+ }
215
+
216
+ if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
217
+ const processedBody = processNode(node.body, depth + 1);
218
+ return t.arrowFunctionExpression(
219
+ node.params,
220
+ processedBody
221
+ );
222
+ }
223
+
224
+ // Handle TSAsExpression (e.g., { ... } as const)
225
+ if (t.isTSAsExpression(node)) {
226
+ const processedExpr = processNode(node.expression, depth + 1);
227
+ return t.tsAsExpression(processedExpr, node.typeAnnotation);
228
+ }
229
+
230
+ // Handle ParenthesizedExpression
231
+ if (t.isParenthesizedExpression(node)) {
232
+ const processedExpr = processNode(node.expression, depth + 1);
233
+ return t.parenthesizedExpression(processedExpr);
234
+ }
235
+
236
+ // Handle BlockStatement (for nested arrow functions with block bodies)
237
+ if (t.isBlockStatement(node)) {
238
+ const newBody = node.body.map(stmt => {
239
+ if (t.isReturnStatement(stmt) && stmt.argument) {
240
+ return t.returnStatement(processNode(stmt.argument, depth + 1));
241
+ }
242
+ // Handle variable declarations that might contain style objects
243
+ if (t.isVariableDeclaration(stmt)) {
244
+ return t.variableDeclaration(
245
+ stmt.kind,
246
+ stmt.declarations.map(decl => {
247
+ if (decl.init) {
248
+ return t.variableDeclarator(decl.id, processNode(decl.init, depth + 1));
249
+ }
250
+ return decl;
251
+ })
252
+ );
253
+ }
254
+ return stmt;
255
+ });
256
+ return t.blockStatement(newBody);
257
+ }
258
+
259
+ return node;
260
+ }
261
+
262
+ // Handle the callback body - may be ObjectExpression, TSAsExpression, ParenthesizedExpression, or BlockStatement
263
+ let bodyToProcess = cloned.body;
264
+
265
+ // Unwrap ParenthesizedExpression
266
+ if (t.isParenthesizedExpression(bodyToProcess)) {
267
+ bodyToProcess = bodyToProcess.expression;
268
+ }
269
+
270
+ // Unwrap TSAsExpression
271
+ if (t.isTSAsExpression(bodyToProcess)) {
272
+ const processedExpr = processNode(bodyToProcess.expression);
273
+ cloned.body = t.tsAsExpression(processedExpr, bodyToProcess.typeAnnotation);
274
+ } else if (t.isObjectExpression(bodyToProcess)) {
275
+ cloned.body = processNode(bodyToProcess);
276
+ } else if (t.isBlockStatement(cloned.body)) {
277
+ cloned.body.body = cloned.body.body.map(stmt => {
278
+ if (t.isReturnStatement(stmt) && stmt.argument) {
279
+ return t.returnStatement(processNode(stmt.argument));
280
+ }
281
+ return stmt;
282
+ });
283
+ }
284
+
285
+ return cloned;
286
+ }
287
+
288
+ function expandVariantsObject(t, variantsObj, themeParam, keys, verbose, expandedVariants) {
289
+ const newProperties = [];
290
+
291
+ verbose(` expandVariantsObject: processing ${variantsObj.properties?.length || 0} properties`);
292
+
293
+ for (const prop of variantsObj.properties) {
294
+ if (!t.isObjectProperty(prop)) {
295
+ newProperties.push(prop);
296
+ continue;
297
+ }
298
+
299
+ let variantName;
300
+ if (t.isIdentifier(prop.key)) {
301
+ variantName = prop.key.name;
302
+ } else if (t.isStringLiteral(prop.key)) {
303
+ variantName = prop.key.value;
304
+ } else {
305
+ newProperties.push(prop);
306
+ continue;
307
+ }
308
+
309
+ verbose(` Checking variant: ${variantName}`);
310
+ const iteratorInfo = findIteratorPattern(t, prop.value, themeParam);
311
+ verbose(` Iterator info for ${variantName}: ${JSON.stringify(iteratorInfo)}`);
312
+
313
+ if (iteratorInfo) {
314
+ verbose(` Expanding ${variantName} variant with ${iteratorInfo.type} iterator`);
315
+ verbose(` Keys available: ${JSON.stringify(keys?.sizes?.[iteratorInfo.componentName] || keys?.intents || [])}`);
316
+ const expanded = expandVariantWithIterator(t, prop.value, themeParam, keys, iteratorInfo, verbose);
317
+ newProperties.push(t.objectProperty(prop.key, expanded));
318
+
319
+ const iteratorKey = iteratorInfo.componentName
320
+ ? `${iteratorInfo.type}.${iteratorInfo.componentName}`
321
+ : iteratorInfo.type;
322
+ expandedVariants.push({ variant: variantName, iterator: iteratorKey });
323
+ } else {
324
+ newProperties.push(prop);
325
+ }
326
+ }
327
+
328
+ return t.objectExpression(newProperties);
329
+ }
330
+
331
+ function findIteratorPattern(t, node, themeParam, debugLog = () => {}) {
332
+ let result = null;
333
+
334
+ function walk(n, path = '') {
335
+ if (!n || typeof n !== 'object' || result) return;
336
+
337
+ if (t.isMemberExpression(n)) {
338
+ const chain = getMemberChain(t, n, themeParam);
339
+ if (chain) {
340
+ for (let i = 0; i < chain.length; i++) {
341
+ const part = chain[i];
342
+ if (part.startsWith('$')) {
343
+ const iteratorName = part.slice(1);
344
+
345
+ if (iteratorName === 'intents') {
346
+ result = {
347
+ type: 'intents',
348
+ accessPath: chain.slice(i + 1),
349
+ };
350
+ return;
351
+ }
352
+
353
+ if (i > 0 && chain[i - 1] === 'sizes') {
354
+ result = {
355
+ type: 'sizes',
356
+ componentName: iteratorName,
357
+ accessPath: chain.slice(i + 1),
358
+ };
359
+ return;
360
+ }
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ for (const key of Object.keys(n)) {
367
+ if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;
368
+ if (n[key] && typeof n[key] === 'object') {
369
+ walk(n[key]);
370
+ }
371
+ }
372
+ }
373
+
374
+ walk(node);
375
+ return result;
376
+ }
377
+
378
+ function getMemberChain(t, node, themeParam) {
379
+ const parts = [];
380
+
381
+ function walk(n) {
382
+ if (t.isIdentifier(n)) {
383
+ if (n.name === themeParam) {
384
+ return true;
385
+ }
386
+ return false;
387
+ }
388
+
389
+ if (t.isMemberExpression(n)) {
390
+ if (!walk(n.object)) return false;
391
+
392
+ if (t.isIdentifier(n.property)) {
393
+ parts.push(n.property.name);
394
+ } else if (t.isStringLiteral(n.property)) {
395
+ parts.push(n.property.value);
396
+ }
397
+ return true;
398
+ }
399
+
400
+ return false;
401
+ }
402
+
403
+ return walk(node) ? parts : null;
404
+ }
405
+
406
+ function expandVariantWithIterator(t, valueNode, themeParam, keys, iteratorInfo, verbose) {
407
+ let keysToExpand = [];
408
+
409
+ if (iteratorInfo.type === 'intents') {
410
+ keysToExpand = keys?.intents || [];
411
+ } else if (iteratorInfo.type === 'sizes' && iteratorInfo.componentName) {
412
+ keysToExpand = keys?.sizes?.[iteratorInfo.componentName] || [];
413
+ }
414
+
415
+ if (keysToExpand.length === 0) {
416
+ return valueNode;
417
+ }
418
+
419
+ verbose(` Expanding to keys: ${keysToExpand.join(', ')}`);
420
+
421
+ const expandedProps = [];
422
+
423
+ for (const key of keysToExpand) {
424
+ const expandedValue = replaceIteratorRefs(t, valueNode, themeParam, iteratorInfo, key);
425
+ expandedProps.push(
426
+ t.objectProperty(
427
+ t.identifier(key),
428
+ expandedValue
429
+ )
430
+ );
431
+ }
432
+
433
+ return t.objectExpression(expandedProps);
434
+ }
435
+
436
+ function replaceIteratorRefs(t, node, themeParam, iteratorInfo, key) {
437
+ if (!node || typeof node !== 'object') return node;
438
+
439
+ const cloned = t.cloneDeep(node);
440
+
441
+ function walk(n) {
442
+ if (!n || typeof n !== 'object') return n;
443
+
444
+ if (Array.isArray(n)) {
445
+ return n.map(walk);
446
+ }
447
+
448
+ if (t.isMemberExpression(n)) {
449
+ const chain = getMemberChain(t, n, themeParam);
450
+ if (chain) {
451
+ let hasIterator = false;
452
+ let dollarIndex = -1;
453
+
454
+ for (let i = 0; i < chain.length; i++) {
455
+ if (chain[i].startsWith('$')) {
456
+ const iterName = chain[i].slice(1);
457
+ if (iteratorInfo.type === 'intents' && iterName === 'intents') {
458
+ hasIterator = true;
459
+ dollarIndex = i;
460
+ break;
461
+ }
462
+ if (iteratorInfo.type === 'sizes' && iterName === iteratorInfo.componentName && i > 0 && chain[i - 1] === 'sizes') {
463
+ hasIterator = true;
464
+ dollarIndex = i;
465
+ break;
466
+ }
467
+ }
468
+ }
469
+
470
+ if (hasIterator) {
471
+ const newChain = [];
472
+ for (let i = 0; i < chain.length; i++) {
473
+ if (i === dollarIndex) {
474
+ const iterName = chain[i].slice(1);
475
+ newChain.push(iterName);
476
+ newChain.push(key);
477
+ } else {
478
+ newChain.push(chain[i]);
479
+ }
480
+ }
481
+ return buildMemberExpression(t, themeParam, newChain);
482
+ }
483
+ }
484
+ }
485
+
486
+ if (t.isObjectExpression(n)) {
487
+ return t.objectExpression(
488
+ n.properties.map(prop => walk(prop))
489
+ );
490
+ }
491
+
492
+ if (t.isObjectProperty(n)) {
493
+ return t.objectProperty(
494
+ n.key,
495
+ walk(n.value),
496
+ n.computed,
497
+ n.shorthand
498
+ );
499
+ }
500
+
501
+ return n;
502
+ }
503
+
504
+ return walk(cloned);
505
+ }
506
+
507
+ function buildMemberExpression(t, base, chain) {
508
+ let expr = t.identifier(base);
509
+ for (const part of chain) {
510
+ expr = t.memberExpression(expr, t.identifier(part));
511
+ }
512
+ return expr;
513
+ }
514
+
515
+ // ============================================================================
516
+ // Babel Plugin
517
+ // ============================================================================
518
+
519
+ module.exports = function idealystStylesPlugin({ types: t }) {
520
+ // Store babel types for use in extractThemeKeysFromAST
521
+ babelTypes = t;
522
+
523
+ let debugMode = false;
524
+ let verboseMode = false;
525
+
526
+ function debug(...args) {
527
+ if (debugMode || verboseMode) {
528
+ console.log('[idealyst-plugin]', ...args);
529
+ }
530
+ }
531
+
532
+ function verbose(...args) {
533
+ if (verboseMode) {
534
+ console.log('[idealyst-plugin]', ...args);
535
+ }
536
+ }
537
+
538
+ // Log plugin initialization
539
+ console.log('[idealyst-plugin] Plugin loaded (AST-based theme analysis)');
540
+
541
+ return {
542
+ name: 'idealyst-styles',
543
+
544
+ visitor: {
545
+ Program: {
546
+ enter(path, state) {
547
+ const filename = state.filename || '';
548
+ const opts = state.opts || {};
549
+
550
+ // Check if this file should be processed
551
+ const shouldProcess =
552
+ opts.processAll ||
553
+ (opts.autoProcessPaths?.some(p => filename.includes(p)));
554
+
555
+ state.needsStyleSheetImport = false;
556
+ state.hasStyleSheetImport = false;
557
+
558
+ path.traverse({
559
+ ImportDeclaration(importPath) {
560
+ if (importPath.node.source.value === 'react-native-unistyles') {
561
+ for (const spec of importPath.node.specifiers) {
562
+ if (t.isImportSpecifier(spec) &&
563
+ t.isIdentifier(spec.imported, { name: 'StyleSheet' })) {
564
+ state.hasStyleSheetImport = true;
565
+ }
566
+ }
567
+ state.unistylesImportPath = importPath;
568
+ }
569
+ }
570
+ });
571
+ },
572
+ exit(path, state) {
573
+ // Always ensure StyleSheet import and marker when needed
574
+ // (existing import might have been removed by tree-shaking)
575
+ if (state.needsStyleSheetImport) {
576
+ // Check if StyleSheet is currently in the AST
577
+ let hasStyleSheet = false;
578
+ let hasVoidMarker = false;
579
+ let unistylesImport = null;
580
+
581
+ path.traverse({
582
+ ImportDeclaration(importPath) {
583
+ if (importPath.node.source.value === 'react-native-unistyles') {
584
+ unistylesImport = importPath;
585
+ for (const spec of importPath.node.specifiers) {
586
+ if (t.isImportSpecifier(spec) &&
587
+ t.isIdentifier(spec.imported, { name: 'StyleSheet' })) {
588
+ hasStyleSheet = true;
589
+ }
590
+ }
591
+ }
592
+ },
593
+ // Check for existing void StyleSheet; marker
594
+ ExpressionStatement(exprPath) {
595
+ if (t.isUnaryExpression(exprPath.node.expression, { operator: 'void' }) &&
596
+ t.isIdentifier(exprPath.node.expression.argument, { name: 'StyleSheet' })) {
597
+ hasVoidMarker = true;
598
+ }
599
+ }
600
+ });
601
+
602
+ if (!hasStyleSheet) {
603
+ if (unistylesImport) {
604
+ // Add StyleSheet to existing import
605
+ unistylesImport.node.specifiers.push(
606
+ t.importSpecifier(t.identifier('StyleSheet'), t.identifier('StyleSheet'))
607
+ );
608
+ } else {
609
+ // Create new import
610
+ const importDecl = t.importDeclaration(
611
+ [t.importSpecifier(t.identifier('StyleSheet'), t.identifier('StyleSheet'))],
612
+ t.stringLiteral('react-native-unistyles')
613
+ );
614
+ path.unshiftContainer('body', importDecl);
615
+ }
616
+ }
617
+
618
+ // Add void StyleSheet; marker so Unistyles detects the file
619
+ // This must be present for Unistyles to process the file
620
+ if (!hasVoidMarker) {
621
+ const voidMarker = t.expressionStatement(
622
+ t.unaryExpression('void', t.identifier('StyleSheet'))
623
+ );
624
+ // Insert after imports (find first non-import statement)
625
+ const body = path.node.body;
626
+ let insertIndex = 0;
627
+ for (let i = 0; i < body.length; i++) {
628
+ if (!t.isImportDeclaration(body[i])) {
629
+ insertIndex = i;
630
+ break;
631
+ }
632
+ insertIndex = i + 1;
633
+ }
634
+ body.splice(insertIndex, 0, voidMarker);
635
+ }
636
+ }
637
+ }
638
+ },
639
+
640
+ CallExpression(path, state) {
641
+ const { node } = path;
642
+ const opts = state.opts || {};
643
+ debugMode = opts.debug === true;
644
+ verboseMode = opts.verbose === true;
645
+ const filename = state.filename || '';
646
+
647
+ const shouldProcess =
648
+ opts.processAll ||
649
+ (opts.autoProcessPaths?.some(p => filename.includes(p)));
650
+
651
+ if (!shouldProcess) return;
652
+
653
+ // ============================================================
654
+ // Handle extendStyle - Store extension AST for later merging
655
+ // ============================================================
656
+ if (t.isIdentifier(node.callee, { name: 'extendStyle' })) {
657
+ debug(`FOUND extendStyle in: ${filename}`);
658
+
659
+ const [componentNameArg, stylesCallback] = node.arguments;
660
+
661
+ if (!t.isStringLiteral(componentNameArg)) {
662
+ debug(` SKIP - componentName is not a string literal`);
663
+ return;
664
+ }
665
+
666
+ if (!t.isArrowFunctionExpression(stylesCallback) &&
667
+ !t.isFunctionExpression(stylesCallback)) {
668
+ debug(` SKIP - callback is not a function`);
669
+ return;
670
+ }
671
+
672
+ const componentName = componentNameArg.value;
673
+ debug(` Processing extendStyle('${componentName}')`);
674
+
675
+ // Get theme parameter name
676
+ let themeParam = 'theme';
677
+ if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
678
+ themeParam = stylesCallback.params[0].name;
679
+ }
680
+
681
+ // Load theme keys and expand iterators
682
+ const rootDir = opts.rootDir || state.cwd || process.cwd();
683
+ const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
684
+ const expandedVariants = [];
685
+ const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
686
+
687
+ // Store in registry for later merging
688
+ const entry = getOrCreateEntry(componentName);
689
+ entry.extensions.push(expandedCallback);
690
+ entry.themeParam = themeParam;
691
+
692
+ debug(` -> Stored extension for '${componentName}' (${entry.extensions.length} total)`);
693
+
694
+ // Remove the extendStyle call (it's been captured)
695
+ path.remove();
696
+ return;
697
+ }
698
+
699
+ // ============================================================
700
+ // Handle overrideStyle - Store as override (replaces base)
701
+ // ============================================================
702
+ if (t.isIdentifier(node.callee, { name: 'overrideStyle' })) {
703
+ debug(`FOUND overrideStyle in: ${filename}`);
704
+
705
+ const [componentNameArg, stylesCallback] = node.arguments;
706
+
707
+ if (!t.isStringLiteral(componentNameArg)) {
708
+ debug(` SKIP - componentName is not a string literal`);
709
+ return;
710
+ }
711
+
712
+ if (!t.isArrowFunctionExpression(stylesCallback) &&
713
+ !t.isFunctionExpression(stylesCallback)) {
714
+ debug(` SKIP - callback is not a function`);
715
+ return;
716
+ }
717
+
718
+ const componentName = componentNameArg.value;
719
+ debug(` Processing overrideStyle('${componentName}')`);
720
+
721
+ let themeParam = 'theme';
722
+ if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
723
+ themeParam = stylesCallback.params[0].name;
724
+ }
725
+
726
+ const rootDir = opts.rootDir || state.cwd || process.cwd();
727
+ const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
728
+ const expandedVariants = [];
729
+ const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
730
+
731
+ // Store as override (replaces base entirely)
732
+ const entry = getOrCreateEntry(componentName);
733
+ entry.override = expandedCallback;
734
+ entry.themeParam = themeParam;
735
+
736
+ debug(` -> Stored override for '${componentName}'`);
737
+
738
+ // Remove the overrideStyle call
739
+ path.remove();
740
+ return;
741
+ }
742
+
743
+ // ============================================================
744
+ // Handle defineStyle - Merge with extensions and output StyleSheet.create
745
+ // ============================================================
746
+ if (t.isIdentifier(node.callee, { name: 'defineStyle' })) {
747
+ debug(`FOUND defineStyle in: ${filename}`);
748
+
749
+ const [componentNameArg, stylesCallback] = node.arguments;
750
+
751
+ if (!t.isStringLiteral(componentNameArg)) {
752
+ debug(` SKIP - componentName is not a string literal`);
753
+ return;
754
+ }
755
+
756
+ if (!t.isArrowFunctionExpression(stylesCallback) &&
757
+ !t.isFunctionExpression(stylesCallback)) {
758
+ debug(` SKIP - callback is not a function`);
759
+ return;
760
+ }
761
+
762
+ const componentName = componentNameArg.value;
763
+ debug(` Processing defineStyle('${componentName}')`);
764
+
765
+ let themeParam = 'theme';
766
+ if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
767
+ themeParam = stylesCallback.params[0].name;
768
+ }
769
+
770
+ const rootDir = opts.rootDir || state.cwd || process.cwd();
771
+ const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
772
+ const expandedVariants = [];
773
+
774
+ // Debug for View only
775
+ if (componentName === 'View') {
776
+ console.log(`\n========== VIEW KEYS DEBUG ==========`);
777
+ console.log(`rootDir: ${rootDir}`);
778
+ console.log(`keys.sizes.view: ${JSON.stringify(keys?.sizes?.view)}`);
779
+ console.log(`keys.intents: ${JSON.stringify(keys?.intents)}`);
780
+ console.log(`======================================\n`);
781
+ }
782
+
783
+ try {
784
+ // Expand iterators in the base callback
785
+ let expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
786
+
787
+ // Check for registered override or extensions
788
+ const entry = getOrCreateEntry(componentName);
789
+
790
+ if (entry.override) {
791
+ // Override completely replaces base
792
+ debug(` -> Using override for '${componentName}'`);
793
+ expandedCallback = entry.override;
794
+ } else if (entry.extensions.length > 0) {
795
+ // Merge extensions into base
796
+ debug(` -> Merging ${entry.extensions.length} extensions for '${componentName}'`);
797
+ for (const ext of entry.extensions) {
798
+ expandedCallback = mergeCallbackBodies(t, expandedCallback, ext);
799
+ }
800
+ }
801
+
802
+ if (!expandedCallback) {
803
+ console.error(`[idealyst-plugin] ERROR: expandedCallback is null/undefined for ${componentName}`);
804
+ return;
805
+ }
806
+
807
+ // Transform to StyleSheet.create
808
+ state.needsStyleSheetImport = true;
809
+
810
+ const newCall = t.callExpression(
811
+ t.memberExpression(
812
+ t.identifier('StyleSheet'),
813
+ t.identifier('create')
814
+ ),
815
+ [expandedCallback]
816
+ );
817
+
818
+ path.replaceWith(newCall);
819
+
820
+ debug(` -> Transformed to StyleSheet.create`);
821
+ if (expandedVariants.length > 0) {
822
+ debug(` Expanded: ${expandedVariants.map(v => `${v.variant}(${v.iterator})`).join(', ')}`);
823
+ }
824
+
825
+ // Output generated code when verbose
826
+ if (verboseMode) {
827
+ const generate = require('@babel/generator').default;
828
+ const output = generate(newCall);
829
+ console.log(`[idealyst-plugin] Generated code for ${componentName}:`);
830
+ console.log(output.code.substring(0, 2000) + (output.code.length > 2000 ? '...' : ''));
831
+ }
832
+ } catch (err) {
833
+ console.error(`[idealyst-plugin] ERROR transforming defineStyle('${componentName}'):`, err);
834
+ }
835
+ return;
836
+ }
837
+
838
+ // ============================================================
839
+ // Handle StyleSheet.create - Just expand $iterator patterns
840
+ // ============================================================
841
+ if (t.isMemberExpression(node.callee) &&
842
+ t.isIdentifier(node.callee.object, { name: 'StyleSheet' }) &&
843
+ t.isIdentifier(node.callee.property, { name: 'create' })) {
844
+
845
+ const [stylesCallback] = node.arguments;
846
+
847
+ if (!t.isArrowFunctionExpression(stylesCallback) &&
848
+ !t.isFunctionExpression(stylesCallback)) {
849
+ return;
850
+ }
851
+
852
+ let themeParam = 'theme';
853
+ if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
854
+ themeParam = stylesCallback.params[0].name;
855
+ }
856
+
857
+ const rootDir = opts.rootDir || state.cwd || process.cwd();
858
+ const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
859
+ const expandedVariants = [];
860
+
861
+ const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
862
+ node.arguments[0] = expandedCallback;
863
+
864
+ if (expandedVariants.length > 0) {
865
+ debug(` -> Expanded $iterator patterns in StyleSheet.create: ${expandedVariants.map(v => `${v.variant}(${v.iterator})`).join(', ')}`);
866
+ }
867
+ }
868
+ },
869
+ },
870
+ };
871
+ };